Upload to github

This commit is contained in:
Strencher 2023-07-16 11:06:49 +02:00
parent 54f655c204
commit 835976568b
35 changed files with 547 additions and 199 deletions

View File

@ -47,6 +47,7 @@ export default class BetterDiscord {
}
static async injectRenderer(browserWindow) {
return;
const location = path.join(__dirname, "renderer.js");
if (!fs.existsSync(location)) return; // TODO: cut a fatal log
const content = fs.readFileSync(location).toString();
@ -81,7 +82,7 @@ export default class BetterDiscord {
process.env.BETTERDISCORD_DATA_PATH = dataPath;
// When DOM is available, pass the renderer over the wall
browserWindow.webContents.on("dom-ready", () => {
/*browserWindow.webContents.on*/(/*"ready-to-show",*/ () => {
if (!hasCrashed) return this.injectRenderer(browserWindow);
// If a previous crash was detected, show a message explaining why BD isn't there
@ -101,7 +102,7 @@ export default class BetterDiscord {
}
});
hasCrashed = false;
});
})();
// This is used to alert renderer code to onSwitch events
browserWindow.webContents.on("did-navigate-in-page", () => {

View File

@ -1,7 +1,30 @@
import {ipcRenderer as IPC} from "electron";
import {ipcRenderer as IPC, webFrame} from "electron";
import fs from "fs";
import path from "path";
import * as IPCEvents from "common/constants/ipcevents";
export default function() {
export default function () {
try {
const location = path.join(__dirname, "renderer.js");
if (!fs.existsSync(location)) return; // TODO: cut a fatal log
const content = fs.readFileSync(location).toString();
webFrame.top.executeJavaScript(`
(() => {
console.log("We are early baby!");
try {
${content}
return true;
} catch(error) {
console.error(error);
return false;
}
})();
//# sourceURL=betterdiscord/renderer.js
`);
} catch (error) {
console.error(error);
}
// Load Discord's original preload
const preload = process.env.DISCORD_PRELOAD;
if (preload) {
@ -19,4 +42,4 @@ export default function() {
// TODO bail out
}
}
}
}

View File

@ -33,6 +33,7 @@ export default function () {
};
if (!Reflect.has(window, chunkName)) {
return;
predefine(window, chunkName, instance => {
predefine(instance, "push", () => {
instance.push([[Symbol()], {}, require => {

View File

@ -1,5 +1,5 @@
import Builtin from "../structs/builtin";
import {Settings, DataStore, React, WebpackModules, Events, DOMManager, Strings, DiscordModules} from "modules";
import {React, Settings, DataStore, WebpackModules, Events, DOMManager, Strings, DiscordModules} from "modules";
import CSSEditor from "../ui/customcss/csseditor";
import FloatingWindows from "../ui/floatingwindows";
import SettingsTitle from "../ui/settings/title";
@ -27,7 +27,7 @@ export default new class CustomCSS extends Builtin {
async enabled() {
Settings.registerPanel(this.id, Strings.Panels.customcss, {
order: 2,
element: () => [<SettingsTitle text={Strings.CustomCSS.editorTitle} />, React.createElement(CSSEditor, {
element: () => [<SettingsTitle text={Strings.CustomCSS.editorTitle} />, DiscordModules.React.createElement(CSSEditor, {
css: this.savedCss,
save: this.saveCSS.bind(this),
update: this.insertCSS.bind(this),
@ -115,8 +115,8 @@ export default new class CustomCSS extends Builtin {
}
openDetached(currentCSS) {
const editorRef = React.createRef();
const editor = React.createElement(CSSEditor, {
const editorRef = DiscordModules.React.createRef();
const editor = DiscordModules.React.createElement(CSSEditor, {
id: "bd-floating-editor",
ref: editorRef,
css: currentCSS,
@ -152,4 +152,4 @@ export default new class CustomCSS extends Builtin {
UserSettings.close();
Dispatcher.dispatch({type: "LAYER_POP"});
}
};
};

View File

@ -2,17 +2,23 @@ import require from "./polyfill"; // eslint-disable-line no-unused-vars
import secure from "./secure";
import LoadingIcon from "./loadingicon";
import BetterDiscord from "./modules/core";
import Events from "./modules/emitter";
import BdApi from "./modules/api/index";
// Perform some setup
secure();
Object.defineProperty(window, "BdApi", {
value: BdApi,
writable: false,
configurable: false
});
// Perform some setup
secure();
window.global = window;
// Add loading icon at the bottom right
Events.addListener("CLIENT_READY", async () => {
BetterDiscord.startup();
});
BetterDiscord.preload();
LoadingIcon.show();
BetterDiscord.startup();

View File

@ -1,45 +1,47 @@
const css = `/* BEGIN V2 LOADER */
/* =============== */
#bd-loading-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==);
}
#bd-loading-icon {
position: fixed;
bottom:5px;
right:5px;
z-index: 2147483647;
display: block;
width: 20px;
height: 20px;
background-size: 100% 100%;
animation: bd-loading-animation 1.5s ease-in-out infinite;
}
@keyframes bd-loading-animation {
0% { opacity: 0.05; }
50% { opacity: 0.6; }
100% { opacity: 0.05; }
}
/* =============== */
/* END V2 LOADER */`;
const iconStyle = document.createElement("style");
iconStyle.textContent = css;
const loadingIcon = document.createElement("div");
loadingIcon.id = "bd-loading-icon";
loadingIcon.className = "bd-loaderv2";
loadingIcon.title = "BetterDiscord is loading...";
export default class {
static show() {
document.body.appendChild(iconStyle);
document.body.appendChild(loadingIcon);
}
static hide() {
if (iconStyle) iconStyle.remove();
if (loadingIcon) loadingIcon.remove();
}
}
const css = `/* BEGIN V2 LOADER */
/* =============== */
#bd-loading-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==);
}
#bd-loading-icon {
position: fixed;
bottom:5px;
right:5px;
z-index: 2147483647;
display: block;
width: 20px;
height: 20px;
background-size: 100% 100%;
animation: bd-loading-animation 1.5s ease-in-out infinite;
}
@keyframes bd-loading-animation {
0% { opacity: 0.05; }
50% { opacity: 0.6; }
100% { opacity: 0.05; }
}
/* =============== */
/* END V2 LOADER */`;
const iconStyle = document.createElement("style");
iconStyle.textContent = css;
const loadingIcon = document.createElement("div");
loadingIcon.id = "bd-loading-icon";
loadingIcon.className = "bd-loaderv2";
loadingIcon.title = "BetterDiscord is loading...";
export default class {
static show() {
window.addEventListener("DOMContentLoaded", () => {
document.body.appendChild(iconStyle);
document.body.appendChild(loadingIcon);
}, {once: true});
}
static hide() {
if (iconStyle) iconStyle.remove();
if (loadingIcon) loadingIcon.remove();
}
}

View File

@ -6,10 +6,9 @@ import AddonError from "../structs/addonerror";
import Toasts from "../ui/toasts";
import DiscordModules from "./discordmodules";
import Strings from "./strings";
import AddonEditor from "../ui/misc/addoneditor";
import FloatingWindows from "../ui/floatingwindows";
const React = DiscordModules.React;
import Utilities from "./utilities";
import Notices from "../ui/notices";
const path = require("path");
const fs = require("fs");
@ -26,6 +25,10 @@ const stripBOM = function(fileContent) {
return fileContent;
};
const AddonEditor = Utilities.makeLazy(() => import("../ui/misc/addoneditor"));
let needsReload = false;
export default class AddonManager {
get name() {return "";}
@ -41,10 +44,24 @@ export default class AddonManager {
this.addonList = [];
this.state = {};
this.windows = new Set();
this.delayedAddons = [];
}
initialize() {
return this.loadAllAddons();
const partial = this.loadAllAddons();
Events.addListener("LOAD_DELAYED_ADDONS", () => {
if (typeof this.onClientReady === "function") {
this.onClientReady();
}
partial.push(
...this.delayedAddons.map(this.finalizeAddon.bind(this))
.filter(Boolean)
);
});
return partial;
}
// Subclasses should overload this and modify the addon object as needed to fully load it
@ -68,6 +85,24 @@ export default class AddonManager {
Logger.log(this.name, `Starting to watch ${this.prefix} addons.`);
this.watcher = fs.watch(this.addonFolder, {persistent: false}, async (eventType, filename) => {
// console.log("watcher", eventType, filename, !eventType || !filename, !filename.endsWith(this.extension));
const isEarly = this.addonList.some(addon => addon.filename === filename && addon["run-at"] === "client-start");
if (isEarly) {
if (needsReload) return;
needsReload = true;
Notices.show("One or more addons require a reload", {
type: "error",
buttons: [
{label: "Reload Now", onClick() {location.reload();} },
{label: "Ignore", onClick() {} }
]
});
return;
}
if (!eventType || !filename) return;
// console.log(eventType, filename)
@ -211,7 +246,12 @@ export default class AddonManager {
return e;
}
// if (!this.delayedAddons.includes(addon)) {
// return this.finalizeAddon(addon, shouldToast);
// }
}
finalizeAddon(addon, shouldToast) {
const error = this.initializeAddon(addon);
if (error) {
this.state[addon.id] = false;
@ -361,8 +401,8 @@ export default class AddonManager {
if (this.windows.has(fullPath)) return;
this.windows.add(fullPath);
const editorRef = React.createRef();
const editor = React.createElement(AddonEditor, {
const editorRef = DiscordModules.React.createRef();
const editor = DiscordModules.React.createElement(AddonEditor, {
id: "bd-floating-editor-" + addon.id,
ref: editorRef,
content: content,
@ -394,4 +434,4 @@ export default class AddonManager {
confirmationText: Strings.Addons.confirmationText.format({name: addon.name})
});
}
}
}

View File

@ -7,12 +7,10 @@ import Data from "./data";
import DOM from "./dom";
import Patcher from "./patcher";
import ReactUtils from "./reactutils";
import UI from "./ui";
import Utils from "./utils";
import Webpack from "./webpack";
import * as Legacy from "./legacy";
import ContextMenu from "./contextmenu";
import {DiscordModules} from "modules";
import {DiscordModules, Events} from "modules";
const bounded = new Map();
const PluginAPI = new AddonAPI(PluginManager);
@ -20,7 +18,7 @@ const ThemeAPI = new AddonAPI(ThemeManager);
const PatcherAPI = new Patcher();
const DataAPI = new Data();
const DOMAPI = new DOM();
const ContextMenuAPI = new ContextMenu();
// const ContextMenuAPI = new ContextMenu();
/**
* `BdApi` is a globally (`window.BdApi`) accessible object for use by plugins and developers to make their lives easier.
@ -51,9 +49,9 @@ export default class BdApi {
get Themes() {return ThemeAPI;}
get Webpack() {return Webpack;}
get Utils() {return Utils;}
get UI() {return UI;}
get UI() {return BdApi.UI;}
get ReactUtils() {return ReactUtils;}
get ContextMenu() {return ContextMenuAPI;}
get ContextMenu() {return BdApi.ContextMenuAPI;}
Components = {
get Tooltip() {return DiscordModules.Tooltip;}
}
@ -90,13 +88,13 @@ BdApi.Webpack = Webpack;
* An instance of {@link Data} to manage data.
* @type Data
*/
BdApi.Data = DataAPI;
BdApi.Data = DataAPI;
/**
* An instance of {@link UI} to create interfaces.
* @type UI
*/
BdApi.UI = UI;
// BdApi.UI = UI;
/**
* An instance of {@link ReactUtils} to work with React.
@ -120,12 +118,22 @@ BdApi.DOM = DOMAPI;
* An instance of {@link ContextMenu} for interacting with context menus.
* @type ContextMenu
*/
BdApi.ContextMenu = ContextMenuAPI;
// BdApi.ContextMenu = ContextMenuAPI;
BdApi.Components = {
get Tooltip() {return DiscordModules.Tooltip;}
};
Object.freeze(BdApi);
Events.addListener("CLIENT_READY", async () => {
const [UI, ContextMenu] = await Promise.all([
import("./ui"),
import("./contextmenu")
].map(e => e.then(e => e.default)));
BdApi.UI = UI;
BdApi.ContextMenu = new ContextMenu;
});
// Object.freeze(BdApi);
Object.freeze(BdApi.prototype);
Object.freeze(BdApi.Components);

View File

@ -10,20 +10,21 @@ import Settings from "../settingsmanager";
import Logger from "common/logger";
import Patcher from "../patcher";
import ipc from "../ipc";
import {React, ReactDOM} from "modules";
/**
* The React module being used inside Discord.
* @type React
* @memberof BdApi
*/
const React = DiscordModules.React;
// const React = DiscordModules.React;
/**
* The ReactDOM module being used inside Discord.
* @type ReactDOM
* @memberof BdApi
*/
const ReactDOM = DiscordModules.ReactDOM;
// const ReactDOM = DiscordModules.ReactDOM;
/**
* A reference object to get BD's settings.
@ -500,4 +501,4 @@ export {
getBDData,
setBDData,
openDialog
};
};

View File

@ -1,5 +1,6 @@
import Logger from "common/logger";
import {default as MainPatcher} from "../patcher";
import {Patcher as WebpackPatcher} from "../webpackmodules";
/**
* `Patcher` is a utility class for modifying existing functions. Instance is accessible through the {@link BdApi}.
@ -98,8 +99,18 @@ class Patcher {
if (typeof(caller) !== "string") return Logger.err("BdApi.Patcher", "Parameter 0 of unpatchAll must be a string representing the caller");
MainPatcher.unpatchAll(caller);
}
patchSource(patch) {
const required = ["test", "regex", "replace"];
let missing;
if ((missing = required.filter(p => typeof patch[p] === "undefined")).length) {
return Logger.error("BdApi.Patcher", `Parameter 0 of patchSource is missing the following properties: ${missing.join(", ")}`);
}
return WebpackPatcher.addPatch(patch);
}
}
Object.freeze(Patcher);
Object.freeze(Patcher.prototype);
export default Patcher;
export default Patcher;

View File

@ -1,30 +1,77 @@
import LocaleManager from "./localemanager";
// import LocaleManager from "./localemanager";
import Logger from "common/logger";
// 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 FloatingWindows from "../ui/floatingwindows";
import DataStore from "./datastore";
import DiscordModules from "./discordmodules";
import LoadingIcon from "../loadingicon";
import Styles from "../styles/index.css";
import Editor from "./editor";
import Updater from "./updater";
import Events from "./emitter";
// 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 FloatingWindows from "../ui/floatingwindows";
// import DataStore from "./datastore";
// import DiscordModules from "./discordmodules";
// import LoadingIcon from "../loadingicon";
// import Styles from "../styles/index.css";
// import Editor from "./editor";
// import Updater from "./updater";
console.log("[BD] We are early baby!");
export default new class Core {
async startup() {
if (this.hasStarted) return;
this.hasStarted = true;
async preload() {
Config.appPath = process.env.DISCORD_APP_PATH;
Config.userData = process.env.DISCORD_USER_DATA;
Config.dataPath = process.env.BETTERDISCORD_DATA_PATH;
const [DataStore, PluginManager] = await Promise.all([
import("./datastore"),
import("./pluginmanager")
].map(i => i.then(v => v.default ? v.default : v)));
DataStore.initialize();
this.pluginErrors = PluginManager.initialize();
};
async startup() {
if (this.hasStarted) return;
const [
{default: DiscordModules},
{default: Logger},
{default: LocaleManager},
{default: DOMManager},
{default: PluginManager},
{default: ThemeManager},
{default: Settings},
Builtins,
{default: Modals},
{default: FloatingWindows},
{default: DataStore},
{default: LoadingIcon},
{default: Styles},
{default: Editor},
{default: Updater}
] = await Promise.all([
import("./discordmodules"),
import("common/logger"),
import("./localemanager"),
import("./dommanager"),
import("./pluginmanager"),
import("./thememanager"),
import("./settingsmanager"),
import("builtins"),
import("../ui/modals"),
import("../ui/floatingwindows"),
import("./datastore"),
import("../loadingicon"),
import("../styles/index.css"),
import("./editor"),
import("./updater")
]);
this.DiscordModules = DiscordModules;
this.hasStarted = true;
// Load css early
Logger.log("Startup", "Injecting BD Styles");
DOMManager.injectStyle("bd-stylesheet", Styles.toString());
@ -57,7 +104,7 @@ export default new class Core {
Logger.log("Startup", "Loading Plugins");
// const pluginErrors = [];
const pluginErrors = PluginManager.initialize();
Events.dispatch("LOAD_DELAYED_ADDONS");
Logger.log("Startup", "Loading Themes");
// const themeErrors = [];
@ -71,7 +118,7 @@ export default new class Core {
// Show loading errors
Logger.log("Startup", "Collecting Startup Errors");
Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors});
Modals.showAddonErrors({plugins: this.pluginErrors, themes: themeErrors});
const previousVersion = DataStore.getBDData("version");
if (Config.version !== previousVersion) {
@ -82,8 +129,8 @@ export default new class Core {
waitForConnection() {
return new Promise(done => {
if (DiscordModules.UserStore.getCurrentUser()) return done();
DiscordModules.Dispatcher.subscribe("CONNECTION_OPEN", done);
if (this.DiscordModules.UserStore.getCurrentUser()) return done();
this.DiscordModules.Dispatcher.subscribe("CONNECTION_OPEN", done);
});
}
};

View File

@ -198,9 +198,11 @@ export default class DOMManager {
}
}
DOMManager.createElement("bd-head", {target: document.head});
DOMManager.createElement("bd-body", {target: document.body});
DOMManager.createElement("bd-scripts", {target: DOMManager.bdHead});
DOMManager.createElement("bd-styles", {target: DOMManager.bdHead});
DOMManager.createElement("bd-themes", {target: DOMManager.bdHead});
DOMManager.createElement("style", {id: "customcss", target: DOMManager.bdHead});
document.addEventListener("DOMContentLoaded", () => {
DOMManager.createElement("bd-head", {target: document.head});
DOMManager.createElement("bd-body", {target: document.body});
DOMManager.createElement("bd-scripts", {target: DOMManager.bdHead});
DOMManager.createElement("bd-styles", {target: DOMManager.bdHead});
DOMManager.createElement("bd-themes", {target: DOMManager.bdHead});
DOMManager.createElement("style", {id: "customcss", target: DOMManager.bdHead});
}, {once: true});

View File

@ -1,12 +1,20 @@
const EventEmitter = require("events");
export default new class BDEvents extends EventEmitter {
constructor() {
super();
this.setMaxListeners(20);
}
dispatch(eventName, ...args) {
this.emit(eventName, ...args);
}
};
import EventEmitter from "../../../common/events";
export default new class BDEvents extends EventEmitter {
constructor() {
super();
this.setMaxListeners(20);
}
addListener() {
return this.on.apply(this, arguments);
}
removeListener() {
return this.off.apply(this, arguments);
}
dispatch(eventName, ...args) {
this.emit(eventName, ...args);
}
};

View File

@ -3,10 +3,8 @@ import DiscordModules from "./discordmodules";
import Utilities from "./utilities";
import Events from "./emitter";
const {LocaleStore} = DiscordModules;
export default new class LocaleManager {
get discordLocale() {return LocaleStore?.locale ?? this.defaultLocale;}
get discordLocale() {return DiscordModules.LocaleStore?.locale ?? this.defaultLocale;}
get defaultLocale() {return "en-US";}
constructor() {
@ -16,7 +14,7 @@ export default new class LocaleManager {
initialize() {
this.setLocale(this.discordLocale);
LocaleStore?.addChangeListener((newLocale) => this.setLocale(newLocale));
DiscordModules.LocaleStore?.addChangeListener((newLocale) => this.setLocale(newLocale));
}
setLocale(newLocale) {
@ -32,4 +30,4 @@ export default new class LocaleManager {
Utilities.extendTruthy(this.strings, newStrings);
Events.emit("strings-updated");
}
};
};

View File

@ -1,13 +1,25 @@
import DiscordModules from "./discordmodules";
import Events from "./emitter";
export {default as WebpackModules} from "./webpackmodules";
import DiscordModules from "./discordmodules";
export const React = DiscordModules.React;
export const ReactDOM = DiscordModules.ReactDOM;
export {DiscordModules};
// import DiscordModules from "./discordmodules";
const makeProxy = name => new Proxy({}, {
get(_, key) {
return DiscordModules[name][key];
},
set(_, key, value) {
return DiscordModules[name][key] = value;
}
})
export const React = makeProxy("React");
export const ReactDOM = makeProxy("ReactDOM");
export {Events, DiscordModules};
export {default as Utilities} from "./utilities";
export {default as DataStore} from "./datastore";
export {default as Events} from "./emitter";
export {default as Settings} from "./settingsmanager";
export {default as DOMManager} from "./dommanager";
export {default as Patcher} from "./patcher";
@ -15,4 +27,4 @@ export {default as LocaleManager} from "./localemanager";
export {default as Strings} from "./strings";
export {default as IPC} from "./ipc";
export {default as Logger} from "common/logger";
export {default as DiscordClasses} from "./discordclasses";
export {default as DiscordClasses} from "./discordclasses";

View File

@ -39,8 +39,7 @@ export default new class PluginManager extends AddonManager {
});
}
initialize() {
const errors = super.initialize();
onClientReady() {
this.setupFunctions();
Settings.registerPanel("plugins", Strings.Panels.plugins, {
order: 3,
@ -56,7 +55,6 @@ export default new class PluginManager extends AddonManager {
prefix: this.prefix
})
});
return errors;
}
/* Aliases */
@ -117,21 +115,37 @@ export default new class PluginManager extends AddonManager {
requireAddon(filename) {
const addon = super.requireAddon(filename);
if (addon["run-at"] !== "client-start") {
this.delayedAddons.push(addon);
return addon;
}
this.finalizeAddon(addon);
return addon;
}
finalizeAddon(addon) {
if (!addon) debugger;
try {
const module = {filename, exports: {}};
const module = {filename: addon.filename, exports: {}};
// Test if the code is valid gracefully
vm.compileFunction(addon.fileContent, ["require", "module", "exports", "__filename", "__dirname"], {filename: path.basename(filename)});
vm.compileFunction(addon.fileContent, ["require", "module", "exports", "__filename", "__dirname"], {filename: path.basename(addon.filename)});
addon.fileContent += normalizeExports(addon.exports || addon.name);
addon.fileContent += `\n//# sourceURL=betterdiscord://plugins/${addon.filename}`;
const wrappedPlugin = new Function(["require", "module", "exports", "__filename", "__dirname"], addon.fileContent); // eslint-disable-line no-new-func
wrappedPlugin(window.require, module, module.exports, module.filename, this.addonFolder);
addon.exports = module.exports;
delete addon.fileContent;
return addon;
}
catch (err) {
Logger.error("PluginManager", "Could not compile addon.", err, {addon});
throw new AddonError(addon.name || addon.filename, module.filename, Strings.Addons.compileError, {message: err.message, stack: err.stack}, this.prefix);
}
return super.finalizeAddon(addon);
}
startAddon(id) {return this.startPlugin(id);}
@ -207,4 +221,4 @@ export default new class PluginManager extends AddonManager {
}
}
}
};
};

View File

@ -20,7 +20,6 @@ export default new class ThemeManager extends AddonManager {
get language() {return "css";}
initialize() {
const errors = super.initialize();
Settings.registerPanel("themes", Strings.Panels.themes, {
order: 4,
element: SettingsRenderer.getAddonPanel(Strings.Panels.themes, this.addonList, this.state, {
@ -35,7 +34,8 @@ export default new class ThemeManager extends AddonManager {
prefix: this.prefix
})
});
return errors;
return super.initialize();
}
/* Aliases */
@ -85,4 +85,4 @@ export default new class ThemeManager extends AddonManager {
DOMManager.removeTheme(addon.slug + "-theme-container");
Toasts.show(Strings.Addons.disabled.format({name: addon.name, version: addon.version}));
}
};
};

View File

@ -1,4 +1,5 @@
import Logger from "common/logger";
import DiscordModules from "./discordmodules";
export default class Utilities {
/**
@ -215,4 +216,34 @@ export default class Utilities {
return classes.join(" ");
}
}
static makeLazy(component, fallback = "Loading...") {
let cache = null;
const comp = props => {
cache ??= DiscordModules.React.lazy(component);
return DiscordModules.React.createElement(DiscordModules.React.Suspense, {
fallback,
}, DiscordModules.React.createElement(cache, props));
};
comp.displayName = "LazyComponent";
return comp;
}
static makeModuleLazy(getter) {
let cache = null;
return () => {
cache ??= getter();
if (typeof cache === "undefined") {
Logger.warn("Utilities~makeModuleLazy", "The following module getter resulted in undefined.", getter);
}
return cache;
}
}
}

View File

@ -3,8 +3,36 @@
* @module WebpackModules
* @version 0.0.2
*/
import Events from "./emitter";
import Logger from "../../../common/logger";
const predefine = function (target, prop, effect) {
const value = target[prop];
Object.defineProperty(target, prop, {
get() {return value;},
set(newValue) {
Object.defineProperty(target, prop, {
value: newValue,
configurable: true,
enumerable: true,
writable: true
});
try {
effect(newValue);
}
catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
// eslint-disable-next-line no-setter-return
return newValue;
},
configurable: true
});
};
/**
* Checks if a given module matches a set of parameters.
* @callback module:WebpackModules.Filters~filter
@ -143,7 +171,72 @@ const wrapFilter = filter => (exports, module, moduleId) => {
}
};
export class Patcher {
static patches = [];
static patchesCount = new Map;
static initialize() {
window.$$bd_string_patches = {};
}
static findPatches(moduleSource) {
return this.patches.filter(patch => (patch.once ? !patch._ran : true) && patch.test.test(moduleSource));
}
static constructModule(moduleId, moduleSource) {
return (
"{" +
`const count = ${this.patchesCount.get(moduleId)};\n` +
`const id = "${moduleId}";\n` +
"const capture = function (variable) {\n" +
" return window.$$bd_string_patches[id]?.[variable];\n" +
"};\n" +
moduleSource +
"}\n" +
`//# sourceURL=${moduleId}.patched.js`
);
}
static patchModule(moduleId, moduleSource) {
const patches = this.findPatches(moduleSource);
if (!patches.length) return {code: moduleSource, count: 0};
window.$$bd_string_patches[moduleId] = {};
this.patchesCount.set(moduleId, 0);
for (const patch of patches) {
if (!patch.regex.test(moduleSource)) {
Logger.warn("WebpackModules~Patcher", `The following patch`, patch, "didn't have any affect.");
continue;
}
if (patch.once) patch._ran = true;
moduleSource = moduleSource.replace(patch.regex, patch.replace);
if (patch.variables) {
for (const key in patch.variables) {
Object.defineProperty(window.$$bd_string_patches, key, Object.getOwnPropertyDescriptor(patch.variables, key));
}
}
this.patchesCount.set(moduleId, this.patchesCount.get(moduleId) + 1);
}
return {
code: this.constructModule(moduleId, moduleSource),
count: this.patchesCount.get(moduleId)
};
}
static addPatch(patch) {
return this.patches.push(patch);
}
}
export default class WebpackModules {
static ready = false;
static find(filter, first = true) {return this.getModule(filter, {first});}
static findAll(filter) {return this.getModule(filter, {first: false});}
@ -458,7 +551,7 @@ export default class WebpackModules {
* @return {Array}
*/
static getAllModules() {
return this.require.c;
return this.ready ? this.require.c : {};
}
// Webpack Chunk Observing
@ -467,20 +560,48 @@ export default class WebpackModules {
static initialize() {
this.handlePush = this.handlePush.bind(this);
this.listeners = new Set();
this.__ORIGINAL_PUSH__ = window[this.chunkName].push;
Object.defineProperty(window[this.chunkName], "push", {
configurable: true,
get: () => this.handlePush,
set: (newPush) => {
this.__ORIGINAL_PUSH__ = newPush;
Object.defineProperty(window[this.chunkName], "push", {
value: this.handlePush,
configurable: true,
writable: true
});
}
Patcher.initialize();
predefine(window, this.chunkName, webpack => {
predefine(webpack, "push", originalPush => {
this.__ORIGINAL_PUSH__ = originalPush;
webpack.push([[Symbol()], {}, require => {
require.d = (target, exports) => {
for (const key in exports) {
if (!Reflect.has(exports, key) || target[key]) continue;
Object.defineProperty(target, key, {
get: () => exports[key](),
set: v => {exports[key] = () => v;},
enumerable: true,
configurable: true
});
}
};
}]);
webpack.pop();
webpack.push = this.handlePush;
});
const fn = exports => {
if (!exports?.Z?.addInterceptor) return;
this.removeListener(fn);
const cb = () => {
exports.Z.unsubscribe("CONNECTION_OPEN", cb);
this.ready = true;
Events.dispatch("CLIENT_READY");
};
exports.Z.subscribe("CONNECTION_OPEN", cb);
};
this.addListener(fn);
});
}
@ -505,7 +626,19 @@ export default class WebpackModules {
const [, modules] = chunk;
for (const moduleId in modules) {
const originalModule = modules[moduleId];
const moduleSource = Patcher.patchModule(moduleId, modules[moduleId].toString());
let originalModule = modules[moduleId];
if (moduleSource.count) try {
const res = window.eval(moduleSource.code);
if (typeof res === "function") {
originalModule = res;
}
} catch (err) {
Logger.error("WebpackModules~Patcher", `Couldn't patch module ${moduleId}:`, err);
}
modules[moduleId] = (module, exports, require) => {
try {

View File

@ -1,9 +1,12 @@
import Events from "../modules/emitter";
import WebpackModules from "../modules/webpackmodules";
Object.defineProperty(window, "Buffer", {
get() {return Buffer.getBuffer().Buffer;},
configurable: true,
enumerable: false
Events.addListener("CLIENT_READY", () => {
Object.defineProperty(window, "Buffer", {
get() {return Buffer.getBuffer().Buffer;},
configurable: true,
enumerable: false
});
});
export default class Buffer {
@ -14,4 +17,4 @@ export default class Buffer {
return this.cached;
}
}
}

View File

@ -76,4 +76,4 @@ export default function AddonErrorModal({pluginErrors, themeErrors}) {
</div>
</div>
</>;
}
}

View File

@ -12,4 +12,4 @@ export default function EmptyImage(props) {
</div>
{props.children}
</div>;
}
}

View File

@ -8,4 +8,4 @@ export default function NoResults(props) {
{props.text || DiscordModules.Strings.SEARCH_NO_RESULTS || ""}
</div>
</div>;
}
}

View File

@ -2,7 +2,6 @@ import {React} from "modules";
const {useState, useCallback} = React;
export default function Checkbox({checked: initialState, text, onChange: notifyParent}) {
const [checked, setChecked] = useState(initialState);
const onClick = useCallback(() => {
@ -20,4 +19,4 @@ export default function Checkbox({checked: initialState, text, onChange: notifyP
<span></span>
</div>
</div>;
}
}

View File

@ -62,4 +62,4 @@ export default forwardRef(function CssEditor({css, openNative, update, save, onC
].filter(c => c)}
value={css}
/>;
});
});

View File

@ -1,4 +1,4 @@
import {React, DiscordModules, Settings} from "modules";
import {React, Settings} from "modules";
import Checkbox from "./checkbox";
@ -144,4 +144,4 @@ export default forwardRef(function CodeEditor({value, language: requestedLang =
<div id={id} className={"editor " + theme}></div>
</div>
</div>;
});
});

View File

@ -1,3 +1,5 @@
import {React} from "modules";
import {DiscordModules} from "modules";
export default ({className}) => <div className={`bd-divider ${className || ""}`}></div>;
const React = DiscordModules.React;
export default ({className}) => <div className={`bd-divider ${className || ""}`}></div>;

View File

@ -1,5 +1,7 @@
import Logger from "common/logger";
import {React, IPC} from "modules";
import {DiscordModules, IPC} from "modules";
const React = DiscordModules.React;
export default class ErrorBoundary extends React.Component {
constructor(props) {

View File

@ -1,4 +1,4 @@
import {React, Events} from "modules";
import {Events, React} from "modules";
import FloatingWindow from "./window";
@ -33,4 +33,4 @@ export default function FloatingWindowContainer() {
{window.children}
</FloatingWindow>
);
}
}

View File

@ -154,4 +154,4 @@ export default function FloatingWindow({id, title, resizable, children, classNam
{children}
</div>
</div>;
}
}

View File

@ -1,17 +1,15 @@
import {WebpackModules, React, ReactDOM, DOMManager, Events} from "modules";
import FloatingWindowContainer from "./floating/container";
import {React, ReactDOM, WebpackModules, DOMManager, Events} from "modules";
import Utilities from "../modules/utilities";
/* eslint-disable new-cap */
const AppLayerProvider = WebpackModules.getByDisplayName("AppLayerProvider");
const FloatingWindowContainer = Utilities.makeLazy(() => import("./floating/container"));
const AppLayerProvider = Utilities.makeModuleLazy(() => WebpackModules.getByDisplayName("AppLayerProvider")());
let hasInitialized = false;
export default class FloatingWindows {
static initialize() {
const container = <FloatingWindowContainer />;
const wrapped = AppLayerProvider
? React.createElement(AppLayerProvider().props.layerContext.Provider, {value: [document.querySelector("#app-mount > .layerContainer-2v_Sit")]}, container) // eslint-disable-line new-cap
: container;
const wrapped = container;
const div = DOMManager.parseHTML(`<div id="floating-windows-layer">`);
DOMManager.bdBody.append(div);
ReactDOM.render(wrapped, div);
@ -22,4 +20,4 @@ export default class FloatingWindows {
if (!hasInitialized) this.initialize();
return Events.emit("open-window", window);
}
}
}

View File

@ -1,10 +1,10 @@
import {React, Strings} from "modules";
import {DiscordModules, Strings} from "modules";
import Editor from "../customcss/editor";
import Save from "../icons/save";
import Edit from "../icons/edit";
const {useState, useCallback, forwardRef, useImperativeHandle, useRef} = React;
const {useState, useCallback, forwardRef, useImperativeHandle, useRef} = DiscordModules.React;
export default forwardRef(function AddonEditor({content, language, save, openNative, id = "bd-addon-editor"}, ref) {
@ -39,4 +39,4 @@ export default forwardRef(function AddonEditor({content, language, save, openNat
value={content}
onChange={onChange}
/>;
});
});

View File

@ -1,10 +1,10 @@
import {Config} from "data";
import Logger from "common/logger";
import {WebpackModules, React, ReactDOM, Settings, Strings, DOMManager, DiscordModules, DiscordClasses} from "modules";
import {WebpackModules, React, ReactDOM, Settings, Strings, DOMManager, DiscordModules, DiscordClasses, Utilities} from "modules";
import FormattableString from "../structs/string";
import AddonErrorModal from "./addonerrormodal";
import ErrorBoundary from "./errorboundary";
const AddonErrorModal = Utilities.makeLazy(() => import("./addonerrormodal"));
const ErrorBoundary = Utilities.makeLazy(() => import("./errorboundary"));
export default class Modals {

View File

@ -1,11 +1,12 @@
import {React, WebpackModules, Patcher, Utilities, Settings, Events, DataStore} from "modules";
import AddonList from "./settings/addonlist";
import SettingsGroup from "./settings/group";
import SettingsTitle from "./settings/title";
import Header from "./settings/sidebarheader";
import {Filters} from "../modules/webpackmodules";
const AddonList = Utilities.makeLazy(() => import("./settings/addonlist"));
const SettingsGroup = Utilities.makeLazy(() => import("./settings/group"));
const SettingsTitle = Utilities.makeLazy(() => import("./settings/title"));
const Header = Utilities.makeLazy(() => import("./settings/sidebarheader"));
export default new class SettingsRenderer {
constructor() {
@ -99,4 +100,4 @@ export default new class SettingsRenderer {
const stateNode = Utilities.findInTree(node?.__reactFiber$, m => m && m.getPredicateSections, {walkable: ["return", "stateNode"]});
if (stateNode) stateNode.forceUpdate();
}
};
};

View File

@ -4,9 +4,10 @@ const CircularDependencyPlugin = require("circular-dependency-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const basePkg = require("../package.json");
/**@type {import("webpack").Configuration} */
module.exports = {
mode: "development",
target: "node",
target: "web",
devtool: false,
entry: "./src/index.js",
output: {
@ -14,6 +15,7 @@ module.exports = {
path: path.resolve(__dirname, "..", "dist")
},
externals: {
"vm": `require("vm")`,
"electron": `require("electron")`,
"fs": `require("fs")`,
"original-fs": `require("original-fs")`,
@ -53,6 +55,9 @@ module.exports = {
}),
new webpack.DefinePlugin({
"process.env.__VERSION__": JSON.stringify(basePkg.version)
}),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
],
optimization: {
@ -64,4 +69,4 @@ module.exports = {
})
]
}
};
};