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)
This commit is contained in:
Zack Rauen 2021-04-02 23:16:23 -04:00
parent 59d9ec3dea
commit a223806060
18 changed files with 136 additions and 70 deletions

View File

@ -8,4 +8,5 @@ export const RUN_SCRIPT = "bd-run-script";
export const NAVIGATE = "bd-did-navigate-in-page"; export const NAVIGATE = "bd-did-navigate-in-page";
export const OPEN_DEVTOOLS = "bd-open-devtools"; export const OPEN_DEVTOOLS = "bd-open-devtools";
export const CLOSE_DEVTOOLS = "bd-close-devtools"; export const CLOSE_DEVTOOLS = "bd-close-devtools";
export const OPEN_WINDOW = "bd-open-window"; export const OPEN_WINDOW = "bd-open-window";
export const INSPECT_ELEMENT = "bd-inspect-element";

View File

@ -1,34 +1,35 @@
const path = require("path"); import path from "path";
const electron = require("electron"); import {app} from "electron";
const Module = require("module"); import Module from "module";
import ipc from "./modules/ipc"; import ipc from "./modules/ipc";
import BrowserWindow from "./modules/browserwindow"; import BrowserWindow from "./modules/browserwindow";
import CSP from "./modules/csp"; import CSP from "./modules/csp";
if (!process.argv.includes("--vanilla")) {
process.env.NODE_OPTIONS = "--no-force-async-hooks-checks"; process.env.NODE_OPTIONS = "--no-force-async-hooks-checks";
electron.app.commandLine.appendSwitch("no-force-async-hooks-checks"); app.commandLine.appendSwitch("no-force-async-hooks-checks");
process.electronBinding("command_line").appendSwitch("no-force-async-hooks-checks"); process.electronBinding("command_line").appendSwitch("no-force-async-hooks-checks");
// Patch and replace the built-in BrowserWindow // Patch and replace the built-in BrowserWindow
BrowserWindow.patchBrowserWindow(); BrowserWindow.patchBrowserWindow();
// Register all IPC events // Register all IPC events
ipc.registerEvents(); ipc.registerEvents();
// Remove CSP immediately on linux since they install to discord_desktop_core still // 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); if (process.platform == "win32" || process.platform == "darwin") app.once("ready", CSP.remove);
else CSP.remove(); else CSP.remove();
}
// Use Discord's info to run the app // Use Discord's info to run the app
if (process.platform == "win32" || process.platform == "darwin") { 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")); const pkg = __non_webpack_require__(path.join(basePath, "package.json"));
electron.app.setAppPath(basePath); app.setAppPath(basePath);
electron.app.name = pkg.name; app.name = pkg.name;
Module._load(path.join(basePath, pkg.main), null, true); Module._load(path.join(basePath, pkg.main), null, true);
} }

View File

