Merge pull request #1408 from BetterDiscord/electron17
Prepare for electron 17 and patch for swc
This commit is contained in:
commit
b2aae545c4
13
.eslintrc
13
.eslintrc
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true
|
||||
"node": true,
|
||||
"es2020": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
|
@ -82,15 +83,7 @@
|
|||
"yoda": "error"
|
||||
},
|
||||
"globals": {
|
||||
"Proxy": "readonly",
|
||||
"Set": "readonly",
|
||||
"WeakMap": "readonly",
|
||||
"WeakSet": "readonly",
|
||||
"Map": "readonly",
|
||||
"Promise": "readonly",
|
||||
"Reflect": "readonly",
|
||||
"DiscordNative": "readonly",
|
||||
"__non_webpack_require__": "readonly",
|
||||
"Symbol": "readonly"
|
||||
"__non_webpack_require__": "readonly"
|
||||
}
|
||||
}
|
14
README.md
14
README.md
|
@ -40,7 +40,7 @@ For normal users, installing via the installers makes the most sense. However wh
|
|||
|
||||
### Prerequisites
|
||||
- [Git](https://git-scm.com)
|
||||
- [Node.js](https://nodejs.org/en/) with `npm`.
|
||||
- [Node.js](https://nodejs.org/en/) with [pnpm](https://pnpm.io/).
|
||||
- Command line of your choice.
|
||||
|
||||
### 1: Clone the repository
|
||||
|
@ -49,25 +49,25 @@ git clone https://github.com/BetterDiscord/BetterDiscord.git
|
|||
```
|
||||
### 2: Install dependencies
|
||||
```ps
|
||||
npm install
|
||||
pnpm recursive install
|
||||
```
|
||||
### 3: Run Build Script
|
||||
This will create a `injector.js`, `preload.js`, and `renderer.js` in the `dist` folder.
|
||||
```ps
|
||||
npm run build
|
||||
pnpm run build
|
||||
```
|
||||
### 4: Inject into your Discord client
|
||||
#### Install to Stable
|
||||
```ps
|
||||
npm run inject
|
||||
pnpm run inject
|
||||
```
|
||||
#### Install to PTB
|
||||
```ps
|
||||
npm run inject ptb
|
||||
pnpm run inject ptb
|
||||
```
|
||||
#### Install to Canary
|
||||
```ps
|
||||
npm run inject canary
|
||||
pnpm run inject canary
|
||||
```
|
||||
|
||||
## Additional Scripts
|
||||
|
@ -75,7 +75,7 @@ npm run inject canary
|
|||
### Compiling & Distribution
|
||||
This will create a `betterdiscord.asar` file in the `dist` folder.
|
||||
```ps
|
||||
npm run dist
|
||||
pnpm run dist
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
export function getKeys(object) {
|
||||
const keys = [];
|
||||
|
||||
for (const key in object) keys.push(key);
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
export default function cloneObject(target, newObject = {}, keys) {
|
||||
if (!Array.isArray(keys)) keys = getKeys(target);
|
||||
|
||||
return keys.reduce((clone, key) => {
|
||||
if (typeof(target[key]) === "object" && !Array.isArray(target[key]) && target[key] !== null) clone[key] = cloneObject(target[key], {});
|
||||
else if (typeof target[key] === "function") clone[key] = target[key].bind(target);
|
||||
else clone[key] = target[key];
|
||||
|
||||
return clone;
|
||||
}, newObject);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import Logger from "./logger";
|
||||
|
||||
export default class EventEmitter {
|
||||
static get EventEmitter() {return EventEmitter;}
|
||||
|
||||
constructor() {
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
setMaxListeners() {}
|
||||
|
||||
on(event, callback) {
|
||||
if (!this.events[event]) this.events[event] = new Set();
|
||||
|
||||
this.events[event].add(callback);
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
if (!this.events[event]) return;
|
||||
|
||||
for (const [index, listener] of this.events[event].entries()) {
|
||||
try {
|
||||
listener(...args);
|
||||
}
|
||||
catch (error) {
|
||||
Logger.error("Emitter", `Cannot fire listener for event ${event} at position ${index}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
off(event, callback) {
|
||||
if (!this.events[event]) return;
|
||||
|
||||
return this.events[event].delete(callback);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
# BetterDiscord Injector
|
||||
|
||||
You're probably looking for the main app, [click here](https://github.com/rauenzi/BetterDiscordApp) to go there.
|
||||
You're probably looking for the main app, [click here](https://github.com/BetterDiscord/BetterDiscord) to go there.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "betterdiscord-injector",
|
||||
"name": "@betterdiscord/injector",
|
||||
"version": "0.6.2",
|
||||
"description": "BetterDiscord injector module",
|
||||
"main": "src/index.js",
|
||||
|
@ -11,10 +11,6 @@
|
|||
"lint": "eslint --ext .js src/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"copy-webpack-plugin": "^8.0.0",
|
||||
"eslint": "^7.21.0",
|
||||
"webpack": "^5.24.2",
|
||||
"webpack-cli": "^4.5.0"
|
||||
"webpack": "^5.73.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,8 @@ export default class BetterDiscord {
|
|||
try {
|
||||
${content}
|
||||
return true;
|
||||
} catch {
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
const Module = require("module");
|
||||
const path = require("path");
|
||||
const electron = require("electron");
|
||||
const NodeEvents = require("events");
|
||||
|
||||
const cloneObject = function (target, newObject = {}, keys) {
|
||||
if (!Array.isArray(keys)) keys = Object.keys(Object.getOwnPropertyDescriptors(target));
|
||||
return keys.reduce((clone, key) => {
|
||||
if (typeof(target[key]) === "object" && !Array.isArray(target[key]) && target[key] !== null && !(target[key] instanceof NodeEvents)) clone[key] = cloneObject(target[key], {});
|
||||
else clone[key] = target[key];
|
||||
|
||||
return clone;
|
||||
}, newObject);
|
||||
};
|
||||
|
||||
/* 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.Buffer = Buffer;
|
||||
|
||||
|
||||
electron.webFrame.top.context.process = new class PatchedProcess extends NodeEvents {
|
||||
get __ORIGINAL_PROCESS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__() {return process;}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
Object.assign(this,
|
||||
cloneObject(process, {}, Object.keys(NodeEvents.prototype)),
|
||||
cloneObject(process, {})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Load Discord's original preload
|
||||
const preload = process.env.DISCORD_PRELOAD;
|
||||
if (preload) {
|
||||
|
||||
// Restore original preload for future windows
|
||||
electron.ipcRenderer.send("bd-register-preload", preload);
|
||||
// Run original preload
|
||||
try {
|
||||
const originalKill = process.kill;
|
||||
process.kill = function() {};
|
||||
require(preload);
|
||||
process.kill = originalKill;
|
||||
}
|
||||
catch (e) {
|
||||
// TODO bail out
|
||||
}
|
||||
}
|
||||
|
||||
Module.globalPaths.push(path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules"));
|
|
@ -1,6 +1,4 @@
|
|||
const path = require("path");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const CircularDependencyPlugin = require("circular-dependency-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports = (env, argv) => ({
|
||||
|
@ -29,20 +27,6 @@ module.exports = (env, argv) => ({
|
|||
common: path.resolve(__dirname, "..", "common")
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CircularDependencyPlugin({
|
||||
exclude: /node_modules/,
|
||||
cwd: process.cwd(),
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, "src", "preload.js"),
|
||||
to: path.resolve(__dirname, "..", "dist", "preload.js")
|
||||
},
|
||||
],
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
|
|
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
|
@ -1,26 +1,31 @@
|
|||
{
|
||||
"name": "betterdiscord",
|
||||
"version": "1.6.3",
|
||||
"version": "1.7.0",
|
||||
"description": "Enhances Discord by adding functionality and themes.",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"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",
|
||||
"build": "pnpm run build-injector && pnpm run build-preload && pnpm run build-renderer",
|
||||
"build-prod": "pnpm --filter injector build-prod && pnpm --filter preload build-prod && pnpm --filter renderer build-prod",
|
||||
"build-injector": "pnpm --filter injector build",
|
||||
"build-renderer": "pnpm --filter renderer build",
|
||||
"build-preload": "pnpm --filter preload build",
|
||||
"pack-emotes": "node scripts/emotes.js",
|
||||
"inject": "node scripts/inject.js",
|
||||
"lint": "eslint --ext .js common/ && npm run lint --prefix injector && npm run lint --prefix renderer",
|
||||
"lint": "eslint --ext .js common/ && pnpm --filter injector lint && pnpm --filter preload lint && pnpm --filter renderer lint-js",
|
||||
"test": "mocha --require @babel/register --recursive \"./tests/renderer/*.js\"",
|
||||
"dist": "npm run build-prod && node scripts/pack.js",
|
||||
"dist": "pnpm run build-prod && node scripts/pack.js",
|
||||
"api": "jsdoc -X renderer/src/modules/pluginapi.js > jsdoc-ast.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"asar": "^3.0.3",
|
||||
"eslint": "^7.12.0",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"jsdoc": "^3.6.11",
|
||||
"mocha": "^10.0.0"
|
||||
"asar": "^3.2.0",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-plugin-react": "^7.31.6",
|
||||
"mocha": "^10.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"pnpm": ">=7"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
|||
packages:
|
||||
- "injector/"
|
||||
- "preload/"
|
||||
- "renderer/"
|
|
@ -0,0 +1,3 @@
|
|||
# BetterDiscord Preload
|
||||
|
||||
You're probably looking for the main app, [click here](https://github.com/BetterDiscord/BetterDiscord) to go there.
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"allowSyntheticDefaultImports": false,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"common": ["../common"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@betterdiscord/preload",
|
||||
"version": "0.0.1",
|
||||
"description": "BetterDiscord preload module",
|
||||
"main": "src/index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack --progress --color",
|
||||
"watch": "webpack --progress --color --watch",
|
||||
"build-prod": "webpack --stats minimal --mode production",
|
||||
"lint": "eslint --ext .js src/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.73.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
const crypto = (() => {
|
||||
let cache = null;
|
||||
|
||||
return () => {
|
||||
if (cache) return cache;
|
||||
|
||||
return cache = __non_webpack_require__("crypto");
|
||||
};
|
||||
})();
|
||||
|
||||
export function createHash(type) {
|
||||
const hash = crypto().createHash(type);
|
||||
|
||||
const ctx = {
|
||||
update(data) {
|
||||
hash.update(data);
|
||||
|
||||
return ctx;
|
||||
},
|
||||
digest(encoding) {return hash.digest(encoding);}
|
||||
};
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function randomBytes(length) {
|
||||
return crypto().randomBytes(length);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import {ipcRenderer as IPC, shell} from "electron";
|
||||
|
||||
export const ipcRenderer = {
|
||||
send: IPC.send.bind(IPC),
|
||||
sendToHost: IPC.sendToHost.bind(IPC),
|
||||
sendTo: IPC.sendTo.bind(IPC),
|
||||
sendSync: IPC.sendSync.bind(IPC),
|
||||
invoke: IPC.invoke.bind(IPC),
|
||||
on: IPC.on.bind(IPC),
|
||||
off: IPC.off.bind(IPC)
|
||||
};
|
||||
|
||||
export {shell};
|
|
@ -0,0 +1,77 @@
|
|||
import * as fs from "fs";
|
||||
import cloneObject from "common/clone";
|
||||
import Logger from "common/logger";
|
||||
|
||||
export function readFile(path, options = "utf8") {
|
||||
return fs.readFileSync(path, options);
|
||||
}
|
||||
|
||||
export function writeFile(path, content, options) {
|
||||
if (content instanceof Uint8Array) {
|
||||
content = Buffer.from(content);
|
||||
}
|
||||
|
||||
const doWriteFile = options?.originalFs ? __non_webpack_require__("original-fs").writeFileSync : fs.writeFileSync;
|
||||
|
||||
return doWriteFile(path, content, options);
|
||||
}
|
||||
|
||||
export function readDirectory(path, options) {
|
||||
return fs.readdirSync(path, options);
|
||||
}
|
||||
|
||||
export function createDirectory(path, options) {
|
||||
return fs.mkdirSync(path, options);
|
||||
}
|
||||
|
||||
export function deleteDirectory(path, options) {
|
||||
fs.rmdirSync(path, options);
|
||||
}
|
||||
|
||||
export function exists(path) {
|
||||
return fs.existsSync(path);
|
||||
}
|
||||
|
||||
export function getRealPath(path, options) {
|
||||
return fs.realpathSync(path, options);
|
||||
}
|
||||
|
||||
export function rename(oldPath, newPath) {
|
||||
return fs.renameSync(oldPath, newPath);
|
||||
}
|
||||
|
||||
export function unlinkSync(fileToDelete) {
|
||||
return fs.unlinkSync(fileToDelete);
|
||||
}
|
||||
|
||||
export function createWriteStream(path, options) {
|
||||
return cloneObject(fs.createWriteStream(path, options));
|
||||
}
|
||||
|
||||
export function watch(path, options, callback) {
|
||||
const watcher = fs.watch(path, options, (event, filename) => {
|
||||
try {
|
||||
callback(event, filename);
|
||||
}
|
||||
catch (error) {
|
||||
Logger.stacktrace("filesystem", "Failed to watch path", error);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
watcher.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getStats(path, options) {
|
||||
const stats = fs.statSync(path, options);
|
||||
|
||||
return {
|
||||
...stats,
|
||||
isFile: stats.isFile.bind(stats),
|
||||
isDirectory: stats.isDirectory.bind(stats),
|
||||
isSymbolicLink: stats.isSymbolicLink.bind(stats)
|
||||
};
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import * as https from "https";
|
||||
|
||||
const methods = ["get", "put", "post", "delete"];
|
||||
const headersToClone = ["statusCode", "statusMessage", "url", "headers", "method", "aborted", "complete", "rawHeaders", "end"];
|
||||
|
||||
const request = function (url, options, callback) {
|
||||
let responseObject = undefined;
|
||||
let pipe = undefined;
|
||||
|
||||
const req = https.request(url, Object.assign({method: "GET"}, options), res => {
|
||||
const chunks = [];
|
||||
let error = null;
|
||||
|
||||
responseObject = res;
|
||||
|
||||
if (pipe) {
|
||||
res.pipe(pipe);
|
||||
}
|
||||
|
||||
res.addListener("error", err => {error = err;});
|
||||
|
||||
res.addListener("data", chunk => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
res.addListener("end", () => {
|
||||
const headers = Object.fromEntries(headersToClone.map(h => [h, res[h]]));
|
||||
|
||||
callback(error, headers, Buffer.concat(chunks));
|
||||
req.end();
|
||||
});
|
||||
});
|
||||
|
||||
req.end();
|
||||
|
||||
return {
|
||||
end() {req.end();},
|
||||
pipe(fsStream) {
|
||||
if (!responseObject) {
|
||||
pipe = fsStream;
|
||||
} else {
|
||||
responseObject.pipe(fsStream);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default Object.assign({request},
|
||||
Object.fromEntries(methods.map(method => [
|
||||
method,
|
||||
function () {
|
||||
arguments[1] ??= {};
|
||||
|
||||
arguments[1].method ??= method.toUpperCase();
|
||||
|
||||
return Reflect.apply(request, this, arguments);
|
||||
}
|
||||
]))
|
||||
);
|
|
@ -0,0 +1,44 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Module from "module";
|
||||
|
||||
// const Module = require("module");
|
||||
Module.globalPaths.push(path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules"));
|
||||
// module.paths.push(path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules"));
|
||||
|
||||
Module._load = (load => (req, parent, isMain) => {
|
||||
if (req.includes("./") || req.includes("..")) return load(req, parent, isMain);
|
||||
const found = Module.globalPaths.find(m => fs.existsSync(path.resolve(m, req)));
|
||||
|
||||
return found ? load(path.resolve(found, req), parent, isMain) : load(req, parent, isMain);
|
||||
})(Module._load);
|
||||
|
||||
// const originalLoad = Module.prototype.load;
|
||||
// Module.prototype.load = function() {
|
||||
// const returnValue = Reflect.apply(originalLoad, this, arguments);
|
||||
// console.log(this, arguments, returnValue);
|
||||
// return returnValue;
|
||||
// };
|
||||
|
||||
|
||||
// const nodeModulePaths = Module._nodeModulePaths;
|
||||
// console.log(nodeModulePaths);
|
||||
// Module._nodeModulePaths = (from) => {
|
||||
// return nodeModulePaths(from).concat([path.resolve(process.env.DISCORD_APP_PATH, "..", "app.asar", "node_modules")]);
|
||||
// };
|
||||
|
||||
// console.log(Module._nodeModulePaths, Module._nodeModulePaths("request"));
|
||||
// console.dir(Module);
|
||||
// console.log(Object.keys(Module));
|
||||
// console.log(require("request"));
|
||||
|
||||
export * as filesystem from "./filesystem";
|
||||
export {default as https} from "./https";
|
||||
export * as electron from "./electron";
|
||||
export * as crypto from "./crypto";
|
||||
export * as vm from "./vm";
|
||||
|
||||
// We can expose that without any issues.
|
||||
export * as path from "path";
|
||||
export * as net from "net"; // TODO: evaluate need and create wrapper
|
||||
export * as os from "os";
|
|
@ -0,0 +1,14 @@
|
|||
import vm from "vm";
|
||||
|
||||
export function compileFunction(code, params = [], options = {}) {
|
||||
try {
|
||||
return vm.compileFunction(code, params, options);
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import {contextBridge} from "electron";
|
||||
import newProcess from "./process";
|
||||
import * as BdApi from "./api";
|
||||
import init from "./init";
|
||||
|
||||
let hasInitialized = false;
|
||||
contextBridge.exposeInMainWorld("BetterDiscord", BdApi);
|
||||
contextBridge.exposeInMainWorld("process", newProcess);
|
||||
contextBridge.exposeInMainWorld("BetterDiscordPreload", () => {
|
||||
if (hasInitialized) return null;
|
||||
hasInitialized = true;
|
||||
return BdApi;
|
||||
});
|
||||
|
||||
init();
|
|
@ -0,0 +1,22 @@
|
|||
import {ipcRenderer as IPC} from "electron";
|
||||
import * as IPCEvents from "common/constants/ipcevents";
|
||||
|
||||
export default function() {
|
||||
// Load Discord's original preload
|
||||
const preload = process.env.DISCORD_PRELOAD;
|
||||
if (preload) {
|
||||
|
||||
// Restore original preload for future windows
|
||||
IPC.send(IPCEvents.REGISTER_PRELOAD, preload);
|
||||
// Run original preload
|
||||
try {
|
||||
const originalKill = process.kill;
|
||||
process.kill = function() {};
|
||||
__non_webpack_require__(preload);
|
||||
process.kill = originalKill;
|
||||
}
|
||||
catch (e) {
|
||||
// TODO bail out
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import cloneObject, {getKeys} from "common/clone";
|
||||
|
||||
export default cloneObject(process, {}, getKeys(process).filter(p => p !== "config"));
|
|
@ -0,0 +1,42 @@
|
|||
const path = require("path");
|
||||
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: "preload.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")`,
|
||||
os: `require("os")`,
|
||||
net: `require("net")`
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js"],
|
||||
alias: {
|
||||
common: path.resolve(__dirname, "..", "common")
|
||||
}
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
compress: {drop_debugger: false},
|
||||
keep_classnames: true
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "betterdiscord-renderer",
|
||||
"name": "@betterdiscord/renderer",
|
||||
"description": "Renderer portion of the BetterDiscord application.",
|
||||
"private": true,
|
||||
"main": "src/index.js",
|
||||
|
@ -24,16 +24,13 @@
|
|||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss-cli": "^9.1.0",
|
||||
"postcss-csso": "^6.0.0",
|
||||
"postcss-easy-import": "^4.0.0",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"stylelint": "^14.3.0",
|
||||
"stylelint-config-standard": "^24.0.0",
|
||||
"webpack": "^5.67.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
"webpack": "^5.73.0",
|
||||
"stylelint-config-standard": "^24.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ export {default as PublicServers} from "./general/publicservers";
|
|||
export {default as VoiceDisconnect} from "./general/voicedisconnect";
|
||||
export {default as MediaKeys} from "./general/mediakeys";
|
||||
|
||||
export {default as EmoteModule} from "./emotes/emotes";
|
||||
export {default as EmoteMenu} from "./emotes/emotemenu";
|
||||
// export {default as EmoteModule} from "./emotes/emotes";
|
||||
// export {default as EmoteMenu} from "./emotes/emotemenu";
|
||||
// export {default as EmoteAutocaps} from "./emotes/emoteautocaps";
|
||||
|
||||
export {default as DevToolsListener} from "./developer/devtools";
|
||||
|
|
|
@ -1,8 +1,42 @@
|
|||
import Builtin from "../../structs/builtin";
|
||||
import {DiscordModules, WebpackModules, Strings, DOM} from "modules";
|
||||
import {DiscordModules, WebpackModules, Strings, DOM, React} from "modules";
|
||||
import PublicServersMenu from "../../ui/publicservers/menu";
|
||||
import Globe from "../../ui/icons/globe";
|
||||
|
||||
const LayerStack = WebpackModules.getByProps("pushLayer");
|
||||
const LayerManager = {
|
||||
pushLayer(component) {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_PUSH",
|
||||
component
|
||||
});
|
||||
},
|
||||
popLayer() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP"
|
||||
});
|
||||
},
|
||||
popAllLayers() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP_ALL"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {hasError: false};
|
||||
}
|
||||
|
||||
componentDidCatch() {
|
||||
this.setState({hasError: true});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) return null;
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default new class PublicServers extends Builtin {
|
||||
get name() {return "PublicServers";}
|
||||
|
@ -10,17 +44,37 @@ export default new class PublicServers extends Builtin {
|
|||
get id() {return "publicServers";}
|
||||
|
||||
enabled() {
|
||||
this._appendButton();
|
||||
const ListNavigators = WebpackModules.getByProps("ListNavigatorProvider");
|
||||
this.after(ListNavigators, "ListNavigatorProvider", (_, __, returnValue) => {
|
||||
if (returnValue.props.value.id !== "guildsnav") return;
|
||||
this._appendButton();
|
||||
});
|
||||
// let target = null;
|
||||
// WebpackModules.getModule((_, m) => {
|
||||
// if (m.exports?.toString().includes("privateChannelIds")) {
|
||||
// target = m.exports;
|
||||
// }
|
||||
// });
|
||||
// if (!target || !target.Z) return;
|
||||
// const PrivateChannelListComponents = WebpackModules.getByProps("LinkButton");
|
||||
// this.after(target, "Z", (_, __, returnValue) => {
|
||||
// const destination = returnValue?.props?.children?.props?.children;
|
||||
// if (!destination || !Array.isArray(destination)) return;
|
||||
// if (destination.find(b => b?.props?.children?.props?.id === "public-server-button")) return;
|
||||
|
||||
// destination.push(
|
||||
// React.createElement(ErrorBoundary, null,
|
||||
// React.createElement(PrivateChannelListComponents.LinkButton,
|
||||
// {
|
||||
// id: "public-server-button",
|
||||
// onClick: () => this.openPublicServers(),
|
||||
// text: "Public Servers",
|
||||
// icon: () => React.createElement(Globe, {color: "currentColor"})
|
||||
// }
|
||||
// )
|
||||
// )
|
||||
// );
|
||||
// });
|
||||
}
|
||||
|
||||
disabled() {
|
||||
this.unpatchAll();
|
||||
DOM.query("#bd-pub-li").remove();
|
||||
// this.unpatchAll();
|
||||
// DOM.query("#bd-pub-li").remove();
|
||||
}
|
||||
|
||||
async _appendButton() {
|
||||
|
@ -36,7 +90,7 @@ export default new class PublicServers extends Builtin {
|
|||
}
|
||||
|
||||
openPublicServers() {
|
||||
LayerStack.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerStack.popLayer}));
|
||||
LayerManager.pushLayer(() => DiscordModules.React.createElement(PublicServersMenu, {close: LayerManager.popLayer}));
|
||||
}
|
||||
|
||||
get button() {
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
// fixed, improved, added, progress
|
||||
export default {
|
||||
description: "Discord is _still_ making a lot of internal changes!",
|
||||
description: "BetterDiscord is alive! At least... _sorta_.",
|
||||
changes: [
|
||||
{
|
||||
title: "Changes",
|
||||
title: "Known Issues",
|
||||
type: "improved",
|
||||
items: [
|
||||
"Plugin startup errors should be more descriptive for developers.",
|
||||
"**Many many plugins are either completely broken or missing functionality.** Please refer to the respective developers for ETAs.",
|
||||
"The Twitch Emote system is completely broken, and there is no ETA on being fixed.",
|
||||
"The Public Servers module is also broken with no ETA for a fix.",
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Fixes",
|
||||
title: "Important News!",
|
||||
type: "fixed",
|
||||
items: [
|
||||
"Fixed an issue where custom css crashed Discord.",
|
||||
"Fixed an issue where `waitForModule` returned a boolean instead of a module.",
|
||||
"Due to recent and upcoming changes, BetterDiscord is going to go through a rewrite.",
|
||||
"There is no ETA or timeline for this rewrite.",
|
||||
"We will continue to try and __maintain__ this version of BetterDiscord without adding new features."
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -4,7 +4,7 @@ export default [
|
|||
id: "general",
|
||||
collapsible: true,
|
||||
settings: [
|
||||
{type: "switch", id: "emotes", value: true},
|
||||
{type: "switch", id: "emotes", value: true, disabled: true},
|
||||
{type: "switch", id: "publicServers", value: true},
|
||||
{type: "switch", id: "voiceDisconnect", value: false},
|
||||
{type: "switch", id: "showToasts", value: true},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import require from "./polyfill"; // eslint-disable-line no-unused-vars
|
||||
import secure from "./secure";
|
||||
import patchModuleLoad from "./moduleloader";
|
||||
import LoadingIcon from "./loadingicon";
|
||||
|
@ -8,6 +9,7 @@ import BdApi from "./modules/pluginapi";
|
|||
secure();
|
||||
patchModuleLoad();
|
||||
window.BdApi = BdApi;
|
||||
window.global = window;
|
||||
|
||||
// Add loading icon at the bottom right
|
||||
LoadingIcon.show();
|
||||
|
|
|
@ -68,7 +68,9 @@ export default class AddonManager {
|
|||
if (this.watcher) return Logger.err(this.name, `Already watching ${this.prefix} addons.`);
|
||||
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));
|
||||
if (!eventType || !filename) return;
|
||||
// console.log(eventType, filename)
|
||||
|
||||
const absolutePath = path.resolve(this.addonFolder, filename);
|
||||
if (!filename.endsWith(this.extension)) {
|
||||
|
@ -93,10 +95,11 @@ export default class AddonManager {
|
|||
Logger.err(this.name, `Could not rename file: ${filename} ${newFilename}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// console.log("watcher", "before promise");
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
try {
|
||||
const stats = fs.statSync(absolutePath);
|
||||
// console.log("watcher", stats);
|
||||
if (!stats.isFile()) return;
|
||||
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
|
||||
if (typeof(stats.mtime.getTime()) !== "number") return;
|
||||
|
@ -106,7 +109,10 @@ export default class AddonManager {
|
|||
if (eventType == "change") this.reloadAddon(filename, true);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== "ENOENT") return;
|
||||
// window.watcherError = err;
|
||||
// console.log("watcher", err);
|
||||
// console.dir(err);
|
||||
if (err.code !== "ENOENT" && !err?.message.startsWith("ENOENT")) return;
|
||||
delete this.timeCache[filename];
|
||||
this.unloadAddon(filename, true);
|
||||
}
|
||||
|
@ -207,6 +213,7 @@ export default class AddonManager {
|
|||
|
||||
unloadAddon(idOrFileOrAddon, shouldToast = true, isReload = false) {
|
||||
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
|
||||
// console.log("watcher", "unloadAddon", idOrFileOrAddon, addon);
|
||||
if (!addon) return false;
|
||||
if (this.state[addon.id]) isReload ? this.stopAddon(addon) : this.disableAddon(addon);
|
||||
|
||||
|
@ -314,6 +321,7 @@ export default class AddonManager {
|
|||
|
||||
deleteAddon(idOrFileOrAddon) {
|
||||
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
|
||||
// console.log(path.resolve(this.addonFolder, addon.filename), fs.unlinkSync)
|
||||
return fs.unlinkSync(path.resolve(this.addonFolder, addon.filename));
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import BDLogo from "../ui/icons/bdlogo";
|
|||
import Logger from "common/logger";
|
||||
|
||||
const React = DiscordModules.React;
|
||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||
const Anchor = WebpackModules.getByDisplayName("Anchor");
|
||||
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
|
||||
const Anchor = WebpackModules.getByProps("Link");
|
||||
|
||||
const Developers = [
|
||||
/* Zerebos#7790 */
|
||||
|
@ -21,7 +21,7 @@ const Developers = [
|
|||
|
||||
const DeveloperBadge = function DeveloperBadge({type, size = 16}) {
|
||||
return React.createElement(Tooltip, {color: "primary", position: "top", text: "BetterDiscord Developer"},
|
||||
props => React.createElement(Anchor, Object.assign({className: `bd-${type}-badge`, href: "https://github.com/BetterDiscord/BetterDiscord", title: "BetterDiscord", target: "_blank"}, props),
|
||||
props => React.createElement(Anchor.Link, Object.assign({className: `bd-${type}-badge`, href: "https://github.com/BetterDiscord/BetterDiscord", title: "BetterDiscord", target: "_blank"}, props),
|
||||
React.createElement(BDLogo, {size, className: "bd-logo"})
|
||||
)
|
||||
);
|
||||
|
@ -33,17 +33,14 @@ export default new class ComponentPatcher {
|
|||
debug(...message) {return Logger.debug("ComponentPatcher", ...message);}
|
||||
|
||||
initialize() {
|
||||
Utilities.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")();
|
||||
Utilities.suppressErrors(this.patchGuildPills.bind(this), "BD Guild Pills Patch")();
|
||||
Utilities.suppressErrors(this.patchGuildListItems.bind(this), "BD Guild List Items Patch")();
|
||||
Utilities.suppressErrors(this.patchMessageHeader.bind(this), "BD Message Header Patch")();
|
||||
Utilities.suppressErrors(this.patchMemberList.bind(this), "BD Member List Patch")();
|
||||
Utilities.suppressErrors(this.patchProfile.bind(this), "BD Profile Badges Patch")();
|
||||
// Utilities.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")();
|
||||
// Utilities.suppressErrors(this.patchMemberList.bind(this), "BD Member List Patch")();
|
||||
// Utilities.suppressErrors(this.patchProfile.bind(this), "BD Profile Badges Patch")();
|
||||
}
|
||||
|
||||
patchSocial() {
|
||||
if (this.socialPatch) return;
|
||||
const TabBar = WebpackModules.getByDisplayName("TabBar");
|
||||
const TabBar = WebpackModules.getByProps("Types", "Looks", "Header");
|
||||
if (!TabBar) return;
|
||||
this.socialPatch = Patcher.after("ComponentPatcher", TabBar.prototype, "render", (thisObject, args, returnValue) => {
|
||||
const children = returnValue.props.children;
|
||||
|
@ -76,102 +73,6 @@ export default new class ComponentPatcher {
|
|||
});
|
||||
}
|
||||
|
||||
patchGuildListItems() {
|
||||
if (this.guildListItemsPatch) return;
|
||||
const ListNavigators = WebpackModules.getByProps("ListNavigatorProvider");
|
||||
const GuildComponent = WebpackModules.find(m => m.type && m.type.toString().includes("guildNode") && m.type.toString().includes("treeitem"));
|
||||
if (!GuildComponent || typeof(GuildComponent.type) !== "function") return this.warn("Failed to get Guild component.");
|
||||
if (!ListNavigators || typeof(ListNavigators.ListNavigatorProvider) !== "function") return this.warn("Failed to get ListNavigatorProvider component.");
|
||||
|
||||
this.guildListItemsPatch = Patcher.after("ComponentPatcher", GuildComponent, "type", (_, [props], returnValue) => {
|
||||
if (!returnValue || !returnValue.props) return;
|
||||
|
||||
try {
|
||||
returnValue.props.className += " bd-guild";
|
||||
if (props.unread) returnValue.props.className += " bd-unread";
|
||||
if (props.selected) returnValue.props.className += " bd-selected";
|
||||
if (props.mediaState.audio) returnValue.props.className += " bd-audio";
|
||||
if (props.mediaState.video) returnValue.props.className += " bd-video";
|
||||
if (props.badge) returnValue.props.className += " bd-badge";
|
||||
if (props.animatable) returnValue.props.className += " bd-animatable";
|
||||
if (props.unavailable) returnValue.props.className += " bd-unavailable";
|
||||
if (props.mediaState.screenshare) returnValue.props.className += " bd-screenshare";
|
||||
if (props.mediaState.liveStage) returnValue.props.className += " bd-live-stage";
|
||||
if (props.muted) returnValue.props.className += " bd-muted";
|
||||
}
|
||||
catch (err) {
|
||||
Logger.error("ComponentPatcher:Guilds", `Error inside BDGuild:`, err);
|
||||
this.guildListItemsPatch();
|
||||
}
|
||||
});
|
||||
|
||||
const {useState} = DiscordModules.React;
|
||||
function useForceUpdate() {
|
||||
const [, setValue] = useState(false);
|
||||
return () => setValue(v => !v); // update the state to force render
|
||||
}
|
||||
|
||||
let hasForced = false;
|
||||
this.cancelForceUpdate = Patcher.after("ComponentPatcher", ListNavigators, "ListNavigatorProvider", (_, __, returnValue) => {
|
||||
if (returnValue.props.value.id !== "guildsnav") return;
|
||||
|
||||
const originalParent = Utilities.findInTree(returnValue, m => m?.props?.className, {walkable: ["children", "props"]});
|
||||
if (!originalParent) return;
|
||||
const original = originalParent.type;
|
||||
originalParent.type = e => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
if (!hasForced) {
|
||||
hasForced = true;
|
||||
setTimeout(() => {
|
||||
forceUpdate();
|
||||
this.cancelForceUpdate();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
return Reflect.apply(original, null, [e]);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
patchGuildPills() {
|
||||
if (this.guildPillPatch) return;
|
||||
const guildPill = WebpackModules.find(m => m?.default?.displayName === "AnimatedHalfPill");
|
||||
if (!guildPill) return;
|
||||
this.guildPillPatch = Patcher.after("ComponentPatcher", guildPill, "default", (_, args, returnValue) => {
|
||||
const props = args[0];
|
||||
if (props.unread) returnValue.props.className += " bd-unread";
|
||||
if (props.selected) returnValue.props.className += " bd-selected";
|
||||
if (props.hovered) returnValue.props.className += " bd-hovered";
|
||||
return returnValue;
|
||||
});
|
||||
}
|
||||
|
||||
patchMessageHeader() {
|
||||
if (this.messageHeaderPatch) return;
|
||||
// const MessageTimestamp = WebpackModules.getModule(m => m?.default?.toString().indexOf("showTimestampOnHover") > -1);
|
||||
// this.messageHeaderPatch = Patcher.after("ComponentPatcher", MessageTimestamp, "default", (_, [{message}], returnValue) => {
|
||||
// const userId = Utilities.getNestedProp(message, "author.id");
|
||||
// if (Developers.indexOf(userId) < 0) return;
|
||||
// if (!returnValue?.type) return;
|
||||
// const orig = returnValue.type;
|
||||
// returnValue.type = function() {
|
||||
// const retVal = Reflect.apply(orig, this, arguments);
|
||||
|
||||
// const children = Utilities.getNestedProp(retVal, "props.children.1.props.children");
|
||||
// if (!Array.isArray(children)) return;
|
||||
|
||||
// children.splice(3, 0,
|
||||
// React.createElement(DeveloperBadge, {
|
||||
// type: "chat"
|
||||
// })
|
||||
// );
|
||||
|
||||
// return retVal;
|
||||
// };
|
||||
|
||||
// });
|
||||
}
|
||||
|
||||
async patchMemberList() {
|
||||
if (this.memberListPatch) return;
|
||||
const memo = WebpackModules.find(m => m?.type?.toString().includes("useGlobalHasAvatarDecorations"));
|
||||
|
@ -198,7 +99,7 @@ export default new class ComponentPatcher {
|
|||
|
||||
patchProfile() {
|
||||
if (this.profilePatch) return;
|
||||
const UserProfileBadgeLists = WebpackModules.getModule(m => m?.default?.displayName === "UserProfileBadgeList", {first: false});
|
||||
const UserProfileBadgeLists = WebpackModules.getModule(m => m?.toString()?.includes("PROFILE_USER_BADGES"), {first: false});
|
||||
for (const UserProfileBadgeList of UserProfileBadgeLists) {
|
||||
this.profilePatch = Patcher.after("ComponentPatcher", UserProfileBadgeList, "default", (_, [{user}], res) => {
|
||||
if (Developers.indexOf(user?.id) < 0) return;
|
||||
|
|
|
@ -3,6 +3,7 @@ import LocaleManager from "./localemanager";
|
|||
|
||||
import Logger from "common/logger";
|
||||
import {Config, Changelog} from "data";
|
||||
import WebpackModules from "./webpackmodules";
|
||||
import DOMManager from "./dommanager";
|
||||
import PluginManager from "./pluginmanager";
|
||||
import ThemeManager from "./thememanager";
|
||||
|
@ -18,7 +19,6 @@ import IPC from "./ipc";
|
|||
import LoadingIcon from "../loadingicon";
|
||||
import Styles from "../styles/index.css";
|
||||
import Editor from "./editor";
|
||||
import {WebpackModules} from "modules";
|
||||
|
||||
export default new class Core {
|
||||
async startup() {
|
||||
|
@ -48,6 +48,7 @@ export default new class Core {
|
|||
|
||||
Logger.log("Startup", "Initializing Settings");
|
||||
Settings.initialize();
|
||||
// SettingsRenderer.patchSections();
|
||||
|
||||
Logger.log("Startup", "Initializing DOMManager");
|
||||
DOMManager.initialize();
|
||||
|
@ -68,6 +69,7 @@ export default new class Core {
|
|||
for (const module in Builtins) {
|
||||
Builtins[module].initialize();
|
||||
}
|
||||
|
||||
this.polyfillWebpack();
|
||||
Logger.log("Startup", "Loading Plugins");
|
||||
// const pluginErrors = [];
|
||||
|
@ -86,9 +88,18 @@ export default new class Core {
|
|||
|
||||
const previousVersion = DataStore.getBDData("version");
|
||||
if (Config.version > previousVersion) {
|
||||
Modals.showChangelogModal(Changelog);
|
||||
// Modals.showChangelogModal(Changelog);
|
||||
const md = [Changelog.description];
|
||||
for (const type of Changelog.changes) {
|
||||
md.push(`**${type.title}**`);
|
||||
for (const entry of type.items) {
|
||||
md.push(` - ${entry}`);
|
||||
}
|
||||
}
|
||||
Modals.showConfirmationModal(`BetterDiscord v${Config.version}`, md, {cancelText: ""});
|
||||
DataStore.setBDData("version", Config.version);
|
||||
}
|
||||
// SettingsRenderer.patchSections();
|
||||
}
|
||||
|
||||
polyfillWebpack() {
|
||||
|
|
|
@ -47,7 +47,7 @@ export default Utilities.memoizeObject({
|
|||
get MentionStore() {return WebpackModules.getByProps("getMentions");},
|
||||
|
||||
/* User Stores and Utils */
|
||||
get UserStore() {return WebpackModules.getByProps("getCurrentUser");},
|
||||
get UserStore() {return WebpackModules.getByProps("getCurrentUser", "getUser");},
|
||||
get UserStatusStore() {return WebpackModules.getByProps("getStatus", "getState");},
|
||||
get UserTypingStore() {return WebpackModules.getByProps("isTyping");},
|
||||
get UserActivityStore() {return WebpackModules.getByProps("getActivity");},
|
||||
|
@ -133,14 +133,14 @@ export default Utilities.memoizeObject({
|
|||
|
||||
/* Electron & Other Internals with Utils*/
|
||||
get ElectronModule() {return WebpackModules.getByProps("setBadge");},
|
||||
get Dispatcher() {return WebpackModules.getByProps("dispatch", "subscribe");},
|
||||
get Dispatcher() {return WebpackModules.getByProps("dispatch", "subscribe", "register");},
|
||||
get PathUtils() {return WebpackModules.getByProps("hasBasename");},
|
||||
get NotificationModule() {return WebpackModules.getByProps("showNotification");},
|
||||
get RouterModule() {return WebpackModules.getByProps("Router");},
|
||||
get APIModule() {return WebpackModules.getByProps("getAPIBaseURL");},
|
||||
get AnalyticEvents() {return WebpackModules.getByProps("AnalyticEventConfigs");},
|
||||
get KeyGenerator() {return WebpackModules.getByRegex(/"binary"/);},
|
||||
get Buffers() {return WebpackModules.getByProps("Buffer", "kMaxLength");},
|
||||
get Buffers() {return WebpackModules.getByProps("INSPECT_MAX_BYTES", "kMaxLength");},
|
||||
get DeviceStore() {return WebpackModules.getByProps("getDevices");},
|
||||
get SoftwareInfo() {return WebpackModules.getByProps("os");},
|
||||
get CurrentContext() {return WebpackModules.getByProps("setTagsContext");},
|
||||
|
|
|
@ -16,10 +16,7 @@ export default new class LocaleManager {
|
|||
|
||||
initialize() {
|
||||
this.setLocale(this.discordLocale);
|
||||
Dispatcher.subscribe("USER_SETTINGS_UPDATE", ({settings}) => {
|
||||
const newLocale = settings.locale;
|
||||
if (newLocale && newLocale != this.locale) this.setLocale(newLocale);
|
||||
});
|
||||
Dispatcher.subscribe("USER_SETTINGS_UPDATE", (newLocale) => this.setLocale(newLocale));
|
||||
}
|
||||
|
||||
setLocale(newLocale) {
|
||||
|
|
|
@ -117,7 +117,7 @@ export default new class PluginManager extends AddonManager {
|
|||
try {
|
||||
const module = {filename, exports: {}};
|
||||
// Test if the code is valid gracefully
|
||||
vm.compileFunction(addon.fileContent, ["require", "module", "exports", "__filename", "__dirname"]);
|
||||
vm.compileFunction(addon.fileContent, ["require", "module", "exports", "__filename", "__dirname"], {filename: path.basename(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
|
||||
|
|
|
@ -113,24 +113,6 @@ export class Filters {
|
|||
}
|
||||
}
|
||||
|
||||
const protect = theModule => {
|
||||
if (theModule.remove && theModule.set && theModule.clear && theModule.get && !theModule.sort) return null;
|
||||
if (!theModule.getToken && !theModule.getEmail && !theModule.showToken) return theModule;
|
||||
const proxy = new Proxy(theModule, {
|
||||
getOwnPropertyDescriptor: function(obj, prop) {
|
||||
if (prop === "getToken" || prop === "getEmail" || prop === "showToken") return undefined;
|
||||
return Object.getOwnPropertyDescriptor(obj, prop);
|
||||
},
|
||||
get: function(obj, func) {
|
||||
if (func == "getToken") return () => "mfa.XCnbKzo0CLIqdJzBnL0D8PfDruqkJNHjwHXtr39UU3F8hHx43jojISyi5jdjO52e9_e9MjmafZFFpc-seOMa";
|
||||
if (func == "getEmail") return () => "puppet11112@gmail.com";
|
||||
if (func == "showToken") return () => true;
|
||||
// if (func == "__proto__") return proxy;
|
||||
return obj[func];
|
||||
}
|
||||
});
|
||||
return proxy;
|
||||
};
|
||||
|
||||
const hasThrown = new WeakSet();
|
||||
|
||||
|
@ -152,6 +134,10 @@ export default class WebpackModules {
|
|||
const {first = true, defaultExport = true} = options;
|
||||
const wrappedFilter = (exports, module, moduleId) => {
|
||||
try {
|
||||
if (exports?.default?.remove && exports?.default?.set && exports?.default?.clear && exports?.default?.get && !exports?.default?.sort) return false;
|
||||
if (exports.remove && exports.set && exports.clear && exports.get && !exports.sort) return false;
|
||||
if (exports?.default?.getToken || exports?.default?.getEmail || exports?.default?.showToken) return false;
|
||||
if (exports.getToken || exports.getEmail || exports.showToken) return false;
|
||||
return filter(exports, module, moduleId);
|
||||
}
|
||||
catch (err) {
|
||||
|
@ -160,6 +146,7 @@ export default class WebpackModules {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const modules = this.getAllModules();
|
||||
const rm = [];
|
||||
const indices = Object.keys(modules);
|
||||
|
@ -168,14 +155,42 @@ export default class WebpackModules {
|
|||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const {exports} = module;
|
||||
if (exports === window) continue;
|
||||
let foundModule = null;
|
||||
|
||||
if (typeof(exports) === "object") {
|
||||
const wrappers = Object.getOwnPropertyDescriptors(exports);
|
||||
const getters = Object.keys(wrappers).filter(k => wrappers[k].get);
|
||||
if (getters.length) {
|
||||
for (const getter of getters) {
|
||||
const wrappedExport = exports[getter];
|
||||
if (!wrappedExport) continue;
|
||||
if (wrappedExport.__esModule && wrappedExport.default && wrappedFilter(wrappedExport.default, module, index)) foundModule = defaultExport ? wrappedExport.default : wrappedExport;
|
||||
if (wrappedFilter(wrappedExport, module, index)) foundModule = wrappedExport;
|
||||
if (!foundModule) continue;
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports;
|
||||
if (wrappedFilter(exports, module, index)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) return protect(foundModule);
|
||||
rm.push(protect(foundModule));
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports;
|
||||
if (wrappedFilter(exports, module, index)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return first || rm.length == 0 ? undefined : rm;
|
||||
|
@ -219,11 +234,36 @@ export default class WebpackModules {
|
|||
};
|
||||
|
||||
let foundModule = null;
|
||||
if (typeof(exports) === "object") {
|
||||
const wrappers = Object.getOwnPropertyDescriptors(exports);
|
||||
const getters = Object.keys(wrappers).filter(k => wrappers[k].get);
|
||||
if (getters.length) {
|
||||
for (const getter of getters) {
|
||||
const wrappedExport = exports[getter];
|
||||
if (!wrappedExport) continue;
|
||||
if (wrappedExport.__esModule && wrappedExport.default && wrappedFilter(wrappedExport.default, module, index)) foundModule = defaultExport ? wrappedExport.default : wrappedExport;
|
||||
if (wrappedFilter(wrappedExport, module, index)) foundModule = wrappedExport;
|
||||
if (!foundModule) continue;
|
||||
if (first) returnedModules[q] = foundModule;
|
||||
else returnedModules[q].push(foundModule);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports;
|
||||
if (wrappedFilter(exports, module, index)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) returnedModules[q] = protect(foundModule);
|
||||
else returnedModules[q].push(protect(foundModule));
|
||||
if (first) returnedModules[q] = foundModule;
|
||||
else returnedModules[q].push(foundModule);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (exports.__esModule && exports.default && wrappedFilter(exports.default, module, index)) foundModule = defaultExport ? exports.default : exports;
|
||||
if (wrappedFilter(exports, module, index)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) returnedModules[q] = foundModule;
|
||||
else returnedModules[q].push(foundModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,12 +381,30 @@ export default class WebpackModules {
|
|||
if (!exports) return;
|
||||
|
||||
let foundModule = null;
|
||||
if (typeof(exports) === "object") {
|
||||
const wrappers = Object.getOwnPropertyDescriptors(exports);
|
||||
const getters = Object.keys(wrappers).filter(k => wrappers[k].get);
|
||||
if (getters.length) {
|
||||
for (const getter of getters) {
|
||||
const wrappedExport = exports[getter];
|
||||
if (!wrappedExport) continue;
|
||||
if (wrappedExport.__esModule && wrappedExport.default && wrappedFilter(wrappedExport.default)) foundModule = defaultExport ? wrappedExport.default : wrappedExport;
|
||||
if (wrappedFilter(wrappedExport)) foundModule = wrappedExport;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = defaultExport ? exports.default : exports;
|
||||
if (wrappedFilter(exports)) foundModule = exports;
|
||||
if (!foundModule) return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = defaultExport ? exports.default : exports;
|
||||
if (wrappedFilter(exports)) foundModule = exports;
|
||||
}
|
||||
|
||||
if (!foundModule) return;
|
||||
cancel();
|
||||
resolve(protect(foundModule));
|
||||
resolve(foundModule);
|
||||
};
|
||||
|
||||
this.addListener(listener);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import WebpackModules from "../modules/webpackmodules";
|
||||
|
||||
Object.defineProperty(window, "Buffer", {
|
||||
get() {return Buffer.getBuffer().Buffer;},
|
||||
configurable: true,
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
export default class Buffer {
|
||||
static getBuffer() {
|
||||
if (this.cached) return this.cached;
|
||||
|
||||
this.cached = WebpackModules.getByProps("INSPECT_MAX_BYTES");
|
||||
|
||||
return this.cached;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import Remote from "./remote";
|
||||
|
||||
export default {
|
||||
...Remote.crypto,
|
||||
// Wrap it in Buffer
|
||||
randomBytes(length) {
|
||||
return Buffer.from(Remote.crypto.randomBytes(length));
|
||||
}
|
||||
};
|
|
@ -0,0 +1,172 @@
|
|||
|
||||
import Remote from "./remote";
|
||||
|
||||
export const readFileSync = function (path, options = "utf8") {
|
||||
return Remote.filesystem.readFile(path, options);
|
||||
};
|
||||
|
||||
export const readFile = function (path, options = "utf8", callback) {
|
||||
try {
|
||||
const contents = Remote.filesystem.readFile(path, options);
|
||||
callback(null, contents);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const writeFile = function (path, data, options = "utf8", callback) {
|
||||
if (typeof(options) === "function") {
|
||||
callback = options;
|
||||
if (!["object", "string"].includes(typeof(options))) options = undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
Remote.filesystem.writeFile(path, data, options);
|
||||
callback(null);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const writeFileSync = function (path, data, options) {
|
||||
Remote.filesystem.writeFile(path, data, options);
|
||||
};
|
||||
|
||||
export const readdir = function (path, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.readDirectory(path, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const readdirSync = function (path, options) {
|
||||
return Remote.filesystem.readDirectory(path, options);
|
||||
};
|
||||
|
||||
export const mkdir = function (path, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.createDirectory(path, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const mkdirSync = function (path, options) {
|
||||
Remote.filesystem.createDirectory(path, options);
|
||||
};
|
||||
|
||||
export const rmdir = function (path, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.deleteDirectory(path, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const rmdirSync = function (path, options) {
|
||||
Remote.filesystem.deleteDirectory(path, options);
|
||||
};
|
||||
|
||||
export const exists = function (path, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.exists(path, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const existsSync = function (path, options) {
|
||||
return Remote.filesystem.exists(path, options);
|
||||
};
|
||||
|
||||
export const stat = function (path, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.getStats(path, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const statSync = function (path, options) {
|
||||
return Remote.filesystem.getStats(path, options);
|
||||
};
|
||||
|
||||
export const lstat = stat;
|
||||
export const lstatSync = statSync;
|
||||
|
||||
export const rename = function (oldPath, newPath, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.rename(oldPath, newPath, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const renameSync = function (oldPath, newPath, options) {
|
||||
return Remote.filesystem.renameSync(oldPath, newPath, options);
|
||||
};
|
||||
|
||||
export const realpath = function (path, options, callback) {
|
||||
try {
|
||||
const result = Remote.filesystem.getStats(path, options);
|
||||
callback(null, result);
|
||||
}
|
||||
catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
};
|
||||
|
||||
export const realpathSync = function (path, options) {
|
||||
return Remote.filesystem.getRealPath(path, options);
|
||||
};
|
||||
|
||||
export const watch = (path, options, callback) => {
|
||||
return Remote.filesystem.watch(path, options, callback);
|
||||
};
|
||||
|
||||
export const createWriteStream = (path, options) => {
|
||||
return Remote.filesystem.createWriteStream(path, options);
|
||||
};
|
||||
|
||||
export const unlinkSync = (path) => Remote.filesystem.unlinkSync(path);
|
||||
export const unlink = (path) => Remote.filesystem.unlinkSync(path);
|
||||
|
||||
export default {
|
||||
readFile,
|
||||
exists,
|
||||
existsSync,
|
||||
lstat,
|
||||
lstatSync,
|
||||
mkdir,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdir,
|
||||
readdirSync,
|
||||
realpath,
|
||||
realpathSync,
|
||||
rename,
|
||||
renameSync,
|
||||
rmdir,
|
||||
rmdirSync,
|
||||
unlink,
|
||||
unlinkSync,
|
||||
watch,
|
||||
writeFile,
|
||||
writeFileSync,
|
||||
createWriteStream
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
import EventEmitter from "common/events";
|
||||
import Remote from "./remote";
|
||||
|
||||
export function get(url, options = {}, callback) {
|
||||
if (typeof(options) === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
callback(emitter);
|
||||
|
||||
Remote.https.get(url, options, (error, res, body) => {
|
||||
if (error) return emitter.emit("error", error);
|
||||
emitter.emit("data", body);
|
||||
emitter.emit("end", res);
|
||||
});
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
export default {get};
|
|
@ -0,0 +1,45 @@
|
|||
import Module from "./module";
|
||||
import * as vm from "./vm";
|
||||
import * as fs from "./fs";
|
||||
import request from "./request";
|
||||
import EventEmitter from "common/events";
|
||||
import * as https from "./https";
|
||||
import Buffer from "./buffer";
|
||||
import crypto from "./crypto";
|
||||
import Remote from "./remote";
|
||||
|
||||
const originalFs = Object.assign({}, fs);
|
||||
originalFs.writeFileSync = (path, data, options) => fs.writeFileSync(path, data, Object.assign({}, options, {originalFs: true}));
|
||||
originalFs.writeFile = (path, data, options) => fs.writeFile(path, data, Object.assign({}, options, {originalFs: true}));
|
||||
|
||||
export const createRequire = function (path) {
|
||||
return mod => {
|
||||
switch (mod) {
|
||||
case "request": return request;
|
||||
case "https": return https;
|
||||
case "original-fs": return originalFs;
|
||||
case "fs": return fs;
|
||||
case "path": return Remote.path;
|
||||
case "events": return EventEmitter;
|
||||
case "electron": return Remote.electron;
|
||||
case "process": return window.process;
|
||||
case "vm": return vm;
|
||||
case "module": return Module;
|
||||
case "buffer": return Buffer.getBuffer();
|
||||
case "crypto": return crypto;
|
||||
|
||||
default:
|
||||
return Module._load(mod, path, createRequire);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const require = window.require = createRequire(".");
|
||||
require.cache = {};
|
||||
require.resolve = (path) => {
|
||||
for (const key of Object.keys(require.cache)) {
|
||||
if (key.startsWith(path)) return require.cache[key];
|
||||
}
|
||||
};
|
||||
|
||||
export default require;
|
|
@ -0,0 +1,103 @@
|
|||
import Logger from "common/logger";
|
||||
import {compileFunction} from "./vm";
|
||||
import Remote from "./remote";
|
||||
import fs from "./fs";
|
||||
|
||||
const path = Remote.path;
|
||||
|
||||
export const RequireExtensions = {
|
||||
".js": (module, filename) => {
|
||||
const fileContent = Remote.filesystem.readFile(filename, "utf8");
|
||||
module.fileContent = fileContent;
|
||||
module._compile(fileContent);
|
||||
return module.exports;
|
||||
},
|
||||
".json": (module, filename) => {
|
||||
const fileContent = Remote.filesystem.readFile(filename, "utf8");
|
||||
module.fileContent = fileContent;
|
||||
module.exports = JSON.parse(fileContent);
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
};
|
||||
|
||||
export default class Module {
|
||||
static resolveMainFile(mod, basePath) {
|
||||
const parent = path.extname(basePath) ? path.dirname(basePath) : basePath;
|
||||
const files = Remote.filesystem.readDirectory(parent);
|
||||
if (!Array.isArray(files)) return null;
|
||||
|
||||
for (const file of files) {
|
||||
const ext = path.extname(file);
|
||||
|
||||
if (file === "package.json") {
|
||||
const pkg = require(path.resolve(parent, file));
|
||||
if (!Reflect.has(pkg, "main")) continue;
|
||||
|
||||
return path.resolve(parent, pkg.main);
|
||||
}
|
||||
|
||||
if (path.slice(0, -ext.length) == "index" && RequireExtensions[ext]) return mod;
|
||||
}
|
||||
}
|
||||
|
||||
static getExtension(mod) {
|
||||
return path.extname(mod) || Reflect.ownKeys(RequireExtensions).find(e => Remote.filesystem.exists(mod + e));
|
||||
}
|
||||
|
||||
static getFilePath(basePath, mod) {
|
||||
if (!path.isAbsolute(mod)) mod = path.resolve(basePath, mod);
|
||||
const defaultExtension = path.extname(mod);
|
||||
if (!defaultExtension) {
|
||||
const ext = Reflect.ownKeys(RequireExtensions).find(e => Remote.filesystem.exists(mod + e));
|
||||
if (ext) {
|
||||
mod = mod + ext;
|
||||
}
|
||||
}
|
||||
|
||||
return fs.realpathSync(mod);
|
||||
}
|
||||
|
||||
static _load(mod, basePath, createRequire) {
|
||||
const originalReq = mod;
|
||||
if (!path.isAbsolute(mod)) mod = path.resolve(basePath, mod);
|
||||
const filePath = this.getFilePath(basePath, mod);
|
||||
if (!Remote.filesystem.exists(filePath)) throw new Error(`Cannot find module ${mod}`);
|
||||
if (window.require.cache[filePath]) return window.require.cache[filePath].exports;
|
||||
const stats = Remote.filesystem.getStats(filePath);
|
||||
if (stats.isDirectory()) mod = this.resolveMainFile(mod, basePath);
|
||||
const ext = this.getExtension(filePath);
|
||||
const loader = RequireExtensions[ext];
|
||||
|
||||
if (!loader) throw new Error(`Cannot find module ${originalReq}`);
|
||||
const module = window.require.cache[mod] = new Module(filePath, internalModule, createRequire(mod));
|
||||
loader(module, filePath);
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
static get Module() {return Module;}
|
||||
|
||||
static get createRequire() {return Logger.warn("ContextModule", "Module.createRequire not implemented yet.");}
|
||||
|
||||
static get _extensions() {return RequireExtensions;}
|
||||
|
||||
constructor(id, parent, require) {
|
||||
this.id = id;
|
||||
this.path = Remote.path.dirname(id);
|
||||
this.exports = {};
|
||||
this.parent = parent;
|
||||
this.filename = id;
|
||||
this.loaded = false;
|
||||
this.children = [];
|
||||
this.require = require;
|
||||
|
||||
if (parent) parent.children.push(this);
|
||||
}
|
||||
|
||||
_compile(code) {
|
||||
const wrapped = compileFunction(code, ["require", "module", "exports", "__filename", "__dirname", "global"], this.filename);
|
||||
wrapped(this.require, this, this.exports, this.filename, this.path, window);
|
||||
}
|
||||
}
|
||||
|
||||
const internalModule = new Module(".", null);
|
|
@ -0,0 +1,3 @@
|
|||
/** @type {import("../../../preload/src/api/index")} */
|
||||
const RemoteAPI = window.BetterDiscordPreload(); // eslint-disable-line new-cap
|
||||
export default RemoteAPI;
|
|
@ -0,0 +1,68 @@
|
|||
import Remote from "./remote";
|
||||
|
||||
const methods = ["get", "put", "post", "delete", "head"];
|
||||
const aliases = {del: "delete"};
|
||||
|
||||
function parseArguments() {
|
||||
let url, options, callback;
|
||||
|
||||
for (const arg of arguments) {
|
||||
switch (typeof arg) {
|
||||
case (arg !== null && "object"):
|
||||
options = arg;
|
||||
if ("url" in options) {
|
||||
url = options.url;
|
||||
}
|
||||
break;
|
||||
|
||||
case (!url && "string"):
|
||||
url = arg;
|
||||
break;
|
||||
|
||||
case (!callback && "function"):
|
||||
callback = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {url, options, callback};
|
||||
}
|
||||
|
||||
function validOptions(url, callback) {
|
||||
return typeof url === "string" && typeof callback === "function";
|
||||
}
|
||||
|
||||
function fixBuffer(options, callback) {
|
||||
return (error, res, body) => {
|
||||
if ("Content-Type" in Object(options.headers) && options.headers["Content-Type"] !== "text/plain") {
|
||||
body = Buffer.from(body);
|
||||
}
|
||||
else {
|
||||
body = Buffer.from(body).toString();
|
||||
}
|
||||
|
||||
callback(error, res, body);
|
||||
};
|
||||
}
|
||||
|
||||
export default function request() {
|
||||
const {url, options = {}, callback} = parseArguments.apply(this, arguments);
|
||||
|
||||
if (!validOptions(url, callback)) return null;
|
||||
|
||||
if ("method" in options && methods.indexOf(options.method.toLowerCase()) >= 0) {
|
||||
return Remote.https[options.method](url, options, fixBuffer(options, callback));
|
||||
}
|
||||
|
||||
return Remote.https.request(url, options, fixBuffer(options, callback));
|
||||
}
|
||||
|
||||
Object.assign(request, Object.fromEntries(
|
||||
methods.concat(Object.keys(aliases)).map(method => [method, function () {
|
||||
const {url, options = {}, callback} = parseArguments.apply(this, arguments);
|
||||
|
||||
if (!validOptions(url, callback)) return null;
|
||||
|
||||
return Remote.https[aliases[method] || method](url, options, fixBuffer(options, callback));
|
||||
}])
|
||||
));
|
|
@ -0,0 +1,9 @@
|
|||
import Remote from "./remote";
|
||||
|
||||
export const compileFunction = function(code, params = [], options = {}) {
|
||||
const returned = Remote.vm.compileFunction(code, params, options);
|
||||
if (typeof(returned) === "function") return returned;
|
||||
const syntaxError = new SyntaxError(returned.message);
|
||||
syntaxError.stack = returned.stack;
|
||||
throw syntaxError;
|
||||
};
|
|
@ -2,7 +2,7 @@ import {React, WebpackModules, DiscordModules, Settings} from "modules";
|
|||
|
||||
import Checkbox from "./checkbox";
|
||||
|
||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
|
||||
const ThemeStore = DiscordModules.ThemeStore;
|
||||
|
||||
const languages = ["abap", "abc", "actionscript", "ada", "apache_conf", "asciidoc", "assembly_x86", "autohotkey", "batchfile", "bro", "c_cpp", "c9search", "cirru", "clojure", "cobol", "coffee", "coldfusion", "csharp", "csound_document", "csound_orchestra", "csound_score", "css", "curly", "d", "dart", "diff", "dockerfile", "dot", "drools", "dummy", "dummysyntax", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "ftl", "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", "io", "jack", "jade", "java", "javascript", "json", "jsoniq", "jsp", "jssm", "jsx", "julia", "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "lsl", "lua", "luapage", "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mel", "mushcode", "mysql", "nix", "nsis", "objectivec", "ocaml", "pascal", "perl", "pgsql", "php", "pig", "powershell", "praat", "prolog", "properties", "protobuf", "python", "r", "razor", "rdoc", "red", "rhtml", "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "smarty", "snippets", "soy_template", "space", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", "tex", "text", "textile", "toml", "tsx", "twig", "typescript", "vala", "vbscript", "velocity", "verilog", "vhdl", "wollok", "xml", "xquery", "yaml", "django"];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Settings, React, WebpackModules, Events, Strings} from "modules";
|
||||
|
||||
const TooltipWrapper = WebpackModules.getByDisplayName("Tooltip");
|
||||
const TooltipWrapper = WebpackModules.getByPrototypes("renderTooltip");
|
||||
|
||||
export default class BDEmote extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
|
@ -2,9 +2,9 @@ import {React, WebpackModules} from "modules";
|
|||
import EmoteModule from "../builtins/emotes/emotes";
|
||||
|
||||
const ContextMenuActions = WebpackModules.getByProps("openContextMenu");
|
||||
const {MenuItem, MenuGroup} = WebpackModules.find(m => m.MenuRadioItem && !m.default);
|
||||
const ContextMenu = WebpackModules.getByProps("default", "MenuStyle").default;
|
||||
const {ComponentDispatch} = WebpackModules.getByProps("ComponentDispatch");
|
||||
const {MenuItem, MenuGroup} = WebpackModules.find(m => m.MenuRadioItem && !m.default) ?? {MenuItem: () => null, MenuGroup: () => null};
|
||||
const ContextMenu = WebpackModules.getByProps("default", "MenuStyle")?.default;
|
||||
const {ComponentDispatch} = WebpackModules.getByProps("ComponentDispatch") ?? {ComponentDispatch: () => null};
|
||||
|
||||
export default class EmoteIcon extends React.Component {
|
||||
render() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {React, WebpackModules} from "modules";
|
||||
const {ScrollerAuto: Scroller} = WebpackModules.getByProps("ScrollerAuto");
|
||||
const {ScrollerAuto: Scroller} = WebpackModules.getByProps("ScrollerAuto") ?? {ScrollerAuto: () => null};
|
||||
export default class EmoteMenuCard extends React.Component {
|
||||
render() {
|
||||
return <div className={`bd-emote-menu`}>
|
||||
|
|
|
@ -3,14 +3,14 @@ import FloatingWindowContainer from "./floating/container";
|
|||
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
const LayerProviders = WebpackModules.getByProps("AppReferencePositionLayer");
|
||||
const AppLayerProvider = WebpackModules.getByDisplayName("AppLayerProvider");
|
||||
|
||||
export default class FloatingWindows {
|
||||
static initialize() {
|
||||
const containerRef = React.createRef();
|
||||
const container = <FloatingWindowContainer ref={containerRef} />;
|
||||
const wrapped = LayerProviders
|
||||
? React.createElement(LayerProviders.AppLayerProvider().props.layerContext.Provider, {value: [document.querySelector("#app-mount > .layerContainer-yqaFcK")]}, container) // eslint-disable-line new-cap
|
||||
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 div = DOM.createElement(`<div id="floating-windows-layer">`);
|
||||
DOMManager.bdBody.append(div);
|
||||
|
|
|
@ -3,7 +3,8 @@ import {React} from "modules";
|
|||
export default class Globe extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "18px";
|
||||
return <svg viewBox="2 2 20 20" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
const color = this.props.color || "#FFFFFF";
|
||||
return <svg viewBox="2 2 20 20" fill={color} style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>
|
||||
</svg>;
|
||||
|
|
|
@ -5,21 +5,27 @@ import FormattableString from "../structs/string";
|
|||
import AddonErrorModal from "./addonerrormodal";
|
||||
import ErrorBoundary from "./errorboundary";
|
||||
|
||||
|
||||
export default class Modals {
|
||||
|
||||
static get shouldShowAddonErrors() {return Settings.get("settings", "addons", "addonErrors");}
|
||||
|
||||
static get ModalActions() {return WebpackModules.getByProps("openModal", "updateModal");}
|
||||
static get ModalActions() {
|
||||
return {
|
||||
openModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback") && m?.toString().includes("Layer")),
|
||||
closeModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback()"))
|
||||
};
|
||||
}
|
||||
static get ModalStack() {return WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
|
||||
static get ModalComponents() {return WebpackModules.getByProps("ModalRoot");}
|
||||
static get ModalComponents() {return WebpackModules.getByProps("Header", "Footer");}
|
||||
static get ModalRoot() {return WebpackModules.getModule(m => m?.toString().includes("ENTERING"));}
|
||||
static get ModalClasses() {return WebpackModules.getByProps("modal", "content");}
|
||||
static get AlertModal() {return WebpackModules.getByPrototypes("handleCancel", "handleSubmit", "handleMinorConfirm");}
|
||||
static get FlexElements() {return WebpackModules.getByProps("Child", "Align");}
|
||||
static get FormTitle() {return WebpackModules.findByDisplayName("FormTitle");}
|
||||
static get TextElement() {return WebpackModules.getByProps("Sizes", "Weights");}
|
||||
static get ConfirmationModal() {return WebpackModules.findByDisplayName("ConfirmModal");}
|
||||
static get Markdown() {return WebpackModules.find(m => m.displayName === "Markdown" && m.rules);}
|
||||
static get Buttons() {return WebpackModules.getByProps("ButtonSizes");}
|
||||
static get FormTitle() {return WebpackModules.getByProps("Tags", "Sizes");}
|
||||
static get TextElement() {return WebpackModules.getModule(m => m?.Sizes?.SIZE_32 && m.Colors);}
|
||||
static get ConfirmationModal() {return WebpackModules.getModule(m => m?.toString()?.includes("confirmText"));}
|
||||
static get Markdown() {return WebpackModules.find(m => m?.prototype?.render && m.rules);}
|
||||
static get Buttons() {return WebpackModules.getByProps("BorderColors");}
|
||||
|
||||
static default(title, content) {
|
||||
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
|
||||
|
@ -86,7 +92,7 @@ export default class Modals {
|
|||
return ModalActions.openModal(props => {
|
||||
return React.createElement(ConfirmationModal, Object.assign({
|
||||
header: title,
|
||||
confirmButtonColor: danger ? this.Buttons.ButtonColors.RED : this.Buttons.ButtonColors.BRAND,
|
||||
confirmButtonColor: danger ? this.Buttons.Colors.RED : this.Buttons.Colors.BRAND,
|
||||
confirmText: confirmText,
|
||||
cancelText: cancelText,
|
||||
onConfirm: onConfirm,
|
||||
|
@ -104,7 +110,7 @@ export default class Modals {
|
|||
}
|
||||
|
||||
this.addonErrorsRef = React.createRef();
|
||||
this.ModalActions.openModal(props => React.createElement(this.ModalComponents.ModalRoot, Object.assign(props, {
|
||||
this.ModalActions.openModal(props => React.createElement(this.ModalRoot, Object.assign(props, {
|
||||
size: "medium",
|
||||
className: "bd-error-modal",
|
||||
children: [
|
||||
|
@ -114,9 +120,9 @@ export default class Modals {
|
|||
themeErrors: Array.isArray(themeErrors) ? themeErrors : [],
|
||||
onClose: props.onClose
|
||||
}),
|
||||
React.createElement(this.ModalComponents.ModalFooter, {
|
||||
React.createElement(this.ModalComponents.Footer, {
|
||||
className: "bd-error-modal-footer",
|
||||
}, React.createElement(this.Buttons.default, {
|
||||
}, React.createElement(this.Buttons, {
|
||||
onClick: props.onClose,
|
||||
className: "bd-button"
|
||||
}, Strings.Modals.okay))
|
||||
|
@ -221,17 +227,17 @@ export default class Modals {
|
|||
|
||||
const mc = this.ModalComponents;
|
||||
const modal = props => {
|
||||
return React.createElement(mc.ModalRoot, Object.assign({size: mc.ModalSize.MEDIUM, className: "bd-addon-modal"}, props),
|
||||
React.createElement(mc.ModalHeader, {separator: false, className: "bd-addon-modal-header"},
|
||||
return React.createElement(ErrorBoundary, {}, React.createElement(this.ModalRoot, Object.assign({size: mc.Sizes.MEDIUM, className: "bd-addon-modal" + " " + mc.Sizes.MEDIUM}, props),
|
||||
React.createElement(mc.Header, {separator: false, className: "bd-addon-modal-header"},
|
||||
React.createElement(this.FormTitle, {tag: "h4"}, `${name} Settings`)
|
||||
),
|
||||
React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"},
|
||||
React.createElement(mc.Content, {className: "bd-addon-modal-settings"},
|
||||
React.createElement(ErrorBoundary, {}, child)
|
||||
),
|
||||
React.createElement(mc.ModalFooter, {className: "bd-addon-modal-footer"},
|
||||
React.createElement(this.Buttons.default, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done)
|
||||
React.createElement(mc.Footer, {className: "bd-addon-modal-footer"},
|
||||
React.createElement(this.Buttons, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done)
|
||||
)
|
||||
);
|
||||
));
|
||||
};
|
||||
|
||||
return this.ModalActions.openModal(props => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {React, WebpackModules, Strings} from "modules";
|
||||
import {React, WebpackModules, Strings, DiscordModules} from "modules";
|
||||
import Modals from "../modals";
|
||||
import SettingsTitle from "../settings/title";
|
||||
import ServerCard from "./card";
|
||||
|
@ -8,9 +8,26 @@ import Search from "../settings/components/search";
|
|||
import Previous from "../icons/previous";
|
||||
import Next from "../icons/next";
|
||||
|
||||
const SettingsView = WebpackModules.getByDisplayName("SettingsView");
|
||||
const SettingsView = WebpackModules.getByPrototypes("renderSidebar");
|
||||
const GuildActions = WebpackModules.getByProps("transitionToGuildSync");
|
||||
const LayerManager = WebpackModules.getByProps("popLayer");
|
||||
const LayerManager = {
|
||||
pushLayer(component) {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_PUSH",
|
||||
component
|
||||
});
|
||||
},
|
||||
popLayer() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP"
|
||||
});
|
||||
},
|
||||
popAllLayers() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP_ALL"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const EMPTY_RESULTS = {
|
||||
servers: [],
|
||||
|
|
|
@ -62,7 +62,7 @@ export default new class SettingsRenderer {
|
|||
}
|
||||
|
||||
async patchSections() {
|
||||
const UserSettings = await WebpackModules.getLazy(Filters.byDisplayName("SettingsView"));
|
||||
const UserSettings = await WebpackModules.getLazy(Filters.byPrototypeFields(["getPredicateSections"]));
|
||||
|
||||
Patcher.after("SettingsManager", UserSettings.prototype, "getPredicateSections", (thisObject, args, returnValue) => {
|
||||
let location = returnValue.findIndex(s => s.section.toLowerCase() == "changelog") - 1;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Logger from "common/logger";
|
||||
import {React, Strings, WebpackModules, DiscordModules} from "modules";
|
||||
import SimpleMarkdown from "../../structs/markdown";
|
||||
import ReloadIcon from "../icons/reload";
|
||||
import EditIcon from "../icons/edit";
|
||||
import DeleteIcon from "../icons/delete";
|
||||
import CogIcon from "../icons/cog";
|
||||
|
@ -25,8 +24,25 @@ const LinkIcons = {
|
|||
patreon: PatreonIcon
|
||||
};
|
||||
|
||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||
const LayerStack = WebpackModules.getByProps("popLayer");
|
||||
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
|
||||
const LayerManager = {
|
||||
pushLayer(component) {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_PUSH",
|
||||
component
|
||||
});
|
||||
},
|
||||
popLayer() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP"
|
||||
});
|
||||
},
|
||||
popAllLayers() {
|
||||
DiscordModules.Dispatcher.dispatch({
|
||||
type: "LAYER_POP_ALL"
|
||||
});
|
||||
}
|
||||
};
|
||||
const UserStore = WebpackModules.getByProps("getCurrentUser");
|
||||
const ChannelStore = WebpackModules.getByProps("getDMFromUserId");
|
||||
const PrivateChannelActions = WebpackModules.getByProps("openPrivateChannel");
|
||||
|
@ -41,7 +57,6 @@ export default class AddonCard extends React.Component {
|
|||
this.panelRef = React.createRef();
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.reload = this.reload.bind(this);
|
||||
this.showSettings = this.showSettings.bind(this);
|
||||
this.messageAuthor = this.messageAuthor.bind(this);
|
||||
}
|
||||
|
@ -58,12 +73,6 @@ export default class AddonCard extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
reload() {
|
||||
if (!this.props.reload) return;
|
||||
this.props.addon = this.props.reload(this.props.addon.id);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
getString(value) {return typeof value == "string" ? value : value.toString();}
|
||||
|
||||
onChange() {
|
||||
|
@ -74,7 +83,7 @@ export default class AddonCard extends React.Component {
|
|||
|
||||
messageAuthor() {
|
||||
if (!this.props.addon.authorId) return;
|
||||
if (LayerStack) LayerStack.popLayer();
|
||||
if (LayerManager) LayerManager.popLayer();
|
||||
if (!UserStore || !ChannelActions || !ChannelStore || !PrivateChannelActions) return;
|
||||
const selfId = UserStore.getCurrentUser().id;
|
||||
if (selfId == this.props.addon.authorId) return;
|
||||
|
@ -114,7 +123,7 @@ export default class AddonCard extends React.Component {
|
|||
let code = url;
|
||||
const tester = /\.gg\/(.*)$/;
|
||||
if (tester.test(code)) code = code.match(tester)[1];
|
||||
DiscordModules.LayerStack.popLayer();
|
||||
LayerManager.popLayer();
|
||||
DiscordModules.InviteActions.acceptInviteAndTransitionToInviteChannel(code);
|
||||
};
|
||||
}
|
||||
|
@ -124,7 +133,6 @@ export default class AddonCard extends React.Component {
|
|||
get controls() { // {this.props.hasSettings && <button onClick={this.showSettings} className="bd-button bd-button-addon-settings" disabled={!this.props.enabled}>{Strings.Addons.addonSettings}</button>}
|
||||
return <div className="bd-controls">
|
||||
{this.props.hasSettings && this.makeControlButton(Strings.Addons.addonSettings, <CogIcon size={"20px"} />, this.showSettings, {disabled: !this.props.enabled})}
|
||||
{this.props.showReloadIcon && this.makeControlButton(Strings.Addons.reload, <ReloadIcon size={"20px"} />, this.reload)}
|
||||
{this.props.editAddon && this.makeControlButton(Strings.Addons.editAddon, <EditIcon size={"20px"} />, this.props.editAddon)}
|
||||
{this.props.deleteAddon && this.makeControlButton(Strings.Addons.deleteAddon, <DeleteIcon size={"20px"} />, this.props.deleteAddon, {danger: true})}
|
||||
</div>;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import Logger from "common/logger";
|
||||
import {React, Settings, Strings, Events, WebpackModules, DataStore} from "modules";
|
||||
import {React, Strings, Events, WebpackModules, DataStore} from "modules";
|
||||
|
||||
import Modals from "../modals";
|
||||
import SettingsTitle from "./title";
|
||||
import ReloadIcon from "../icons/reload";
|
||||
import AddonCard from "./addoncard";
|
||||
import Dropdown from "./components/dropdown";
|
||||
import Search from "./components/search";
|
||||
|
@ -14,7 +13,7 @@ import GridIcon from "../icons/grid";
|
|||
import NoResults from "../blankslates/noresults";
|
||||
import EmptyImage from "../blankslates/emptyimage";
|
||||
|
||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
|
||||
|
||||
export default class AddonList extends React.Component {
|
||||
|
||||
|
@ -125,7 +124,6 @@ export default class AddonList extends React.Component {
|
|||
|
||||
render() {
|
||||
const {title, folder, addonList, addonState, onChange, reload} = this.props;
|
||||
const showReloadIcon = !Settings.get("settings", "addons", "autoReload");
|
||||
const button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: this.openFolder} : null;
|
||||
let sortedAddons = addonList.sort((a, b) => {
|
||||
const sortByEnabled = this.state.sort === "isEnabled";
|
||||
|
@ -152,7 +150,7 @@ export default class AddonList extends React.Component {
|
|||
const renderedCards = sortedAddons.map(addon => {
|
||||
const hasSettings = addon.instance && typeof(addon.instance.getSettingsPanel) === "function";
|
||||
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
|
||||
return <ErrorBoundary><AddonCard type={this.props.type} editAddon={this.editAddon.bind(this, addon.id)} deleteAddon={this.deleteAddon.bind(this, addon.id)} showReloadIcon={showReloadIcon} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
||||
return <ErrorBoundary><AddonCard type={this.props.type} editAddon={this.editAddon.bind(this, addon.id)} deleteAddon={this.deleteAddon.bind(this, addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
||||
});
|
||||
|
||||
const hasAddonsInstalled = this.props.addonList.length !== 0;
|
||||
|
@ -160,7 +158,7 @@ export default class AddonList extends React.Component {
|
|||
const hasResults = sortedAddons.length !== 0;
|
||||
|
||||
return [
|
||||
<SettingsTitle key="title" text={title} button={button} otherChildren={showReloadIcon && <ReloadIcon className="bd-reload" onClick={this.reload.bind(this)} />} />,
|
||||
<SettingsTitle key="title" text={title} button={button} />,
|
||||
<div className={"bd-controls bd-addon-controls"}>
|
||||
<Search onChange={this.search} placeholder={`${Strings.Addons.search.format({type: this.props.title})}...`} />
|
||||
<div className="bd-controls-advanced">
|
||||
|
|
|
@ -4,7 +4,7 @@ import HistoryIcon from "../icons/history";
|
|||
import Modals from "../modals";
|
||||
|
||||
const SidebarComponents = WebpackModules.getModule(m => m.Header && m.Separator && m.Item);
|
||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
|
||||
|
||||
export default class SettingsTitle extends React.Component {
|
||||
render() {
|
||||
|
|
Loading…
Reference in New Issue