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:
Zack Rauen 2021-03-06 03:30:16 -05:00
parent 59f2025309
commit 74b2dace04
165 changed files with 13759 additions and 9891 deletions

View File

@ -1,15 +1,5 @@
{
"extends": ["eslint:recommended", "plugin:react/recommended"],
"plugins": [
"react"
],
"settings": {
"react": {
"version": "16.12.0"
}
},
"env": {
"browser": true,
"node": true
},
"parserOptions": {
@ -91,64 +81,17 @@
"template-curly-spacing": "error",
"wrap-iife": ["error", "inside"],
"yield-star-spacing": "error",
"yoda": "error",
"react/display-name": "off",
"react/prop-types": "off",
"react/jsx-key": "off",
"react/jsx-no-target-blank": "error",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error"
"yoda": "error"
},
"globals": {
"webpackJsonp": "readonly",
"Proxy": "readonly",
"Set": "readonly",
"WeakMap": "readonly",
"Map": "readonly",
"Promise": "readonly",
"ace": "readonly",
"Reflect": "readonly",
"DiscordNative": "readonly",
"__non_webpack_require__": "readonly",
"Symbol": "readonly",
"alert": "off",
"blur": "off",
"caches": "off",
"close": "off",
"closed": "off",
"confirm": "off",
"crypto": "off",
"defaultstatus": "off",
"event": "off",
"external": "off",
"find": "off",
"focus": "off",
"frames": "off",
"history": "off",
"length": "off",
"location": "off",
"locationbar": "off",
"menubar": "off",
"name": "off",
"navigator": "off",
"open": "off",
"opener": "off",
"origin": "off",
"parent": "off",
"personalbar": "off",
"print": "off",
"prompt": "off",
"screen": "off",
"scroll": "off",
"scrollbars": "off",
"self": "off",
"status": "off",
"statusbar": "off",
"stop": "off",
"toolbar": "off",
"top": "off"
"Symbol": "readonly"
}
}

View File

