Add Plugin.start, make Settings actually start/stop plugins

This commit is contained in:
Vendicated 2022-08-31 22:08:05 +02:00
parent bac8a648b6
commit f60ccb766f
No known key found for this signature in database
GPG Key ID: EC781ADFB93EFFA3
10 changed files with 146 additions and 58 deletions

2
pnpm-lock.yaml generated

@ -1,4 +1,4 @@
lockfileVersion: 5.4 lockfileVersion: 5.3
specifiers: specifiers:
'@types/flux': ^3.1.11 '@types/flux': ^3.1.11

@ -1,24 +1,31 @@
import IPC_EVENTS from './utils/IpcEvents'; import IPC_EVENTS from './utils/IpcEvents';
import { IpcRenderer, ipcRenderer } from 'electron'; import { IpcRenderer, ipcRenderer } from 'electron';
function assertEventAllowed(event: string) {
if (!(event in IPC_EVENTS)) throw new Error(`Event ${event} not allowed.`);
}
export default { export default {
getVersions: () => process.versions, getVersions: () => process.versions,
ipc: { ipc: {
send(event: string, ...args: any[]) { send(event: string, ...args: any[]) {
if (event in IPC_EVENTS) ipcRenderer.send(event, ...args); assertEventAllowed(event);
else throw new Error(`Event ${event} not allowed.`); ipcRenderer.send(event, ...args);
}, },
sendSync<T = any>(event: string, ...args: any[]): T { sendSync<T = any>(event: string, ...args: any[]): T {
if (event in IPC_EVENTS) return ipcRenderer.sendSync(event, ...args); assertEventAllowed(event);
else throw new Error(`Event ${event} not allowed.`); return ipcRenderer.sendSync(event, ...args);
}, },
on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) { on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) {
if (event in IPC_EVENTS) ipcRenderer.on(event, listener); assertEventAllowed(event);
else throw new Error(`Event ${event} not allowed.`); ipcRenderer.on(event, listener);
},
off(event: string, listener: Parameters<IpcRenderer["off"]>[1]) {
assertEventAllowed(event);
ipcRenderer.off(event, listener);
}, },
invoke<T = any>(event: string, ...args: any[]): Promise<T> { invoke<T = any>(event: string, ...args: any[]): Promise<T> {
if (event in IPC_EVENTS) return ipcRenderer.invoke(event, ...args); assertEventAllowed(event);
else throw new Error(`Event ${event} not allowed.`); return ipcRenderer.invoke(event, ...args);
} }
}, },
require(mod: string) { require(mod: string) {

@ -3,10 +3,10 @@ import Logger from '../utils/logger';
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890"); const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
interface Emoji { export interface Emoji {
require_colons: boolean, require_colons: boolean,
originalName: string, originalName: string,
animated: boolean animated: boolean;
guildId: string, guildId: string,
name: string, name: string,
url: string, url: string,
@ -15,11 +15,11 @@ interface Emoji {
interface MessageObject { interface MessageObject {
content: string, content: string,
validNonShortcutEmojis: Emoji[] validNonShortcutEmojis: Emoji[];
} }
type SendListener = (channelId: string, messageObj: MessageObject, extra: any) => void; export type SendListener = (channelId: string, messageObj: MessageObject, extra: any) => void;
type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => void; export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => void;
const sendListeners = new Set<SendListener>(); const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>(); const editListeners = new Set<EditListener>();
@ -28,7 +28,7 @@ export function _handlePreSend(channelId: string, messageObj: MessageObject, ext
for (const listener of sendListeners) { for (const listener of sendListeners) {
try { try {
listener(channelId, messageObj, extra); listener(channelId, messageObj, extra);
} catch (e) { MessageEventsLogger.error(`MessageSendHandler: Listener encoutered an unknown error. (${e})`) } } catch (e) { MessageEventsLogger.error(`MessageSendHandler: Listener encoutered an unknown error. (${e})`); }
} }
} }
@ -36,20 +36,31 @@ export function _handlePreEdit(channeld: string, messageId: string, messageObj:
for (const listener of editListeners) { for (const listener of editListeners) {
try { try {
listener(channeld, messageId, messageObj); listener(channeld, messageId, messageObj);
} catch (e) { MessageEventsLogger.error(`MessageEditHandler: Listener encoutered an unknown error. (${e})`) } } catch (e) { MessageEventsLogger.error(`MessageEditHandler: Listener encoutered an unknown error. (${e})`); }
} }
} }
/** /**
* Note: This event fires off before a message is sent, allowing you to edit the message. * Note: This event fires off before a message is sent, allowing you to edit the message.
*/ */
export function addPreSendListener(listener: SendListener) { sendListeners.add(listener) } export function addPreSendListener(listener: SendListener) {
sendListeners.add(listener);
return listener;
}
/** /**
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message. * Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
*/ */
export function addPreEditListener(listener: EditListener) { editListeners.add(listener) } export function addPreEditListener(listener: EditListener) {
export function removePreSendListener(listener: SendListener) { sendListeners.delete(listener) } editListeners.add(listener);
export function removePreEditListener(listener: EditListener) { editListeners.delete(listener) } return listener;
}
export function removePreSendListener(listener: SendListener) {
return sendListeners.delete(listener);
}
export function removePreEditListener(listener: EditListener) {
return editListeners.delete(listener);
}
// Message clicks // Message clicks
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void; type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
@ -60,12 +71,13 @@ export function _handleClick(message, channel, event) {
for (const listener of listeners) { for (const listener of listeners) {
try { try {
listener(message, channel, event); listener(message, channel, event);
} catch (e) { MessageEventsLogger.error(`MessageClickHandler: Listener encoutered an unknown error. (${e})`) } } catch (e) { MessageEventsLogger.error(`MessageClickHandler: Listener encoutered an unknown error. (${e})`); }
} }
} }
export function addClickListener(listener: ClickListener) { export function addClickListener(listener: ClickListener) {
listeners.add(listener); listeners.add(listener);
return listener;
} }
export function removeClickListener(listener: ClickListener) { export function removeClickListener(listener: ClickListener) {

@ -5,6 +5,8 @@ import IpcEvents from "../utils/IpcEvents";
import { Button, ButtonProps, Flex, Switch, Forms } from "../webpack/common"; import { Button, ButtonProps, Flex, Switch, Forms } from "../webpack/common";
import ErrorBoundary from "./ErrorBoundary"; import ErrorBoundary from "./ErrorBoundary";
import { startPlugin } from "../plugins";
import { stopPlugin } from '../plugins/index';
export default ErrorBoundary.wrap(function Settings(props) { export default ErrorBoundary.wrap(function Settings(props) {
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading..."); const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading...");
@ -52,8 +54,19 @@ export default ErrorBoundary.wrap(function Settings(props) {
settings.plugins[p.name].enabled = v; settings.plugins[p.name].enabled = v;
if (v) { if (v) {
p.dependencies?.forEach(d => { p.dependencies?.forEach(d => {
// TODO: start every dependency
settings.plugins[d].enabled = true; settings.plugins[d].enabled = true;
}); });
if (!p.started && !startPlugin(p)) {
// TODO show notification
}
} else {
if (p.started && !stopPlugin(p)) {
// TODO show notification
}
}
if (p.patches) {
// TODO show notification
} }
}} }}
note={p.description} note={p.description}

@ -1,7 +1,7 @@
import Plugins from "plugins"; import Plugins from "plugins";
import { Settings } from "../api/settings"; import { Settings } from "../api/settings";
import Logger from "../utils/logger"; import Logger from "../utils/logger";
import { Patch } from "../utils/types"; import { Patch, Plugin } from "../utils/types";
const logger = new Logger("PluginManager", "#a6d189"); const logger = new Logger("PluginManager", "#a6d189");
@ -17,12 +17,43 @@ for (const plugin of Plugins) if (plugin.patches && Settings.plugins[plugin.name
} }
export function startAll() { export function startAll() {
for (const plugin of plugins) if (plugin.start && Settings.plugins[plugin.name].enabled) { for (const plugin of plugins) if (Settings.plugins[plugin.name].enabled) {
startPlugin(plugin);
}
}
export function startPlugin(p: Plugin) {
if (p.start) {
logger.info("Starting plugin", p.name);
if (p.started) {
logger.warn(`${p.name} already started`);
return false;
}
try { try {
logger.info("Starting plugin", plugin.name); p.start();
plugin.start(); p.started = true;
} catch (err) { return true;
logger.error("Failed to start plugin", plugin.name, err); } catch (err: any) {
logger.error(`Failed to start ${p.name}\n`, err);
return false;
}
}
}
export function stopPlugin(p: Plugin) {
if (p.stop) {
logger.info("Stopping plugin", p.name);
if (!p.started) {
logger.warn(`${p.name} already stopped / never started`);
return false;
}
try {
p.stop();
p.started = false;
return true;
} catch (err: any) {
logger.error(`Failed to stop ${p.name}\n`, err);
return false;
} }
} }
} }

@ -1,12 +1,17 @@
import { addClickListener } from "../api/MessageEvents"; import { addClickListener, removeClickListener } from '../api/MessageEvents';
import definePlugin from "../utils/types"; import definePlugin from "../utils/types";
import { find, findByProps } from "../webpack"; import { find, findByProps } from "../webpack";
let isDeletePressed = false;
const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true);
const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = false);
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: ["MessageEventsAPI"], dependencies: ["MessageEventsAPI"],
start() { start() {
const { deleteMessage, startEditMessage } = findByProps("deleteMessage"); const { deleteMessage, startEditMessage } = findByProps("deleteMessage");
const { can } = findByProps("can", "initialize"); const { can } = findByProps("can", "initialize");
@ -14,14 +19,10 @@ export default definePlugin({
const { getCurrentUser } = findByProps("getCurrentUser"); const { getCurrentUser } = findByProps("getCurrentUser");
let isDeletePressed = false; let isDeletePressed = false;
document.addEventListener("keydown", e => { document.addEventListener("keydown", keydown);
if (e.key === "Backspace") isDeletePressed = true; document.addEventListener("keyup", keyup);
});
document.addEventListener("keyup", e => {
if (e.key === "Backspace") isDeletePressed = false;
});
addClickListener((msg, chan, event) => { this.onClick = addClickListener((msg, chan, event) => {
const isMe = msg.author.id === getCurrentUser().id; const isMe = msg.author.id === getCurrentUser().id;
if (!isDeletePressed) { if (!isDeletePressed) {
if (isMe && event.detail >= 2) { if (isMe && event.detail >= 2) {
@ -33,5 +34,11 @@ export default definePlugin({
event.preventDefault(); event.preventDefault();
} }
}); });
},
stop() {
removeClickListener(this.onClick);
document.removeEventListener("keydown", keydown);
document.removeEventListener("keyup", keyup);
} }
}); });

@ -1,4 +1,4 @@
import { addPreSendListener, addPreEditListener } from "../api/MessageEvents"; import { addPreSendListener, addPreEditListener, SendListener, removePreSendListener, removePreEditListener } from '../api/MessageEvents';
import { findByProps } from "../webpack"; import { findByProps } from "../webpack";
import definePlugin from "../utils/types"; import definePlugin from "../utils/types";
@ -22,6 +22,7 @@ export default definePlugin({
}) })
}, },
], ],
start() { start() {
const { getCustomEmojiById } = findByProps("getCustomEmojiById"); const { getCustomEmojiById } = findByProps("getCustomEmojiById");
@ -33,7 +34,7 @@ export default definePlugin({
delete x.guildPremiumTier; delete x.guildPremiumTier;
}); });
addPreSendListener((_, messageObj) => { this.preSend = addPreSendListener((_, messageObj) => {
const guildId = window.location.href.split("channels/")[1].split("/")[0]; const guildId = window.location.href.split("channels/")[1].split("/")[0];
for (const emoji of messageObj.validNonShortcutEmojis) { for (const emoji of messageObj.validNonShortcutEmojis) {
if (!emoji.require_colons) continue; if (!emoji.require_colons) continue;
@ -44,7 +45,7 @@ export default definePlugin({
messageObj.content = messageObj.content.replace(emojiString, ` ${url} `); messageObj.content = messageObj.content.replace(emojiString, ` ${url} `);
} }
}); });
addPreEditListener((_, __, messageObj) => { this.preEdit = addPreEditListener((_, __, messageObj) => {
const guildId = window.location.href.split("channels/")[1].split("/")[0]; const guildId = window.location.href.split("channels/")[1].split("/")[0];
for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) { for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) {
@ -56,4 +57,9 @@ export default definePlugin({
} }
}); });
}, },
stop() {
removePreSendListener(this.preSend);
removePreEditListener(this.preEdit);
}
}); });

