From b4cc9844d56d405619350e64d6e2a2c8967528a7 Mon Sep 17 00:00:00 2001
From: vishnyanetchereshnya
<151846235+vishnyanetchereshnya@users.noreply.github.com>
Date: Fri, 21 Jun 2024 05:59:40 +0300
Subject: [PATCH] new way of users caching & receive notes in READY
---
.../notesSearcher/components/CachePopout.tsx | 14 +-
.../components/NotesDataModal.tsx | 42 +++---
.../notesSearcher/components/NotesDataRow.tsx | 4 +-
src/plugins/notesSearcher/data.ts | 131 +++++++++++++++---
src/plugins/notesSearcher/index.tsx | 102 ++++++++++----
src/plugins/notesSearcher/noteStore.d.ts | 14 ++
6 files changed, 227 insertions(+), 80 deletions(-)
create mode 100644 src/plugins/notesSearcher/noteStore.d.ts
diff --git a/src/plugins/notesSearcher/components/CachePopout.tsx b/src/plugins/notesSearcher/components/CachePopout.tsx
index 6d9043645..5029959c7 100644
--- a/src/plugins/notesSearcher/components/CachePopout.tsx
+++ b/src/plugins/notesSearcher/components/CachePopout.tsx
@@ -8,7 +8,7 @@ import { classNameFactory } from "@api/Styles";
import { LazyComponent } from "@utils/lazyReact";
import { Button, Popout, React, Text, useState } from "@webpack/common";
-import { cacheUsers, getRunning, NotesMap, setupStates, stopCacheProcess, usersCache } from "../data";
+import { cacheUsers, getNotes, getRunning, setupStates, stopCacheProcess, usersCache } from "../data";
import { CrossIcon, ProblemIcon, SuccessIcon } from "./Icons";
import { LoadingSpinner } from "./LoadingSpinner";
@@ -33,6 +33,8 @@ export default LazyComponent(() => React.memo(() => {
setCacheStatus,
});
+ const notesLength = Object.keys(getNotes()).length;
+
return
Fetch the profile of all users to filter notes by global name or username
@@ -47,14 +49,14 @@ export default LazyComponent(() => React.memo(() => {
onClick={() => cacheUsers()}
>
{
- cacheStatus === 0 ? "Cache Users" : "Re-Cache Users"
+ cacheStatus === 10 ? "Cache Users" : "Re-Cache Users"
}
diff --git a/src/plugins/notesSearcher/components/NotesDataModal.tsx b/src/plugins/notesSearcher/components/NotesDataModal.tsx
index cbaff0633..4d7116d48 100644
--- a/src/plugins/notesSearcher/components/NotesDataModal.tsx
+++ b/src/plugins/notesSearcher/components/NotesDataModal.tsx
@@ -11,7 +11,7 @@ import {
import { LazyComponent } from "@utils/react";
import { Button, React, RelationshipStore, Select, Text, TextInput, useCallback, useMemo, useReducer, useState } from "@webpack/common";
-import { NotesMap, usersCache } from "../data";
+import { getNotes, usersCache } from "../data";
import CachePopout from "./CachePopout";
import NotesDataRow from "./NotesDataRow";
@@ -54,8 +54,8 @@ export function NotesDataModal({ modalProps, close }: {
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
const [usersNotesData, refreshNotesData] = useReducer(() => {
- return Array.from(NotesMap);
- }, Array.from(NotesMap));
+ return Object.entries(getNotes());
+ }, Object.entries(getNotes()));
RefreshNotesDataEx = refreshNotesData;
@@ -110,25 +110,23 @@ export function NotesDataModal({ modalProps, close }: {
- {
-
- {
- modalProps.transitionState === 1 &&
-
- {
- !visibleNotes.length ? : (
-
- )
- }
-
- }
-
- }
+
+ {
+ modalProps.transitionState === 1 &&
+
+ {
+ !visibleNotes.length ? : (
+
+ )
+ }
+
+ }
+
);
}
diff --git a/src/plugins/notesSearcher/components/NotesDataRow.tsx b/src/plugins/notesSearcher/components/NotesDataRow.tsx
index 866e23bb5..16e21e5de 100644
--- a/src/plugins/notesSearcher/components/NotesDataRow.tsx
+++ b/src/plugins/notesSearcher/components/NotesDataRow.tsx
@@ -10,7 +10,7 @@ import { copyWithToast } from "@utils/misc";
import { LazyComponent, useAwaiter } from "@utils/react";
import { Alerts, Avatar, Button, ContextMenuApi, Menu, React, Text, TextArea, Tooltip, UserUtils, useState } from "@webpack/common";
-import { putNote, updateNote, usersCache } from "../data";
+import { updateNote, usersCache } from "../data";
import { DeleteIcon, PopupIcon, RefreshIcon, SaveIcon } from "./Icons";
import { LoadingSpinner } from "./LoadingSpinner";
@@ -141,7 +141,6 @@ export default LazyComponent(() => React.memo(({ userId, userNotes: userNotesArg
size={Button.Sizes.NONE}
color={Button.Colors.GREEN}
onClick={() => {
- putNote(userId, userNotes);
updateNote(userId, userNotes);
refreshNotesData();
}}
@@ -166,7 +165,6 @@ export default LazyComponent(() => React.memo(({ userId, userNotes: userNotesArg
confirmText: "Delete",
cancelText: "Cancel",
onConfirm: () => {
- putNote(userId, "");
updateNote(userId, "");
refreshNotesData();
},
diff --git a/src/plugins/notesSearcher/data.ts b/src/plugins/notesSearcher/data.ts
index 89fdbf04a..95206b598 100644
--- a/src/plugins/notesSearcher/data.ts
+++ b/src/plugins/notesSearcher/data.ts
@@ -4,18 +4,25 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import { Constants, RestAPI, UserUtils, useState } from "@webpack/common";
+import { Constants, FluxDispatcher, GuildStore, RestAPI, SnowflakeUtils, UserStore, UserUtils, useState } from "@webpack/common";
+import { waitForStore } from "webpack/common/internal";
+
+import { refreshNotesData } from "./components/NotesDataModal";
+import * as t from "./noteStore";
-export const NotesMap = new Map();
+let NoteStore: t.NoteStore;
-export const updateNote = (userId: string, note: string) => {
- if (!note || note === "")
- NotesMap.delete(userId);
- else
- NotesMap.set(userId, note);
+waitForStore("NoteStore", s => NoteStore = s);
+
+export const getNotes = () => {
+ return NoteStore.getNotes();
};
-export const putNote = (userId: string, note: string | null) => {
+export const onNoteUpdate = () => {
+ refreshNotesData();
+};
+
+export const updateNote = (userId: string, note: string | null) => {
RestAPI.put({
url: Constants.Endpoints.NOTE(userId),
body: { note },
@@ -42,7 +49,7 @@ const fetchUser = async (userId: string) => {
}
};
-type Dispatch = ReturnType>[1]
+type Dispatch = ReturnType>[1];
const states: {
setRunning?: Dispatch;
@@ -72,32 +79,114 @@ export const stopCacheProcess = () => {
cacheProcessNeedStop = true;
};
+const stop = () => {
+ cacheProcessNeedStop = false;
+ isRunning = false;
+ states.setRunning?.(false);
+};
+
export const cacheUsers = async (onlyMissing = false) => {
isRunning = true;
states.setRunning?.(true);
onlyMissing || usersCache.clear();
- for (const userId of NotesMap.keys()) {
- if (cacheProcessNeedStop) {
- cacheProcessNeedStop = false;
- break;
- }
+ const toRequest: string[] = [];
- if (onlyMissing && usersCache.get(userId)) continue;
-
- const user = await fetchUser(userId);
+ for (const userId of Object.keys(getNotes())) {
+ const user = UserStore.getUser(userId);
if (user) {
usersCache.set(user.id, {
globalName: (user as any).globalName,
username: user.username,
});
-
- states.setCacheStatus?.(usersCache.size);
+ continue;
}
+
+ toRequest.push(userId);
}
- isRunning = false;
- states.setRunning?.(false);
+ if (usersCache.size >= Object.keys(getNotes()).length) {
+ stop();
+ states.setCacheStatus?.(usersCache.size);
+ return;
+ }
+
+ states.setCacheStatus?.(usersCache.size);
+
+ const sentNonce = SnowflakeUtils.fromTimestamp(Date.now());
+
+ const allGuildIds = Object.keys(GuildStore.getGuilds());
+ let count = allGuildIds.length * Math.ceil(toRequest.length / 100);
+
+ const processed = new Set();
+
+ const callback = async ({ chunks }) => {
+ for (const chunk of chunks) {
+ if (cacheProcessNeedStop) {
+ stop();
+ FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
+ break;
+ }
+
+ const { nonce, members } = chunk;
+
+ if (nonce !== sentNonce) {
+ return;
+ }
+
+ members.forEach(member => {
+ if (processed.has(member.user.id)) return;
+
+ processed.add(member.user.id);
+
+ usersCache.set(member.id, {
+ globalName: (member as any).globalName,
+ username: member.username,
+ });
+
+ states.setCacheStatus?.(usersCache.size);
+ });
+
+ if (--count === 0) {
+ const userIds = Object.keys(getNotes());
+
+ if (usersCache.size !== userIds.length) {
+ for (const userId of userIds) {
+ if (cacheProcessNeedStop) {
+ stop();
+ FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
+ break;
+ }
+
+ const user = await fetchUser(userId);
+
+ if (user) {
+ usersCache.set(user.id, {
+ globalName: (user as any).globalName,
+ username: user.username,
+ });
+
+ states.setCacheStatus?.(usersCache.size);
+ }
+ }
+ }
+
+ stop();
+ FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
+ }
+ }
+ };
+
+ FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
+
+ for (let i = 0; i < toRequest.length; i += 100) {
+ FluxDispatcher.dispatch({
+ type: "GUILD_MEMBERS_REQUEST",
+ guildIds: allGuildIds,
+ userIds: toRequest.slice(i, i + 100),
+ nonce: sentNonce,
+ });
+ }
};
diff --git a/src/plugins/notesSearcher/index.tsx b/src/plugins/notesSearcher/index.tsx
index c0defe465..f8b51c510 100644
--- a/src/plugins/notesSearcher/index.tsx
+++ b/src/plugins/notesSearcher/index.tsx
@@ -9,34 +9,95 @@ import "./styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
-import { Constants, RestAPI } from "@webpack/common";
+import { FluxDispatcher } from "@webpack/common";
import { OpenNotesDataButton } from "./components/NotesDataButton";
-import { refreshNotesData } from "./components/NotesDataModal";
-import { NotesMap, updateNote } from "./data";
+import { getNotes, onNoteUpdate } from "./data";
+import { Notes } from "./noteStore";
import settings from "./settings";
-
+
export default definePlugin({
name: "NotesSearcher",
description: "Allows you to open a modal with all of your notes and search through them by user ID, note text, and username",
authors: [Devs.Vishnya],
settings,
patches: [
- {
- find: "noteRef",
- replacement: {
- match: /\i\.\i\.updateNote\((\i),(\i)\)/,
- replace: "$self.updateNote($1, $2) || $self.refreshNotesData() || $&",
- },
- },
{
find: "toolbar:function",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
- replace: "$1$self.addToolBarButton(arguments[0]);$2"
- }
+ replace: "$1$self.addToolBarButton(arguments[0]);$2",
+ },
+ },
+ {
+ find: '="NoteStore",',
+ replacement: {
+ match: /getNote\(\i\){return (\i)/,
+ replace: "getNotes(){return $1}$&",
+ },
+ },
+ // not sure it won't break anything but should be fine
+ {
+ find: '="NoteStore",',
+ replacement: {
+ match: /CONNECTION_OPEN:_,OVERLAY_INITIALIZE:_,/,
+ replace: "",
+ },
+ },
+ {
+ find: ".REQUEST_GUILD_MEMBERS",
+ replacement: {
+ match: /\.send\(8,{(?!nonce)/,
+ replace: "$&nonce:arguments[1].nonce,",
+ },
+ },
+ {
+ find: "GUILD_MEMBERS_REQUEST:",
+ replacement: {
+ match: /presences:!!(\i)\.presences(?!,nonce)/,
+ replace: "$&,nonce:$1.nonce",
+ },
+ },
+ {
+ find: ".not_found",
+ replacement: {
+ match: /notFound:(\i)\.not_found(?!,nonce)/,
+ replace: "$&,nonce:$1.nonce",
+ },
+ },
+ {
+ find: "[IDENTIFY]",
+ replacement: {
+ match: /capabilities:(\i\.\i),/,
+ replace: "capabilities:$1&~1,",
+ },
+ },
+ {
+ find: "_handleDispatch",
+ replacement: {
+ match: /let \i=(\i).session_id;/,
+ replace: "$&$self.ready($1);",
+ },
},
],
+
+ start: async () => {
+ FluxDispatcher.subscribe("USER_NOTE_UPDATE", onNoteUpdate);
+ },
+ stop: () => {
+ FluxDispatcher.unsubscribe("USER_NOTE_UPDATE", onNoteUpdate);
+ },
+
+ ready: ({ notes }: { notes: Notes; }) => {
+ const notesFromStore = getNotes();
+
+ for (const userId of Object.keys(notesFromStore)) {
+ delete notesFromStore[userId];
+ }
+
+ Object.assign(notesFromStore, notes);
+ },
+
addToolBarButton: (children: { toolbar: React.ReactNode[] | React.ReactNode; }) => {
if (Array.isArray(children.toolbar))
return children.toolbar.push(
@@ -52,19 +113,4 @@ export default definePlugin({
children.toolbar,
];
},
-
- updateNote,
- refreshNotesData,
-
- start: async () => {
- const result = await RestAPI.get({ url: Constants.Endpoints.NOTES });
-
- const userNotes: { [userId: string]: string; } | undefined = result.body;
-
- if (!userNotes) return;
-
- for (const [userId, note] of Object.entries(userNotes)) {
- NotesMap.set(userId, note);
- }
- }
});
diff --git a/src/plugins/notesSearcher/noteStore.d.ts b/src/plugins/notesSearcher/noteStore.d.ts
new file mode 100644
index 000000000..c40bc4fa8
--- /dev/null
+++ b/src/plugins/notesSearcher/noteStore.d.ts
@@ -0,0 +1,14 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { FluxStore } from "@webpack/types";
+
+export type Notes = { [userId: string]: string; };
+
+export class NoteStore extends FluxStore {
+ getNotes(): Notes;
+ getNote(userId: string): string | null;
+}