diff --git a/injector/src/modules/betterdiscord.js b/injector/src/modules/betterdiscord.js index 9698e676..6836d29a 100644 --- a/injector/src/modules/betterdiscord.js +++ b/injector/src/modules/betterdiscord.js @@ -22,6 +22,8 @@ electron.app.once("ready", async () => { await ReactDevTools.install(); }); +let hasCrashed = false; + export default class BetterDiscord { static getWindowPrefs() { if (!fs.existsSync(buildInfoFile)) return {}; @@ -89,12 +91,24 @@ export default class BetterDiscord { // When DOM is available, pass the renderer over the wall browserWindow.webContents.on("dom-ready", () => { - this.injectRenderer(browserWindow); + if (!hasCrashed) return this.injectRenderer(browserWindow); + + // If a previous crash was detected, show a message explaining why BD isn't there + electron.dialog.showMessageBox({ + title: "BetterDiscord Crashed", + type: "warning", + message: "BetterDiscord seems to have crashed your Discord client.", + detail: "BetterDiscord has automatically disabled itself temporarily. Try removing all your plugins then restarting Discord." + }); }); // This is used to alert renderer code to onSwitch events browserWindow.webContents.on("did-navigate-in-page", () => { browserWindow.webContents.send(IPCEvents.NAVIGATE); }); + + browserWindow.webContents.on("render-process-gone", (event, details) => { + hasCrashed = true; + }); } } \ No newline at end of file diff --git a/injector/src/preload.js b/injector/src/preload.js index a5e0c814..305c425a 100644 --- a/injector/src/preload.js +++ b/injector/src/preload.js @@ -21,7 +21,12 @@ if (preload) { process.electronBinding("command_line").appendSwitch("preload", preload); // Run original preload - try {require(preload);} + try { + const originalKill = process.kill; + process.kill = function() {}; + require(preload); + process.kill = originalKill; + } catch (e) { // TODO bail out } diff --git a/renderer/src/builtins/builtins.js b/renderer/src/builtins/builtins.js index b7b248ab..26d4bd2b 100644 --- a/renderer/src/builtins/builtins.js +++ b/renderer/src/builtins/builtins.js @@ -17,6 +17,5 @@ export {default as EmoteModule} from "./emotes/emotes"; export {default as EmoteMenu} from "./emotes/emotemenu"; // export {default as EmoteAutocaps} from "./emotes/emoteautocaps"; -export {default as CopySelector} from "./developer/copyselector"; export {default as Debugger} from "./developer/debugger"; export {default as ReactDevTools} from "./developer/reactdevtools"; \ No newline at end of file diff --git a/renderer/src/builtins/developer/copyselector.js b/renderer/src/builtins/developer/copyselector.js deleted file mode 100644 index b91c3ca0..00000000 --- a/renderer/src/builtins/developer/copyselector.js +++ /dev/null @@ -1,79 +0,0 @@ -import Builtin from "../../structs/builtin"; -import {DOM, DiscordModules, Strings} from "modules"; - -export default new class DeveloperMode extends Builtin { - get name() {return "DeveloperMode";} - get category() {return "developer";} - get id() {return "developerMode";} - get selectorModeID() {return "copySelector";} - get selectorMode() {return this.get(this.selectorModeID);} - - constructor() { - super(); - this.copySelectorListener = this.copySelectorListener.bind(this); - } - - enabled() { - document.addEventListener("contextmenu", this.copySelectorListener); - } - - disabled() { - document.removeEventListener("contextmenu", this.copySelectorListener); - } - - copySelectorListener(ctxEvent) { - ctxEvent.stopPropagation(); - const selector = this.getSelector(ctxEvent.target); - function attach() { - let cm = DOM.query(".contextMenu-HLZMGh"); - if (!cm) { - const container = DOM.query("#app-mount"); - const cmWrap = DOM.createElement(`
`); - cm = DOM.createElement(`
`); - cmWrap.append(cm); - container.append(cmWrap); - cmWrap.style.top = ctxEvent.clientY + "px"; - cmWrap.style.left = ctxEvent.clientX + "px"; - cmWrap.style.zIndex = "1002"; - const removeCM = function(removeEvent) { - if (removeEvent.keyCode && removeEvent.keyCode !== 27) return; - cmWrap.remove(); - document.removeEventListener("click", removeCM); - document.removeEventListener("contextmenu", removeCM); - document.removeEventListener("keyup", removeCM); - }; - document.addEventListener("click", removeCM); - document.addEventListener("contextmenu", removeCM); - document.addEventListener("keyup", removeCM); - } - - const cmg = DOM.createElement(`
`); - const cmi = DOM.createElement(`
`); - cmi.append(DOM.createElement(`
${Strings.Developer.copySelector}
`)); - cmi.addEventListener("click", () => { - DiscordModules.ElectronModule.copy(selector); - cm.style.display = "none"; - }); - cmg.append(cmi); - cm.append(cmg); - } - - setImmediate(attach); - } - - getSelector(element) { - if (element.id) return `#${element.id}`; - const rules = this.getRules(element); - const latestRule = rules[rules.length - 1]; - if (latestRule) return latestRule.selectorText; - else if (element.classList.length) return `.${Array.from(element.classList).join(".")}`; - return `.${Array.from(element.parentElement.classList).join(".")}`; - } - - getRules(element, css = element.ownerDocument.styleSheets) { - const sheets = [...css].filter(s => !s.href || !s.href.includes("BetterDiscordApp")); - const rules = sheets.map(s => [...(s.cssRules || [])]).flat(); - const elementRules = rules.filter(r => r && r.selectorText && element.matches(r.selectorText) && r.style.length && r.selectorText.split(", ").length < 8 && !r.selectorText.split(", ").includes("*")); - return elementRules; - } -}; \ No newline at end of file diff --git a/renderer/src/data/settings/config.js b/renderer/src/data/settings/config.js index 5b83d1a0..35aeffb2 100644 --- a/renderer/src/data/settings/config.js +++ b/renderer/src/data/settings/config.js @@ -52,7 +52,6 @@ export default [ shown: false, settings: [ {type: "switch", id: "debuggerHotkey", value: false}, - {type: "switch", id: "copySelector", value: false}, {type: "switch", id: "reactDevTools", value: false} ] }, diff --git a/renderer/src/data/strings.js b/renderer/src/data/strings.js index 5d8a20df..c28879af 100644 --- a/renderer/src/data/strings.js +++ b/renderer/src/data/strings.js @@ -106,10 +106,6 @@ export default { name: "Debugger Hotkey", note: "Allows activating debugger when pressing F8" }, - copySelector: { - name: "Copy Selector", - note: "Adds a \"Copy Selector\" option to context menus when developer mode is active" - }, reactDevTools: { name: "React Developer Tools", note: "Injects your local installation of React Developer Tools into Discord" @@ -227,9 +223,6 @@ export default { settings: "Editor Settings", editorTitle: "Custom CSS Editor" }, - Developer: { - copySelector: "Copy Selector" - }, Emotes: { loading: "Loading emotes in the background do not reload.", loaded: "All emotes successfully loaded.", diff --git a/renderer/src/modules/addonmanager.js b/renderer/src/modules/addonmanager.js index 30c1efe4..358b6551 100644 --- a/renderer/src/modules/addonmanager.js +++ b/renderer/src/modules/addonmanager.js @@ -48,14 +48,14 @@ export default class AddonManager { this.windows = new Set(); } - async initialize() { + initialize() { this.originalRequire = Module._extensions[this.moduleExtension]; Module._extensions[this.moduleExtension] = this.getAddonRequire(); Settings.on(this.collection, this.category, this.id, (enabled) => { if (enabled) this.watchAddons(); else this.unwatchAddons(); }); - return await this.loadAllAddons(); + return this.loadAllAddons(); } // Subclasses should overload this and modify the addon object as needed to fully load it @@ -195,11 +195,10 @@ export default class AddonManager { } // Subclasses should use the return (if not AddonError) and push to this.addonList - async loadAddon(filename, shouldToast = false) { + loadAddon(filename, shouldToast = false) { if (typeof(filename) === "undefined") return; try { const addon = __non_webpack_require__(path.resolve(this.addonFolder, filename)); - await Promise.resolve(addon); } catch (error) { return new AddonError(filename, filename, Strings.Addons.compileError, {message: error.message, stack: error.stack}); @@ -234,11 +233,11 @@ export default class AddonManager { return true; } - async reloadAddon(idOrFileOrAddon, shouldToast = true) { + reloadAddon(idOrFileOrAddon, shouldToast = true) { const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon; const didUnload = this.unloadAddon(addon, shouldToast, true); if (addon && !didUnload) return didUnload; - return await this.loadAddon(addon ? addon.filename : idOrFileOrAddon, shouldToast); + return this.loadAddon(addon ? addon.filename : idOrFileOrAddon, shouldToast); } isLoaded(idOrFile) { @@ -293,7 +292,7 @@ export default class AddonManager { for (const name of results.removed) this.unloadAddon(name); } - async loadAllAddons() { + loadAllAddons() { this.loadState(); const errors = []; const files = fs.readdirSync(this.addonFolder); @@ -321,7 +320,7 @@ export default class AddonManager { // Rename the file and let it go on fs.renameSync(absolutePath, path.resolve(this.addonFolder, newFilename)); } - const addon = await this.loadAddon(filename, false); + const addon = this.loadAddon(filename, false); if (addon instanceof AddonError) errors.push(addon); } diff --git a/renderer/src/modules/core.js b/renderer/src/modules/core.js index 50fdc461..2d649b5e 100644 --- a/renderer/src/modules/core.js +++ b/renderer/src/modules/core.js @@ -26,11 +26,18 @@ export default new class Core { // (() => { // const fs = require("fs"); // fs.appendFileSync("Z:\\debug.log", "\n\n\n"); - // window.ocl = console.log; - // console.log = (...args) => { + + // const toFile = orig => (...args) => { // fs.appendFileSync("Z:\\debug.log", JSON.stringify(args) + "\n"); - // window.ocl(...args); + // orig(...args); // }; + + // window.ocl = console.log; + // window.oce = console.error; + // window.ocx = console.exception; + // console.log = toFile(window.ocl); + // console.error = toFile(window.oce); + // console.exception = toFile(window.ocx); // })(); Config.appPath = process.env.DISCORD_APP_PATH; @@ -106,7 +113,7 @@ export default new class Core { const guild = GuildClasses.listItem.split(" ")[0]; const blob = GuildClasses.blobContainer.split(" ")[0]; if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(Config.deferLoaded = true); - else if (timesChecked >= 50) return resolve(Config.deferLoaded = true); + // else if (timesChecked >= 50) return resolve(Config.deferLoaded = true); setTimeout(checkForGuilds, 100); }; diff --git a/renderer/src/modules/datastore.js b/renderer/src/modules/datastore.js index d242bc76..592a6475 100644 --- a/renderer/src/modules/datastore.js +++ b/renderer/src/modules/datastore.js @@ -68,7 +68,7 @@ export default new class DataStore { general: {publicServers: oldSettings["bda-gs-1"], voiceDisconnect: oldSettings["bda-dc-0"], classNormalizer: oldSettings["fork-ps-4"], showToasts: oldSettings["fork-ps-2"]}, appearance: {twentyFourHour: oldSettings["bda-gs-6"], minimalMode: oldSettings["bda-gs-2"], coloredText: oldSettings["bda-gs-7"]}, addons: {addonErrors: oldSettings["fork-ps-1"], autoReload: oldSettings["fork-ps-5"]}, - developer: {debuggerHotkey: oldSettings["bda-gs-8"], copySelector: oldSettings["fork-dm-1"], reactDevTools: oldSettings.reactDevTools} + developer: {debuggerHotkey: oldSettings["bda-gs-8"], reactDevTools: oldSettings.reactDevTools} }; const newEmotes = { diff --git a/renderer/src/modules/pluginmanager.js b/renderer/src/modules/pluginmanager.js index 724e8a14..7e5a3c48 100644 --- a/renderer/src/modules/pluginmanager.js +++ b/renderer/src/modules/pluginmanager.js @@ -38,8 +38,8 @@ export default new class PluginManager extends AddonManager { }); } - async initialize() { - const errors = await super.initialize(); + initialize() { + const errors = super.initialize(); this.setupFunctions(); Settings.registerPanel("plugins", Strings.Panels.plugins, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.plugins, this.addonList, this.state, { type: this.prefix, @@ -66,13 +66,13 @@ export default new class PluginManager extends AddonManager { unloadPlugin(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);} loadPlugin(filename) {return this.loadAddon(filename);} - async loadAddon(filename) { - const error = await super.loadAddon(filename); + loadAddon(filename) { + const error = super.loadAddon(filename); if (error) Modals.showAddonErrors({plugins: [error]}); } - async reloadPlugin(idOrFileOrAddon) { - const error = await this.reloadAddon(idOrFileOrAddon); + reloadPlugin(idOrFileOrAddon) { + const error = this.reloadAddon(idOrFileOrAddon); if (error) Modals.showAddonErrors({plugins: [error]}); return typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon; } @@ -107,21 +107,19 @@ export default new class PluginManager extends AddonManager { window.__filename = path.basename(module.filename); window.__dirname = this.addonFolder; const wrapped = `(${vm.compileFunction(fileContent, ["exports", "require", "module", "__filename", "__dirname"]).toString()})`; - // console.log(module); - module.exports = new Promise(resolve => { - IPC.runScript(`${wrapped}(window.module.exports, window.require, window.module, window.__filename, window.__dirname)`).then(() => { - // console.log(window.module); - meta.exports = module.exports; - module.exports = meta; - delete window.module; - delete window.__filename; - delete window.__dirname; - resolve(); - }); - }); - // module._compile(fileContent, module.filename); - // meta.exports = module.exports; - // module.exports = meta; + const final = `${wrapped}(window.module.exports, window.require, window.module, window.__filename, window.__dirname)`; + + const container = document.createElement("script"); + container.innerHTML = final; + container.id = `${meta.id}-script-container`; + // container.src = `data:text/javascript;${btoa(final)}`; + document.head.append(container); + + meta.exports = module.exports; + module.exports = meta; + delete window.module; + delete window.__filename; + delete window.__dirname; return ""; } @@ -139,7 +137,7 @@ export default new class PluginManager extends AddonManager { catch (err) { this.state[addon.id] = false; Toasts.error(Strings.Addons.couldNotStart.format({name: addon.name, version: addon.version})); - Logger.stacktrace(this.name, addon.name + " could not be started.", err); + Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be started.`, err); return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "start()"}), {message: err.message, stack: err.stack}); } this.emit("started", addon.id); @@ -156,7 +154,7 @@ export default new class PluginManager extends AddonManager { catch (err) { this.state[addon.id] = false; Toasts.error(Strings.Addons.couldNotStop.format({name: addon.name, version: addon.version})); - Logger.stacktrace(this.name, addon.name + " could not be stopped.", err); + Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be started.`, err); return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "stop()"}), {message: err.message, stack: err.stack}); } this.emit("stopped", addon.id); @@ -185,7 +183,7 @@ export default new class PluginManager extends AddonManager { if (!this.state[this.addonList[i].id]) continue; if (typeof(plugin.onSwitch) === "function") { try {plugin.onSwitch();} - catch (err) {Logger.stacktrace(this.name, "Unable to fire onSwitch for " + this.addonList[i].name + ".", err);} + catch (err) {Logger.stacktrace(this.name, `Unable to fire onSwitch for ${this.addonList[i].name} v${this.addonList[i].version}`, err);} } } } @@ -196,7 +194,7 @@ export default new class PluginManager extends AddonManager { if (!this.state[this.addonList[i].id]) continue; if (typeof plugin.observer === "function") { try {plugin.observer(mutation);} - catch (err) {Logger.stacktrace(this.name, "Unable to fire observer for " + this.addonList[i].name + ".", err);} + catch (err) {Logger.stacktrace(this.name, `Unable to fire observer for ${this.addonList[i].name} v${this.addonList[i].version}`, err);} } } } diff --git a/renderer/src/modules/thememanager.js b/renderer/src/modules/thememanager.js index 47abdf8d..0a1c36b7 100644 --- a/renderer/src/modules/thememanager.js +++ b/renderer/src/modules/thememanager.js @@ -19,8 +19,8 @@ export default new class ThemeManager extends AddonManager { get prefix() {return "theme";} get language() {return "css";} - async initialize() { - const errors = await super.initialize(); + initialize() { + const errors = super.initialize(); Settings.registerPanel("themes", Strings.Panels.themes, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.themes, this.addonList, this.state, { type: this.prefix, folder: this.addonFolder, @@ -45,10 +45,10 @@ export default new class ThemeManager extends AddonManager { unloadTheme(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);} loadTheme(filename) {return this.loadAddon(filename);} - async reloadTheme(idOrFileOrAddon) {return await this.reloadAddon(idOrFileOrAddon);} + reloadTheme(idOrFileOrAddon) {return this.reloadAddon(idOrFileOrAddon);} - async loadAddon(filename) { - const error = await super.loadAddon(filename); + loadAddon(filename) { + const error = super.loadAddon(filename); if (error) Modals.showAddonErrors({themes: [error]}); } diff --git a/renderer/src/ui/modals.js b/renderer/src/ui/modals.js index 92ebb701..ea531217 100644 --- a/renderer/src/ui/modals.js +++ b/renderer/src/ui/modals.js @@ -215,7 +215,7 @@ export default class Modals { }; const ModalActions = this.ModalActions; - const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen"); + const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen", "root"); const originalRoot = OriginalModalClasses.root; if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`; const key = ModalActions.openModal(props => { diff --git a/renderer/src/ui/settings/addonlist.jsx b/renderer/src/ui/settings/addonlist.jsx index 6ad4dbd1..75a7f73e 100644 --- a/renderer/src/ui/settings/addonlist.jsx +++ b/renderer/src/ui/settings/addonlist.jsx @@ -108,7 +108,7 @@ export default class AddonList extends React.Component { } get emptyImage() { - const message = Strings.Addons.blankSlateMessage.format({link: `https://betterdiscordlibrary.com/${this.props.type}`, type: this.props.type}).toString(); + const message = Strings.Addons.blankSlateMessage.format({link: `https://betterdiscordlibrary.com/${this.props.type}s`, type: this.props.type}).toString(); return ;