Merge pull request 'Settings Menu injection.' (#2) from sink/demoncord:PATCH_settings-inj into master

Reviewed-on: ruthenic/demoncord#2
master
Drake 2 years ago
commit 90d3756f23

@ -1,13 +1,14 @@
import flow from 'rollup-plugin-flow'
import { minify } from 'rollup-plugin-esbuild'
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import flow from "rollup-plugin-flow";
import { minify } from "rollup-plugin-esbuild";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import { defineConfig } from "rollup";
export default {
input: 'src/index.js',
plugins: [flow(), commonjs(), nodeResolve(), minify()],
output: {
file: 'dist/build.js',
format: "iife"
}
}
export default defineConfig({
input: "src/index.js",
plugins: [flow(), commonjs(), nodeResolve(), minify()],
output: {
file: "dist/build.js",
format: "iife",
},
});

@ -1,35 +1,43 @@
// @flow
const commandsSym = Symbol("__commands");
function init(obj: Object) {
obj.demon.__commands = {}
obj.demon.patcher.after("sendMessage", obj.demon.webpack.findByProps("sendMessage"),
(args, otherRes) => {
let res;
for (const key of Reflect.ownKeys(obj.demon.__commands)) {
let command = obj.demon.__commands[key]
if (args[1].content.split(" ")[0] === ">" + command.name) {
res = command.callback(args)
break
}
}
if (res !== undefined) args[1].content = res
return args
}
)
obj.demon[commandsSym] = {};
obj.demon.patcher.after(
"sendMessage",
obj.demon.webpack.findByProps("sendMessage"),
(args, otherRes) => {
let res;
for (const key of Reflect.ownKeys(obj.demon[commandsSym])) {
let command = obj.demon[commandsSym][key];
if (args[1].content.split(" ")[0] === ">" + command.name) {
res = command.callback(args);
break;
}
}
if (res !== undefined) args[1].content = res;
return args;
}
);
}
// command = {
// name: "name",
// callback: (args)=>"Hello, world!"
// }
function add(command: {name: string, callback: (args: Array<any>) => string}): ()=>void {
let sym = Symbol(command.name);
window.demon.__commands[sym] = command;
return () => {
delete window.demon.__commands[sym];
}
function add(command: {
name: string,
callback: (args: Array<any>) => string,
}): () => void {
let sym = Symbol(command.name);
window.demon[commandsSym][sym] = command;
return () => {
delete window.demon[commandsSym][sym];
};
}
export default {
add: add,
init: init
}
add: add,
init: init,
};

@ -1,108 +1,122 @@
// @flow
import { get, set } from 'idb-keyval';
import logger from './utils/logger.js'
import { get, set } from "idb-keyval";
import logger from "./utils/logger.js";
import settingsInj from "./settingsInj.js";
const pluginsSym = Symbol("__plugins");
async function init(obj: Object): Promise<boolean> {
//TODO: check for enabled plugins in the idb, start them
let globalSettings = await get("demoncord")
if (globalSettings === undefined) {
await set("demoncord", {plugin: {}})
globalSettings = await get("demoncord")
}
obj.demon.__plugins = {}
for (let plug: Object in globalSettings.plugin) {
plug = globalSettings.plugin[plug]
if (plug.enabled) {
startPlugin(plug.metadata.name)
}
}
return true
//TODO: check for enabled plugins in the idb, start them
let globalSettings = await get("demoncord");
if (globalSettings === undefined) {
await set("demoncord", { plugin: {} });
globalSettings = await get("demoncord");
}
obj.demon[pluginsSym] = {};
for (let plug: Object in globalSettings.plugin) {
plug = globalSettings.plugin[plug];
if (plug.enabled) {
startPlugin(plug.metadata.name);
}
}
return true;
}
async function addPlugin(iife: string, metadata: Object): Promise<boolean> {
// expected metadata: {name: "name", desc: "description", author: "author"}
const obj: Object = { // whether the plugin is started or stopped isn't going to be stored in the iDB, so it can be accessed more easily by all components
metadata: metadata,
iife: iife,
enabled: false // should plugins be enabled by default? not sure
}
const globalSettings: Object = await get("demoncord");
/*try {
// expected metadata: {name: "name", desc: "description", author: "author"}
const obj: Object = {
// whether the plugin is started or stopped isn't going to be stored in the iDB, so it can be accessed more easily by all components
metadata: metadata,
iife: iife,
enabled: false, // should plugins be enabled by default? not sure
};
const globalSettings: Object = await get("demoncord");
/*try {
if (globalSettings["plugin"][metadata.name] !== undefined) {
log("Cannot add plugin that already exists!", "error", "Plugins")
console.error("[Demoncord] Cannot add plugin that already exists!")
return false
}
} catch (error) {*/
globalSettings["plugin"][metadata.name] = obj
//} //Disabling checking for previous plugins for now as it is breaking literally fucking everything
await set("demoncord", globalSettings)
return true
globalSettings["plugin"][metadata.name] = obj;
//} //Disabling checking for previous plugins for now as it is breaking literally fucking everything
await set("demoncord", globalSettings);
return true;
}
async function delPlugin(name: string): Promise<boolean> {
const globalSettings = await get("demoncord")
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot remove non-existant plugin!", ["Plugins"])
return false
} else {
globalSettings["plugin"][name] = undefined
delete globalSettings["plugin"][name]
}
await set("demoncord", globalSettings)
return true
const globalSettings = await get("demoncord");
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot remove non-existant plugin!", ["Plugins"]);
return false;
} else {
globalSettings["plugin"][name] = undefined;
delete globalSettings["plugin"][name];
}
await set("demoncord", globalSettings);
return true;
}
async function startPlugin(name: string): Promise<boolean> {
const globalSettings = await get("demoncord")
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot start non-existant plugin!", ["Plugins"])
return false
} else {
logger.log(`Starting ${name}...`, ["Plugins"])
const plug = globalSettings["plugin"][name]
const exports: Object = (0, eval)(plug.iife)
const onStart: (ctx: Object)=>void = exports.onStart
let ctx = {} // ctx is how you're going to store things that need to be accessed in both onStart and onStop. dumb, i know
onStart(ctx)
logger.log(`Started ${name}!`, ["Plugins"])
window.demon.__plugins[name] = {status: 1, ctx: ctx}
return true
}
const globalSettings = await get("demoncord");
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot start non-existant plugin!", ["Plugins"]);
return false;
} else {
logger.log(`Starting ${name}...`, ["Plugins"]);
const plug = globalSettings["plugin"][name];
const exports: Object = (0, eval)(plug.iife);
const onStart: (ctx: Object) => void = exports.onStart;
let ctx = {}; // ctx is how you're going to store things that need to be accessed in both onStart and onStop. dumb, i know
onStart(ctx);
if (exports.settings)
settingsInj.registerSettingsEntry(
name,
"DEMON_PLUGIN_SETTINGS_" + name,
exports.settings
);
logger.log(`Started ${name}!`, ["Plugins"]);
window.demon[pluginsSym][name] = { status: 1, ctx: ctx };
return true;
}
}
async function stopPlugin(name: string): Promise<boolean> {
const globalSettings = await get("demoncord")
if (globalSettings["plugin"][name] === undefined ) {
logger.error("Cannot stop non-existant or non-running plugin!", ["Plugins"])
return false
} else {
logger.log(`Stopping ${name}...`, ["Plugins"])
const plug = globalSettings["plugin"][name]
const exports: Object = (0, eval)(plug.iife)
const onStop: (ctx: Object)=>void = exports.onStop
onStop(window.demon.__plugins[name].ctx)
logger.log(`Stopped ${name}!`, ["Plugins"])
return true
}
const globalSettings = await get("demoncord");
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot stop non-existant or non-running plugin!", [
"Plugins",
]);
return false;
} else {
logger.log(`Stopping ${name}...`, ["Plugins"]);
const plug = globalSettings["plugin"][name];
const exports: Object = (0, eval)(plug.iife);
const onStop: (ctx: Object) => void = exports.onStop;
onStop(window.demon[pluginsSym][name].ctx);
settingsInj.unregisterSettingsEntry("DEMON_PLUGIN_SETTINGS_" + name);
logger.log(`Stopped ${name}!`, ["Plugins"]);
return true;
}
}
async function togglePlugin(name: string): Promise<boolean> {
const globalSettings = await get("demoncord")
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot toggle non-existant plugin!", ["Plugins"])
return false
} else {
globalSettings["plugin"][name].enabled = !globalSettings["plugin"][name].enabled
await set("demoncord", globalSettings)
return true
}
const globalSettings = await get("demoncord");
if (globalSettings["plugin"][name] === undefined) {
logger.error("Cannot toggle non-existant plugin!", ["Plugins"]);
return false;
} else {
globalSettings["plugin"][name].enabled =
!globalSettings["plugin"][name].enabled;
await set("demoncord", globalSettings);
return true;
}
}
export default {
init,
addPlugin,
delPlugin,
startPlugin,
stopPlugin,
togglePlugin
}
init,
addPlugin,
delPlugin,
startPlugin,
stopPlugin,
togglePlugin,
};

