Fix multiple bugs, change to sync loading

- Removes copy selector developer option
- Add a crash check in the injector
- Switch from throwing over the wall to using <script> elements
- Revert to synchronous loading due to above
- Don't load on login screen
- Plugin logging includes version numbers
- Fix blankslate links
- Fix changelog overflow
This commit is contained in:
Zack Rauen 2021-03-18 17:50:47 -04:00
parent 1312bca78f
commit f3f15ec72c
13 changed files with 70 additions and 135 deletions

View File

@ -22,6 +22,8 @@ electron.app.once("ready", async () => {
await ReactDevTools.install(); await ReactDevTools.install();
}); });
let hasCrashed = false;
export default class BetterDiscord { export default class BetterDiscord {
static getWindowPrefs() { static getWindowPrefs() {
if (!fs.existsSync(buildInfoFile)) return {}; if (!fs.existsSync(buildInfoFile)) return {};
@ -89,12 +91,24 @@ export default class BetterDiscord {
// When DOM is available, pass the renderer over the wall // When DOM is available, pass the renderer over the wall
browserWindow.webContents.on("dom-ready", () => { 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 // This is used to alert renderer code to onSwitch events
browserWindow.webContents.on("did-navigate-in-page", () => { browserWindow.webContents.on("did-navigate-in-page", () => {
browserWindow.webContents.send(IPCEvents.NAVIGATE); browserWindow.webContents.send(IPCEvents.NAVIGATE);
}); });
browserWindow.webContents.on("render-process-gone", (event, details) => {
hasCrashed = true;
});
} }
} }

View File

@ -21,7 +21,12 @@ if (preload) {
process.electronBinding("command_line").appendSwitch("preload", preload); process.electronBinding("command_line").appendSwitch("preload", preload);
// Run original preload // Run original preload
try {require(preload);} try {
const originalKill = process.kill;
process.kill = function() {};
require(preload);
process.kill = originalKill;
}
catch (e) { catch (e) {
// TODO bail out // TODO bail out
} }

View File

@ -17,6 +17,5 @@ export {default as EmoteModule} from "./emotes/emotes";
export {default as EmoteMenu} from "./emotes/emotemenu"; export {default as EmoteMenu} from "./emotes/emotemenu";
// export {default as EmoteAutocaps} from "./emotes/emoteautocaps"; // export {default as EmoteAutocaps} from "./emotes/emoteautocaps";
export {default as CopySelector} from "./developer/copyselector";
export {default as Debugger} from "./developer/debugger"; export {default as Debugger} from "./developer/debugger";
export {default as ReactDevTools} from "./developer/reactdevtools"; export {default as ReactDevTools} from "./developer/reactdevtools";

View File

@ -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(`<div class="layer-v9HyYc da-layer">`);
cm = DOM.createElement(`<div class="contextMenu-HLZMGh da-contextMenu bd-context-menu"></div>`);
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(`<div class="itemGroup-1tL0uz da-itemGroup">`);
const cmi = DOM.createElement(`<div class="item-1Yvehc itemBase-tz5SeC da-item da-itemBase clickable-11uBi- da-clickable">`);
cmi.append(DOM.createElement(`<div class="label-JWQiNe da-label">${Strings.Developer.copySelector}</div>`));
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;
}
};

View File

@ -52,7 +52,6 @@ export default [
shown: false, shown: false,
settings: [ settings: [
{type: "switch", id: "debuggerHotkey", value: false}, {type: "switch", id: "debuggerHotkey", value: false},
{type: "switch", id: "copySelector", value: false},
{type: "switch", id: "reactDevTools", value: false} {type: "switch", id: "reactDevTools", value: false}
] ]
}, },

View File

@ -106,10 +106,6 @@ export default {
name: "Debugger Hotkey", name: "Debugger Hotkey",
note: "Allows activating debugger when pressing F8" 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: { reactDevTools: {
name: "React Developer Tools", name: "React Developer Tools",
note: "Injects your local installation of React Developer Tools into Discord" note: "Injects your local installation of React Developer Tools into Discord"
@ -227,9 +223,6 @@ export default {
settings: "Editor Settings", settings: "Editor Settings",
editorTitle: "Custom CSS Editor" editorTitle: "Custom CSS Editor"
}, },
Developer: {
copySelector: "Copy Selector"
},
Emotes: { Emotes: {
loading: "Loading emotes in the background do not reload.", loading: "Loading emotes in the background do not reload.",
loaded: "All emotes successfully loaded.", loaded: "All emotes successfully loaded.",

View File

@ -48,14 +48,14 @@ export default class AddonManager {
this.windows = new Set(); this.windows = new Set();
} }
async initialize() { initialize() {
this.originalRequire = Module._extensions[this.moduleExtension]; this.originalRequire = Module._extensions[this.moduleExtension];
Module._extensions[this.moduleExtension] = this.getAddonRequire(); Module._extensions[this.moduleExtension] = this.getAddonRequire();
Settings.on(this.collection, this.category, this.id, (enabled) => { Settings.on(this.collection, this.category, this.id, (enabled) => {
if (enabled) this.watchAddons(); if (enabled) this.watchAddons();
else this.unwatchAddons(); 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 // 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 // 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; if (typeof(filename) === "undefined") return;
try { try {
const addon = __non_webpack_require__(path.resolve(this.addonFolder, filename)); const addon = __non_webpack_require__(path.resolve(this.addonFolder, filename));
await Promise.resolve(addon);
} }
catch (error) { catch (error) {
return new AddonError(filename, filename, Strings.Addons.compileError, {message: error.message, stack: error.stack}); return new AddonError(filename, filename, Strings.Addons.compileError, {message: error.message, stack: error.stack});
@ -234,11 +233,11 @@ export default class AddonManager {
return true; 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 addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
const didUnload = this.unloadAddon(addon, shouldToast, true); const didUnload = this.unloadAddon(addon, shouldToast, true);
if (addon && !didUnload) return didUnload; if (addon && !didUnload) return didUnload;
return await this.loadAddon(addon ? addon.filename : idOrFileOrAddon, shouldToast); return this.loadAddon(addon ? addon.filename : idOrFileOrAddon, shouldToast);
} }
isLoaded(idOrFile) { isLoaded(idOrFile) {
@ -293,7 +292,7 @@ export default class AddonManager {
for (const name of results.removed) this.unloadAddon(name); for (const name of results.removed) this.unloadAddon(name);
} }
async loadAllAddons() { loadAllAddons() {
this.loadState(); this.loadState();
const errors = []; const errors = [];
const files = fs.readdirSync(this.addonFolder); const files = fs.readdirSync(this.addonFolder);
@ -321,7 +320,7 @@ export default class AddonManager {
// Rename the file and let it go on // Rename the file and let it go on
fs.renameSync(absolutePath, path.resolve(this.addonFolder, newFilename)); 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); if (addon instanceof AddonError) errors.push(addon);
} }

View File

@ -26,11 +26,18 @@ export default new class Core {
// (() => { // (() => {
// const fs = require("fs"); // const fs = require("fs");
// fs.appendFileSync("Z:\\debug.log", "\n\n\n"); // 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"); // 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; Config.appPath = process.env.DISCORD_APP_PATH;
@ -106,7 +113,7 @@ export default new class Core {
const guild = GuildClasses.listItem.split(" ")[0]; const guild = GuildClasses.listItem.split(" ")[0];
const blob = GuildClasses.blobContainer.split(" ")[0]; const blob = GuildClasses.blobContainer.split(" ")[0];
if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(Config.deferLoaded = true); 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); setTimeout(checkForGuilds, 100);
}; };

View File

@ -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"]}, 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"]}, 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"]}, 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 = { const newEmotes = {

View File

@ -38,8 +38,8 @@ export default new class PluginManager extends AddonManager {
}); });
} }
async initialize() { initialize() {
const errors = await super.initialize(); const errors = super.initialize();
this.setupFunctions(); this.setupFunctions();
Settings.registerPanel("plugins", Strings.Panels.plugins, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.plugins, this.addonList, this.state, { Settings.registerPanel("plugins", Strings.Panels.plugins, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.plugins, this.addonList, this.state, {
type: this.prefix, type: this.prefix,
@ -66,13 +66,13 @@ export default new class PluginManager extends AddonManager {
unloadPlugin(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);} unloadPlugin(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);}
loadPlugin(filename) {return this.loadAddon(filename);} loadPlugin(filename) {return this.loadAddon(filename);}
async loadAddon(filename) { loadAddon(filename) {
const error = await super.loadAddon(filename); const error = super.loadAddon(filename);
if (error) Modals.showAddonErrors({plugins: [error]}); if (error) Modals.showAddonErrors({plugins: [error]});
} }
async reloadPlugin(idOrFileOrAddon) { reloadPlugin(idOrFileOrAddon) {
const error = await this.reloadAddon(idOrFileOrAddon); const error = this.reloadAddon(idOrFileOrAddon);
if (error) Modals.showAddonErrors({plugins: [error]}); if (error) Modals.showAddonErrors({plugins: [error]});
return typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon; 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.__filename = path.basename(module.filename);
window.__dirname = this.addonFolder; window.__dirname = this.addonFolder;
const wrapped = `(${vm.compileFunction(fileContent, ["exports", "require", "module", "__filename", "__dirname"]).toString()})`; const wrapped = `(${vm.compileFunction(fileContent, ["exports", "require", "module", "__filename", "__dirname"]).toString()})`;
// console.log(module); const final = `${wrapped}(window.module.exports, window.require, window.module, window.__filename, window.__dirname)`;
module.exports = new Promise(resolve => {
IPC.runScript(`${wrapped}(window.module.exports, window.require, window.module, window.__filename, window.__dirname)`).then(() => { const container = document.createElement("script");
// console.log(window.module); container.innerHTML = final;
meta.exports = module.exports; container.id = `${meta.id}-script-container`;
module.exports = meta; // container.src = `data:text/javascript;${btoa(final)}`;
delete window.module; document.head.append(container);
delete window.__filename;
delete window.__dirname; meta.exports = module.exports;
resolve(); module.exports = meta;
}); delete window.module;
}); delete window.__filename;
// module._compile(fileContent, module.filename); delete window.__dirname;
// meta.exports = module.exports;
// module.exports = meta;
return ""; return "";
} }
@ -139,7 +137,7 @@ export default new class PluginManager extends AddonManager {
catch (err) { catch (err) {
this.state[addon.id] = false; this.state[addon.id] = false;
Toasts.error(Strings.Addons.couldNotStart.format({name: addon.name, version: addon.version})); 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}); return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "start()"}), {message: err.message, stack: err.stack});
} }
this.emit("started", addon.id); this.emit("started", addon.id);
@ -156,7 +154,7 @@ export default new class PluginManager extends AddonManager {
catch (err) { catch (err) {
this.state[addon.id] = false; this.state[addon.id] = false;
Toasts.error(Strings.Addons.couldNotStop.format({name: addon.name, version: addon.version})); 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}); return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "stop()"}), {message: err.message, stack: err.stack});
} }
this.emit("stopped", addon.id); 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 (!this.state[this.addonList[i].id]) continue;
if (typeof(plugin.onSwitch) === "function") { if (typeof(plugin.onSwitch) === "function") {
try {plugin.onSwitch();} 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 (!this.state[this.addonList[i].id]) continue;
if (typeof plugin.observer === "function") { if (typeof plugin.observer === "function") {
try {plugin.observer(mutation);} 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);}
} }
} }
} }

