From b60f6cb18d1d55d367ae2f3c322d76b709eacdfe Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 25 Nov 2022 18:07:29 +0100 Subject: [PATCH] WhoReacted: Make more reliable & don't spam api --- src/components/Monaco.ts | 2 +- src/ipcMain/index.ts | 4 +-- src/plugins/whoReacted.tsx | 60 +++++++++++++++++++++++++++++++++----- src/utils/Queue.ts | 45 ++++++++++++++++++++++++++-- src/utils/misc.tsx | 5 ++++ src/webpack/common.tsx | 1 + 6 files changed, 104 insertions(+), 13 deletions(-) diff --git a/src/components/Monaco.ts b/src/components/Monaco.ts index 16eff859..6d16c76b 100644 --- a/src/components/Monaco.ts +++ b/src/components/Monaco.ts @@ -25,7 +25,7 @@ import { find } from "../webpack/webpack"; const queue = new Queue(); const setCss = debounce((css: string) => { - queue.add(() => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, css)); + queue.push(() => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, css)); }); export async function launchMonacoEditor() { diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts index 8a60bc6e..535c005d 100644 --- a/src/ipcMain/index.ts +++ b/src/ipcMain/index.ts @@ -66,14 +66,14 @@ const settingsWriteQueue = new Queue(); ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss()); ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) => - cssWriteQueue.add(() => writeFile(QUICKCSS_PATH, css)) + cssWriteQueue.push(() => writeFile(QUICKCSS_PATH, css)) ); ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR); ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings()); ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => { - settingsWriteQueue.add(() => writeFile(SETTINGS_FILE, s)); + settingsWriteQueue.push(() => writeFile(SETTINGS_FILE, s)); }); diff --git a/src/plugins/whoReacted.tsx b/src/plugins/whoReacted.tsx index 00799831..b1fb27d5 100644 --- a/src/plugins/whoReacted.tsx +++ b/src/plugins/whoReacted.tsx @@ -16,20 +16,56 @@ * along with this program. If not, see . */ -import { User } from "discord-types/general"; +import { ReactionEmoji, User } from "discord-types/general"; import ErrorBoundary from "../components/ErrorBoundary"; import { Devs } from "../utils/constants"; -import { LazyComponent, lazyWebpack } from "../utils/misc"; +import { LazyComponent, lazyWebpack, sleep, useForceUpdater } from "../utils/misc"; +import { Queue } from "../utils/Queue"; import definePlugin from "../utils/types"; import { filters, findByCode } from "../webpack"; -import { ChannelStore, React, Tooltip } from "../webpack/common"; +import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "../webpack/common"; const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); const AvatarStyles = lazyWebpack(filters.byProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar")); const ReactionStore = lazyWebpack(filters.byProps("getReactions")); +const queue = new Queue(); + +function fetchReactions(msg: Message, emoji: ReactionEmoji) { + const key = emoji.name + (emoji.id ? `:${emoji.id}` : ""); + return RestAPI.get({ + url: `/channels/${msg.channel_id}/messages/${msg.id}/reactions/${key}`, + query: { + limit: 100 + }, + oldFormErrors: true + }) + .then(res => FluxDispatcher.dispatch({ + type: "MESSAGE_REACTION_ADD_USERS", + channelId: msg.channel_id, + messageId: msg.id, + users: res.body, + emoji + })) + .catch(console.error) + .finally(() => sleep(250)); +} + +function getReactionsWithQueue(msg: Message, e: ReactionEmoji) { + const key = `${msg.id}:${e.name}:${e.id ?? ""}`; + const cache = ReactionStore.__getLocalVars().reactions[key] ??= { fetched: false, users: {} }; + if (!cache.fetched) { + queue.unshift(() => + fetchReactions(msg, e) + ); + cache.fetched = true; + } + + return cache.users; +} + function makeRenderMoreUsers(users: User[]) { return function renderMoreUsers(_label: string, _count: number) { return ( @@ -62,7 +98,7 @@ export default definePlugin({ }], renderUsers(props: RootObject) { - return ( + return props.message.reactions.length > 10 ? null : ( @@ -70,8 +106,19 @@ export default definePlugin({ }, _renderUsers({ message, emoji }: RootObject) { - const reactions = ReactionStore.getReactions(message.channel_id, message.id, emoji); - const users = Object.values(reactions) as User[]; + const forceUpdate = useForceUpdater(); + React.useEffect(() => { + const cb = (e: any) => { + if (e.messageId === message.id) + forceUpdate(); + }; + FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb); + + return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); + }, [message.id]); + + const reactions = getReactionsWithQueue(message, emoji); + const users = Object.values(reactions).filter(Boolean) as User[]; return (
= Promise.resolve(); + /** + * @param maxSize The maximum amount of functions that can be queued at once. + * If the queue is full, the oldest function will be removed. + */ + constructor(public maxSize = Infinity) { } - add(func: (lastValue: unknown) => Promisable): Promise { - return (this.promise = this.promise.then(func)); + queue = [] as Array<() => Promisable>; + + private promise?: Promise; + + private next() { + const func = this.queue.shift(); + if (func) + this.promise = Promise.resolve() + .then(func) + .then(() => this.next()); + else + this.promise = undefined; + } + + private run() { + if (!this.promise) + this.next(); + } + + push(func: () => Promisable) { + if (this.size >= this.maxSize) + this.queue.shift(); + + this.queue.push(func); + this.run(); + } + + unshift(func: () => Promisable) { + if (this.size >= this.maxSize) + this.queue.pop(); + + this.queue.unshift(func); + this.run(); + } + + get size() { + return this.queue.length; } } diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index 005bcc08..44fc819a 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -70,6 +70,11 @@ export function useAwaiter(factory: () => Promise, fallbackValue: T | null return [state.value, state.error, state.pending, () => setSignal(signal + 1)]; } +export function useForceUpdater() { + const [, set] = React.useState(0); + return () => set(s => s + 1); +} + /** * A lazy component. The factory method is called on first render. For example useful * for const Component = LazyComponent(() => findByDisplayName("...").default) diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx index 6ebad167..2f3768eb 100644 --- a/src/webpack/common.tsx +++ b/src/webpack/common.tsx @@ -32,6 +32,7 @@ export const Flux = lazyWebpack(filters.byProps("connectStores")); export let React: typeof import("react"); export const ReactDOM: typeof import("react-dom") = lazyWebpack(filters.byProps("createPortal", "render")); +export const RestAPI = lazyWebpack(filters.byProps("getAPIBaseURL", "get")); export const moment: typeof import("moment") = lazyWebpack(filters.byProps("parseTwoDigitYear")); export const MessageStore = lazyWebpack(filters.byProps("getRawMessages")) as Omit & { getMessages(chanId: string): any; };