@ -33,15 +33,15 @@ This project and everyone participating in it is governed by the [Code of Conduc
### BetterDiscord Architecture
BetterDiscord is currently broken up into two main pieces--the local injector, and the remote application.
BetterDiscord is currently broken up into two main pieces--the local injector, and the renderer application.
#### Injector
The injector is the piece that runs on the user's computer, and the piece added by the [installer](https://github.com/rauenzi/BBDInstaller). The main job of this package is to inject into Discord and load the remote package. The injector and its code lives on the [injector branch](https://github.com/rauenzi/BetterDiscordApp/tree/injector).
The injector is the piece that runs on the user's computer, and the piece added by the [installer](https://github.com/rauenzi/BBDInstaller). The main job of this package is to inject into Discord and load the renderer package. The injector and its code lives in the `injector` folder.
#### Remote Application
#### Renderer Application
This is the main payload of BetterDiscord. This is what gets linked remotely by the [injector](#injector). This portion is where most of the user interaction and development will be. This module is responsible for loading plugins and themes, as well as handling settings, emotes and more.
This is the main payload of BetterDiscord. This is what gets linked executed in the renderer context by the [injector](#injector). This portion is where most of the user interaction and development will be. This module is responsible for loading plugins and themes, as well as handling settings, emotes and more.
## How Can I Contribute?

View File

@ -0,0 +1,11 @@
/* eslint-disable no-multi-spaces */
export const MINIMIZE = "bd-window-minimize";
export const MAXIMIZE = "bd-window-maximize";
export const RELAUNCH = "bd-relaunch-app";
export const GET_PATH = "bd-get-path";
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";

1
injector/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

3
injector/README.md Normal file
View File

@ -0,0 +1,3 @@
# BetterDiscord Injector
You're probably looking for the main app, [click here](https://github.com/rauenzi/BetterDiscordApp) to go there.

11
injector/jsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2020",
"allowSyntheticDefaultImports": false,
"baseUrl": "./",
"paths": {
"common": ["../common"]
}
},
"exclude": ["node_modules"]
}

1810
injector/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
injector/package.json Normal file
View File

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

34
injector/src/index.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

30
injector/src/preload.js Normal file
View File

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

View File

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

11237
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,23 @@
{
"name": "betterdiscord",
"version": "1.0.0",
"description": "Enhances Discord adding functionality and themes.",
"description": "Enhances Discord by adding functionality and themes.",
"main": "src/index.js",
"scripts": {
"build": "webpack --progress --colors",
"watch": "webpack --progress --colors --watch",
"build-prod": "webpack --display errors-only --mode production -o dist/remote.js --devtool none",
"lint-js": "eslint --ext .jsx,.js src/",
"build-css": "postcss src/styles/index.css -o dist/style.css",
"watch-css": "postcss src/styles/index.css -o dist/style.css -w",
"build-prod-css": "postcss src/styles/index.css -o dist/style.css --no-map --env production",
"lint-css": "stylelint src/styles/*.css && stylelint src/styles/**/*.css",
"lint": "npm run lint-js && npm run lint-css",
"test": "mocha --require @babel/register --recursive \"./tests/*.js\"",
"lint-prod": "npm run lint-js -- --quiet && npm run lint-css -- --quiet",
"test-prod": "npm run test -- --reporter min"
"install": "cd injector && npm install && cd ../renderer && npm install",
"build": "npm run build-injector && npm run build-renderer",
"build-prod": "npm run build-prod --prefix injector && npm run build-prod --prefix renderer",
"build-injector": "npm run build --prefix injector",
"build-renderer": "npm run build --prefix renderer",
"inject": "node scripts/inject.js",
"lint": "eslint --ext .js common/ && npm run lint --prefix injector && npm run lint --prefix renderer",
"test": "mocha --require @babel/register --recursive \"./tests/renderer/*.js\"",
"dist": "npm run build-prod && node scripts/pack.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rauenzi/BetterDiscordApp.git"
},
"author": "rauenzi",
"bugs": {
"url": "https://github.com/rauenzi/BetterDiscordApp/issues"
},
"homepage": "https://github.com/rauenzi/BetterDiscordApp",
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.12.1",
"@babel/register": "^7.12.1",
"babel-loader": "^8.1.0",
"babel-plugin-module-resolver": "^4.0.0",
"circular-dependency-plugin": "^5.2.0",
"css-loader": "^5.1.0",
"asar": "^3.0.3",
"eslint": "^7.12.0",
"eslint-plugin-react": "^7.21.5",
"mocha": "^8.2.0",
"postcss": "^8.1.4",
"postcss-cli": "^8.1.0",
"postcss-csso": "^4.0.0",
"postcss-easy-import": "^3.0.0",
"postcss-loader": "^5.0.0",
"stylelint": "^13.7.2",
"stylelint-config-standard": "^20.0.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
"mocha": "^8.2.0"
}
}

View File

@ -3,7 +3,7 @@
"@babel/env",
{
"targets": {
"node": "12.8.1"
"node": "12.14.1"
}
}
]],
@ -13,7 +13,8 @@
"alias": {
"builtins": "./src/builtins/builtins.js",
"data": "./src/data/data.js",
"modules": "./src/modules/modules.js"
"modules": "./src/modules/modules.js",
"common": "../common"
}
}
]]

62
renderer/.eslintrc Normal file
View File

@ -0,0 +1,62 @@
{
"extends": ["plugin:react/recommended"],
"plugins": [
"react"
],
"settings": {
"react": {
"version": "16.12.0"
}
},
"env": {
"browser": true
},
"rules": {
"react/display-name": "off",
"react/prop-types": "off",
"react/jsx-key": "off",
"react/jsx-no-target-blank": "error",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error"
},
"globals": {
"webpackJsonp": "readonly",
"alert": "off",
"blur": "off",
"caches": "off",
"close": "off",
"closed": "off",
"confirm": "off",
"crypto": "off",
"defaultstatus": "off",
"event": "off",
"external": "off",
"find": "off",
"focus": "off",
"frames": "off",
"history": "off",
"length": "off",
"location": "off",
"locationbar": "off",
"menubar": "off",
"name": "off",
"navigator": "off",
"open": "off",
"opener": "off",
"origin": "off",
"parent": "off",
"personalbar": "off",
"print": "off",
"prompt": "off",
"screen": "off",
"scroll": "off",
"scrollbars": "off",
"self": "off",
"status": "off",
"statusbar": "off",
"stop": "off",
"toolbar": "off",
"top": "off"
}
}

View File

