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();
});
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;
});
}
}

View File

@ -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
}

View File

@ -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";

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,
settings: [
{type: "switch", id: "debuggerHotkey", value: false},
{type: "switch", id: "copySelector", value: false},
{type: "switch", id: "reactDevTools", value: false}
]
},

View File

@ -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.",

View File

@ -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);
}

View File

@ -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);
};

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

View File

@ -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);}
}
}
}

View File

@ -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]});
}

View File

@ -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 => {

View File

@ -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 <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>
</EmptyImage>;