@ -1,27 +1,33 @@
export default class Logger { export default class Logger {
constructor(public name: string, public color: string) { } constructor(public name: string, public color: string) { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", args: any[]) { private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[]) {
console[level](`%c ${this.name} `, `background: ${this.color}; color: black; font-weight: bold`, ...args); console[level](
`%c Vencord %c %c ${this.name} `,
`background: ${levelColor}; color: black; font-weight: bold; border-radius: 5px;`,
"",
`background: ${this.color}; color: black; font-weight: bold; border-radius: 5px;`
, ...args
);
} }
public log(...args: any[]) { public log(...args: any[]) {
this._log("log", args); this._log("log", "#a6d189", args);
} }
public info(...args: any[]) { public info(...args: any[]) {
this._log("info", args); this._log("info", "#a6d189", args);
} }
public error(...args: any[]) { public error(...args: any[]) {
this._log("error", args); this._log("error", "#e78284", args);
} }
public warn(...args: any[]) { public warn(...args: any[]) {
this._log("warn", args); this._log("warn", "#e5c890", args);
} }
public debug(...args: any[]) { public debug(...args: any[]) {
this._log("debug", args); this._log("debug", "#eebebe", args);
} }
} }

@ -1,5 +1,5 @@
// exists to export default definePlugin({...}) // exists to export default definePlugin({...})
export default function definePlugin(p: PluginDef) { export default function definePlugin(p: PluginDef & Record<string, any>) {
return p; return p;
} }
@ -14,17 +14,18 @@ export interface Patch {
replacement: PatchReplacement | PatchReplacement[]; replacement: PatchReplacement | PatchReplacement[];
} }
export interface Plugin { export interface Plugin extends PluginDef {
patches?: Patch[];
started: boolean;
}
interface PluginDef {
name: string; name: string;
description: string; description: string;
author: string; author: string;
start?(): void; start?(): void;
patches?: Patch[]; stop?(): void;
patches?: Omit<Patch, "plugin">[];
dependencies?: string[], dependencies?: string[],
required?: boolean; required?: boolean;
}
// @ts-ignore lole
interface PluginDef extends Plugin {
patches?: Omit<Patch, "plugin">[];
} }