View File

@ -19,8 +19,8 @@ export default new class ThemeManager extends AddonManager {
get prefix() {return "theme";} get prefix() {return "theme";}
get language() {return "css";} get language() {return "css";}
async initialize() { initialize() {
const errors = await super.initialize(); const errors = super.initialize();
Settings.registerPanel("themes", Strings.Panels.themes, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.themes, this.addonList, this.state, { Settings.registerPanel("themes", Strings.Panels.themes, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.themes, this.addonList, this.state, {
type: this.prefix, type: this.prefix,
folder: this.addonFolder, folder: this.addonFolder,
@ -45,10 +45,10 @@ export default new class ThemeManager extends AddonManager {
unloadTheme(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);} unloadTheme(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);}
loadTheme(filename) {return this.loadAddon(filename);} loadTheme(filename) {return this.loadAddon(filename);}
async reloadTheme(idOrFileOrAddon) {return await this.reloadAddon(idOrFileOrAddon);} reloadTheme(idOrFileOrAddon) {return this.reloadAddon(idOrFileOrAddon);}
async loadAddon(filename) { loadAddon(filename) {
const error = await super.loadAddon(filename); const error = super.loadAddon(filename);
if (error) Modals.showAddonErrors({themes: [error]}); if (error) Modals.showAddonErrors({themes: [error]});
} }

View File

@ -215,7 +215,7 @@ export default class Modals {
}; };
const ModalActions = this.ModalActions; const ModalActions = this.ModalActions;
const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen"); const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen", "root");
const originalRoot = OriginalModalClasses.root; const originalRoot = OriginalModalClasses.root;
if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`; if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`;
const key = ModalActions.openModal(props => { const key = ModalActions.openModal(props => {

View File

@ -108,7 +108,7 @@ export default class AddonList extends React.Component {
} }
get emptyImage() { 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 <EmptyImage title={Strings.Addons.blankSlateHeader.format({type: this.props.type})} message={message}> return <EmptyImage title={Strings.Addons.blankSlateHeader.format({type: this.props.type})} message={message}>
<button className="bd-button" onClick={this.openFolder}>{Strings.Addons.openFolder.format({type: this.props.type})}</button> <button className="bd-button" onClick={this.openFolder}>{Strings.Addons.openFolder.format({type: this.props.type})}</button>
</EmptyImage>; </EmptyImage>;