@ -1,13 +1,14 @@
{
"compilerOptions": {
"target": "es2017",
"target": "es2020",
"allowSyntheticDefaultImports": false,
"baseUrl": "./",
"paths": {
"modules": ["./src/modules/modules.js"],
"builtins": ["./src/builtins/builtins.js"],
"data": ["./src/data/data.js"]
"data": ["./src/data/data.js"],
"common": ["../common"]
}
},
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules"]
}

9119
renderer/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
renderer/package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "betterdiscord-renderer",
"version": "1.0.0",
"description": "Renderer portion of the BetterDiscord application.",
"private": true,
"main": "src/index.js",
"scripts": {
"build": "webpack --progress --colors",
"watch": "webpack --progress --colors --watch",
"build-prod": "webpack --display errors-only --mode production --devtool none",
"lint-js": "eslint --ext .jsx,.js src/",
"build-css": "postcss src/styles/index.css -o dist/style.css",
"watch-css": "postcss src/styles/index.css -o dist/style.css -w",
"lint-css": "stylelint src/styles/*.css && stylelint src/styles/**/*.css",
"lint": "npm run lint-js && npm run lint-css",
"lint-prod": "npm run lint-js -- --quiet && npm run lint-css -- --quiet",
"test-prod": "npm run test -- --reporter min"
},
"devDependencies": {
"@babel/core": "^7.12.3",
"@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.12.1",
"@babel/register": "^7.12.1",
"babel-loader": "^8.1.0",
"babel-plugin-module-resolver": "^4.0.0",
"circular-dependency-plugin": "^5.2.0",
"css-loader": "^5.1.0",
"eslint": "^7.12.0",
"eslint-plugin-react": "^7.21.5",
"postcss": "^8.1.4",
"postcss-cli": "^8.1.0",
"postcss-csso": "^4.0.0",
"postcss-easy-import": "^3.0.0",
"postcss-loader": "^4.2.0",
"stylelint": "^13.7.2",
"stylelint-config-standard": "^20.0.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
}
}

View File