@ -1,15 +1,19 @@
import { startAll } from "../plugins"; import { startAll } from "../plugins";
import { waitFor, filters } from './webpack'; import { waitFor, filters } from './webpack';
import type Components from "discord-types/components";
import type Stores from "discord-types/stores";
import type Other from "discord-types/other";
export let FluxDispatcher: any; export let FluxDispatcher: Other.FluxDispatcher;
export let React: typeof import("react"); export let React: typeof import("react");
export let UserStore: any; export let UserStore: Stores.UserStore;
export let Forms: any; export let Forms: any;
export let Button: any; export let Button: any;
export let ButtonProps: any; export let ButtonProps: any;
export let Switch: any; export let Switch: any;
export let Flex: any; export let Flex: Components.Flex;
export let Card: any; export let Card: Components.Card;
export let Tooltip: Components.Tooltip;
waitFor("useState", m => React = m); waitFor("useState", m => React = m);
waitFor(["dispatch", "subscribe"], m => { waitFor(["dispatch", "subscribe"], m => {
@ -28,4 +32,5 @@ waitFor(["ButtonLooks", "default"], m => {
}); });
waitFor(filters.byDisplayName("SwitchItem"), m => Switch = m.default); waitFor(filters.byDisplayName("SwitchItem"), m => Switch = m.default);
waitFor(filters.byDisplayName("Flex"), m => Flex = m.default); waitFor(filters.byDisplayName("Flex"), m => Flex = m.default);
waitFor(filters.byDisplayName("Card"), m => Card = m.default); waitFor(filters.byDisplayName("Card"), m => Card = m.default);
waitFor(filters.byDisplayName("Tooltip"), m => Tooltip = m.default);