@ -1,6 +1,6 @@
const fs = require("fs"); import fs from "fs";
const path = require("path"); import path from "path";
const electron = require("electron"); import electron from "electron";
import ReactDevTools from "./reactdevtools"; import ReactDevTools from "./reactdevtools";
import * as IPCEvents from "common/constants/ipcevents"; import * as IPCEvents from "common/constants/ipcevents";
@ -21,8 +21,12 @@ electron.app.once("ready", async () => {
await ReactDevTools.install(); 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 { export default class BetterDiscord {
static getWindowPrefs() { static getWindowPrefs() {
if (!fs.existsSync(buildInfoFile)) return {}; if (!fs.existsSync(buildInfoFile)) return {};
@ -37,7 +41,7 @@ export default class BetterDiscord {
try { try {
const buildInfo = __non_webpack_require__(buildInfoFile); 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) ?? {}; this._settings = __non_webpack_require__(settingsFile) ?? {};
return this._settings[category]?.[key]; return this._settings[category]?.[key];
} }

View File

@ -1,5 +1,5 @@
const electron = require("electron"); import electron from "electron";
const path = require("path"); import path from "path";
import BetterDiscord from "./betterdiscord"; import BetterDiscord from "./betterdiscord";

View File

@ -1,4 +1,4 @@
const electron = require("electron"); import electron from "electron";
export default class { export default class {
static remove() { static remove() {

View File

@ -8,20 +8,20 @@ const getPath = (event, pathReq) => {
case "appPath": case "appPath":
returnPath = app.getAppPath(); returnPath = app.getAppPath();
break; break;
case "appData": case "appData":
case "userData": case "userData":
case "home": case "home":
case "cache": case "cache":
case "temp": case "temp":
case "exe": case "exe":
case "module": case "module":
case "desktop": case "desktop":
case "documents": case "documents":
case "downloads": case "downloads":
case "music": case "music":
case "pictures": case "pictures":
case "videos": case "videos":
case "recent": case "recent":
case "logs": case "logs":
returnPath = app.getPath(pathReq); returnPath = app.getPath(pathReq);
break; 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 { export default class IPCMain {
static registerEvents() { static registerEvents() {
ipc.on(IPCEvents.GET_PATH, getPath); ipc.on(IPCEvents.GET_PATH, getPath);
ipc.on(IPCEvents.RELAUNCH, relaunch); ipc.on(IPCEvents.RELAUNCH, relaunch);
ipc.on(IPCEvents.OPEN_DEVTOOLS, openDevTools); ipc.on(IPCEvents.OPEN_DEVTOOLS, openDevTools);
ipc.on(IPCEvents.CLOSE_DEVTOOLS, closeDevTools); ipc.on(IPCEvents.CLOSE_DEVTOOLS, closeDevTools);
ipc.on(IPCEvents.INSPECT_ELEMENT, inspectElement);
ipc.handle(IPCEvents.RUN_SCRIPT, runScript); ipc.handle(IPCEvents.RUN_SCRIPT, runScript);
ipc.handle(IPCEvents.OPEN_WINDOW, createBrowserWindow); ipc.handle(IPCEvents.OPEN_WINDOW, createBrowserWindow);
} }

View File

@ -6174,9 +6174,9 @@
} }
}, },
"y18n": { "y18n": {
"version": "5.0.4", "version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.4.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ==", "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==",
"dev": true "dev": true
}, },
"yargs": { "yargs": {
@ -8812,8 +8812,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true, "dev": true
"optional": true
}, },
"readdirp": { "readdirp": {
"version": "3.4.0", "version": "3.4.0",

View File

@ -18,24 +18,10 @@ export default new class ColoredText extends Builtin {
} }
injectColoredText() { injectColoredText() {
const MessageContent = WebpackModules.getModule(m => m.default && m.default.displayName && m.default.displayName == "Message"); const MessageContent = WebpackModules.getModule(m => m.type && m.type.displayName === "MessageContent");
this.before(MessageContent, "default", (thisObject, [props]) => { this.after(MessageContent, "type", (thisObject, [props], returnValue) => {
if (!props || !props.childrenMessageContent) return; const roleColor = this.getRoleColor(props.message.channel_id, props.message.author.id) || "";
const messageContent = props.childrenMessageContent; returnValue.props.style = {color: roleColor};
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);
}); });
} }

View File

@ -6,6 +6,7 @@ export {default as WindowPrefs} from "./windowprefs";
export {default as ClassNormalizer} from "./general/classnormalizer"; export {default as ClassNormalizer} from "./general/classnormalizer";
export {default as PublicServers} from "./general/publicservers"; export {default as PublicServers} from "./general/publicservers";
export {default as VoiceDisconnect} from "./general/voicedisconnect"; export {default as VoiceDisconnect} from "./general/voicedisconnect";
export {default as MediaKeys} from "./general/mediakeys";
export {default as TwentyFourHour} from "./appearance/24hour"; export {default as TwentyFourHour} from "./appearance/24hour";
export {default as ColoredText} from "./appearance/coloredtext"; 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 EmoteAutocaps} from "./emotes/emoteautocaps";
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";
export {default as InspectElement} from "./developer/inspectelement";

View File

@ -71,7 +71,7 @@ export default new class CustomCSS extends Builtin {
onClick: (thisObject) => { onClick: (thisObject) => {
if (this.isDetached) return; if (this.isDetached) return;
if (this.nativeOpen) return this.openNative(); 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"]}); const settingsView = Utilities.findInRenderTree(thisObject._reactInternalFiber, m => m && m.onSetSection, {walkable: ["child", "memoizedProps", "props", "children"]});
if (settingsView && settingsView.onSetSection) settingsView.onSetSection(this.id); if (settingsView && settingsView.onSetSection) settingsView.onSetSection(this.id);
} }

View File

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

View File

@ -37,7 +37,7 @@ export default new class EmoteModule extends Builtin {
get(id) {return super.get("emotes", "general", id);} 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 Emotes() {return Emotes;}
get TwitchGlobal() {return Emotes.TwitchGlobal;} get TwitchGlobal() {return Emotes.TwitchGlobal;}

View File

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

View File

@ -1,6 +1,6 @@
import Builtin from "../structs/builtin"; import Builtin from "../structs/builtin";
import Modals from "../ui/modals"; import Modals from "../ui/modals";
import {DataStore, Strings, IPC} from "modules"; import {Strings, IPC} from "modules";
export default new class WindowPrefs extends Builtin { export default new class WindowPrefs extends Builtin {
get name() {return "WindowPrefs";} get name() {return "WindowPrefs";}

View File

@ -8,7 +8,8 @@ export default [
{type: "switch", id: "publicServers", value: true}, {type: "switch", id: "publicServers", value: true},
{type: "switch", id: "voiceDisconnect", value: false}, {type: "switch", id: "voiceDisconnect", value: false},
{type: "switch", id: "classNormalizer", 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, shown: false,
settings: [ settings: [
{type: "switch", id: "debuggerHotkey", value: false}, {type: "switch", id: "debuggerHotkey", value: false},
{type: "switch", id: "reactDevTools", value: false} {type: "switch", id: "reactDevTools", value: false},
{type: "switch", id: "inspectElement", value: false}
] ]
}, },
{ {

View File

@ -109,6 +109,10 @@ export default {
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"
},
inspectElement: {
name: "Inspect Element Hotkey",
note: "Enables the inspect element hotkey (ctrl + shift + c) that is common in most browsers"
} }
}, },
window: { window: {
@ -150,6 +154,10 @@ export default {
animateOnHover: { animateOnHover: {
name: "Animate On Hover", name: "Animate On Hover",
note: "Only animate the emote modifiers 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: { categories: {
@ -269,7 +277,7 @@ export default {
}, },
ReactDevTools: { ReactDevTools: {
notFound: "Extension Not Found", 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: { Sorting: {
sortBy: "Sort By", sortBy: "Sort By",

View File

@ -31,4 +31,8 @@ export default new class IPCRenderer {
openWindow(url, options) { openWindow(url, options) {
return ipc.invoke(IPCEvents.OPEN_WINDOW, url, options); return ipc.invoke(IPCEvents.OPEN_WINDOW, url, options);
} }
inspectElement() {
ipc.send(IPCEvents.INSPECT_ELEMENT);
}
}; };

View File

@ -21,6 +21,7 @@ const BdApi = {
get emotes() { get emotes() {
return new Proxy(Emotes.Emotes, { return new Proxy(Emotes.Emotes, {
get(category) { get(category) {
if (category === "blocklist") return Emotes.blocklist;
const group = Emotes.Emotes[category]; const group = Emotes.Emotes[category];
if (!group) return undefined; if (!group) return undefined;
return new Proxy(group, { return new Proxy(group, {