diff --git a/rollup.config.js b/rollup.config.js index a123e8b..227a57b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -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", + }, +}); diff --git a/src/api/commands.js b/src/api/commands.js index 3d98808..666032c 100644 --- a/src/api/commands.js +++ b/src/api/commands.js @@ -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) => 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) => 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, +}; diff --git a/src/api/plugins.js b/src/api/plugins.js index 5e1628c..65b35b8 100644 --- a/src/api/plugins.js +++ b/src/api/plugins.js @@ -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 { - //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 { - // 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 { - 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 { - 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 { - 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 { - 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, +}; diff --git a/src/api/settingsInj.js b/src/api/settingsInj.js new file mode 100644 index 0000000..aa30458 --- /dev/null +++ b/src/api/settingsInj.js @@ -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, +}; diff --git a/src/api/webpack.js b/src/api/webpack.js index 83f8f3f..8ecb3b5 100644 --- a/src/api/webpack.js +++ b/src/api/webpack.js @@ -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 { - let modules: Array = []; - 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 = []; + 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) => { - return webpack.find((module) => { - return props.every((prop)=>module[prop]!==undefined) - }) - }, - findByPropsAll: (...props: Array) => { - 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) => { + return webpack.find((module) => { + return props.every((prop) => module[prop] !== undefined); + }); + }, + findByPropsAll: (...props: Array) => { + return webpack.findAll((module) => + props.every((prop) => module[prop] !== undefined) + ); + }, +}; export default webpack; diff --git a/src/init.js b/src/init.js index 4df6413..bb89979 100644 --- a/src/init.js +++ b/src/init.js @@ -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 { - 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;