Restructure BD into a small mono-repo
This is a repo-wide refactor that introduces the injector to the main branch. The new architecture is as such: common/ injector/ renderer/
This commit is contained in:
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -0,0 +1,3 @@
|
||||
# BetterDiscord Injector
|
||||
|
||||
You're probably looking for the main app, [click here](https://github.com/rauenzi/BetterDiscordApp) to go there.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"allowSyntheticDefaultImports": false,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"common": ["../common"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Generated
+1810
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "betterdiscord-injector",
|
||||
"version": "0.6.2",
|
||||
"description": "BetterDiscord injector module",
|
||||
"main": "src/index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cp ./src/preload.js ../dist/preload.js && webpack --progress --color",
|
||||
"watch": "cp ./src/preload.js ../dist/preload.js && webpack --progress --color --watch",
|
||||
"build-prod": "cp ./src/preload.js ../dist/preload.js && webpack --stats minimal --mode production",
|
||||
"lint": "eslint --ext .js src/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"eslint": "^7.21.0",
|
||||
"webpack": "^5.24.2",
|
||||
"webpack-cli": "^4.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
const path = require("path");
|
||||
const electron = require("electron");
|
||||
const Module = require("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");
|
||||
|
||||
|
||||
// Patch and replace the built-in BrowserWindow
|
||||
BrowserWindow.patchBrowserWindow();
|
||||
|
||||
// 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();
|
||||
|
||||
// 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 pkg = __non_webpack_require__(path.join(basePath, "package.json"));
|
||||
electron.app.setAppPath(basePath);
|
||||
electron.app.name = pkg.name;
|
||||
Module._load(path.join(basePath, pkg.main), null, true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const electron = require("electron");
|
||||
|
||||
import ReactDevTools from "./reactdevtools";
|
||||
import * as IPCEvents from "common/constants/ipcevents";
|
||||
|
||||
// Build info file only exists for non-linux (for current injection)
|
||||
const appPath = electron.app.getAppPath();
|
||||
const buildInfoFile = path.resolve(appPath, "..", "build_info.json");
|
||||
|
||||
// Locate data path to find transparency settings
|
||||
let dataPath = "";
|
||||
if (process.platform === "win32") dataPath = process.env.APPDATA;
|
||||
else if (process.platform === "darwin") dataPath = path.join(process.env.HOME, "Library", "Preferences");
|
||||
else dataPath = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, ".config");
|
||||
dataPath = path.join(dataPath, "BetterDiscord") + "/";
|
||||
|
||||
|
||||
electron.app.once("ready", async () => {
|
||||
if (!BetterDiscord.getSetting("developer", "reactDevTools")) return;
|
||||
await ReactDevTools.install();
|
||||
});
|
||||
|
||||
export default class BetterDiscord {
|
||||
static getWindowPrefs() {
|
||||
if (!fs.existsSync(buildInfoFile)) return {};
|
||||
const buildInfo = __non_webpack_require__(buildInfoFile);
|
||||
const prefsFile = path.resolve(dataPath, "data", buildInfo.releaseChannel, "windowprefs.json");
|
||||
if (!fs.existsSync(prefsFile)) return {};
|
||||
return __non_webpack_require__(prefsFile);
|
||||
}
|
||||
|
||||
static getSetting(category, key) {
|
||||
if (this._settings) return this._settings[category]?.[key];
|
||||
|
||||
try {
|
||||
const buildInfo = __non_webpack_require__(buildInfoFile);
|
||||
const settingsFile = path.resolve(dataPath, "data", buildInfo.releaseChannel, "settings.json");
|
||||
this._settings = __non_webpack_require__(settingsFile) ?? {};
|
||||
return this._settings[category]?.[key];
|
||||
}
|
||||
catch (_) {
|
||||
this._settings = {};
|
||||
return this._settings[category]?.[key];
|
||||
}
|
||||
}
|
||||
|
||||
static ensureDirectories() {
|
||||
if (!fs.existsSync(dataPath)) fs.mkdirSync(dataPath);
|
||||
if (!fs.existsSync(path.join(dataPath, "plugins"))) fs.mkdirSync(path.join(dataPath, "plugins"));
|
||||
if (!fs.existsSync(path.join(dataPath, "themes"))) fs.mkdirSync(path.join(dataPath, "themes"));
|
||||
}
|
||||
|
||||
static async ensureWebpackModules(browserWindow) {
|
||||
await browserWindow.webContents.executeJavaScript(`new Promise(resolve => {
|
||||
const check = function() {
|
||||
if (window.webpackJsonp && window.webpackJsonp.flat().flat().length >= 7000) return resolve();
|
||||
setTimeout(check, 100);
|
||||
};
|
||||
check();
|
||||
});`);
|
||||
}
|
||||
|
||||
static async injectRenderer(browserWindow) {
|
||||
const location = path.join(__dirname, "renderer.js");
|
||||
if (!fs.existsSync(location)) return; // TODO: cut a fatal log
|
||||
const content = fs.readFileSync(location).toString();
|
||||
const success = await browserWindow.webContents.executeJavaScript(`
|
||||
(() => {
|
||||
try {
|
||||
${content}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
`);
|
||||
|
||||
if (!success) return; // TODO: cut a fatal log
|
||||
}
|
||||
|
||||
static setup(browserWindow) {
|
||||
// Setup some useful vars to avoid blocking IPC calls
|
||||
process.env.DISCORD_PRELOAD = browserWindow.__originalPreload;
|
||||
process.env.DISCORD_APP_PATH = appPath;
|
||||
process.env.DISCORD_USER_DATA = electron.app.getPath("userData");
|
||||
process.env.BETTERDISCORD_DATA_PATH = dataPath;
|
||||
|
||||
// When DOM is available, pass the renderer over the wall
|
||||
browserWindow.webContents.on("dom-ready", () => {
|
||||
this.injectRenderer(browserWindow);
|
||||
});
|
||||
|
||||
// This is used to alert renderer code to onSwitch events
|
||||
browserWindow.webContents.on("did-navigate-in-page", () => {
|
||||
browserWindow.webContents.send(IPCEvents.NAVIGATE);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
const electron = require("electron");
|
||||
const path = require("path");
|
||||
|
||||
import BetterDiscord from "./betterdiscord";
|
||||
|
||||
class BrowserWindow extends electron.BrowserWindow {
|
||||
constructor(options) {
|
||||
if (!options || !options.webPreferences || !options.webPreferences.preload || !options.title) return super(options); // eslint-disable-line constructor-super
|
||||
const originalPreload = options.webPreferences.preload;
|
||||
options.webPreferences.preload = path.join(__dirname, "preload.js");
|
||||
|
||||
// Don't allow just "truthy" values
|
||||
const shouldBeTransparent = BetterDiscord.getSetting("window", "transparency");
|
||||
if (typeof(shouldBeTransparent) === "boolean" && shouldBeTransparent) {
|
||||
options.transparent = true;
|
||||
options.backgroundColor = "#00000000";
|
||||
}
|
||||
|
||||
// Only affect frame if it is *explicitly* set
|
||||
// const shouldHaveFrame = BetterDiscord.getSetting("window", "frame");
|
||||
// if (typeof(shouldHaveFrame) === "boolean") options.frame = shouldHaveFrame;
|
||||
|
||||
super(options);
|
||||
this.__originalPreload = originalPreload;
|
||||
BetterDiscord.setup(this);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(BrowserWindow, electron.BrowserWindow);
|
||||
|
||||
export default class {
|
||||
static patchBrowserWindow() {
|
||||
// Reassign electron using proxy to avoid the onReady issue, thanks Powercord!
|
||||
const newElectron = new Proxy(electron, {
|
||||
get: function(target, prop) {
|
||||
if (prop === "BrowserWindow") return BrowserWindow;
|
||||
return target[prop];
|
||||
}
|
||||
});
|
||||
const electronPath = __non_webpack_require__.resolve("electron");
|
||||
delete __non_webpack_require__.cache[electronPath].exports; // If it didn't work, try to delete existing
|
||||
__non_webpack_require__.cache[electronPath].exports = newElectron; // Try to assign again after deleting
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const electron = require("electron");
|
||||
|
||||
export default class {
|
||||
static remove() {
|
||||
electron.session.defaultSession.webRequest.onHeadersReceived(function(details, callback) {
|
||||
if (!details.responseHeaders["content-security-policy-report-only"] && !details.responseHeaders["content-security-policy"]) return callback({cancel: false});
|
||||
delete details.responseHeaders["content-security-policy-report-only"];
|
||||
delete details.responseHeaders["content-security-policy"];
|
||||
callback({cancel: false, responseHeaders: details.responseHeaders});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import {ipcMain as ipc, BrowserWindow, app, dialog} from "electron";
|
||||
|
||||
import * as IPCEvents from "common/constants/ipcevents";
|
||||
|
||||
const getPath = (event, pathReq) => {
|
||||
let returnPath;
|
||||
switch (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 "logs":
|
||||
returnPath = app.getPath(pathReq);
|
||||
break;
|
||||
default:
|
||||
returnPath = "";
|
||||
}
|
||||
|
||||
event.returnValue = returnPath;
|
||||
};
|
||||
|
||||
const relaunch = () => {
|
||||
app.quit();
|
||||
app.relaunch();
|
||||
};
|
||||
|
||||
const runScript = async (event, script) => {
|
||||
try {
|
||||
// TODO: compile with vm to prevent escape with clever strings
|
||||
await event.sender.executeJavaScript(`(() => {try {${script}} catch {}})();`);
|
||||
}
|
||||
catch (e) {
|
||||
// TODO: cut a log
|
||||
}
|
||||
};
|
||||
|
||||
const openDevTools = event => event.sender.openDevTools();
|
||||
const closeDevTools = event => event.sender.closeDevTools();
|
||||
|
||||
const createBrowserWindow = async (event, url, {windowOptions, closeOnUrl} = {}) => {
|
||||
return await new Promise(resolve => {
|
||||
const windowInstance = new BrowserWindow(windowOptions);
|
||||
windowInstance.webContents.on("did-navigate", (_, navUrl) => {
|
||||
if (navUrl != closeOnUrl) return;
|
||||
windowInstance.close();
|
||||
resolve();
|
||||
});
|
||||
windowInstance.loadURL(url);
|
||||
});
|
||||
};
|
||||
|
||||
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.handle(IPCEvents.RUN_SCRIPT, runScript);
|
||||
ipc.handle(IPCEvents.OPEN_WINDOW, createBrowserWindow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {session} from "electron";
|
||||
|
||||
export const REACT_DEVTOOLS_ID = "fmkadmapgofadopljbjfkapdkoienihi";
|
||||
|
||||
const findExtension = function() {
|
||||
let extensionPath = "";
|
||||
if (process.platform === "win32") extensionPath = path.resolve(process.env.LOCALAPPDATA, "Google/Chrome/User Data");
|
||||
else if (process.platform === "linux") extensionPath = path.resolve(process.env.HOME, ".config/google-chrome");
|
||||
else if (process.platform === "darwin") extensionPath = path.resolve(process.env.HOME, "Library/Application Support/Google/Chrome");
|
||||
else extensionPath = path.resolve(process.env.HOME, ".config/chromium");
|
||||
extensionPath += `/Default/Extensions/${REACT_DEVTOOLS_ID}`;
|
||||
if (fs.existsSync(extensionPath)) {
|
||||
const versions = fs.readdirSync(extensionPath);
|
||||
extensionPath = path.resolve(extensionPath, versions[versions.length - 1]);
|
||||
}
|
||||
|
||||
const isExtensionInstalled = fs.existsSync(extensionPath);
|
||||
if (isExtensionInstalled) return extensionPath;
|
||||
return "";
|
||||
};
|
||||
|
||||
export default class ReactDevTools {
|
||||
static async install() {
|
||||
const extPath = findExtension();
|
||||
if (!extPath) return; // TODO: cut a log
|
||||
|
||||
try {
|
||||
const ext = await session.defaultSession.loadExtension(extPath);
|
||||
if (!ext) return; // TODO: cut a log
|
||||
}
|
||||
catch (err) {
|
||||
// TODO: cut a log
|
||||
}
|
||||
}
|
||||
|
||||
static async remove() {
|
||||
const extPath = findExtension();
|
||||
if (!extPath) return; // TODO: cut a log
|
||||
|
||||
try {
|
||||
await session.defaultSession.removeExtension(extPath);
|
||||
}
|
||||
catch (err) {
|
||||
// TODO: cut a log
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const Module = require("module");
|
||||
const path = require("path");
|
||||
const electron = require("electron");
|
||||
|
||||
/* global window:false */
|
||||
|
||||
// const context = electron.webFrame.top.context;
|
||||
Object.defineProperty(window, "webpackJsonp", {
|
||||
get: () => electron.webFrame.top.context.webpackJsonp
|
||||
});
|
||||
|
||||
electron.webFrame.top.context.global = electron.webFrame.top.context;
|
||||
electron.webFrame.top.context.require = require;
|
||||
electron.webFrame.top.context.process = process;
|
||||
|
||||
// Load Discord's original preload
|
||||
const preload = process.env.DISCORD_PRELOAD;
|
||||
if (preload) {
|
||||
|
||||
// Restore original preload for future windows
|
||||
process.electronBinding("command_line").appendSwitch("preload", preload);
|
||||
|
||||
// Run original preload
|
||||
try {require(preload);}
|
||||
catch (e) {
|
||||
// TODO bail out
|
||||
}
|
||||
}
|
||||
|
||||
Module.globalPaths.push(path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules"));
|
||||
@@ -0,0 +1,47 @@
|
||||
const path = require("path");
|
||||
const CircularDependencyPlugin = require("circular-dependency-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports = (env, argv) => ({
|
||||
mode: "development",
|
||||
target: "node",
|
||||
devtool: argv.mode === "production" ? undefined : "eval-source-map",
|
||||
entry: "./src/index.js",
|
||||
output: {
|
||||
filename: "injector.js",
|
||||
path: path.resolve(__dirname, "..", "dist")
|
||||
},
|
||||
externals: {
|
||||
electron: `require("electron")`,
|
||||
fs: `require("fs")`,
|
||||
path: `require("path")`,
|
||||
request: `require("request")`,
|
||||
events: `require("events")`,
|
||||
rimraf: `require("rimraf")`,
|
||||
yauzl: `require("yauzl")`,
|
||||
mkdirp: `require("mkdirp")`,
|
||||
module: `require("module")`
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js"],
|
||||
alias: {
|
||||
common: path.resolve(__dirname, "..", "common")
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CircularDependencyPlugin({
|
||||
exclude: /node_modules/,
|
||||
cwd: process.cwd(),
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
compress: {drop_debugger: false},
|
||||
keep_classnames: true
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user