diff --git a/package.json b/package.json index 83c27999..cd8e8dad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "betterdiscord", - "version": "1.6.3", + "version": "1.7.0", "description": "Enhances Discord by adding functionality and themes.", "main": "src/index.js", "scripts": { diff --git a/renderer/src/builtins/builtins.js b/renderer/src/builtins/builtins.js index c7be82d2..0c5790d3 100644 --- a/renderer/src/builtins/builtins.js +++ b/renderer/src/builtins/builtins.js @@ -6,8 +6,8 @@ export {default as PublicServers} from "./general/publicservers"; export {default as VoiceDisconnect} from "./general/voicedisconnect"; export {default as MediaKeys} from "./general/mediakeys"; -export {default as EmoteModule} from "./emotes/emotes"; -export {default as EmoteMenu} from "./emotes/emotemenu"; +// export {default as EmoteModule} from "./emotes/emotes"; +// export {default as EmoteMenu} from "./emotes/emotemenu"; // export {default as EmoteAutocaps} from "./emotes/emoteautocaps"; export {default as DevToolsListener} from "./developer/devtools"; diff --git a/renderer/src/builtins/general/publicservers.js b/renderer/src/builtins/general/publicservers.js index 2f891f3e..395acb4b 100644 --- a/renderer/src/builtins/general/publicservers.js +++ b/renderer/src/builtins/general/publicservers.js @@ -3,7 +3,24 @@ import {DiscordModules, WebpackModules, Strings, DOM, React} from "modules"; import PublicServersMenu from "../../ui/publicservers/menu"; import Globe from "../../ui/icons/globe"; -const LayerStack = WebpackModules.getByProps("pushLayer"); +const LayerManager = { + pushLayer(component) { + DiscordModules.Dispatcher.dispatch({ + type: "LAYER_PUSH", + component + }); + }, + popLayer() { + DiscordModules.Dispatcher.dispatch({ + type: "LAYER_POP" + }); + }, + popAllLayers() { + DiscordModules.Dispatcher.dispatch({ + type: "LAYER_POP_ALL" + }); + } + }; class ErrorBoundary extends React.Component { constructor(props) { @@ -27,30 +44,36 @@ export default new class PublicServers extends Builtin { get id() {return "publicServers";} enabled() { - const PrivateChannelList = WebpackModules.getModule(m => m?.displayName === "ConnectedPrivateChannelsList", {defaultExport: false}); - const PrivateChannelListComponents = WebpackModules.getByProps("LinkButton"); - this.after(PrivateChannelList, "default", (_, __, returnValue) => { - const destination = returnValue?.props?.children?.props?.children; - if (!destination || !Array.isArray(destination)) return; - if (destination.find(b => b?.props?.children?.props?.id === "public-server-button")) return; + // let target = null; + // WebpackModules.getModule((_, m) => { + // if (m.exports?.toString().includes("privateChannelIds")) { + // target = m.exports; + // } + // }); + // if (!target || !target.Z) return; + // const PrivateChannelListComponents = WebpackModules.getByProps("LinkButton"); + // this.after(target, "Z", (_, __, returnValue) => { + // const destination = returnValue?.props?.children?.props?.children; + // if (!destination || !Array.isArray(destination)) return; + // if (destination.find(b => b?.props?.children?.props?.id === "public-server-button")) return; - destination.push( - React.createElement(ErrorBoundary, null, - React.createElement(PrivateChannelListComponents.LinkButton, - { - id: "public-server-button", - onClick: () => this.openPublicServers(), - text: "Public Servers", - icon: () => React.createElement(Globe, {color: "currentColor"}) - } - ) - ) - ); - }); + // destination.push( + // React.createElement(ErrorBoundary, null, + // React.createElement(PrivateChannelListComponents.LinkButton, + // { + // id: "public-server-button", + // onClick: () => this.openPublicServers(), + // text: "Public Servers", + // icon: () => React.createElement(Globe, {color: "currentColor"}) + // } + // ) + // ) + // ); + // }); } disabled() { - this.unpatchAll(); + // this.unpatchAll(); // DOM.query("#bd-pub-li").remove(); } @@ -67,7 +90,7 @@ export default new class PublicServers extends Builtin { } openPublicServers() { - LayerStack.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerStack.popLayer})); + LayerManager.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerManager.popLayer})); } get button() { diff --git a/renderer/src/data/changelog.js b/renderer/src/data/changelog.js index cbf7e0be..8569a382 100644 --- a/renderer/src/data/changelog.js +++ b/renderer/src/data/changelog.js @@ -1,20 +1,23 @@ // fixed, improved, added, progress export default { - description: "Discord is _still_ making a lot of internal changes!", + description: "BetterDiscord is alive! At least... _sorta_.", changes: [ { - title: "Changes", + title: "Known Issues", type: "improved", items: [ - "Plugin startup errors should be more descriptive for developers.", + "**Many many plugins are either completely broken or missing functionality.** Please refer to the respective developers for ETAs.", + "The Twitch Emote system is completely broken, and there is no ETA on being fixed.", + "The Public Servers module is also broken with no ETA for a fix.", ] }, { - title: "Fixes", + title: "Important News!", type: "fixed", items: [ - "Fixed an issue where custom css crashed Discord.", - "Fixed an issue where `waitForModule` returned a boolean instead of a module.", + "Due to recent and upcoming changes, BetterDiscord is going to go through a rewrite.", + "There is no ETA or timeline for this rewrite.", + "We will continue to try and __maintain__ this version of BetterDiscord without adding new features." ] } ] diff --git a/renderer/src/data/settings.js b/renderer/src/data/settings.js index 25fd1628..5fb700ea 100644 --- a/renderer/src/data/settings.js +++ b/renderer/src/data/settings.js @@ -4,7 +4,7 @@ export default [ id: "general", collapsible: true, settings: [ - {type: "switch", id: "emotes", value: true}, + {type: "switch", id: "emotes", value: true, disabled: true}, {type: "switch", id: "publicServers", value: true}, {type: "switch", id: "voiceDisconnect", value: false}, {type: "switch", id: "showToasts", value: true}, diff --git a/renderer/src/modules/componentpatcher.js b/renderer/src/modules/componentpatcher.js index dd982b4e..e3d137d8 100644 --- a/renderer/src/modules/componentpatcher.js +++ b/renderer/src/modules/componentpatcher.js @@ -8,8 +8,8 @@ import BDLogo from "../ui/icons/bdlogo"; import Logger from "common/logger"; const React = DiscordModules.React; -const Tooltip = WebpackModules.getByDisplayName("Tooltip"); -const Anchor = WebpackModules.getByDisplayName("Anchor"); +const Tooltip = WebpackModules.getByPrototypes("renderTooltip"); +const Anchor = WebpackModules.getByProps("Link"); const Developers = [ /* Zerebos#7790 */ @@ -21,7 +21,7 @@ const Developers = [ const DeveloperBadge = function DeveloperBadge({type, size = 16}) { return React.createElement(Tooltip, {color: "primary", position: "top", text: "BetterDiscord Developer"}, - props => React.createElement(Anchor, Object.assign({className: `bd-${type}-badge`, href: "https://github.com/BetterDiscord/BetterDiscord", title: "BetterDiscord", target: "_blank"}, props), + props => React.createElement(Anchor.Link, Object.assign({className: `bd-${type}-badge`, href: "https://github.com/BetterDiscord/BetterDiscord", title: "BetterDiscord", target: "_blank"}, props), React.createElement(BDLogo, {size, className: "bd-logo"}) ) ); @@ -33,14 +33,14 @@ export default new class ComponentPatcher { debug(...message) {return Logger.debug("ComponentPatcher", ...message);} initialize() { - Utilities.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")(); - Utilities.suppressErrors(this.patchMemberList.bind(this), "BD Member List Patch")(); - Utilities.suppressErrors(this.patchProfile.bind(this), "BD Profile Badges Patch")(); + // Utilities.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")(); + // Utilities.suppressErrors(this.patchMemberList.bind(this), "BD Member List Patch")(); + // Utilities.suppressErrors(this.patchProfile.bind(this), "BD Profile Badges Patch")(); } patchSocial() { if (this.socialPatch) return; - const TabBar = WebpackModules.getByDisplayName("TabBar"); + const TabBar = WebpackModules.getByProps("Types", "Looks", "Header"); if (!TabBar) return; this.socialPatch = Patcher.after("ComponentPatcher", TabBar.prototype, "render", (thisObject, args, returnValue) => { const children = returnValue.props.children; @@ -99,7 +99,7 @@ export default new class ComponentPatcher { patchProfile() { if (this.profilePatch) return; - const UserProfileBadgeLists = WebpackModules.getModule(m => m?.default?.displayName === "UserProfileBadgeList", {first: false}); + const UserProfileBadgeLists = WebpackModules.getModule(m => m?.toString()?.includes("PROFILE_USER_BADGES"), {first: false}); for (const UserProfileBadgeList of UserProfileBadgeLists) { this.profilePatch = Patcher.after("ComponentPatcher", UserProfileBadgeList, "default", (_, [{user}], res) => { if (Developers.indexOf(user?.id) < 0) return; diff --git a/renderer/src/modules/core.js b/renderer/src/modules/core.js index de3140dd..49f6164e 100644 --- a/renderer/src/modules/core.js +++ b/renderer/src/modules/core.js @@ -3,6 +3,7 @@ import LocaleManager from "./localemanager"; import Logger from "common/logger"; import {Config, Changelog} from "data"; +import WebpackModules from "./webpackmodules"; import DOMManager from "./dommanager"; import PluginManager from "./pluginmanager"; import ThemeManager from "./thememanager"; @@ -18,7 +19,6 @@ import IPC from "./ipc"; import LoadingIcon from "../loadingicon"; import Styles from "../styles/index.css"; import Editor from "./editor"; -import {WebpackModules} from "modules"; export default new class Core { async startup() { @@ -48,6 +48,7 @@ export default new class Core { Logger.log("Startup", "Initializing Settings"); Settings.initialize(); + // SettingsRenderer.patchSections(); Logger.log("Startup", "Initializing DOMManager"); DOMManager.initialize(); @@ -68,6 +69,7 @@ export default new class Core { for (const module in Builtins) { Builtins[module].initialize(); } + this.polyfillWebpack(); Logger.log("Startup", "Loading Plugins"); // const pluginErrors = []; @@ -86,9 +88,18 @@ export default new class Core { const previousVersion = DataStore.getBDData("version"); if (Config.version > previousVersion) { - Modals.showChangelogModal(Changelog); + // Modals.showChangelogModal(Changelog); + const md = [Changelog.description]; + for (const type of Changelog.changes) { + md.push(`**${type.title}**`); + for (const entry of type.items) { + md.push(` - ${entry}`); + } + } + Modals.showConfirmationModal(`BetterDiscord v${Config.version}`, md, {cancelText: ""}); DataStore.setBDData("version", Config.version); } + // SettingsRenderer.patchSections(); } polyfillWebpack() { diff --git a/renderer/src/modules/discordmodules.js b/renderer/src/modules/discordmodules.js index 49dd8e0a..83bf496f 100644 --- a/renderer/src/modules/discordmodules.js +++ b/renderer/src/modules/discordmodules.js @@ -47,7 +47,7 @@ export default Utilities.memoizeObject({ get MentionStore() {return WebpackModules.getByProps("getMentions");}, /* User Stores and Utils */ - get UserStore() {return WebpackModules.getByProps("getCurrentUser");}, + get UserStore() {return WebpackModules.getByProps("getCurrentUser", "getUser");}, get UserStatusStore() {return WebpackModules.getByProps("getStatus", "getState");}, get UserTypingStore() {return WebpackModules.getByProps("isTyping");}, get UserActivityStore() {return WebpackModules.getByProps("getActivity");}, @@ -133,14 +133,14 @@ export default Utilities.memoizeObject({ /* Electron & Other Internals with Utils*/ get ElectronModule() {return WebpackModules.getByProps("setBadge");}, - get Dispatcher() {return WebpackModules.getByProps("dispatch", "subscribe");}, + get Dispatcher() {return WebpackModules.getByProps("dispatch", "subscribe", "register");}, get PathUtils() {return WebpackModules.getByProps("hasBasename");}, get NotificationModule() {return WebpackModules.getByProps("showNotification");}, get RouterModule() {return WebpackModules.getByProps("Router");}, get APIModule() {return WebpackModules.getByProps("getAPIBaseURL");}, get AnalyticEvents() {return WebpackModules.getByProps("AnalyticEventConfigs");}, get KeyGenerator() {return WebpackModules.getByRegex(/"binary"/);}, - get Buffers() {return WebpackModules.getByProps("Buffer", "kMaxLength");}, + get Buffers() {return WebpackModules.getByProps("INSPECT_MAX_BYTES", "kMaxLength");}, get DeviceStore() {return WebpackModules.getByProps("getDevices");}, get SoftwareInfo() {return WebpackModules.getByProps("os");}, get CurrentContext() {return WebpackModules.getByProps("setTagsContext");}, diff --git a/renderer/src/modules/localemanager.js b/renderer/src/modules/localemanager.js index b52c25e8..87a86300 100644 --- a/renderer/src/modules/localemanager.js +++ b/renderer/src/modules/localemanager.js @@ -16,10 +16,7 @@ export default new class LocaleManager { initialize() { this.setLocale(this.discordLocale); - Dispatcher.subscribe("USER_SETTINGS_UPDATE", ({settings}) => { - const newLocale = settings.locale; - if (newLocale && newLocale != this.locale) this.setLocale(newLocale); - }); + Dispatcher.subscribe("USER_SETTINGS_UPDATE", (newLocale) => this.setLocale(newLocale)); } setLocale(newLocale) { diff --git a/renderer/src/modules/webpackmodules.js b/renderer/src/modules/webpackmodules.js index 14eb6ca5..dbba4384 100644 --- a/renderer/src/modules/webpackmodules.js +++ b/renderer/src/modules/webpackmodules.js @@ -146,6 +146,7 @@ export default class WebpackModules { return false; } }; + const modules = this.getAllModules(); const rm = []; const indices = Object.keys(modules); @@ -154,14 +155,42 @@ export default class WebpackModules { if (!modules.hasOwnProperty(index)) continue; const module = modules[index]; const {exports} = module; + if (exports === window) continue; let foundModule = null; - if (!exports) continue; - if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports; - if (wrappedFilter(exports, module, index)) foundModule = exports; - if (!foundModule) continue; - if (first) return foundModule; - rm.push(foundModule); + if (typeof(exports) === "object") { + const wrappers = Object.getOwnPropertyDescriptors(exports); + const getters = Object.keys(wrappers).filter(k => wrappers[k].get); + if (getters.length) { + for (const getter of getters) { + const wrappedExport = exports[getter]; + if (!wrappedExport) continue; + if (wrappedExport.__esModule && wrappedExport.default && wrappedFilter(wrappedExport.default, module, index)) foundModule = defaultExport ? wrappedExport.default : wrappedExport; + if (wrappedFilter(wrappedExport, module, index)) foundModule = wrappedExport; + if (!foundModule) continue; + if (first) return foundModule; + rm.push(foundModule); + } + } + else { + if (!exports) continue; + if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports; + if (wrappedFilter(exports, module, index)) foundModule = exports; + if (!foundModule) continue; + if (first) return foundModule; + rm.push(foundModule); + } + } + else { + if (!exports) continue; + if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports; + if (wrappedFilter(exports, module, index)) foundModule = exports; + if (!foundModule) continue; + if (first) return foundModule; + rm.push(foundModule); + } + + } return first || rm.length == 0 ? undefined : rm; @@ -205,11 +234,36 @@ export default class WebpackModules { }; let foundModule = null; - if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports; - if (wrappedFilter(exports, module, index)) foundModule = exports; - if (!foundModule) continue; - if (first) returnedModules[q] = protect(foundModule); - else returnedModules[q].push(protect(foundModule)); + if (typeof(exports) === "object") { + const wrappers = Object.getOwnPropertyDescriptors(exports); + const getters = Object.keys(wrappers).filter(k => wrappers[k].get); + if (getters.length) { + for (const getter of getters) { + const wrappedExport = exports[getter]; + if (!wrappedExport) continue; + if (wrappedExport.__esModule && wrappedExport.default && wrappedFilter(wrappedExport.default, module, index)) foundModule = defaultExport ? wrappedExport.default : wrappedExport; + if (wrappedFilter(wrappedExport, module, index)) foundModule = wrappedExport; + if (!foundModule) continue; + if (first) returnedModules[q] = foundModule; + else returnedModules[q].push(foundModule); + } + } + else { + if (!exports) continue; + if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports; + if (wrappedFilter(exports, module, index)) foundModule = exports; + if (!foundModule) continue; + if (first) returnedModules[q] = foundModule; + else returnedModules[q].push(foundModule); + } + } + else { + if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports; + if (wrappedFilter(exports, module, index)) foundModule = exports; + if (!foundModule) continue; + if (first) returnedModules[q] = foundModule; + else returnedModules[q].push(foundModule); + } } } @@ -327,12 +381,30 @@ export default class WebpackModules { if (!exports) return; let foundModule = null; - if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = defaultExport ? exports.default : exports; - if (wrappedFilter(exports)) foundModule = exports; - if (!foundModule) return; + if (typeof(exports) === "object") { + const wrappers = Object.getOwnPropertyDescriptors(exports); + const getters = Object.keys(wrappers).filter(k => wrappers[k].get); + if (getters.length) { + for (const getter of getters) { + const wrappedExport = exports[getter]; + if (!wrappedExport) continue; + if (wrappedExport.__esModule && wrappedExport.default && wrappedFilter(wrappedExport.default)) foundModule = defaultExport ? wrappedExport.default : wrappedExport; + if (wrappedFilter(wrappedExport)) foundModule = wrappedExport; + } + } + else { + if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = defaultExport ? exports.default : exports; + if (wrappedFilter(exports)) foundModule = exports; + } + } + else { + if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = defaultExport ? exports.default : exports; + if (wrappedFilter(exports)) foundModule = exports; + } + if (!foundModule) return; cancel(); - resolve(protect(foundModule)); + resolve(foundModule); }; this.addListener(listener); diff --git a/renderer/src/polyfill/buffer.js b/renderer/src/polyfill/buffer.js index ae19f600..a1b299c0 100644 --- a/renderer/src/polyfill/buffer.js +++ b/renderer/src/polyfill/buffer.js @@ -10,7 +10,7 @@ export default class Buffer { static getBuffer() { if (this.cached) return this.cached; - this.cached = WebpackModules.getByProps("Buffer", "SlowBuffer"); + this.cached = WebpackModules.getByProps("INSPECT_MAX_BYTES"); return this.cached; } diff --git a/renderer/src/polyfill/request.js b/renderer/src/polyfill/request.js index a44433d8..a37d2264 100644 --- a/renderer/src/polyfill/request.js +++ b/renderer/src/polyfill/request.js @@ -34,9 +34,10 @@ function validOptions(url, callback) { function fixBuffer(options, callback) { return (error, res, body) => { - if ("Content-Type" in Object(options.headers) && props.headers["Content-Type"] !== "text/plain") { + if ("Content-Type" in Object(options.headers) && options.headers["Content-Type"] !== "text/plain") { body = Buffer.from(body); - } else { + } + else { body = Buffer.from(body).toString(); } diff --git a/renderer/src/ui/customcss/editor.jsx b/renderer/src/ui/customcss/editor.jsx index b2a026b8..efb7a073 100644 --- a/renderer/src/ui/customcss/editor.jsx +++ b/renderer/src/ui/customcss/editor.jsx @@ -2,7 +2,7 @@ import {React, WebpackModules, DiscordModules, Settings} from "modules"; import Checkbox from "./checkbox"; -const Tooltip = WebpackModules.getByDisplayName("Tooltip"); +const Tooltip = WebpackModules.getByPrototypes("renderTooltip"); const ThemeStore = DiscordModules.ThemeStore; const languages = ["abap", "abc", "actionscript", "ada", "apache_conf", "asciidoc", "assembly_x86", "autohotkey", "batchfile", "bro", "c_cpp", "c9search", "cirru", "clojure", "cobol", "coffee", "coldfusion", "csharp", "csound_document", "csound_orchestra", "csound_score", "css", "curly", "d", "dart", "diff", "dockerfile", "dot", "drools", "dummy", "dummysyntax", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "ftl", "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", "io", "jack", "jade", "java", "javascript", "json", "jsoniq", "jsp", "jssm", "jsx", "julia", "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "lsl", "lua", "luapage", "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mel", "mushcode", "mysql", "nix", "nsis", "objectivec", "ocaml", "pascal", "perl", "pgsql", "php", "pig", "powershell", "praat", "prolog", "properties", "protobuf", "python", "r", "razor", "rdoc", "red", "rhtml", "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "smarty", "snippets", "soy_template", "space", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", "tex", "text", "textile", "toml", "tsx", "twig", "typescript", "vala", "vbscript", "velocity", "verilog", "vhdl", "wollok", "xml", "xquery", "yaml", "django"]; diff --git a/renderer/src/ui/emote.js b/renderer/src/ui/emote.js index 9da63e9b..414ad4b0 100644 --- a/renderer/src/ui/emote.js +++ b/renderer/src/ui/emote.js @@ -1,6 +1,6 @@ import {Settings, React, WebpackModules, Events, Strings} from "modules"; -const TooltipWrapper = WebpackModules.getByDisplayName("Tooltip"); +const TooltipWrapper = WebpackModules.getByPrototypes("renderTooltip"); export default class BDEmote extends React.Component { constructor(props) { diff --git a/renderer/src/ui/emoteicon.jsx b/renderer/src/ui/emoteicon.jsx index 3187e2d2..75c511fc 100644 --- a/renderer/src/ui/emoteicon.jsx +++ b/renderer/src/ui/emoteicon.jsx @@ -2,9 +2,9 @@ import {React, WebpackModules} from "modules"; import EmoteModule from "../builtins/emotes/emotes"; const ContextMenuActions = WebpackModules.getByProps("openContextMenu"); -const {MenuItem, MenuGroup} = WebpackModules.find(m => m.MenuRadioItem && !m.default); -const ContextMenu = WebpackModules.getByProps("default", "MenuStyle").default; -const {ComponentDispatch} = WebpackModules.getByProps("ComponentDispatch"); +const {MenuItem, MenuGroup} = WebpackModules.find(m => m.MenuRadioItem && !m.default) ?? {MenuItem: () => null, MenuGroup: () => null}; +const ContextMenu = WebpackModules.getByProps("default", "MenuStyle")?.default; +const {ComponentDispatch} = WebpackModules.getByProps("ComponentDispatch") ?? {ComponentDispatch: () => null}; export default class EmoteIcon extends React.Component { render() { diff --git a/renderer/src/ui/emotemenucard.jsx b/renderer/src/ui/emotemenucard.jsx index b50ffe69..777830c5 100644 --- a/renderer/src/ui/emotemenucard.jsx +++ b/renderer/src/ui/emotemenucard.jsx @@ -1,5 +1,5 @@ import {React, WebpackModules} from "modules"; -const {ScrollerAuto: Scroller} = WebpackModules.getByProps("ScrollerAuto"); +const {ScrollerAuto: Scroller} = WebpackModules.getByProps("ScrollerAuto") ?? {ScrollerAuto: () => null}; export default class EmoteMenuCard extends React.Component { render() { return