@ -0,0 +1,92 @@
// @flow
import webpack from "./webpack";
const settingsSym = Symbol("__settings");
// super secret value that shouldnt be exposed, used internally to avoid section conflicts and the like
const ovrwrtSctnSym: symbol = Symbol("__overwriteSection");
const settingsView = webpack.find(
(m) => m.default && m.default.displayName === "SettingsView"
);
type getPredicateSectionsEntry =
| {
section: "HEADER",
label: string,
}
| {
section: "DIVIDER",
}
| {
section: string,
label: string,
component: Function,
};
function patch(args: mixed, ret: getPredicateSectionsEntry[]) {
const processedEntries = window.demon[settingsSym].entries.map((e) => ({
section:
(e[ovrwrtSctnSym] ? e[ovrwrtSctnSym] : "DEMON_SETTINGS_LOADER_") +
e.name,
label: e.name,
element: e.component,
}));
const injectionIndex =
2 + ret.findIndex((section) => section.section === "Game Activity");
ret.splice(
injectionIndex,
0,
{ section: "HEADER", label: "Demon" },
...processedEntries,
{ section: "DIVIDER" }
);
return ret;
}
function init() {
window.demon[settingsSym] = {
patch: window.demon.patcher.after(
"getPredicateSections",
settingsView.default.prototype,
patch
),
entries: [],
};
// debug
window.__demontemp__UnpatchSettingsInj = unInit();
}
function unInit() {
window.demon[settingsSym].patch();
delete window.demon[settingsSym];
}
function unregisterSettingsEntry(name: string) {
let s = window.demon[settingsSym];
s.entries = s.entries.filter(
(e) => (e[ovrwrtSctnSym] ? e[ovrwrtSctnSym] : e.name) !== name
);
}
function registerSettingsEntry(
name: string,
section: ?string,
component: Function
): () => void {
let entry: { [symbol | string]: any } = { name, component };
if (section) entry[ovrwrtSctnSym] = section;
window.demon[settingsSym].entries.push(entry);
return () => unregisterSettingsEntry(name);
}
export default {
init,
unInit,
registerSettingsEntry,
unregisterSettingsEntry,
ovrwrtSctnSymm: ovrwrtSctnSym,
};

