Make Settings & Settings Page
This commit is contained in:
parent
cb288e204d
commit
98cb301df5
18 changed files with 330 additions and 43 deletions
|
@ -117,7 +117,7 @@ await Promise.all([
|
||||||
],
|
],
|
||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
watch,
|
watch,
|
||||||
minify: true
|
minify: false,
|
||||||
})
|
})
|
||||||
]).then(res => {
|
]).then(res => {
|
||||||
const took = performance.now() - begin;
|
const took = performance.now() - begin;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * as Plugins from "./plugins";
|
export * as Plugins from "./plugins";
|
||||||
export * as Webpack from "./utils/webpack";
|
export * as Webpack from "./webpack";
|
||||||
export * as Api from "./api";
|
export * as Api from "./api";
|
||||||
export * as Components from "./components";
|
export * as Components from "./components";
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,33 @@
|
||||||
import { IPC_QUICK_CSS_UPDATE, IPC_GET_QUICK_CSS } from './utils/ipcEvents';
|
import IPC_EVENTS from './utils/IpcEvents';
|
||||||
import { ipcRenderer } from 'electron';
|
import { IpcRenderer, ipcRenderer } from 'electron';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
handleQuickCssUpdate(cb: (s: string) => void) {
|
getVersions: () => process.versions,
|
||||||
ipcRenderer.on(IPC_QUICK_CSS_UPDATE, (_, css) => {
|
ipc: {
|
||||||
cb(css);
|
send(event: string, ...args: any[]) {
|
||||||
});
|
if (event in IPC_EVENTS) ipcRenderer.send(event, ...args);
|
||||||
|
else throw new Error(`Event ${event} not allowed.`);
|
||||||
|
},
|
||||||
|
sendSync(event: string, ...args: any[]) {
|
||||||
|
if (event in IPC_EVENTS) return ipcRenderer.sendSync(event, ...args);
|
||||||
|
else throw new Error(`Event ${event} not allowed.`);
|
||||||
|
},
|
||||||
|
on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) {
|
||||||
|
if (event in IPC_EVENTS) ipcRenderer.on(event, listener);
|
||||||
|
else throw new Error(`Event ${event} not allowed.`);
|
||||||
|
},
|
||||||
|
invoke(event: string, ...args: any[]) {
|
||||||
|
if (event in IPC_EVENTS) return ipcRenderer.invoke(event, ...args);
|
||||||
|
else throw new Error(`Event ${event} not allowed.`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getQuickCss: () => ipcRenderer.invoke(IPC_GET_QUICK_CSS) as Promise<string>,
|
require(mod: string) {
|
||||||
getVersions: () => process.versions
|
const settings = ipcRenderer.sendSync(IPC_EVENTS.GET_SETTINGS);
|
||||||
|
try {
|
||||||
|
if (!JSON.parse(settings).unsafeRequire) throw "no";
|
||||||
|
} catch {
|
||||||
|
throw new Error("Unsafe require is not allowed. Enable it in settings and try again.");
|
||||||
|
}
|
||||||
|
return require(mod);
|
||||||
|
}
|
||||||
};
|
};
|
81
src/api/settings.ts
Normal file
81
src/api/settings.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import plugins from "plugins";
|
||||||
|
import IpcEvents from "../utils/IpcEvents";
|
||||||
|
import { React } from "../webpack";
|
||||||
|
import { mergeDefaults } from '../utils/misc';
|
||||||
|
|
||||||
|
interface Settings {
|
||||||
|
unsafeRequire: boolean;
|
||||||
|
plugins: {
|
||||||
|
[plugin: string]: {
|
||||||
|
enabled: boolean;
|
||||||
|
[setting: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultSettings: Settings = {
|
||||||
|
unsafeRequire: false,
|
||||||
|
plugins: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
DefaultSettings.plugins[plugin.name] = {
|
||||||
|
enabled: plugin.required ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
||||||
|
for (const key in DefaultSettings) {
|
||||||
|
settings[key] ??= DefaultSettings[key];
|
||||||
|
}
|
||||||
|
mergeDefaults(settings, DefaultSettings);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Corrupt settings file. ", err);
|
||||||
|
var settings = mergeDefaults({} as Settings, DefaultSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptions = new Set<() => void>();
|
||||||
|
|
||||||
|
function makeProxy(settings: Settings, root = settings): Settings {
|
||||||
|
return new Proxy(settings, {
|
||||||
|
get(target, p) {
|
||||||
|
const v = target[p];
|
||||||
|
if (typeof v === "object" && !Array.isArray(v)) return makeProxy(v, root);
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
set(target, p, v) {
|
||||||
|
if (target[p] === v) return true;
|
||||||
|
|
||||||
|
target[p] = v;
|
||||||
|
for (const subscription of subscriptions) {
|
||||||
|
subscription();
|
||||||
|
}
|
||||||
|
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A smart settings object. Altering props automagically saves
|
||||||
|
* the updated settings to disk.
|
||||||
|
*/
|
||||||
|
export const Settings = makeProxy(settings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings hook for React components. Returns a smart settings
|
||||||
|
* object that automagically triggers a rerender if any properties
|
||||||
|
* are altered
|
||||||
|
* @returns Settings
|
||||||
|
*/
|
||||||
|
export function useSettings() {
|
||||||
|
const [, forceUpdate] = React.useReducer(x => ({}), {});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
subscriptions.add(forceUpdate);
|
||||||
|
return () => void subscriptions.delete(forceUpdate);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Settings;
|
||||||
|
}
|
|
@ -1,4 +1,84 @@
|
||||||
|
import { lazy, LazyComponent, useAwaiter } from "../utils/misc";
|
||||||
|
import { findByDisplayName, Forms } from '../webpack';
|
||||||
|
import Plugins from 'plugins';
|
||||||
|
import { useSettings } from "../api/settings";
|
||||||
|
import { findByProps } from '../webpack/index';
|
||||||
|
import IpcEvents from "../utils/IpcEvents";
|
||||||
|
|
||||||
|
// Lazy spam because this is ran before React is a thing. Todo: Fix that and clean this up lmao
|
||||||
|
|
||||||
|
const SwitchItem = LazyComponent<React.PropsWithChildren<{
|
||||||
|
value: boolean;
|
||||||
|
onChange: (v: boolean) => void;
|
||||||
|
note?: string;
|
||||||
|
tooltipNote?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}>>(() => findByDisplayName("SwitchItem").default);
|
||||||
|
|
||||||
|
const getButton = lazy(() => findByProps("ButtonLooks", "default"));
|
||||||
|
const Button = LazyComponent(() => getButton().default);
|
||||||
|
const getFlex = lazy(() => findByDisplayName("Flex"));
|
||||||
|
const Flex = LazyComponent(() => getFlex().default);
|
||||||
|
const FlexChild = LazyComponent(() => getFlex().default.Child);
|
||||||
|
const getMargins = lazy(() => findByProps("marginTop8", "marginBottom8"));
|
||||||
|
|
||||||
export default function Settings(props) {
|
export default function Settings(props) {
|
||||||
console.log(props);
|
const settingsDir = useAwaiter(() => VencordNative.ipc.invoke(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
||||||
return (<p>Hi</p>);
|
const settings = useSettings();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection tag="h1" title="Vencord">
|
||||||
|
<Forms.FormText>SettingsDir: {settingsDir}</Forms.FormText>
|
||||||
|
<Flex className={getMargins().marginTop8 + " " + getMargins().marginBottom8}>
|
||||||
|
<FlexChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_PATH, settingsDir)}
|
||||||
|
size={getButton().ButtonSizes.SMALL}
|
||||||
|
disabled={settingsDir === "Loading..."}
|
||||||
|
>
|
||||||
|
Launch Directory
|
||||||
|
</Button>
|
||||||
|
</FlexChild>
|
||||||
|
<FlexChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_PATH, settingsDir + "/quickCss.css")}
|
||||||
|
size={getButton().ButtonSizes.SMALL}
|
||||||
|
disabled={settingsDir === "Loading..."}
|
||||||
|
>
|
||||||
|
Open QuickCSS File
|
||||||
|
</Button>
|
||||||
|
</FlexChild>
|
||||||
|
</Flex>
|
||||||
|
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
|
||||||
|
<SwitchItem
|
||||||
|
value={settings.unsafeRequire}
|
||||||
|
onChange={v => settings.unsafeRequire = v}
|
||||||
|
note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it."
|
||||||
|
>
|
||||||
|
Enable Ensafe Require
|
||||||
|
</SwitchItem>
|
||||||
|
<Forms.FormDivider />
|
||||||
|
<Forms.FormTitle tag="h5">Plugins</Forms.FormTitle>
|
||||||
|
{Plugins.map(p => (
|
||||||
|
<SwitchItem
|
||||||
|
disabled={p.required === true}
|
||||||
|
key={p.name}
|
||||||
|
value={settings.plugins[p.name].enabled}
|
||||||
|
onChange={v => {
|
||||||
|
settings.plugins[p.name].enabled = v;
|
||||||
|
if (v) {
|
||||||
|
p.dependencies?.forEach(d => {
|
||||||
|
settings.plugins[d].enabled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
note={p.description}
|
||||||
|
tooltipNote={p.required ? "This plugin is required. Thus you cannot disable it." : undefined}
|
||||||
|
>
|
||||||
|
{p.name}
|
||||||
|
</SwitchItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Forms.FormSection >
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -1,25 +1,39 @@
|
||||||
import { app, BrowserWindow, ipcMain } from "electron";
|
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
||||||
import { fstat, watch } from "fs";
|
import { readFileSync, watch } from "fs";
|
||||||
import { open, readFile } from "fs/promises";
|
import { open, readFile, writeFile } from "fs/promises";
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { IPC_GET_SETTINGS_DIR, IPC_GET_QUICK_CSS, IPC_QUICK_CSS_UPDATE } from './utils/ipcEvents';
|
import IpcEvents from './utils/IpcEvents';
|
||||||
|
|
||||||
const DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
const DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||||
const SETTINGS_DIR = join(DATA_DIR, "settings");
|
const SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||||
const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
||||||
|
const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
|
||||||
|
|
||||||
function readCss() {
|
function readCss() {
|
||||||
return readFile(QUICKCSS_PATH, "utf-8").catch(() => "");
|
return readFile(QUICKCSS_PATH, "utf-8").catch(() => "");
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle(IPC_GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
function readSettings() {
|
||||||
ipcMain.handle(IPC_GET_QUICK_CSS, () => readCss());
|
try {
|
||||||
|
return readFileSync(SETTINGS_FILE, "utf-8");
|
||||||
|
} catch {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||||
|
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
|
||||||
|
// .on because we need Settings synchronously (ipcRenderer.sendSync)
|
||||||
|
ipcMain.on(IpcEvents.GET_SETTINGS, (e) => e.returnValue = readSettings());
|
||||||
|
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => void writeFile(SETTINGS_FILE, s));
|
||||||
|
ipcMain.handle(IpcEvents.OPEN_PATH, (_, path) => shell.openPath(path));
|
||||||
|
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => shell.openExternal(url));
|
||||||
|
|
||||||
export function initIpc(mainWindow: BrowserWindow) {
|
export function initIpc(mainWindow: BrowserWindow) {
|
||||||
open(QUICKCSS_PATH, "a+").then(fd => {
|
open(QUICKCSS_PATH, "a+").then(fd => {
|
||||||
fd.close();
|
fd.close();
|
||||||
watch(QUICKCSS_PATH, async () => {
|
watch(QUICKCSS_PATH, async () => {
|
||||||
mainWindow.webContents.postMessage(IPC_QUICK_CSS_UPDATE, await readCss());
|
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Plugins from "plugins";
|
import Plugins from "plugins";
|
||||||
|
import { Settings } from "../api/settings";
|
||||||
import Logger from "../utils/logger";
|
import Logger from "../utils/logger";
|
||||||
import { Patch } from "../utils/types";
|
import { Patch } from "../utils/types";
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ const logger = new Logger("PluginManager", "#a6d189");
|
||||||
export const plugins = Plugins;
|
export const plugins = Plugins;
|
||||||
export const patches = [] as Patch[];
|
export const patches = [] as Patch[];
|
||||||
|
|
||||||
for (const plugin of Plugins) if (plugin.patches) {
|
for (const plugin of Plugins) if (plugin.patches && Settings.plugins[plugin.name].enabled) {
|
||||||
for (const patch of plugin.patches) {
|
for (const patch of plugin.patches) {
|
||||||
patch.plugin = plugin.name;
|
patch.plugin = plugin.name;
|
||||||
if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
|
if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
|
||||||
|
@ -16,7 +17,7 @@ for (const plugin of Plugins) if (plugin.patches) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startAll() {
|
export function startAll() {
|
||||||
for (const plugin of plugins) if (plugin.start) {
|
for (const plugin of plugins) if (plugin.start && Settings.plugins[plugin.name].enabled) {
|
||||||
try {
|
try {
|
||||||
logger.info("Starting plugin", plugin.name);
|
logger.info("Starting plugin", plugin.name);
|
||||||
plugin.start();
|
plugin.start();
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { MessageClicks } from "../api";
|
import { MessageClicks } from "../api";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
import { find, findByProps } from "../utils/webpack";
|
import { find, findByProps } from "../webpack";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageQuickActions",
|
name: "MessageQuickActions",
|
||||||
description: "Quick Delete, Quick edit",
|
description: "Quick Delete, Quick edit",
|
||||||
author: "Vendicated",
|
author: "Vendicated",
|
||||||
|
dependencies: ["MessageClicksApi"],
|
||||||
start() {
|
start() {
|
||||||
const { deleteMessage, startEditMessage } = findByProps("deleteMessage");
|
const { deleteMessage, startEditMessage } = findByProps("deleteMessage");
|
||||||
const { can } = findByProps("can", "initialize");
|
const { can } = findByProps("can", "initialize");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
import { findByProps } from "../utils/webpack";
|
import { findByProps } from "../webpack";
|
||||||
|
|
||||||
const DO_NOTHING = () => void 0;
|
const DO_NOTHING = () => void 0;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ export default definePlugin({
|
||||||
name: "NoTrack",
|
name: "NoTrack",
|
||||||
description: "Disable Discord's tracking and crash reporting",
|
description: "Disable Discord's tracking and crash reporting",
|
||||||
author: "Vendicated",
|
author: "Vendicated",
|
||||||
|
required: true,
|
||||||
start() {
|
start() {
|
||||||
findByProps("getSuperPropertiesBase64", "track").track = DO_NOTHING;
|
findByProps("getSuperPropertiesBase64", "track").track = DO_NOTHING;
|
||||||
findByProps("submitLiveCrashReport").submitLiveCrashReport = DO_NOTHING;
|
findByProps("submitLiveCrashReport").submitLiveCrashReport = DO_NOTHING;
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default definePlugin({
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
description: "Adds Settings UI and debug info",
|
description: "Adds Settings UI and debug info",
|
||||||
author: "Vendicated",
|
author: "Vendicated",
|
||||||
|
required: true,
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "default.versionHash",
|
find: "default.versionHash",
|
||||||
replacement: [
|
replacement: [
|
||||||
|
@ -28,7 +29,7 @@ export default definePlugin({
|
||||||
}, {
|
}, {
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\{section:(.{1,2})\.SectionTypes\.HEADER,label:(.{1,2})\.default\.Messages\.ACTIVITY_SETTINGS\}/,
|
match: /\{section:(.{1,2})\.SectionTypes\.HEADER,\s*label:(.{1,2})\.default\.Messages\.ACTIVITY_SETTINGS\}/,
|
||||||
replace: (m, mod) =>
|
replace: (m, mod) =>
|
||||||
`{section:${mod}.SectionTypes.HEADER,label:"Vencord"},` +
|
`{section:${mod}.SectionTypes.HEADER,label:"Vencord"},` +
|
||||||
`{section:"Vencord",label:"Vencord",element:Vencord.Components.Settings},` +
|
`{section:"Vencord",label:"Vencord",element:Vencord.Components.Settings},` +
|
||||||
|
|
22
src/utils/IpcEvents.ts
Normal file
22
src/utils/IpcEvents.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
type Enum<T extends Record<string, string>> = {
|
||||||
|
[k in keyof T]: T[k];
|
||||||
|
} & { [v in keyof T as T[v]]: v; };
|
||||||
|
|
||||||
|
function strEnum<T extends Record<string, string>>(obj: T): T {
|
||||||
|
const o = {} as T;
|
||||||
|
for (const key in obj) {
|
||||||
|
o[key] = obj[key] as any;
|
||||||
|
o[obj[key]] = key as any;
|
||||||
|
};
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default strEnum({
|
||||||
|
QUICK_CSS_UPDATE: "VencordQuickCssUpdate",
|
||||||
|
GET_QUICK_CSS: "VencordGetQuickCss",
|
||||||
|
GET_SETTINGS_DIR: "VencordGetSettingsDir",
|
||||||
|
GET_SETTINGS: "VencordGetSettings",
|
||||||
|
SET_SETTINGS: "VencordSetSettings",
|
||||||
|
OPEN_EXTERNAL: "VencordOpenExternal",
|
||||||
|
OPEN_PATH: "VencordOpenPath",
|
||||||
|
} as const);
|
|
@ -1,3 +0,0 @@
|
||||||
export const IPC_QUICK_CSS_UPDATE = "VencordQuickCssUpdate";
|
|
||||||
export const IPC_GET_QUICK_CSS = "VencordGetQuickCss";
|
|
||||||
export const IPC_GET_SETTINGS_DIR = "VencordGetSettingsDir";
|
|
61
src/utils/misc.tsx
Normal file
61
src/utils/misc.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { React } from "../webpack";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a lazy function. On first call, the value is computed.
|
||||||
|
* On subsequent calls, the same computed value will be returned
|
||||||
|
* @param factory Factory function
|
||||||
|
*/
|
||||||
|
export function lazy<T>(factory: () => T): () => T {
|
||||||
|
let cache: T;
|
||||||
|
return () => {
|
||||||
|
return cache ?? (cache = factory());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Await a promise
|
||||||
|
* @param factory Factory
|
||||||
|
* @param fallbackValue The fallback value that will be used until the promise resolved
|
||||||
|
* @returns A state that will either be null or the result of the promise
|
||||||
|
*/
|
||||||
|
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T | null = null): T | null {
|
||||||
|
const [res, setRes] = React.useState<T | null>(fallbackValue);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
factory().then(setRes);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lazy component. The factory method is called on first render. For example useful
|
||||||
|
* for const Component = LazyComponent(() => findByDisplayName("...").default)
|
||||||
|
* @param factory Function returning a Component
|
||||||
|
* @returns Result of factory function
|
||||||
|
*/
|
||||||
|
export function LazyComponent<T = any>(factory: () => React.ComponentType<T>) {
|
||||||
|
return (props: T) => {
|
||||||
|
const Component = React.useMemo(factory, []);
|
||||||
|
return <Component {...props} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively merges defaults into an object and returns the same object
|
||||||
|
* @param obj Object
|
||||||
|
* @param defaults Defaults
|
||||||
|
* @returns obj
|
||||||
|
*/
|
||||||
|
export function mergeDefaults<T>(obj: T, defaults: T): T {
|
||||||
|
for (const key in defaults) {
|
||||||
|
const v = defaults[key];
|
||||||
|
if (typeof v === "object" && !Array.isArray(v)) {
|
||||||
|
obj[key] ??= {} as any;
|
||||||
|
mergeDefaults(obj[key], v);
|
||||||
|
} else {
|
||||||
|
obj[key] ??= v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { WEBPACK_CHUNK } from './constants';
|
import { WEBPACK_CHUNK } from './constants';
|
||||||
import Logger from "./logger";
|
import Logger from "./logger";
|
||||||
import { _initWebpack } from "./webpack";
|
import { _initWebpack } from "../webpack";
|
||||||
|
|
||||||
let webpackChunk: any[];
|
let webpackChunk: any[];
|
||||||
|
|
||||||
|
@ -83,9 +83,13 @@ function patchPush() {
|
||||||
const lastCode = code;
|
const lastCode = code;
|
||||||
try {
|
try {
|
||||||
const newCode = code.replace(replacement.match, replacement.replace);
|
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}`);
|
if (newCode === code) {
|
||||||
code = newCode;
|
logger.warn(`Patch by ${patch.plugin} had no effect: ${replacement.match}`);
|
||||||
mod = newMod;
|
} else {
|
||||||
|
const newMod = (0, eval)(`// Webpack Module ${id} - Patched by ${[...patchedBy].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${id}`);
|
||||||
|
code = newCode;
|
||||||
|
mod = newMod;
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("Failed to apply patch of", patch.plugin, err);
|
logger.error("Failed to apply patch of", patch.plugin, err);
|
||||||
code = lastCode;
|
code = lastCode;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import IpcEvents from "./IpcEvents";
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
VencordNative.handleQuickCssUpdate((css: string) => style.innerText = css);
|
VencordNative.ipc.on(IpcEvents.QUICK_CSS_UPDATE, (_, css: string) => style.innerText = css);
|
||||||
style.innerText = await VencordNative.getQuickCss();
|
style.innerText = await VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,8 @@ export interface Plugin {
|
||||||
author: string;
|
author: string;
|
||||||
start?(): void;
|
start?(): void;
|
||||||
patches?: Patch[];
|
patches?: Patch[];
|
||||||
|
dependencies?: string[],
|
||||||
|
required?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore lole
|
// @ts-ignore lole
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { startAll } from "../plugins";
|
import { startAll } from "../plugins";
|
||||||
import Logger from "./logger";
|
|
||||||
|
|
||||||
let webpackCache: typeof window.webpackChunkdiscord_app;
|
let webpackCache: typeof window.webpackChunkdiscord_app;
|
||||||
|
|
||||||
|
@ -9,11 +8,10 @@ export const listeners = new Set<CallbackFn>();
|
||||||
type FilterFn = (mod: any) => boolean;
|
type FilterFn = (mod: any) => boolean;
|
||||||
type CallbackFn = (mod: any) => void;
|
type CallbackFn = (mod: any) => void;
|
||||||
|
|
||||||
export let Common: {
|
export let React: typeof import("react");
|
||||||
React: typeof import("react"),
|
export let FluxDispatcher: any;
|
||||||
FluxDispatcher: any;
|
export let Forms: any;
|
||||||
UserStore: any;
|
export let UserStore: any;
|
||||||
} = {} as any;
|
|
||||||
|
|
||||||
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
||||||
if (webpackCache !== void 0) throw "no.";
|
if (webpackCache !== void 0) throw "no.";
|
||||||
|
@ -24,9 +22,9 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
||||||
// Abandon Hope All Ye Who Enter Here
|
// Abandon Hope All Ye Who Enter Here
|
||||||
|
|
||||||
let started = false;
|
let started = false;
|
||||||
waitFor("getCurrentUser", x => Common.UserStore = x);
|
waitFor("getCurrentUser", x => UserStore = x);
|
||||||
waitFor(["dispatch", "subscribe"], x => {
|
waitFor(["dispatch", "subscribe"], x => {
|
||||||
Common.FluxDispatcher = x;
|
FluxDispatcher = x;
|
||||||
const cb = () => {
|
const cb = () => {
|
||||||
console.info("Connection open");
|
console.info("Connection open");
|
||||||
x.unsubscribe("CONNECTION_OPEN", cb);
|
x.unsubscribe("CONNECTION_OPEN", cb);
|
||||||
|
@ -34,7 +32,8 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
||||||
};
|
};
|
||||||
x.subscribe("CONNECTION_OPEN", cb);
|
x.subscribe("CONNECTION_OPEN", cb);
|
||||||
});
|
});
|
||||||
waitFor("useState", x => Common.React = x);
|
waitFor("useState", x => (React = x));
|
||||||
|
waitFor("FormSection", x => Forms = x);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function find(filter: FilterFn, getDefault = true) {
|
export function find(filter: FilterFn, getDefault = true) {
|
|
@ -9,7 +9,7 @@
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"target": "ESNEXT",
|
"target": "ESNEXT",
|
||||||
// https://esbuild.github.io/api/#jsx-factory
|
// https://esbuild.github.io/api/#jsx-factory
|
||||||
"jsxFactory": "Vencord.Webpack.Common.React.createElement",
|
"jsxFactory": "Vencord.Webpack.React.createElement",
|
||||||
"jsx": "react"
|
"jsx": "react"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
|
|
Loading…
Reference in a new issue