WhoReacted: Make more reliable & don't spam api

This commit is contained in:
Vendicated 2022-11-25 18:07:29 +01:00
parent bb398970ef
commit b60f6cb18d
No known key found for this signature in database
GPG Key ID: EC781ADFB93EFFA3
6 changed files with 104 additions and 13 deletions

@ -25,7 +25,7 @@ import { find } from "../webpack/webpack";
const queue = new Queue(); const queue = new Queue();
const setCss = debounce((css: string) => { 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() { export async function launchMonacoEditor() {

@ -66,14 +66,14 @@ const settingsWriteQueue = new Queue();
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss()); ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) => 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.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings()); ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => { ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
settingsWriteQueue.add(() => writeFile(SETTINGS_FILE, s)); settingsWriteQueue.push(() => writeFile(SETTINGS_FILE, s));
}); });

@ -16,20 +16,56 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { User } from "discord-types/general"; import { ReactionEmoji, User } from "discord-types/general";
import ErrorBoundary from "../components/ErrorBoundary"; import ErrorBoundary from "../components/ErrorBoundary";
import { Devs } from "../utils/constants"; 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 definePlugin from "../utils/types";
import { filters, findByCode } from "../webpack"; 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 UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = lazyWebpack(filters.byProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar")); const AvatarStyles = lazyWebpack(filters.byProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"));
const ReactionStore = lazyWebpack(filters.byProps("getReactions")); 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[]) { function makeRenderMoreUsers(users: User[]) {
return function renderMoreUsers(_label: string, _count: number) { return function renderMoreUsers(_label: string, _count: number) {
return ( return (
@ -62,7 +98,7 @@ export default definePlugin({
}], }],
renderUsers(props: RootObject) { renderUsers(props: RootObject) {
return ( return props.message.reactions.length > 10 ? null : (
<ErrorBoundary noop> <ErrorBoundary noop>
<this._renderUsers {...props} /> <this._renderUsers {...props} />
</ErrorBoundary> </ErrorBoundary>
@ -70,8 +106,19 @@ export default definePlugin({
}, },
_renderUsers({ message, emoji }: RootObject) { _renderUsers({ message, emoji }: RootObject) {
const reactions = ReactionStore.getReactions(message.channel_id, message.id, emoji); const forceUpdate = useForceUpdater();
const users = Object.values(reactions) as User[]; 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 ( return (
<div <div
@ -79,7 +126,6 @@ export default definePlugin({
> >
<UserSummaryItem <UserSummaryItem
users={users} users={users}
count={users.length}
guildId={ChannelStore.getChannel(message.channel_id)?.guild_id} guildId={ChannelStore.getChannel(message.channel_id)?.guild_id}
renderIcon={false} renderIcon={false}
max={5} max={5}

@ -19,9 +19,48 @@
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
export class Queue { export class Queue {
private promise: Promise<any> = 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<T>(func: (lastValue: unknown) => Promisable<T>): Promise<T> { queue = [] as Array<() => Promisable<unknown>>;
return (this.promise = this.promise.then(func));
private promise?: Promise<any>;
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<T>(func: () => Promisable<T>) {
if (this.size >= this.maxSize)
this.queue.shift();
this.queue.push(func);
this.run();
}
unshift<T>(func: () => Promisable<T>) {
if (this.size >= this.maxSize)
this.queue.pop();
this.queue.unshift(func);
this.run();
}
get size() {
return this.queue.length;
} }
} }

@ -70,6 +70,11 @@ export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T | null
return [state.value, state.error, state.pending, () => setSignal(signal + 1)]; 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 * A lazy component. The factory method is called on first render. For example useful
* for const Component = LazyComponent(() => findByDisplayName("...").default) * for const Component = LazyComponent(() => findByDisplayName("...").default)

@ -32,6 +32,7 @@ export const Flux = lazyWebpack(filters.byProps("connectStores"));
export let React: typeof import("react"); export let React: typeof import("react");
export const ReactDOM: typeof import("react-dom") = lazyWebpack(filters.byProps("createPortal", "render")); 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 moment: typeof import("moment") = lazyWebpack(filters.byProps("parseTwoDigitYear"));
export const MessageStore = lazyWebpack(filters.byProps("getRawMessages")) as Omit<Stores.MessageStore, "getMessages"> & { getMessages(chanId: string): any; }; export const MessageStore = lazyWebpack(filters.byProps("getRawMessages")) as Omit<Stores.MessageStore, "getMessages"> & { getMessages(chanId: string): any; };