@ -48,14 +48,15 @@ export default new class CustomCSS extends Builtin {
const commonjsLoader = window.require;
delete window.module; // Make monaco think this isn't a local node script or else it freaks out
DOMManager.linkStyle("monaco-style", "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/editor/editor.main.min.css", {documentHead: true});
DOMManager.injectScript("monaco-script", "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/loader.min.js").then(() => {
const amdLoader = window.require; // Grab Monaco's amd loader
window.require = commonjsLoader; // Revert to commonjs
amdLoader.config({paths: {vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs"}});
amdLoader(["vs/editor/editor.main"], () => {}); // exposes the monaco global
});
DOMManager.linkStyle("monaco-style", "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/editor/editor.main.min.css", {documentHead: true});
await DOMManager.injectScript("monaco-script", "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/loader.min.js");
const amdLoader = window.require; // Grab Monaco's amd loader
window.require = commonjsLoader; // Revert to commonjs
this.log(amdLoader, window.require);
amdLoader.config({paths: {vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs"}});
amdLoader(["vs/editor/editor.main"], () => {}); // exposes the monaco global
Settings.registerPanel(this.id, Strings.Panels.customcss, {
order: 2,

View File

@ -0,0 +1,29 @@
import Builtin from "../../structs/builtin";
import Modals from "../../ui/modals";
import {Strings, IPC} from "modules";
const fs = require("fs");
const path = require("path");
export default new class ReactDevTools extends Builtin {
get name() {return "ReactDevTools";}
get category() {return "developer";}
get id() {return "reactDevTools";}
async enabled() {
this.showModal();
}
async disabled() {
this.showModal();
}
showModal() {
if (!this.initialized) return;
Modals.showConfirmationModal(Strings.Modals.additionalInfo, Strings.Modals.restartPrompt, {
confirmText: Strings.Modals.restartNow,
cancelText: Strings.Modals.restartLater,
onConfirm: () => IPC.relaunch()
});
}
};

View File

@ -10,10 +10,10 @@ export default new class PublicServers extends Builtin {
get id() {return "publicServers";}
enabled() {
const GuildList = WebpackModules.find(m => m.default && m.default.displayName == "NavigableGuilds");
const GuildList = WebpackModules.find(m => m.type && m.type.displayName == "NavigableGuilds");
const GuildListOld = WebpackModules.findByDisplayName("Guilds");
if (!GuildList && !GuildListOld) this.warn("Can't find GuildList component");
this.guildPatch = this.after(GuildList ? GuildList : GuildListOld.prototype, GuildList ? "default" : "render", this._appendButton);
this.guildPatch = this.after(GuildList ? GuildList : GuildListOld.prototype, GuildList ? "type" : "render", this._appendButton);
this._appendButton();
}

View File

@ -0,0 +1,26 @@
import Builtin from "../structs/builtin";
import Modals from "../ui/modals";
import {DataStore, Strings, IPC} from "modules";
export default new class WindowPrefs extends Builtin {
get name() {return "WindowPrefs";}
get category() {return "window";}
get id() {return "transparency";}
enabled() {
this.showModal(Strings.WindowPrefs.enabledInfo);
}
disabled() {
this.showModal(Strings.WindowPrefs.disabledInfo);
}
showModal(info) {
if (!this.initialized) return;
Modals.showConfirmationModal(Strings.Modals.additionalInfo, info, {
confirmText: Strings.Modals.restartNow,
cancelText: Strings.Modals.restartLater,
onConfirm: () => IPC.relaunch()
});
}
};

View File

@ -279,11 +279,12 @@ export default {
restartRequired: "Restart Required",
restartNow: "Restart Now",
restartLater: "Restart Later",
additionalInfo: "Additional Info"
additionalInfo: "Additional Info",
restartPrompt: "In order to take effect, Discord needs to be restarted. Do you want to restart now?"
},
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",

View File

@ -1,5 +1,5 @@
import Utilities from "./utilities";
import Logger from "./logger";
import Logger from "common/logger";
import Settings from "./settingsmanager";
import Events from "./emitter";
import DataStore from "./datastore";
@ -16,7 +16,6 @@ const React = DiscordModules.React;
const path = require("path");
const fs = require("fs");
const Module = require("module").Module;
// Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
const splitRegex = /[^\S\r\n]*?\r?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;

View File

@ -0,0 +1,116 @@
import LocaleManager from "./localemanager";
import Logger from "common/logger";
import {Config, Changelog} from "data";
import DOMManager from "./dommanager";
import PluginManager from "./pluginmanager";
import ThemeManager from "./thememanager";
import Settings from "./settingsmanager";
import * as Builtins from "builtins";
import Modals from "../ui/modals";
import ReactComponents from "./reactcomponents";
import DataStore from "./datastore";
import DiscordModules from "./discordmodules";
import ComponentPatcher from "./componentpatcher";
import Strings from "./strings";
import LoadingIcon from "../loadingicon";
import Styles from "../styles/index.css";
const GuildClasses = DiscordModules.GuildClasses;
export default new class Core {
async startup() {
if (this.hasStarted) return;
this.hasStarted = true;
// (() => {
// const fs = require("fs");
// fs.appendFileSync("Z:\\debug.log", "\n\n\n");
// window.ocl = console.log;
// console.log = (...args) => {
// fs.appendFileSync("Z:\\debug.log", JSON.stringify(args) + "\n");
// window.ocl(...args);
// };
// })();
Config.appPath = process.env.DISCORD_APP_PATH;
Config.userData = process.env.DISCORD_USER_DATA;
Config.dataPath = process.env.BETTERDISCORD_DATA_PATH;
// Load css early
Logger.log("Startup", "Injecting BD Styles");
DOMManager.injectStyle("bd-stylesheet", Styles.toString());
Logger.log("Startup", "Initializing DataStore");
DataStore.initialize();
Logger.log("Startup", "Initializing LocaleManager");
await LocaleManager.initialize();
Logger.log("Startup", "Performing incompatibility checks");
if (Config.version < Config.minSupportedVersion) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.versionMismatch.format({injector: Config.version, remote: Config.bdVersion}));
if (window.ED) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.incompatibleApp.format({app: "EnhancedDiscord"}));
if (window.WebSocket && window.WebSocket.name && window.WebSocket.name.includes("Patched")) return Modals.alert(Strings.Startup.notSupported, Strings.Startup.incompatibleApp.format({app: "Powercord"}));
Logger.log("Startup", "Initializing Settings");
Settings.initialize();
Logger.log("Startup", "Initializing DOMManager");
DOMManager.initialize();
Logger.log("Startup", "Waiting for guilds...");
await this.waitForGuilds();
Logger.log("Startup", "Initializing ReactComponents");
ReactComponents.initialize();
Logger.log("Startup", "Initializing ComponentPatcher");
ComponentPatcher.initialize();
Logger.log("Startup", "Initializing Builtins");
for (const module in Builtins) {
if (module === "CustomCSS") await Builtins[module].initialize();
else Builtins[module].initialize();
}
Logger.log("Startup", "Loading Plugins");
// const pluginErrors = [];
const pluginErrors = PluginManager.initialize();
Logger.log("Startup", "Loading Themes");
// const themeErrors = [];
const themeErrors = ThemeManager.initialize();
Logger.log("Startup", "Removing Loading Icon");
LoadingIcon.hide();
// Show loading errors
Logger.log("Startup", "Collecting Startup Errors");
Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors});
const previousVersion = DataStore.getBDData("version");
if (Config.bdVersion > previousVersion) {
Modals.showChangelogModal(Changelog);
DataStore.setBDData("version", Config.bdVersion);
}
}
waitForGuilds() {
let timesChecked = 0;
return new Promise(resolve => {
const checkForGuilds = function () {
timesChecked++;
if (document.readyState != "complete") setTimeout(checkForGuilds, 100);
const wrapper = GuildClasses.wrapper.split(" ")[0];
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);
setTimeout(checkForGuilds, 100);
};
checkForGuilds();
});
}
};

View File

@ -1,11 +1,14 @@
import {Config} from "data";
import Utilities from "./utilities";
import Logger from "./logger";
import Logger from "common/logger";
const fs = require("fs");
const path = require("path");
const releaseChannel = window?.DiscordNative?.app?.getReleaseChannel?.() ?? "stable";
const discordVersion = window?.DiscordNative?.remoteApp?.getVersion?.() ?? "0.0.309";
// const releaseChannel = "stable";
// const discordVersion = "0.0.309";
// Schema
// =======================
// %appdata%\BetterDiscord
@ -25,11 +28,14 @@ export default new class DataStore {
initialize() {
const newStorageExists = fs.existsSync(this.baseFolder);
if (!newStorageExists) fs.mkdirSync(this.baseFolder);
if (!fs.existsSync(this.dataFolder)) fs.mkdirSync(this.dataFolder);
if (!fs.existsSync(this.localeFolder)) fs.mkdirSync(this.localeFolder);
if (!fs.existsSync(this.emoteFolder)) fs.mkdirSync(this.emoteFolder);
if (!fs.existsSync(this.cacheFile)) fs.writeFileSync(this.cacheFile, JSON.stringify({}));
if (!fs.existsSync(this.customCSS)) fs.writeFileSync(this.customCSS, "");
const dataFiles = fs.readdirSync(this.dataFolder).filter(f => !fs.statSync(path.resolve(this.dataFolder, f)).isDirectory() && f.endsWith(".json"));
for (const file of dataFiles) {
this.data[file.split(".")[0]] = __non_webpack_require__(path.resolve(this.dataFolder, file));

View File

@ -6,18 +6,18 @@ export default class DOMManager {
static get bdStyles() {return this.getElement("bd-styles");}
static get bdThemes() {return this.getElement("bd-themes");}
static get bdCustomCSS() {return this.getElement("#customcss");}
// static get bdTooltips() { return this.getElement("bd-tooltips") || this.createElement("bd-tooltips").appendTo(this.bdBody); }
// static get bdModals() { return this.getElement("bd-modals") || this.createElement("bd-modals").appendTo(this.bdBody); }
// static get bdToasts() { return this.getElement("bd-toasts") || this.createElement("bd-toasts").appendTo(this.bdBody); }
static get bdTooltips() {return this.getElement("bd-tooltips") || this.createElement("bd-tooltips").appendTo(this.bdBody);}
static get bdModals() {return this.getElement("bd-modals") || this.createElement("bd-modals").appendTo(this.bdBody);}
static get bdToasts() {return this.getElement("bd-toasts") || this.createElement("bd-toasts").appendTo(this.bdBody);}
// static initialize() {
// this.createElement("bd-head", {target: document.head});
// this.createElement("bd-body", {target: document.body});
// this.createElement("bd-scripts", {target: this.bdHead});
// this.createElement("bd-styles", {target: this.bdHead});
// this.createElement("bd-themes", {target: this.bdHead});
// this.createElement("style", {id: "customcss", target: this.bdHead});
// }
static initialize() {
// this.createElement("bd-head", {target: document.head});
// this.createElement("bd-body", {target: document.body});
// this.createElement("bd-scripts", {target: this.bdHead});
// this.createElement("bd-styles", {target: this.bdHead});
// this.createElement("bd-themes", {target: this.bdHead});
// this.createElement("style", {id: "customcss", target: this.bdHead});
}
static escapeID(id) {
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");

View File

@ -1,9 +1,11 @@
const EventEmitter = require("events");
export default new class BDEvents extends EventEmitter {
constructor() {
super();
this.setMaxListeners(20);
}
dispatch(eventName, ...args) {
this.emit(eventName, ...args);
}

View File

@ -0,0 +1,34 @@
import {ipcRenderer as ipc} from "electron";
import Events from "./emitter";
import * as IPCEvents from "common/constants/ipcevents";
export default new class IPCRenderer {
constructor() {
ipc.on(IPCEvents.NAVIGATE, () => Events.dispatch("navigate"));
ipc.on(IPCEvents.MAXIMIZE, () => Events.dispatch("maximize"));
ipc.on(IPCEvents.MINIMIZE, () => Events.dispatch("minimize"));
}
openDevTools() {
return ipc.send(IPCEvents.OPEN_DEVTOOLS);
}
closeDevTools() {
return ipc.send(IPCEvents.CLOSE_DEVTOOLS);
}
relaunch() {
return ipc.send(IPCEvents.RELAUNCH);
}
runScript(script) {
return ipc.invoke(IPCEvents.RUN_SCRIPT, script);
}
openWindow(url, options) {
return ipc.invoke(IPCEvents.OPEN_WINDOW, url, options);
}
};

View File

@ -11,8 +11,9 @@ export {default as Events} from "./emitter";
export {default as Settings} from "./settingsmanager";
export {default as DOMManager} from "./dommanager";
export {default as DOM} from "./domtools";
export {default as Logger} from "./logger";
export {default as Patcher} from "./patcher";
export {default as ReactComponents} from "./reactcomponents";
export {default as LocaleManager} from "./localemanager";
export {default as Strings} from "./strings";
export {default as Strings} from "./strings";
export {default as IPC} from "./ipc";
export {default as Logger} from "common/logger";

View File

@ -8,7 +8,7 @@
* @version 0.0.2
*/
import Logger from "./logger";
import Logger from "common/logger";
import DiscordModules from "./discordmodules";
import WebpackModules from "./webpackmodules";

View File

@ -9,7 +9,7 @@ import Modals from "../ui/modals";
import PluginManager from "./pluginmanager";
import ThemeManager from "./thememanager";
import Settings from "./settingsmanager";
import Logger from "./logger";
import Logger from "common/logger";
import Patcher from "./patcher";
import Emotes from "../builtins/emotes/emotes";
@ -35,17 +35,20 @@ const BdApi = {
};
BdApi.getAllWindowPreferences = function() {
return DataStore.getData("windowprefs") || {};
// return DataStore.getData("windowprefs") || {};
// TODO: mark deprecated
};
BdApi.getWindowPreference = function(key) {
return this.getAllWindowPreferences()[key];
// return this.getAllWindowPreferences()[key];
// TODO: mark deprecated
};
BdApi.setWindowPreference = function(key, value) {
const prefs = this.getAllWindowPreferences();
prefs[key] = value;
return DataStore.setData("windowprefs", prefs);
// const prefs = this.getAllWindowPreferences();
// prefs[key] = value;
// return DataStore.setData("windowprefs", prefs);
// TODO: mark deprecated
};
// Inject CSS to document head

View File

@ -1,9 +1,11 @@
import {Config} from "data";
import Logger from "./logger";
import Logger from "common/logger";
import AddonManager from "./addonmanager";
import AddonError from "../structs/addonerror";
import Settings from "./settingsmanager";
import Strings from "./strings";
import IPC from "./ipc";
import Events from "./emitter";
import Toasts from "../ui/toasts";
import Modals from "../ui/modals";
@ -11,7 +13,6 @@ import SettingsRenderer from "../ui/settings";
const path = require("path");
const electron = require("electron");
const ipc = electron.ipcRenderer;
const vm = require("vm");
// const electronRemote = require("electron").remote;
@ -105,10 +106,10 @@ export default new class PluginManager extends AddonManager {
window.module = module;
window.__filename = path.basename(module.filename);
window.__dirname = this.addonFolder;
const wrapped = `(${vm.compileFunction(fileContent, ["exports", "require", "module", "__filename", "__dirname"])})`;
const wrapped = `(${vm.compileFunction(fileContent, ["exports", "require", "module", "__filename", "__dirname"]).toString()})`;
// console.log(module);
module.exports = new Promise(resolve => {
ipc.invoke("EXEC_JS", `${wrapped}(window.module.exports, window.require, window.module, window.__filename, window.__dirname)`).then(() => {
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;
@ -170,7 +171,8 @@ export default new class PluginManager extends AddonManager {
setupFunctions() {
// electronRemote.getCurrentWebContents().on("did-navigate-in-page", this.onSwitch.bind(this));
ipc.on("DID_NAVIGATE_IN_PAGE", this.onSwitch);
Events.on("navigate", this.onSwitch);
// ipc.on(IPCEvents.NAVIGATE, this.onSwitch);
this.observer.observe(document, {
childList: true,
subtree: true
@ -178,7 +180,6 @@ export default new class PluginManager extends AddonManager {
}
onSwitch() {
this.emit("page-switch");
for (let i = 0; i < this.addonList.length; i++) {
const plugin = this.addonList[i].instance;
if (!this.state[this.addonList[i].id]) continue;

View File

@ -1,5 +1,5 @@
import {SettingsConfig} from "data";
import Logger from "./logger";
import Logger from "common/logger";
import DataStore from "./datastore";
import Events from "./emitter";
import DiscordModules from "./discordmodules";

View File

@ -6,7 +6,7 @@
import PluginUtilities from "./pluginutilities";
import DOMTools from "./domtools";
import Logger from "./logger";
import Logger from "common/logger";
import DiscordClasses from "./discordclasses";
import {EmulatedTooltip, Toasts} from "ui";

View File

@ -1,5 +1,5 @@
import {Config} from "data";
import Logger from "./logger";
import Logger from "common/logger";
import DOM from "./domtools";
export default class Utilities {

View File

@ -1,4 +1,4 @@
import Logger from "../modules/logger";
import Logger from "common/logger";
import Events from "../modules/emitter";
import Settings from "../modules/settingsmanager";
import Patcher from "../modules/patcher";

View File

@ -1,4 +1,5 @@
import {Logger, WebpackModules} from "modules";
import Logger from "common/logger";
import {WebpackModules, IPC} from "modules";
const SortedGuildStore = WebpackModules.getByProps("getSortedGuilds");
const AvatarDefaults = WebpackModules.getByProps("getUserAvatarURL", "DEFAULT_AVATARS");
@ -12,7 +13,7 @@ const betterDiscordServer = {
categories: ["community", "programming", "support"],
description: "Official BetterDiscord server for plugins, themes, support, etc",
identifier: "86004744966914048",
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/292e7f6bfff2b71dfd13e508a859aedd.webp",
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/babd1af3fa6011a50e418a80f4970ceb.webp",
nativejoin: true,
invite_code: "BJD2yvJ",
pinned: true,
@ -29,13 +30,12 @@ export default new class PublicServersConnection {
this.cache.set("popular", []);
this.cache.set("keywords", []);
this.cache.set("accessToken", "");
window.debugPS = this;
}
get endPoint() {return "https://search.discordservers.com";}
get joinEndPoint() {return "https://j.discordservers.com";}
get connectEndPoint() {return "https://auth.discordservers.com/info";}
get authorizeEndPoint() {return `https://auth.discordservers.com/connect?scopes=guilds.join&previousUrl=${this.connectEndPoint}`;}
getDefaultAvatar() {
return AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * 5)];
@ -139,7 +139,7 @@ export default new class PublicServersConnection {
}
}
connect() {
async connect() {
// return new Promise(resolve => {
// const joinWindow = new BrowserWindow(this.windowOptions);
// const url = `https://auth.discordservers.com/connect?scopes=guilds.join&previousUrl=${this.connectEndPoint}`;
@ -150,12 +150,16 @@ export default new class PublicServersConnection {
// });
// joinWindow.loadURL(url);
// });
await IPC.openWindow(this.authorizeEndPoint, {
windowOptions: this.windowOptions,
closeOnUrl: this.connectEndPoint
});
}
get windowOptions() {
return {
width: 490,
height: 500,
width: 520,
height: 580,
backgroundColor: "#282b30",
show: true,
resizable: true,

Some files were not shown because too many files have changed in this diff Show More