@ -1,9 +1,9 @@
// @flow
type ModuleType = {[symbol]: any};
type FilterFunc = (module: ModuleType) => boolean
type ModuleType = Object/* { [symbol]: any } */;
type FilterFunc = (module: ModuleType) => boolean;
function getModules(): any {
let modules: {c: mixed[]} = {};
let modules: { c: mixed[] } = {};
window.webpackChunkdiscord_app.push([
[Math.random().toString(36)],
@ -17,32 +17,44 @@ function getModules(): any {
}
function filter(filter: FilterFunc, moduleList: any): Array<Object> {
let modules: Array<mixed[]> = [];
for (const mod in moduleList) {
const module = moduleList[mod].exports;
if (module) {
if (module.default && module.__esModule && filter(module.default)) { modules.push(module.default); }
else if (filter(module)) { modules.push(module); }
}
}
return modules;
let modules: Array<mixed[]> = [];
for (const mod in moduleList) {
const module = moduleList[mod].exports;
if (module) {
if (module.default && module.__esModule && filter(module.default)) {
modules.push(module.default);
} else if (filter(module)) {
modules.push(module);
}
}
}
return modules;
}
let webpack: Object = {
modules: getModules(),
filter: filter,
find: (filter: FilterFunc)=>webpack.filter(filter, webpack.modules)[0],
findAll: (filter: FilterFunc)=>webpack.filter(filter, webpack.modules),
findByProps: (...props: Array<string>) => {
return webpack.find((module) => {
return props.every((prop)=>module[prop]!==undefined)
})
},
findByPropsAll: (...props: Array<string>) => {
return webpack.findAll((module) =>
props.every((prop)=>module[prop]!==undefined)
)
}
}
type WebpackModules = {
modules: () => any,
filter: (filter: FilterFunc, moduleList: any) => Object[],
find: (filter: FilterFunc) => Object,
findAll: (filter: FilterFunc) => Object[],
findByProps: (...props: string[]) => Object,
findByPropsAll: (...props: string[]) => Object[],
};
let webpack: WebpackModules = {
modules: getModules(),
filter: filter,
find: (filter: FilterFunc) => webpack.filter(filter, webpack.modules)[0],
findAll: (filter: FilterFunc) => webpack.filter(filter, webpack.modules),
findByProps: (...props: Array<string>) => {
return webpack.find((module) => {
return props.every((prop) => module[prop] !== undefined);
});
},
findByPropsAll: (...props: Array<string>) => {
return webpack.findAll((module) =>
props.every((prop) => module[prop] !== undefined)
);
},
};
export default webpack;

@ -1,52 +1,49 @@
// @flow
import Patcher from "simian"
import webpack from "./api/webpack.js"
import common from "./api/common.js"
import commands from "./api/commands.js"
import plugins from "./api/plugins.js"
import Patcher from "simian";
import webpack from "./api/webpack";
import common from "./api/common";
import commands from "./api/commands";
import plugins from "./api/plugins";
import settingsInj from "./api/settingsInj";
async function init(obj: Object): Promise<void> {
const patcher = new Patcher()
obj.demon = {
patcher: {
monkeyPatch: function(name: string, parentObj: Object, patches: Object): ()=>void {
let [upb,upi,upa] = [()=>{},()=>{},()=>{}]
if (patches.before !== undefined) upb = patcher.before(
name,
parentObj,
patches.before
)
if (patches.instead !== undefined) upb = patcher.instead(
name,
parentObj,
patches.instead
)
if (patches.after !== undefined) upb = patcher.after(
name,
parentObj,
patches.after
)
return ()=>{
upb()
upi()
upa()
}
},
before: patcher.before,
instead: patcher.instead,
after: patcher.after
},
webpack,
common,
commands: {
add: commands.add
},
__DO_NOT_USE_OR_YOU_WILL_BE_FIRED_UNTO_THE_DEPTHS_OF_HELL: {
plugins
}
}
commands.init(obj)
plugins.init(obj)
const patcher = new Patcher();
obj.demon = {
patcher: {
monkeyPatch: function (
name: string,
parentObj: Object,
patches: Object
): () => void {
let [upb, upi, upa] = [() => {}, () => {}, () => {}];
if (patches.before !== undefined)
upb = patcher.before(name, parentObj, patches.before);
if (patches.instead !== undefined)
upb = patcher.instead(name, parentObj, patches.instead);
if (patches.after !== undefined)
upb = patcher.after(name, parentObj, patches.after);
return () => {
upb();
upi();
upa();
};
},
before: patcher.before,
instead: patcher.instead,
after: patcher.after,
},
webpack,
common,
commands: {
add: commands.add,
},
__DO_NOT_USE_OR_YOU_WILL_BE_FIRED_UNTO_THE_DEPTHS_OF_HELL: {
plugins,
}
};
commands.init(obj);
settingsInj.init();
plugins.init(obj);
}
export default init;

Loading…
Cancel
Save