From a2238060607a98720b761672f603bbff4a40f99b Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Fri, 2 Apr 2021 23:16:23 -0400 Subject: [PATCH] Add a few small features - Fixes colored text - Fixes detach window not showing saved css (closes #635) - Adds option to prevent Discord from hijacking the media keys (closes #410) - Adds command line flag to launch a vanilla version of Discord `--vanilla` (closes #337) - Adds option for app-wide ctrl+shift+c shortcut for inspect element (closes #349) - Adds emote blocklist to BdApi via `BdApi.Emotes.blocklist` (closes #408) --- common/constants/ipcevents.js | 3 +- injector/src/index.js | 35 +++++++++--------- injector/src/modules/betterdiscord.js | 14 ++++--- injector/src/modules/browserwindow.js | 4 +- injector/src/modules/csp.js | 2 +- injector/src/modules/ipc.js | 37 ++++++++++++------- renderer/package-lock.json | 9 ++--- .../src/builtins/appearance/coloredtext.js | 22 ++--------- renderer/src/builtins/builtins.js | 4 +- renderer/src/builtins/customcss.js | 2 +- .../src/builtins/developer/inspectelement.js | 22 +++++++++++ renderer/src/builtins/emotes/emotes.js | 2 +- renderer/src/builtins/general/mediakeys.js | 27 ++++++++++++++ renderer/src/builtins/windowprefs.js | 2 +- renderer/src/data/settings/config.js | 6 ++- renderer/src/data/strings.js | 10 ++++- renderer/src/modules/ipc.js | 4 ++ renderer/src/modules/pluginapi.js | 1 + 18 files changed, 136 insertions(+), 70 deletions(-) create mode 100644 renderer/src/builtins/developer/inspectelement.js create mode 100644 renderer/src/builtins/general/mediakeys.js diff --git a/common/constants/ipcevents.js b/common/constants/ipcevents.js index 352e3a60..6315a1a0 100644 --- a/common/constants/ipcevents.js +++ b/common/constants/ipcevents.js @@ -8,4 +8,5 @@ export const RUN_SCRIPT = "bd-run-script"; export const NAVIGATE = "bd-did-navigate-in-page"; export const OPEN_DEVTOOLS = "bd-open-devtools"; export const CLOSE_DEVTOOLS = "bd-close-devtools"; -export const OPEN_WINDOW = "bd-open-window"; \ No newline at end of file +export const OPEN_WINDOW = "bd-open-window"; +export const INSPECT_ELEMENT = "bd-inspect-element"; \ No newline at end of file diff --git a/injector/src/index.js b/injector/src/index.js index 09d16da8..4e025614 100644 --- a/injector/src/index.js +++ b/injector/src/index.js @@ -1,34 +1,35 @@ -const path = require("path"); -const electron = require("electron"); -const Module = require("module"); +import path from "path"; +import {app} from "electron"; +import Module from "module"; import ipc from "./modules/ipc"; import BrowserWindow from "./modules/browserwindow"; import CSP from "./modules/csp"; - -process.env.NODE_OPTIONS = "--no-force-async-hooks-checks"; -electron.app.commandLine.appendSwitch("no-force-async-hooks-checks"); -process.electronBinding("command_line").appendSwitch("no-force-async-hooks-checks"); +if (!process.argv.includes("--vanilla")) { + process.env.NODE_OPTIONS = "--no-force-async-hooks-checks"; + app.commandLine.appendSwitch("no-force-async-hooks-checks"); + process.electronBinding("command_line").appendSwitch("no-force-async-hooks-checks"); -// Patch and replace the built-in BrowserWindow -BrowserWindow.patchBrowserWindow(); + // Patch and replace the built-in BrowserWindow + BrowserWindow.patchBrowserWindow(); -// Register all IPC events -ipc.registerEvents(); + // Register all IPC events + ipc.registerEvents(); -// Remove CSP immediately on linux since they install to discord_desktop_core still -if (process.platform == "win32" || process.platform == "darwin") electron.app.once("ready", CSP.remove); -else CSP.remove(); + // Remove CSP immediately on linux since they install to discord_desktop_core still + if (process.platform == "win32" || process.platform == "darwin") app.once("ready", CSP.remove); + else CSP.remove(); +} // Use Discord's info to run the app if (process.platform == "win32" || process.platform == "darwin") { - const basePath = path.join(electron.app.getAppPath(), "..", "app.asar"); + const basePath = path.join(app.getAppPath(), "..", "app.asar"); const pkg = __non_webpack_require__(path.join(basePath, "package.json")); - electron.app.setAppPath(basePath); - electron.app.name = pkg.name; + app.setAppPath(basePath); + app.name = pkg.name; Module._load(path.join(basePath, pkg.main), null, true); } diff --git a/injector/src/modules/betterdiscord.js b/injector/src/modules/betterdiscord.js index 3181c706..59f4c143 100644 --- a/injector/src/modules/betterdiscord.js +++ b/injector/src/modules/betterdiscord.js @@ -1,6 +1,6 @@ -const fs = require("fs"); -const path = require("path"); -const electron = require("electron"); +import fs from "fs"; +import path from "path"; +import electron from "electron"; import ReactDevTools from "./reactdevtools"; import * as IPCEvents from "common/constants/ipcevents"; @@ -21,8 +21,12 @@ electron.app.once("ready", async () => { await ReactDevTools.install(); }); -let hasCrashed = false; +if (BetterDiscord.getSetting("general", "mediaKeys")) { + electron.app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService"); +} + +let hasCrashed = false; export default class BetterDiscord { static getWindowPrefs() { if (!fs.existsSync(buildInfoFile)) return {}; @@ -37,7 +41,7 @@ export default class BetterDiscord { try { const buildInfo = __non_webpack_require__(buildInfoFile); - const settingsFile = path.resolve(dataPath, "data", buildInfo.releaseChannel, "settings.json"); + const settingsFile = path.resolve(dataPath, "data", buildInfo.releaseChannel, "settings.json"); this._settings = __non_webpack_require__(settingsFile) ?? {}; return this._settings[category]?.[key]; } diff --git a/injector/src/modules/browserwindow.js b/injector/src/modules/browserwindow.js index 16db5ffb..266506c4 100644 --- a/injector/src/modules/browserwindow.js +++ b/injector/src/modules/browserwindow.js @@ -1,5 +1,5 @@ -const electron = require("electron"); -const path = require("path"); +import electron from "electron"; +import path from "path"; import BetterDiscord from "./betterdiscord"; diff --git a/injector/src/modules/csp.js b/injector/src/modules/csp.js index 480ce6bc..3d825234 100644 --- a/injector/src/modules/csp.js +++ b/injector/src/modules/csp.js @@ -1,4 +1,4 @@ -const electron = require("electron"); +import electron from "electron"; export default class { static remove() { diff --git a/injector/src/modules/ipc.js b/injector/src/modules/ipc.js index a3b2d102..ea4148f0 100644 --- a/injector/src/modules/ipc.js +++ b/injector/src/modules/ipc.js @@ -8,20 +8,20 @@ const getPath = (event, pathReq) => { case "appPath": returnPath = app.getAppPath(); break; - case "appData": - case "userData": - case "home": - case "cache": - case "temp": - case "exe": - case "module": - case "desktop": - case "documents": - case "downloads": - case "music": - case "pictures": - case "videos": - case "recent": + case "appData": + case "userData": + case "home": + case "cache": + case "temp": + case "exe": + case "module": + case "desktop": + case "documents": + case "downloads": + case "music": + case "pictures": + case "videos": + case "recent": case "logs": returnPath = app.getPath(pathReq); break; @@ -62,12 +62,21 @@ const createBrowserWindow = async (event, url, {windowOptions, closeOnUrl} = {}) }); }; +const inspectElement = async event => { + if (!event.sender.isDevToolsOpened()) { + event.sender.openDevTools(); + while (!event.sender.isDevToolsOpened()) await new Promise(r => setTimeout(r, 100)); + } + event.sender.devToolsWebContents.executeJavaScript("DevToolsAPI.enterInspectElementMode();"); +}; + export default class IPCMain { static registerEvents() { ipc.on(IPCEvents.GET_PATH, getPath); ipc.on(IPCEvents.RELAUNCH, relaunch); ipc.on(IPCEvents.OPEN_DEVTOOLS, openDevTools); ipc.on(IPCEvents.CLOSE_DEVTOOLS, closeDevTools); + ipc.on(IPCEvents.INSPECT_ELEMENT, inspectElement); ipc.handle(IPCEvents.RUN_SCRIPT, runScript); ipc.handle(IPCEvents.OPEN_WINDOW, createBrowserWindow); } diff --git a/renderer/package-lock.json b/renderer/package-lock.json index 6e85d743..680df55b 100644 --- a/renderer/package-lock.json +++ b/renderer/package-lock.json @@ -6174,9 +6174,9 @@ } }, "y18n": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.4.tgz", - "integrity": "sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", "dev": true }, "yargs": { @@ -8812,8 +8812,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true + "dev": true }, "readdirp": { "version": "3.4.0", diff --git a/renderer/src/builtins/appearance/coloredtext.js b/renderer/src/builtins/appearance/coloredtext.js index 8d1275b8..83f1d49c 100644 --- a/renderer/src/builtins/appearance/coloredtext.js +++ b/renderer/src/builtins/appearance/coloredtext.js @@ -18,24 +18,10 @@ export default new class ColoredText extends Builtin { } injectColoredText() { - const MessageContent = WebpackModules.getModule(m => m.default && m.default.displayName && m.default.displayName == "Message"); - this.before(MessageContent, "default", (thisObject, [props]) => { - if (!props || !props.childrenMessageContent) return; - const messageContent = props.childrenMessageContent; - if (!messageContent.type || !messageContent.type.type || messageContent.type.type.displayName != "MessageContent") return; - - const originalType = messageContent.type.type; - if (originalType.__originalMethod) return; // Don't patch again - const self = this; - messageContent.type.type = function (childProps) { - const returnValue = originalType(childProps); - const roleColor = self.getRoleColor(childProps.message.channel_id, childProps.message.author.id) || ""; - returnValue.props.style = {color: roleColor}; - return returnValue; - }; - - messageContent.type.type.__originalMethod = originalType; - Object.assign(messageContent.type.type, originalType); + const MessageContent = WebpackModules.getModule(m => m.type && m.type.displayName === "MessageContent"); + this.after(MessageContent, "type", (thisObject, [props], returnValue) => { + const roleColor = this.getRoleColor(props.message.channel_id, props.message.author.id) || ""; + returnValue.props.style = {color: roleColor}; }); } diff --git a/renderer/src/builtins/builtins.js b/renderer/src/builtins/builtins.js index 26d4bd2b..8bee3643 100644 --- a/renderer/src/builtins/builtins.js +++ b/renderer/src/builtins/builtins.js @@ -6,6 +6,7 @@ export {default as WindowPrefs} from "./windowprefs"; export {default as ClassNormalizer} from "./general/classnormalizer"; export {default as PublicServers} from "./general/publicservers"; export {default as VoiceDisconnect} from "./general/voicedisconnect"; +export {default as MediaKeys} from "./general/mediakeys"; export {default as TwentyFourHour} from "./appearance/24hour"; export {default as ColoredText} from "./appearance/coloredtext"; @@ -18,4 +19,5 @@ export {default as EmoteMenu} from "./emotes/emotemenu"; // export {default as EmoteAutocaps} from "./emotes/emoteautocaps"; export {default as Debugger} from "./developer/debugger"; -export {default as ReactDevTools} from "./developer/reactdevtools"; \ No newline at end of file +export {default as ReactDevTools} from "./developer/reactdevtools"; +export {default as InspectElement} from "./developer/inspectelement"; \ No newline at end of file diff --git a/renderer/src/builtins/customcss.js b/renderer/src/builtins/customcss.js index 35f858d2..18249011 100644 --- a/renderer/src/builtins/customcss.js +++ b/renderer/src/builtins/customcss.js @@ -71,7 +71,7 @@ export default new class CustomCSS extends Builtin { onClick: (thisObject) => { if (this.isDetached) return; if (this.nativeOpen) return this.openNative(); - else if (this.startDetached) return this.openDetached(); + else if (this.startDetached) return this.openDetached(this.savedCss); const settingsView = Utilities.findInRenderTree(thisObject._reactInternalFiber, m => m && m.onSetSection, {walkable: ["child", "memoizedProps", "props", "children"]}); if (settingsView && settingsView.onSetSection) settingsView.onSetSection(this.id); } diff --git a/renderer/src/builtins/developer/inspectelement.js b/renderer/src/builtins/developer/inspectelement.js new file mode 100644 index 00000000..43563c94 --- /dev/null +++ b/renderer/src/builtins/developer/inspectelement.js @@ -0,0 +1,22 @@ +import Builtin from "../../structs/builtin"; +import IPC from "../../modules/ipc"; + +export default new class InspectElement extends Builtin { + get name() {return "InspectElementHotkey";} + get category() {return "developer";} + get id() {return "inspectElement";} + + enabled() { + document.addEventListener("keydown", this.inspectElement); + } + + disabled() { + document.removeEventListener("keydown", this.inspectElement); + } + + inspectElement(e) { + if (e.ctrlKey && e.shiftKey && e.which === 67) { // Ctrl + Shift + C + IPC.inspectElement(); + } + } +}; \ No newline at end of file diff --git a/renderer/src/builtins/emotes/emotes.js b/renderer/src/builtins/emotes/emotes.js index 9df0531c..59700834 100644 --- a/renderer/src/builtins/emotes/emotes.js +++ b/renderer/src/builtins/emotes/emotes.js @@ -37,7 +37,7 @@ export default new class EmoteModule extends Builtin { get(id) {return super.get("emotes", "general", id);} - get MessageComponent() {return WebpackModules.find(m => m.default && m.default.displayName && m.default.displayName == "Message");} + get MessageComponent() {return WebpackModules.find(m => m.default && m.default.toString().search("childrenRepliedMessage") > -1);} get Emotes() {return Emotes;} get TwitchGlobal() {return Emotes.TwitchGlobal;} diff --git a/renderer/src/builtins/general/mediakeys.js b/renderer/src/builtins/general/mediakeys.js new file mode 100644 index 00000000..e2fe1a86 --- /dev/null +++ b/renderer/src/builtins/general/mediakeys.js @@ -0,0 +1,27 @@ +import Builtin from "../../structs/builtin"; +import Modals from "../../ui/modals"; +import {Strings, IPC} from "modules"; + +export default new class MediaKeys extends Builtin { + get name() {return "DisableMediaKeys";} + get category() {return "general";} + get id() {return "mediaKeys";} + + enabled() { + this.showModal(); + } + + disabled() { + this.showModal(); + } + + showModal() { + if (!this.initialized) return; + Modals.showConfirmationModal(Strings.Modals.additionalInfo, Strings.Modals.restartPrompt, { + confirmText: Strings.Modals.restartNow, + cancelText: Strings.Modals.restartLater, + danger: true, + onConfirm: () => IPC.relaunch() + }); + } +}; \ No newline at end of file diff --git a/renderer/src/builtins/windowprefs.js b/renderer/src/builtins/windowprefs.js index 7cdab737..932cd31b 100644 --- a/renderer/src/builtins/windowprefs.js +++ b/renderer/src/builtins/windowprefs.js @@ -1,6 +1,6 @@ import Builtin from "../structs/builtin"; import Modals from "../ui/modals"; -import {DataStore, Strings, IPC} from "modules"; +import {Strings, IPC} from "modules"; export default new class WindowPrefs extends Builtin { get name() {return "WindowPrefs";} diff --git a/renderer/src/data/settings/config.js b/renderer/src/data/settings/config.js index 35aeffb2..c0f7d03e 100644 --- a/renderer/src/data/settings/config.js +++ b/renderer/src/data/settings/config.js @@ -8,7 +8,8 @@ export default [ {type: "switch", id: "publicServers", value: true}, {type: "switch", id: "voiceDisconnect", value: false}, {type: "switch", id: "classNormalizer", value: false}, - {type: "switch", id: "showToasts", value: true} + {type: "switch", id: "showToasts", value: true}, + {type: "switch", id: "mediaKeys", value: false} ] }, { @@ -52,7 +53,8 @@ export default [ shown: false, settings: [ {type: "switch", id: "debuggerHotkey", value: false}, - {type: "switch", id: "reactDevTools", value: false} + {type: "switch", id: "reactDevTools", value: false}, + {type: "switch", id: "inspectElement", value: false} ] }, { diff --git a/renderer/src/data/strings.js b/renderer/src/data/strings.js index c28879af..b13e7808 100644 --- a/renderer/src/data/strings.js +++ b/renderer/src/data/strings.js @@ -109,6 +109,10 @@ export default { reactDevTools: { name: "React Developer Tools", note: "Injects your local installation of React Developer Tools into Discord" + }, + inspectElement: { + name: "Inspect Element Hotkey", + note: "Enables the inspect element hotkey (ctrl + shift + c) that is common in most browsers" } }, window: { @@ -150,6 +154,10 @@ export default { animateOnHover: { name: "Animate On Hover", note: "Only animate the emote modifiers on hover" + }, + mediaKeys: { + name: "Disable Media Keys", + note: "Prevents Discord from hijacking your media keys after playing a video." } }, categories: { @@ -269,7 +277,7 @@ export default { }, ReactDevTools: { notFound: "Extension Not Found", - notFoundDetails: "Unable to find the React Developer Tools extension on your PC. Please install the extension on your local Chrome installation." + notFoundDetails: "Unable to find the React Developer Tools extension on your PC. Please install the extension on your local Chrome installation." }, Sorting: { sortBy: "Sort By", diff --git a/renderer/src/modules/ipc.js b/renderer/src/modules/ipc.js index 528dc2e5..ca9ef79c 100644 --- a/renderer/src/modules/ipc.js +++ b/renderer/src/modules/ipc.js @@ -31,4 +31,8 @@ export default new class IPCRenderer { openWindow(url, options) { return ipc.invoke(IPCEvents.OPEN_WINDOW, url, options); } + + inspectElement() { + ipc.send(IPCEvents.INSPECT_ELEMENT); + } }; \ No newline at end of file diff --git a/renderer/src/modules/pluginapi.js b/renderer/src/modules/pluginapi.js index 4a62be80..bf3c877b 100644 --- a/renderer/src/modules/pluginapi.js +++ b/renderer/src/modules/pluginapi.js @@ -21,6 +21,7 @@ const BdApi = { get emotes() { return new Proxy(Emotes.Emotes, { get(category) { + if (category === "blocklist") return Emotes.blocklist; const group = Emotes.Emotes[category]; if (!group) return undefined; return new Proxy(group, {