mirror of
https://github.com/Vendicated/Vencord.git
synced 2024-09-20 06:30:35 +00:00
new way of users caching & receive notes in READY
This commit is contained in:
parent
f31805c02f
commit
b4cc9844d5
6 changed files with 227 additions and 80 deletions
|
@ -8,7 +8,7 @@ import { classNameFactory } from "@api/Styles";
|
||||||
import { LazyComponent } from "@utils/lazyReact";
|
import { LazyComponent } from "@utils/lazyReact";
|
||||||
import { Button, Popout, React, Text, useState } from "@webpack/common";
|
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 { CrossIcon, ProblemIcon, SuccessIcon } from "./Icons";
|
||||||
import { LoadingSpinner } from "./LoadingSpinner";
|
import { LoadingSpinner } from "./LoadingSpinner";
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ export default LazyComponent(() => React.memo(() => {
|
||||||
setCacheStatus,
|
setCacheStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const notesLength = Object.keys(getNotes()).length;
|
||||||
|
|
||||||
return <div className={cl("cache-container")}>
|
return <div className={cl("cache-container")}>
|
||||||
<Text className={cl("cache-header")} variant="heading-lg/semibold">
|
<Text className={cl("cache-header")} variant="heading-lg/semibold">
|
||||||
Fetch the profile of all users to filter notes by global name or username
|
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()}
|
onClick={() => cacheUsers()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
cacheStatus === 0 ? "Cache Users" : "Re-Cache Users"
|
cacheStatus === 10 ? "Cache Users" : "Re-Cache Users"
|
||||||
}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={cl("cache-cache-missing")}
|
className={cl("cache-cache-missing")}
|
||||||
size={Button.Sizes.NONE}
|
size={Button.Sizes.NONE}
|
||||||
color={Button.Colors.YELLOW}
|
color={Button.Colors.YELLOW}
|
||||||
disabled={isRunning || cacheStatus === 0 || cacheStatus === NotesMap.size}
|
disabled={isRunning || cacheStatus === 0 || cacheStatus >= notesLength}
|
||||||
onClick={() => cacheUsers(true)}
|
onClick={() => cacheUsers(true)}
|
||||||
>
|
>
|
||||||
Cache Missing Users
|
Cache Missing Users
|
||||||
|
@ -74,14 +76,14 @@ export default LazyComponent(() => React.memo(() => {
|
||||||
<div className={cl("cache-status")}>
|
<div className={cl("cache-status")}>
|
||||||
{
|
{
|
||||||
isRunning ? <LoadingSpinner />
|
isRunning ? <LoadingSpinner />
|
||||||
: cacheStatus === NotesMap.size ? <SuccessIcon />
|
: cacheStatus >= notesLength ? <SuccessIcon />
|
||||||
: cacheStatus === 0 ? <CrossIcon />
|
: cacheStatus === 0 ? <CrossIcon />
|
||||||
: <ProblemIcon />
|
: <ProblemIcon />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cacheStatus === NotesMap.size ? "All users cached 👍"
|
cacheStatus >= notesLength ? "Users are cached 👍"
|
||||||
: cacheStatus === 0 ? "Users aren't cached 😔"
|
: cacheStatus === 0 ? "Users aren't cached 😔"
|
||||||
: `${cacheStatus}/${NotesMap.size}`
|
: `${cacheStatus}/${notesLength}`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { LazyComponent } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { Button, React, RelationshipStore, Select, Text, TextInput, useCallback, useMemo, useReducer, useState } from "@webpack/common";
|
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 CachePopout from "./CachePopout";
|
||||||
import NotesDataRow from "./NotesDataRow";
|
import NotesDataRow from "./NotesDataRow";
|
||||||
|
|
||||||
|
@ -54,8 +54,8 @@ export function NotesDataModal({ modalProps, close }: {
|
||||||
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
||||||
|
|
||||||
const [usersNotesData, refreshNotesData] = useReducer(() => {
|
const [usersNotesData, refreshNotesData] = useReducer(() => {
|
||||||
return Array.from(NotesMap);
|
return Object.entries(getNotes());
|
||||||
}, Array.from(NotesMap));
|
}, Object.entries(getNotes()));
|
||||||
|
|
||||||
RefreshNotesDataEx = refreshNotesData;
|
RefreshNotesDataEx = refreshNotesData;
|
||||||
|
|
||||||
|
@ -110,7 +110,6 @@ export function NotesDataModal({ modalProps, close }: {
|
||||||
<CachePopout />
|
<CachePopout />
|
||||||
<ModalCloseButton onClick={close} />
|
<ModalCloseButton onClick={close} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
{
|
|
||||||
<div style={{ opacity: modalProps.transitionState === 1 ? "1" : "0" }} className={cl("content-container")}>
|
<div style={{ opacity: modalProps.transitionState === 1 ? "1" : "0" }} className={cl("content-container")}>
|
||||||
{
|
{
|
||||||
modalProps.transitionState === 1 &&
|
modalProps.transitionState === 1 &&
|
||||||
|
@ -128,7 +127,6 @@ export function NotesDataModal({ modalProps, close }: {
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { copyWithToast } from "@utils/misc";
|
||||||
import { LazyComponent, useAwaiter } from "@utils/react";
|
import { LazyComponent, useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Avatar, Button, ContextMenuApi, Menu, React, Text, TextArea, Tooltip, UserUtils, useState } from "@webpack/common";
|
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 { DeleteIcon, PopupIcon, RefreshIcon, SaveIcon } from "./Icons";
|
||||||
import { LoadingSpinner } from "./LoadingSpinner";
|
import { LoadingSpinner } from "./LoadingSpinner";
|
||||||
|
|
||||||
|
@ -141,7 +141,6 @@ export default LazyComponent(() => React.memo(({ userId, userNotes: userNotesArg
|
||||||
size={Button.Sizes.NONE}
|
size={Button.Sizes.NONE}
|
||||||
color={Button.Colors.GREEN}
|
color={Button.Colors.GREEN}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
putNote(userId, userNotes);
|
|
||||||
updateNote(userId, userNotes);
|
updateNote(userId, userNotes);
|
||||||
refreshNotesData();
|
refreshNotesData();
|
||||||
}}
|
}}
|
||||||
|
@ -166,7 +165,6 @@ export default LazyComponent(() => React.memo(({ userId, userNotes: userNotesArg
|
||||||
confirmText: "Delete",
|
confirmText: "Delete",
|
||||||
cancelText: "Cancel",
|
cancelText: "Cancel",
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
putNote(userId, "");
|
|
||||||
updateNote(userId, "");
|
updateNote(userId, "");
|
||||||
refreshNotesData();
|
refreshNotesData();
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,18 +4,25 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* 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";
|
||||||
|
|
||||||
export const NotesMap = new Map<string, string>();
|
import { refreshNotesData } from "./components/NotesDataModal";
|
||||||
|
import * as t from "./noteStore";
|
||||||
|
|
||||||
export const updateNote = (userId: string, note: string) => {
|
let NoteStore: t.NoteStore;
|
||||||
if (!note || note === "")
|
|
||||||
NotesMap.delete(userId);
|
waitForStore("NoteStore", s => NoteStore = s);
|
||||||
else
|
|
||||||
NotesMap.set(userId, note);
|
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({
|
RestAPI.put({
|
||||||
url: Constants.Endpoints.NOTE(userId),
|
url: Constants.Endpoints.NOTE(userId),
|
||||||
body: { note },
|
body: { note },
|
||||||
|
@ -42,7 +49,7 @@ const fetchUser = async (userId: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type Dispatch = ReturnType<typeof useState<any>>[1]
|
type Dispatch = ReturnType<typeof useState<any>>[1];
|
||||||
|
|
||||||
const states: {
|
const states: {
|
||||||
setRunning?: Dispatch;
|
setRunning?: Dispatch;
|
||||||
|
@ -72,19 +79,86 @@ export const stopCacheProcess = () => {
|
||||||
cacheProcessNeedStop = true;
|
cacheProcessNeedStop = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
cacheProcessNeedStop = false;
|
||||||
|
isRunning = false;
|
||||||
|
states.setRunning?.(false);
|
||||||
|
};
|
||||||
|
|
||||||
export const cacheUsers = async (onlyMissing = false) => {
|
export const cacheUsers = async (onlyMissing = false) => {
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
states.setRunning?.(true);
|
states.setRunning?.(true);
|
||||||
|
|
||||||
onlyMissing || usersCache.clear();
|
onlyMissing || usersCache.clear();
|
||||||
|
|
||||||
for (const userId of NotesMap.keys()) {
|
const toRequest: string[] = [];
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
toRequest.push(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string>();
|
||||||
|
|
||||||
|
const callback = async ({ chunks }) => {
|
||||||
|
for (const chunk of chunks) {
|
||||||
if (cacheProcessNeedStop) {
|
if (cacheProcessNeedStop) {
|
||||||
cacheProcessNeedStop = false;
|
stop();
|
||||||
|
FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onlyMissing && usersCache.get(userId)) continue;
|
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);
|
const user = await fetchUser(userId);
|
||||||
|
|
||||||
|
@ -97,7 +171,22 @@ export const cacheUsers = async (onlyMissing = false) => {
|
||||||
states.setCacheStatus?.(usersCache.size);
|
states.setCacheStatus?.(usersCache.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isRunning = false;
|
stop();
|
||||||
states.setRunning?.(false);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,11 +9,11 @@ import "./styles.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Constants, RestAPI } from "@webpack/common";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
|
|
||||||
import { OpenNotesDataButton } from "./components/NotesDataButton";
|
import { OpenNotesDataButton } from "./components/NotesDataButton";
|
||||||
import { refreshNotesData } from "./components/NotesDataModal";
|
import { getNotes, onNoteUpdate } from "./data";
|
||||||
import { NotesMap, updateNote } from "./data";
|
import { Notes } from "./noteStore";
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -22,21 +22,82 @@ export default definePlugin({
|
||||||
authors: [Devs.Vishnya],
|
authors: [Devs.Vishnya],
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
|
||||||
find: "noteRef",
|
|
||||||
replacement: {
|
|
||||||
match: /\i\.\i\.updateNote\((\i),(\i)\)/,
|
|
||||||
replace: "$self.updateNote($1, $2) || $self.refreshNotesData() || $&",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: "toolbar:function",
|
find: "toolbar:function",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
|
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; }) => {
|
addToolBarButton: (children: { toolbar: React.ReactNode[] | React.ReactNode; }) => {
|
||||||
if (Array.isArray(children.toolbar))
|
if (Array.isArray(children.toolbar))
|
||||||
return children.toolbar.push(
|
return children.toolbar.push(
|
||||||
|
@ -52,19 +113,4 @@ export default definePlugin({
|
||||||
children.toolbar,
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
14
src/plugins/notesSearcher/noteStore.d.ts
vendored
Normal file
14
src/plugins/notesSearcher/noteStore.d.ts
vendored
Normal file
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in a new issue