diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 0ebb500e..1acbd17f 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -36,6 +36,7 @@ import { takeError } from "../../../context/revoltjs/util"; import IconButton from "../../ui/IconButton"; +import { PluginSingleton } from "../../../plugins"; import AutoComplete, { useAutoComplete } from "../AutoComplete"; import { PermissionTooltip } from "../Tooltip"; import FilePreview from "./bars/FilePreview"; @@ -185,8 +186,11 @@ export default function MessageBox({ channel }: Props) { return; const content = draft?.trim() ?? ""; - if (uploadState.type === "attached") return sendFile(content); - if (content.length === 0) return; + const target = { content }; + if (PluginSingleton.emit("Message:Send", target)) return; + + if (uploadState.type === "attached") return sendFile(target.content); + if (target.content.length === 0) return; stopTyping(); setMessage(); @@ -203,7 +207,7 @@ export default function MessageBox({ channel }: Props) { channel: channel._id, author: client.user!._id, - content, + content: target.content, replies, }, }); @@ -217,7 +221,7 @@ export default function MessageBox({ channel }: Props) { try { await client.channels.sendMessage(channel._id, { - content, + content: target.content, nonce, replies, }); diff --git a/src/main.tsx b/src/main.tsx index 28081391..cf562845 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,6 +6,7 @@ import { render } from "preact"; import { internalEmit } from "./lib/eventEmitter"; import { App } from "./pages/app"; +import "./plugins"; export const updateSW = registerSW({ onNeedRefresh() { diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 00000000..4e288d6c --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,74 @@ +import { Children } from "../types/Preact"; + +export interface PluginEvent { + cancel: () => void; + target: T; +} + +export interface PluginEvents { + "Message:Send": ( + event: PluginEvent<{ content?: string; files?: File[] }>, + ) => void; + "Misc:Test": (event: PluginEvent) => void; +} + +export interface Plugin { + render?: () => Children; + events?: Partial; +} + +class Singleton { + listeners: { + [key in keyof PluginEvents]?: PluginEvents[key][]; + }; + + constructor() { + this.listeners = {}; + } + + private getListenerArray( + key: K, + ): PluginEvents[K][] { + const arr = this.listeners[key]; + if (arr) return arr as PluginEvents[K][]; + + this.listeners[key] = []; + return this.listeners[key]! as PluginEvents[K][]; + } + + addListener(key: K, fn: PluginEvents[K]) { + this.getListenerArray(key).push(fn); + } + + emit( + key: K, + target: Parameters[0]["target"], + ): boolean { + let canceled = false; + + const ev: PluginEvent = { + cancel: () => (canceled = true), + target, + }; + + const arr = this.getListenerArray(key); + for (const listener of arr) { + listener(ev); + if (canceled) break; + } + + return canceled; + } +} + +export const PluginSingleton = new Singleton(); + +PluginSingleton.addListener("Message:Send", (event) => { + const { content } = event.target; + if (content?.startsWith(";sus")) { + event.target.content = "baka!"; + } else if (content?.startsWith(";amogus")) { + alert("fat"); + event.cancel(); + } +});