diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Vencord.ts | 7 | ||||
-rw-r--r-- | src/components/Test.tsx | 7 | ||||
-rw-r--r-- | src/globals.d.ts | 2 | ||||
-rw-r--r-- | src/plugins/bar.ts | 13 | ||||
-rw-r--r-- | src/plugins/foo.ts | 13 | ||||
-rw-r--r-- | src/plugins/index.ts | 28 | ||||
-rw-r--r-- | src/pluginsModule.d.ts (renamed from src/plugins.d.ts) | 2 | ||||
-rw-r--r-- | src/utils/logger.ts | 27 | ||||
-rw-r--r-- | src/utils/patchWebpack.ts | 53 | ||||
-rw-r--r-- | src/utils/types.ts | 23 | ||||
-rw-r--r-- | src/utils/webpack.ts | 105 |
11 files changed, 251 insertions, 29 deletions
diff --git a/src/Vencord.ts b/src/Vencord.ts index 449423c..091421e 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -1,5 +1,8 @@ +import * as plugins from "./plugins"; +import * as WP from "./utils/webpack"; + import "./utils/patchWebpack"; import "./utils/quickCss"; -export const Webpack = {}; -import "./plugins"; +export const Webpack = WP; +export const Plugins = plugins;
\ No newline at end of file diff --git a/src/components/Test.tsx b/src/components/Test.tsx new file mode 100644 index 0000000..14a46dd --- /dev/null +++ b/src/components/Test.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +export default () => { + <div> + Hi + </div>; +}; diff --git a/src/globals.d.ts b/src/globals.d.ts index ca43786..02ea088 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,5 +1,6 @@ declare global { export var VencordNative: typeof import("./VencordNative").default; + export var Vencord: typeof import("./Vencord"); export var appSettings: { set(setting: string, v: any): void; }; @@ -7,6 +8,7 @@ declare global { interface Window { webpackChunkdiscord_app: { push(chunk: any): any; + pop(): any; }; } } diff --git a/src/plugins/bar.ts b/src/plugins/bar.ts index d503e8a..76d1a8f 100644 --- a/src/plugins/bar.ts +++ b/src/plugins/bar.ts @@ -1,3 +1,10 @@ -export default { - name: "bar" -};
\ No newline at end of file +import definePlugin from '../utils/types'; + +export default definePlugin({ + name: "bar", + description: "Just to test", + author: ["Vendicated"], + start() { + console.log("bar"); + } +});
\ No newline at end of file diff --git a/src/plugins/foo.ts b/src/plugins/foo.ts index 64c9323..f365b31 100644 --- a/src/plugins/foo.ts +++ b/src/plugins/foo.ts @@ -1,3 +1,10 @@ -export default { - name: "foo" -};
\ No newline at end of file +import definePlugin from "../utils/types"; + +export default definePlugin({ + name: "foo", + description: "Just to test", + author: ["Vendicated"], + start() { + console.log("foo"); + } +});
\ No newline at end of file diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 4e55edb..d5b419b 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,3 +1,27 @@ -import plugins from "plugins"; +import Plugins from "plugins"; +import Logger from "../utils/logger"; +import { Patch } from "../utils/types"; -console.log(plugins);
\ No newline at end of file +const logger = new Logger("PluginManager", "#a6d189"); + +export const plugins = Plugins; +export const patches = [] as Patch[]; + +for (const plugin of Plugins) if (plugin.patches) { + for (const patch of plugin.patches) { + patch.plugin = plugin.name; + if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement]; + patches.push(patch); + } +} + +export function startAll() { + for (const plugin of plugins) if (plugin.start) { + try { + logger.info("Starting plugin", plugin.name); + plugin.start(); + } catch (err) { + logger.error("Failed to start plugin", plugin.name, err); + } + } +}
\ No newline at end of file diff --git a/src/plugins.d.ts b/src/pluginsModule.d.ts index e287823..0ef3819 100644 --- a/src/plugins.d.ts +++ b/src/pluginsModule.d.ts @@ -1,4 +1,4 @@ declare module "plugins" { - var plugins: Record<string, any>[]; + const plugins: import("./utils/types").Plugin[]; export default plugins; }
\ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..be341ee --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,27 @@ +export default class Logger { + constructor(public name: string, public color: string) { } + + private _log(level: "log" | "error" | "warn" | "info" | "debug", args: any[]) { + console[level](`%c ${this.name} `, `background: ${this.color}; color: black; font-weight: bold`, ...args); + } + + public log(...args: any[]) { + this._log("log", args); + } + + public info(...args: any[]) { + this._log("info", args); + } + + public error(...args: any[]) { + this._log("error", args); + } + + public warn(...args: any[]) { + this._log("warn", args); + } + + public debug(...args: any[]) { + this._log("debug", args); + } +}
\ No newline at end of file diff --git a/src/utils/patchWebpack.ts b/src/utils/patchWebpack.ts index 6a379de..0e94694 100644 --- a/src/utils/patchWebpack.ts +++ b/src/utils/patchWebpack.ts @@ -1,7 +1,12 @@ import { WEBPACK_CHUNK } from './constants'; +import Logger from "./logger"; +import { _initWebpack } from "./webpack"; let webpackChunk: any[]; +const logger = new Logger("WebpackInterceptor", "#8caaee"); + + Object.defineProperty(window, WEBPACK_CHUNK, { get: () => webpackChunk, set: (v) => { @@ -10,6 +15,8 @@ Object.defineProperty(window, WEBPACK_CHUNK, { // - Webpack's push with toString result of function() { [native code] } // We don't want to override the native one, so check for "push" if (v && !v.push.toString().includes("push")) { + logger.info(`Patching ${WEBPACK_CHUNK}.push`); + _initWebpack(v); patchPush(); // @ts-ignore delete window[WEBPACK_CHUNK]; @@ -24,8 +31,8 @@ function patchPush() { function handlePush(chunk) { try { const modules = chunk[1]; - const subscriptions = new Set<any>(); - const patches = [] as any[]; + const { subscriptions, listeners } = Vencord.Webpack; + const { patches } = Vencord.Plugins; for (const id in modules) { let mod = modules[id]; @@ -40,10 +47,17 @@ function patchPush() { // Just rethrow discord errors if (mod === originalMod) throw err; - console.error("[Webpack] Error in patched chunk", err); + logger.error("Error in patched chunk", err); return originalMod(module, exports, require); } + for (const callback of listeners) { + try { + callback(exports); + } catch (err) { + logger.error("Error in webpack listener", err); + } + } for (const [filter, callback] of subscriptions) { try { if (filter(exports)) { @@ -54,7 +68,7 @@ function patchPush() { callback(exports.default); } } catch (err) { - console.error("[Webpack] Error while firing callback for webpack chunk", err); + logger.error("Error while firing callback for webpack chunk", err); } } }; @@ -63,25 +77,28 @@ function patchPush() { const patch = patches[i]; if (code.includes(patch.find)) { patchedBy.add(patch.plugin); - const lastMod = mod; - const lastCode = code; - try { - const newCode = code.replace(patch.replacement.match, patch.replacement.replace); - const newMod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`); - code = newCode; - mod = newMod; - patches.splice(i--, 1); - } catch (err) { - console.error("[Webpack] Failed to apply patch of", patch.plugin, err); - code = lastCode; - mod = lastMod; - patchedBy.delete(patch.plugin); + // @ts-ignore we change all patch.replacement to array in plugins/index + for (const replacement of patch.replacement) { + const lastMod = mod; + const lastCode = code; + try { + const newCode = code.replace(replacement.match, replacement.replace); + const newMod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`); + code = newCode; + mod = newMod; + } catch (err) { + logger.error("Failed to apply patch of", patch.plugin, err); + code = lastCode; + mod = lastMod; + patchedBy.delete(patch.plugin); + } } + patches.splice(i--, 1); } } } } catch (err) { - console.error("oopsie", err); + logger.error("oopsie", err); } return handlePush.original.call(window[WEBPACK_CHUNK], chunk); diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..6268d29 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,23 @@ +// exists to export default definePlugin({...}) +export default function definePlugin(p: Plugin) { + return p; +} + +export interface PatchReplacement { + match: string | RegExp; + replace: string | ((match: string, ...groups: string[]) => string); +} + +export interface Patch { + plugin: string; + find: string, + replacement: PatchReplacement | PatchReplacement[]; +} + +export interface Plugin { + name: string; + description: string; + author: string[]; + start?(): void; + patches?: Patch[]; +}
\ No newline at end of file diff --git a/src/utils/webpack.ts b/src/utils/webpack.ts new file mode 100644 index 0000000..3f21106 --- /dev/null +++ b/src/utils/webpack.ts @@ -0,0 +1,105 @@ +import { startAll } from "../plugins"; +import Logger from "./logger"; + +let webpackCache: typeof window.webpackChunkdiscord_app; + +export const subscriptions = new Map<FilterFn, CallbackFn>(); +export const listeners = new Set<CallbackFn>(); + +type FilterFn = (mod: any) => boolean; +type CallbackFn = (mod: any) => void; + +export let Common: { + React: typeof import("react"), + FluxDispatcher: any; + UserStore: any; +} = {} as any; + +export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) { + if (webpackCache !== void 0) throw "no."; + + webpackCache = instance.push([[Symbol()], {}, (r) => r.c]); + instance.pop(); + + // Abandon Hope All Ye Who Enter Here + + let started = false; + waitFor("getCurrentUser", x => Common.UserStore = x); + waitFor(["dispatch", "subscribe"], x => { + Common.FluxDispatcher = x; + const cb = () => { + console.info("Connection open"); + x.unsubscribe("CONNECTION_OPEN", cb); + startAll(); + }; + x.subscribe("CONNECTION_OPEN", cb); + }); + waitFor("useState", x => Common.React = x); +} + +export function find(filter: FilterFn, getDefault = true) { + if (typeof filter !== "function") + throw new Error("Invalid filter. Expected a function got", filter); + + for (const key in webpackCache) { + const mod = webpackCache[key]; + if (mod?.exports && filter(mod.exports)) + return mod.exports; + if (mod?.exports?.default && filter(mod.exports.default)) + return getDefault ? mod.exports.default : mod.exports; + } + + return null; +} + +export function findAll(filter: FilterFn, getDefault = true) { + if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got", filter); + + const ret = [] as any[]; + for (const key in webpackCache) { + const mod = webpackCache[key]; + if (mod?.exports && filter(mod.exports)) ret.push(mod.exports); + if (mod?.exports?.default && filter(mod.exports.default)) ret.push(getDefault ? mod.exports.default : mod.exports); + } + + return ret; +} + +export const filters = { + byProps: (props: string[]): FilterFn => + props.length === 1 + ? m => m[props[0]] !== void 0 + : m => props.every(p => m[p] !== void 0), + byDisplayName: (deezNuts: string): FilterFn => m => m.default?.displayName === deezNuts +}; + +export function findByProps(...props: string[]) { + return find(filters.byProps(props)); +} + +export function findAllByProps(...props: string[]) { + return findAll(filters.byProps(props)); +} + +export function findByDisplayName(deezNuts: string) { + return find(filters.byDisplayName(deezNuts)); +} + +export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) { + if (typeof filter === "string") filter = filters.byProps([filter]); + else if (Array.isArray(filter)) filter = filters.byProps(filter); + else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got", filter); + + const existing = find(filter!); + if (existing) return void callback(existing); + + subscriptions.set(filter, callback); +} + +export function addListener(callback: CallbackFn) { + listeners.add(callback); +} + +export function removeListener(callback: CallbackFn) { + listeners.delete(callback); +}
\ No newline at end of file |