From 18548449ba3eccf52d8395a3e46437feb1c1a1fb Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:30:57 -0400 Subject: [PATCH 01/13] Questify: Fix Patch --- src/equicordplugins/questify/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 72f8d2da3..1390cc486 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1269,7 +1269,7 @@ export default definePlugin({ group: true, replacement: [ { - match: /(?<=resetSortingFiltering\(\)},\[\]\);)/, + match: /(?<=scrollToQuest\(\i\)\}\)\},\[\]\);)/, replace: "const shouldHideSponsoredQuestBanner=$self.shouldHideSponsoredQuestBanner();" }, { From 26829a4204c3f6828e2b3d699f5128befc11cddc Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Thu, 19 Mar 2026 04:01:13 -0400 Subject: [PATCH 02/13] ReviewDB: various fixes (#4062) * fix regression that only showed 4 reviews per page * Newest to oldest review sort * filter out system on preview * fix broken pfps rendering as error image * fix contrast on white profiles --------- Co-authored-by: Vendicated --- src/components/BaseText.css | 5 +- src/components/BaseText.tsx | 4 +- src/plugins/anonymiseFileNames/index.tsx | 4 +- .../reviewDB/components/ReviewComponent.tsx | 5 +- .../reviewDB/components/ReviewsView.tsx | 3 +- src/plugins/reviewDB/index.tsx | 57 ++++++++++++------- src/plugins/reviewDB/reviewDbApi.ts | 16 +++--- src/plugins/reviewDB/style.css | 5 ++ 8 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/components/BaseText.css b/src/components/BaseText.css index d322a5679..cd7f8c0cd 100644 --- a/src/components/BaseText.css +++ b/src/components/BaseText.css @@ -1,8 +1,11 @@ .vc-text-base { font-family: var(--font-primary); - color: var(--text-default); line-height: normal; /* Discord puts an insane default margin on p tags, so reset that here */ margin: 0; +} + +.vc-text-defaultColor { + color: var(--text-default); } \ No newline at end of file diff --git a/src/components/BaseText.tsx b/src/components/BaseText.tsx index 7ce49d69f..dfabcdb48 100644 --- a/src/components/BaseText.tsx +++ b/src/components/BaseText.tsx @@ -56,6 +56,7 @@ export type BaseTextProps = ComponentPropsWithoutRe size?: TextSize; weight?: TextWeight; tag?: Tag; + defaultColor?: boolean; }; export function BaseText(props: BaseTextProps): ReactNode { @@ -63,13 +64,14 @@ export function BaseText(props: BaseTextProps): Re size = "md", weight = "normal", tag: Tag = "div", + defaultColor = true, children, className, ...restProps } = props; return ( - + {children} ); diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 9b7690f34..6b4a5a9ab 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -53,7 +53,7 @@ const settings = definePluginSettings({ randomisedLength: { description: "Random characters length", type: OptionType.NUMBER, - default: 10, + default: 7, disabled: () => settings.store.method !== Methods.Random, }, consistent: { @@ -123,7 +123,7 @@ export default definePlugin({ const newFilename = (() => { switch (settings.store.method) { case Methods.Random: - const chars = "ABCDEFabcdef0123456789"; + const chars = "0123456789bdfhjkmnpqrstvwxz"; return Array.from( { length: settings.store.randomisedLength }, () => chars[Math.floor(Math.random() * chars.length)] diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx index 20bd2e029..a29bd504c 100644 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ b/src/plugins/reviewDB/components/ReviewComponent.tsx @@ -24,7 +24,7 @@ import { canBlockReviewAuthor, canDeleteReview, canReportReview, cl, showToast } import { openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { findCssClassesLazy } from "@webpack"; -import { Alerts, Parser, Timestamp, useState } from "@webpack/common"; +import { Alerts, IconUtils, Parser, Timestamp, useState } from "@webpack/common"; import { openBlockModal } from "./BlockedUserModal"; import { BlockButton, DeleteButton, ReportButton } from "./MessageButton"; @@ -116,8 +116,9 @@ export default function ReviewComponent({ review, refetch, profileId }: { review e.currentTarget.src = IconUtils.getDefaultAvatarURL(review.sender.discordID)} />
getReviews(discordId, (page - 1) * REVIEWS_PER_PAGE), { + const [reviewData] = useAwaiter(() => getReviews(discordId, { offset: (page - 1) * REVIEWS_PER_PAGE }), { fallbackValue: null, deps: [refetchSignal, signal, page], onSuccess: data => { if (settings.store.hideBlockedUsers) data!.reviews = data!.reviews?.filter(r => !RelationshipStore.isBlocked(r.sender.discordID)); + data!.reviews.reverse(); scrollToTop?.(); onFetchReviews(data!); } diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 51e0a3f3e..647d716ae 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -29,14 +29,14 @@ import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { Guild, User } from "@vencord/discord-types"; import { findCssClassesLazy } from "@webpack"; -import { Alerts, Clickable, Menu, Parser } from "@webpack/common"; +import { Alerts, Clickable, IconUtils, Menu, Parser } from "@webpack/common"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; import { NotificationType, ReviewType } from "./entities"; import { getCurrentUserInfo, getReviews, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; -import { showToast } from "./utils"; +import { cl, showToast } from "./utils"; const DMSideBarClasses = findCssClassesLazy("widgetPreviews"); const ProfileCardClasses = findCssClassesLazy("cardsList", "firstCardContainer", "card", "container"); @@ -155,7 +155,7 @@ export default definePlugin({ }, renderProfileComponent: ErrorBoundary.wrap(({ user, isSideBar = false }: { user: User; isSideBar?: boolean; }) => { - const [reviewData] = useAwaiter(() => getReviews(user.id), { deps: [user.id], fallbackValue: null }); + const [reviewData] = useAwaiter(() => getReviews(user.id, { limit: 4 }), { deps: [user.id], fallbackValue: null }); // Discord are masters at using a crap ton of html elements and css classes to create a simple ui that could have // been made with less than half of the number of elements, so we have to do this insanity to replicate their ui @@ -163,27 +163,44 @@ export default definePlugin({
  • - openReviewsModal(user.id, user.username, ReviewType.User)}> + !reviewData?.hasOptedOut && openReviewsModal(user.id, user.username, ReviewType.User)} + >
    User Reviews - {!!reviewData?.reviewCount && ( -
    - {reviewData.reviews.slice(0, 4).map((review, idx) => { - const showCount = idx === 3 && reviewData.reviewCount > 4; + {!!reviewData?.reviewCount + ? ( +
    + {reviewData.reviews + .filter(review => review.id !== 0) + .slice(0, 4) + .reverse() + .map((review, idx) => { + const showCount = idx === 3 && reviewData.reviewCount > 4; - return ( -
    - {review.sender.username} - {showCount && ( -
    - +{reviewData.reviewCount - 3} + return ( +
    + {review.sender.username} e.currentTarget.src = IconUtils.getDefaultAvatarURL(review.sender.discordID)} + /> + {showCount && ( +
    + + +{reviewData.reviewCount - 3} + +
    + )}
    - )} -
    - ); - })} -
    - )} + ); + })} +
    + ) + : {reviewData?.hasOptedOut ? "User opted out" : "No reviews yet"} + }
  • diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts index e899216a3..94c369770 100644 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ b/src/plugins/reviewDB/reviewDbApi.ts @@ -33,6 +33,7 @@ export interface UserReviewsData { updated: boolean; hasNextPage: boolean; reviewCount: number; + hasOptedOut: boolean; } const WarningFlag = 0b00000010; @@ -47,15 +48,15 @@ async function rdbRequest(path: string, options: RequestInit = {}) { }); } -export async function getReviews(id: string, offset = 0): Promise { +export async function getReviews(id: string, { limit, offset = 0 }: { limit?: number; offset?: number; } = {}): Promise { let flags = 0; if (!settings.store.showWarning) flags |= WarningFlag; - const params = new URLSearchParams({ - flags: String(flags), - offset: String(offset), - limit: "4" - }); + const params = new URLSearchParams(); + if (flags) params.append("flags", String(flags)); + if (offset) params.append("offset", String(offset)); + if (limit) params.append("limit", String(limit)); + const req = await fetch(`${API_URL}/users/${id}/reviews?${params}`); const res = (req.ok) @@ -65,7 +66,8 @@ export async function getReviews(id: string, offset = 0): Promise Date: Thu, 19 Mar 2026 09:04:24 +0100 Subject: [PATCH 03/13] fix SpotifyControls, GameActivityToggle & AccPanelServerProfile (#4059) --- src/plugins/accountPanelServerProfile/index.tsx | 4 ++-- src/plugins/decor/index.tsx | 2 +- src/plugins/gameActivityToggle/index.tsx | 2 +- src/plugins/newGuildSettings/index.tsx | 10 ++++++++-- src/plugins/spotifyControls/index.tsx | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index f735ccbf7..f7a5b5ea4 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -67,11 +67,11 @@ export default definePlugin({ patches: [ { - find: ".WIDGETS_RTC_UPSELL_COACHMARK)", + find: "handleOpenSettingsContextMenu=", group: true, replacement: [ { - match: /(\.AVATAR,children:.+?renderPopout:\((\i),\i\)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, + match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})` }, { diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 30100d1d6..fc2e93a99 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -87,7 +87,7 @@ export default definePlugin({ }, // Current user area, at bottom of channels/dm list { - find: ".DISPLAY_NAME_STYLES_COACHMARK),", + find: ".DISPLAY_NAME_STYLES_COACHMARK)", replacement: [ // Use Decor avatar decoration hook { diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 0a0e792d3..102ee3cf4 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -112,7 +112,7 @@ export default definePlugin({ patches: [ { - find: ".DISPLAY_NAME_STYLES_COACHMARK),", + find: ".DISPLAY_NAME_STYLES_COACHMARK)", replacement: { match: /children:\[(?=.{0,25}?accountContainerRef)/, replace: "children:[$self.GameActivityToggleButton(arguments[0])," diff --git a/src/plugins/newGuildSettings/index.tsx b/src/plugins/newGuildSettings/index.tsx index fba0c1c3a..7d8687d30 100644 --- a/src/plugins/newGuildSettings/index.tsx +++ b/src/plugins/newGuildSettings/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { Guild } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; -import { Menu } from "@webpack/common"; +import { Menu, UserStore } from "@webpack/common"; const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", { @@ -141,5 +141,11 @@ export default definePlugin({ } ], settings, - applyDefaultSettings + applyDefaultSettings, + flux: { + GUILD_JOIN_REQUEST_UPDATE({ guildId, request, status }) { + if (status === "APPROVED" && request.user_id === UserStore.getCurrentUser().id) + applyDefaultSettings(guildId); + } + } }); diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index f476165e9..a6bc716e8 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -53,7 +53,7 @@ export default definePlugin({ }, patches: [ { - find: ".DISPLAY_NAME_STYLES_COACHMARK),", + find: ".DISPLAY_NAME_STYLES_COACHMARK)", replacement: { // react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah }) match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,occluded:)/, From 567637c737935e264ff3d7b1a3046bc833a58ba0 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Mar 2026 09:32:30 +0100 Subject: [PATCH 04/13] ci: fix resource exhaustion errors --- package.json | 1 + pnpm-lock.yaml | 20 +++++++++++++++++++- src/debug/loadLazyChunks.ts | 20 +++++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 0061e54ae..6722df788 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "highlight.js": "11.11.1", "html-minifier-terser": "^7.2.0", "moment": "^2.22.2", + "p-limit": "^7.3.0", "puppeteer-core": "^24.30.0", "standalone-electron-types": "^34.2.0", "stylelint": "^16.25.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a0905955..baca9c229 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: moment: specifier: ^2.22.2 version: 2.30.1 + p-limit: + specifier: ^7.3.0 + version: 7.3.0 puppeteer-core: specifier: ^24.30.0 version: 24.30.0 @@ -966,6 +969,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.0, please upgrade boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2154,6 +2158,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@7.3.0: + resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} + engines: {node: '>=20'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -2814,6 +2822,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + zip-local@0.3.5: resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==} @@ -3193,7 +3205,7 @@ snapshots: '@types/react-dom@18.3.1': dependencies: - '@types/react': 18.3.1 + '@types/react': 19.0.12 '@types/react-dom@19.0.4(@types/react@19.0.12)': dependencies: @@ -4899,6 +4911,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@7.3.0: + dependencies: + yocto-queue: 1.2.2 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -5726,6 +5742,8 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + zip-local@0.3.5: dependencies: async: 1.5.2 diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index 94a9f6a3e..cf094f574 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -9,6 +9,7 @@ import { canonicalizeMatch } from "@utils/patches"; import { ModuleFactory } from "@vencord/discord-types/webpack"; import * as Webpack from "@webpack"; import { wreq } from "@webpack"; +import pLimit from "p-limit"; import { AnyModuleFactory } from "webpack"; function getWebpackChunkMap() { @@ -31,6 +32,7 @@ function getWebpackChunkMap() { export async function loadLazyChunks() { const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); + const queue = pLimit(50); try { LazyChunkLoaderLogger.log("Loading all chunks..."); @@ -59,11 +61,15 @@ export async function loadLazyChunks() { const shouldForceDefer = false; - await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { - const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => { - const numChunkId = Number(m[1]); - return Number.isNaN(numChunkId) ? m[1] : numChunkId; - }) : []; + await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => queue(async () => { + const chunkIds = rawChunkIds + ?.matchAll(Webpack.ChunkIdsRegex) + .map(m => { + const numChunkId = Number(m[1]); + return Number.isNaN(numChunkId) ? m[1] : numChunkId; + }) + .toArray() + ?? []; if (chunkIds.length === 0) { return; @@ -101,13 +107,13 @@ export async function loadLazyChunks() { const numEntryPoint = Number(entryPoint); validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]); } - })); + }))); // Loads all found valid chunk groups await Promise.all( Array.from(validChunkGroups) .map(([chunkIds]) => - Promise.all(chunkIds.map(id => wreq.e(id))) + Promise.all(chunkIds.map(id => queue(() => wreq.e(id)))) ) ); From cdf933120401c06d347ee3d37cd9dadc904480e3 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Mar 2026 09:34:57 +0100 Subject: [PATCH 05/13] Revert "ci: fix resource exhaustion errors" doesnt solve issue guh --- package.json | 1 - pnpm-lock.yaml | 20 +------------------- src/debug/loadLazyChunks.ts | 20 +++++++------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 6722df788..0061e54ae 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "highlight.js": "11.11.1", "html-minifier-terser": "^7.2.0", "moment": "^2.22.2", - "p-limit": "^7.3.0", "puppeteer-core": "^24.30.0", "standalone-electron-types": "^34.2.0", "stylelint": "^16.25.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index baca9c229..0a0905955 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,9 +98,6 @@ importers: moment: specifier: ^2.22.2 version: 2.30.1 - p-limit: - specifier: ^7.3.0 - version: 7.3.0 puppeteer-core: specifier: ^24.30.0 version: 24.30.0 @@ -969,7 +966,6 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} - deprecated: Security vulnerability fixed in 5.2.0, please upgrade boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2158,10 +2154,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@7.3.0: - resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} - engines: {node: '>=20'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -2822,10 +2814,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.2.2: - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} - engines: {node: '>=12.20'} - zip-local@0.3.5: resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==} @@ -3205,7 +3193,7 @@ snapshots: '@types/react-dom@18.3.1': dependencies: - '@types/react': 19.0.12 + '@types/react': 18.3.1 '@types/react-dom@19.0.4(@types/react@19.0.12)': dependencies: @@ -4911,10 +4899,6 @@ snapshots: dependencies: yocto-queue: 0.1.0 - p-limit@7.3.0: - dependencies: - yocto-queue: 1.2.2 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -5742,8 +5726,6 @@ snapshots: yocto-queue@0.1.0: {} - yocto-queue@1.2.2: {} - zip-local@0.3.5: dependencies: async: 1.5.2 diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index cf094f574..94a9f6a3e 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -9,7 +9,6 @@ import { canonicalizeMatch } from "@utils/patches"; import { ModuleFactory } from "@vencord/discord-types/webpack"; import * as Webpack from "@webpack"; import { wreq } from "@webpack"; -import pLimit from "p-limit"; import { AnyModuleFactory } from "webpack"; function getWebpackChunkMap() { @@ -32,7 +31,6 @@ function getWebpackChunkMap() { export async function loadLazyChunks() { const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); - const queue = pLimit(50); try { LazyChunkLoaderLogger.log("Loading all chunks..."); @@ -61,15 +59,11 @@ export async function loadLazyChunks() { const shouldForceDefer = false; - await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => queue(async () => { - const chunkIds = rawChunkIds - ?.matchAll(Webpack.ChunkIdsRegex) - .map(m => { - const numChunkId = Number(m[1]); - return Number.isNaN(numChunkId) ? m[1] : numChunkId; - }) - .toArray() - ?? []; + await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { + const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => { + const numChunkId = Number(m[1]); + return Number.isNaN(numChunkId) ? m[1] : numChunkId; + }) : []; if (chunkIds.length === 0) { return; @@ -107,13 +101,13 @@ export async function loadLazyChunks() { const numEntryPoint = Number(entryPoint); validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]); } - }))); + })); // Loads all found valid chunk groups await Promise.all( Array.from(validChunkGroups) .map(([chunkIds]) => - Promise.all(chunkIds.map(id => queue(() => wreq.e(id)))) + Promise.all(chunkIds.map(id => wreq.e(id))) ) ); From c42cf2e5750fba53504ddcd99672e60083727ae3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 19 Mar 2026 07:59:20 -0400 Subject: [PATCH 06/13] Fix Plugin Finds --- src/equicordplugins/_api/userArea.ts | 2 +- src/equicordplugins/declutter/index.tsx | 2 +- src/equicordplugins/songSpotlight.desktop/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equicordplugins/_api/userArea.ts b/src/equicordplugins/_api/userArea.ts index 53cf52cde..112ac9bce 100644 --- a/src/equicordplugins/_api/userArea.ts +++ b/src/equicordplugins/_api/userArea.ts @@ -19,7 +19,7 @@ export default definePlugin({ patches: [ { - find: ".DISPLAY_NAME_STYLES_COACHMARK),", + find: ".DISPLAY_NAME_STYLES_COACHMARK)", replacement: [ { match: /(?<=className:(\i)\.\i,style:\i,)children:\[/, diff --git a/src/equicordplugins/declutter/index.tsx b/src/equicordplugins/declutter/index.tsx index 0eb0d2a88..98d28384c 100644 --- a/src/equicordplugins/declutter/index.tsx +++ b/src/equicordplugins/declutter/index.tsx @@ -172,7 +172,7 @@ export default definePlugin({ }, { // Always show username - find: ".DISPLAY_NAME_STYLES_COACHMARK),", + find: ".DISPLAY_NAME_STYLES_COACHMARK)", replacement: { match: /hoverText:(\i),forceHover:\i,children:/g, replace: "hoverText:$1,forceHover:!0,children:" diff --git a/src/equicordplugins/songSpotlight.desktop/index.tsx b/src/equicordplugins/songSpotlight.desktop/index.tsx index ed8d84c12..b6b44da53 100644 --- a/src/equicordplugins/songSpotlight.desktop/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/index.tsx @@ -25,7 +25,7 @@ export default definePlugin({ patches: [ // Personal profile popout { - find: ".WIDGETS_USER_PROFILE_ACCOUNT_POPOUT_NEW_BADGE]", + find: '"UserProfileAccountPopout"', replacement: { match: /user:(\i),bio:.{0,60}}\)/, replace: "$&,$self.renderProfileSongs({userId:$1.id})", From df197d4994de94c68450f66f047e262c0b0ebafd Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 19 Mar 2026 08:12:03 -0400 Subject: [PATCH 07/13] LoadLazyChunks: Retry --- src/debug/loadLazyChunks.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index 32ab77041..78eeaa6f9 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -96,9 +96,18 @@ export async function loadLazyChunks() { if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue; - const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) - .then(r => r.text()) - .then(t => /importScripts\(|self\.postMessage/.test(t)); + let isWorkerAsset = false; + for (let attempt = 0; attempt < 3; attempt++) { + try { + isWorkerAsset = await fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => /importScripts\(|self\.postMessage/.test(t)); + break; + } catch (err) { + if (attempt === 2) throw err; + await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1))); + } + } if (isWorkerAsset) { invalidChunks.add(id); @@ -195,9 +204,18 @@ export async function loadLazyChunks() { }); await promiseAllSettledBatched(chunksLeft.map(id => async () => { - const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) - .then(r => r.text()) - .then(t => /importScripts\(|self\.postMessage/.test(t)); + let isWorkerAsset = false; + for (let attempt = 0; attempt < 3; attempt++) { + try { + isWorkerAsset = await fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => /importScripts\(|self\.postMessage/.test(t)); + break; + } catch (err) { + if (attempt === 2) throw err; + await new Promise(resolve => setTimeout(resolve, 500 * (attempt + 1))); + } + } // Loads the chunk. Currently this only happens with the language packs which are loaded differently if (!isWorkerAsset) { From 2448edfc480ad7da3cd5159e35bbc82613b6a45c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Mar 2026 13:25:12 +0100 Subject: [PATCH 08/13] add vesktop vs vencord explainer to updater & improve cards --- src/components/Card.css | 20 ++++++++-- src/components/Card.tsx | 2 +- .../settings/tabs/updater/Components.tsx | 2 - .../settings/tabs/updater/index.tsx | 40 ++++++++++++++++++- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/components/Card.css b/src/components/Card.css index 97e36ed84..74260f37e 100644 --- a/src/components/Card.css +++ b/src/components/Card.css @@ -1,18 +1,30 @@ .vc-card-base { background: var(--card-background-default); - border-radius: var(--radius-sm, 8px) + border-radius: var(--radius-sm, 8px); } .vc-card-normal { - border: 1px solid var(--border-subtle); + border: 1px var(--border-subtle); } .vc-card-warning { - border: 1px solid var(--icon-status-idle); + background-color: var(--background-feedback-warning); + border: 1px solid var(--icon-feedback-warning); } .vc-card-danger { - border: 1px solid var(--icon-status-dnd); + background-color: var(--background-feedback-critical); + border: 1px solid var(--icon-feedback-critical); +} + +.vc-card-success { + background-color: var(--background-feedback-positive); + border: 1px solid var(--icon-feedback-positive); +} + +.vc-card-info { + background: var(--background-feedback-info); + border: 1px solid var(--icon-feedback-info); } .vc-card-defaultPadding { diff --git a/src/components/Card.tsx b/src/components/Card.tsx index dfa6caaa0..128e17f32 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -13,7 +13,7 @@ import { ComponentPropsWithRef } from "react"; const cl = classNameFactory("vc-card-"); export interface CardProps extends ComponentPropsWithRef<"div"> { - variant?: "normal" | "warning" | "danger"; + variant?: "normal" | "warning" | "danger" | "info" | "success"; /** Add a default padding of 1em to the card. This is implied if no className prop is passed */ defaultPadding?: boolean; } diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx index f30587d59..33ea34d7a 100644 --- a/src/components/settings/tabs/updater/Components.tsx +++ b/src/components/settings/tabs/updater/Components.tsx @@ -94,7 +94,6 @@ export function Updatable(props: CommonProps) { {isOutdated && ( )} + + + )} + + ); +} + function Updater() { const settings = useSettings(["autoUpdate", "autoUpdateNotification"]); @@ -45,6 +79,8 @@ function Updater() { return ( + + - Repo + Repo {repoPending @@ -76,7 +112,7 @@ function Updater() { () - + Updates From 2f129373ca114e5a07660ed845536539921cbde5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Mar 2026 09:32:30 +0100 Subject: [PATCH 09/13] ci: fix resource exhaustion errors --- .github/workflows/reportBrokenPlugins.yml | 8 +------- package.json | 1 + pnpm-lock.yaml | 20 ++++++++++++++++++- src/debug/loadLazyChunks.ts | 24 ++++++++++++++--------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index f1e53e4d0..8c90a4e52 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -44,12 +44,6 @@ jobs: run: | pnpm install --frozen-lockfile - - name: Install Google Chrome - id: setup-chrome - uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2 - with: - chrome-version: stable - - name: Build Vencord Reporter Version run: pnpm buildReporter @@ -57,7 +51,7 @@ jobs: timeout-minutes: 10 run: | export PATH="$PWD/node_modules/.bin:$PATH" - export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} + export CHROMIUM_BIN=/usr/bin/google-chrome esbuild scripts/generateReport.ts > dist/report.mjs diff --git a/package.json b/package.json index 0061e54ae..6722df788 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "highlight.js": "11.11.1", "html-minifier-terser": "^7.2.0", "moment": "^2.22.2", + "p-limit": "^7.3.0", "puppeteer-core": "^24.30.0", "standalone-electron-types": "^34.2.0", "stylelint": "^16.25.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a0905955..baca9c229 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: moment: specifier: ^2.22.2 version: 2.30.1 + p-limit: + specifier: ^7.3.0 + version: 7.3.0 puppeteer-core: specifier: ^24.30.0 version: 24.30.0 @@ -966,6 +969,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.0, please upgrade boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2154,6 +2158,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@7.3.0: + resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} + engines: {node: '>=20'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -2814,6 +2822,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + zip-local@0.3.5: resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==} @@ -3193,7 +3205,7 @@ snapshots: '@types/react-dom@18.3.1': dependencies: - '@types/react': 18.3.1 + '@types/react': 19.0.12 '@types/react-dom@19.0.4(@types/react@19.0.12)': dependencies: @@ -4899,6 +4911,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@7.3.0: + dependencies: + yocto-queue: 1.2.2 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -5726,6 +5742,8 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + zip-local@0.3.5: dependencies: async: 1.5.2 diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index 94a9f6a3e..424724bb5 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -9,6 +9,7 @@ import { canonicalizeMatch } from "@utils/patches"; import { ModuleFactory } from "@vencord/discord-types/webpack"; import * as Webpack from "@webpack"; import { wreq } from "@webpack"; +import pLimit from "p-limit"; import { AnyModuleFactory } from "webpack"; function getWebpackChunkMap() { @@ -31,6 +32,7 @@ function getWebpackChunkMap() { export async function loadLazyChunks() { const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); + const queue = pLimit(50); try { LazyChunkLoaderLogger.log("Loading all chunks..."); @@ -59,11 +61,15 @@ export async function loadLazyChunks() { const shouldForceDefer = false; - await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { - const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => { - const numChunkId = Number(m[1]); - return Number.isNaN(numChunkId) ? m[1] : numChunkId; - }) : []; + await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => queue(async () => { + const chunkIds = rawChunkIds + ?.matchAll(Webpack.ChunkIdsRegex) + .map(m => { + const numChunkId = Number(m[1]); + return Number.isNaN(numChunkId) ? m[1] : numChunkId; + }) + .toArray() + ?? []; if (chunkIds.length === 0) { return; @@ -101,13 +107,13 @@ export async function loadLazyChunks() { const numEntryPoint = Number(entryPoint); validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]); } - })); + }))); // Loads all found valid chunk groups await Promise.all( Array.from(validChunkGroups) .map(([chunkIds]) => - Promise.all(chunkIds.map(id => wreq.e(id))) + Promise.all(chunkIds.map(id => queue(() => wreq.e(id)))) ) ); @@ -182,7 +188,7 @@ export async function loadLazyChunks() { return !(validChunks.has(id) || invalidChunks.has(id)); }); - await Promise.all(chunksLeft.map(async id => { + await Promise.all(chunksLeft.map(async id => queue(async () => { const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) .then(t => /importScripts\(|self\.postMessage/.test(t)); @@ -191,7 +197,7 @@ export async function loadLazyChunks() { if (!isWorkerAsset) { await wreq.e(id); } - })); + }))); LazyChunkLoaderLogger.log("Finished loading all chunks!"); } catch (e) { From cba0eb9897419432e68277b0b60c301a6f8323cf Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Mar 2026 15:12:09 +0100 Subject: [PATCH 10/13] 1.14.6 --- package.json | 2 +- src/debug/loadLazyChunks.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 6722df788..088ffe3ee 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.14.5", + "version": "1.14.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index 424724bb5..44e483faf 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -61,7 +61,7 @@ export async function loadLazyChunks() { const shouldForceDefer = false; - await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => queue(async () => { + await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { const chunkIds = rawChunkIds ?.matchAll(Webpack.ChunkIdsRegex) .map(m => { @@ -90,9 +90,11 @@ export async function loadLazyChunks() { if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue; - const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) - .then(r => r.text()) - .then(t => /importScripts\(|self\.postMessage/.test(t)); + const isWorkerAsset = await queue(() => + fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => /importScripts\(|self\.postMessage/.test(t)) + ); if (isWorkerAsset) { invalidChunks.add(id); @@ -107,13 +109,13 @@ export async function loadLazyChunks() { const numEntryPoint = Number(entryPoint); validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]); } - }))); + })); // Loads all found valid chunk groups await Promise.all( Array.from(validChunkGroups) .map(([chunkIds]) => - Promise.all(chunkIds.map(id => queue(() => wreq.e(id)))) + Promise.all(chunkIds.map(id => wreq.e(id))) ) ); From b0fdb4c0ba65c18f786ba36edf5236e48ce6de59 Mon Sep 17 00:00:00 2001 From: creations Date: Thu, 19 Mar 2026 13:38:04 -0400 Subject: [PATCH 11/13] fix: vcNarratorCustom (#919) * fix ignore self, add blocklist, add join/leave cooldown, remove comments * fix some anys * whoops --- .../vcNarratorCustom/index.tsx | 156 ++++++++---------- src/equicordplugins/vcNarratorCustom/util.ts | 126 ++++---------- 2 files changed, 100 insertions(+), 182 deletions(-) diff --git a/src/equicordplugins/vcNarratorCustom/index.tsx b/src/equicordplugins/vcNarratorCustom/index.tsx index f4711d878..c9d80123c 100644 --- a/src/equicordplugins/vcNarratorCustom/index.tsx +++ b/src/equicordplugins/vcNarratorCustom/index.tsx @@ -28,6 +28,7 @@ import { import { wordsToTitle } from "@utils/text"; import definePlugin, { OptionType } from "@utils/types"; import type { User } from "@vencord/discord-types"; +import { ChannelType } from "@vencord/discord-types/enums"; import { Button as DiscordButton, ChannelStore, @@ -45,6 +46,7 @@ import { } from "@webpack/common"; import { + addUserToList, addUserToStateChangeFilterList, clean, clearTtsCache, @@ -53,7 +55,9 @@ import { getPersistentTtsCacheStats, getVoiceForUser, parseStateChangeFilterList, + parseUserIdList, parseUserVoiceMap, + removeUserFromList, removeUserFromStateChangeFilterList, removeUserVoiceFromMap, setCachedVoiceInDB, @@ -64,12 +68,6 @@ import { const cl = classNameFactory("vc-narrator-"); -/* - * TTS API maintained by example-git - * The original TikTok TTS API went offline, so I set up a new working cloudflare worker. - * I made sure it's intentionally rate-limited to keep it feasible. Please don't abuse it - * so it can stay available for plugins like this one. Thanks! - example-git - */ const API_BASE = "https://tiktok-tts-aio.exampleuser.workers.dev"; type LastApiCallStatus = { @@ -189,10 +187,6 @@ interface VoiceState { stream?: boolean; } -// Two-queue system for TTS playback: -// - mainQueue: Non-interruptable messages (user names, join/leave announcements) -// - stateQueue: Interruptable messages (mute/deafen/stream state changes) -// This allows rapid state changes to interrupt each other while preserving important announcements interface QueueItem { text: string; userId?: string; @@ -207,8 +201,6 @@ let currentAudio: HTMLAudioElement | null = null; let currentInterruptKey: string | undefined; let currentStop: (() => void) | null = null; -// Pre-cache common state action phrases on plugin start for faster playback -// Uses phonetic spellings for muted/deafened to ensure clear pronunciation across all voices const COMMON_ACTIONS = ["myooted", "un-myooted", "deafind", "un-deafind", "started streaming", "stopped streaming"]; const DEFAULT_VOICE = "en_us_001"; let preCacheInitialized = false; @@ -217,26 +209,21 @@ async function preCacheCommonActions() { if (preCacheInitialized) return; preCacheInitialized = true; - // Initial delay before starting pre-cache to not impact startup await new Promise(r => setTimeout(r, 3000)); - // Pre-fetch common action words in DEFAULT voice (universal for all users) for (const action of COMMON_ACTIONS) { const cacheKey = `${DEFAULT_VOICE}_${action}`; - // 1. Check in-memory cache first if (ttsCache.has(cacheKey)) continue; - // 2. Check persistent IndexedDB cache - load into memory if found try { const cachedBlob = await getCachedVoiceFromDB(cacheKey); if (cachedBlob) { ttsCache.set(cacheKey, URL.createObjectURL(cachedBlob)); continue; } - } catch { /* ignore */ } + } catch { } - // 3. Fetch from API and store in both memory and persistent DB try { const response = await fetch(`${API_BASE}/api/generate`, { method: "POST", @@ -252,18 +239,14 @@ async function preCacheCommonActions() { } const blob = new Blob([binaryData], { type: "audio/mpeg" }); - // Store in memory cache ttsCache.set(cacheKey, URL.createObjectURL(blob)); - // Persist to IndexedDB for future sessions await setCachedVoiceInDB(cacheKey, blob); } } catch (e) { recordApiError(e); - /* ignore pre-cache failures */ } - // Space out API requests to avoid rate limiting (2 seconds between each) await new Promise(r => setTimeout(r, 2000)); } } @@ -281,8 +264,6 @@ function interruptPlayback(key: string) { async function processQueue() { if (isSpeaking) return; - // Optimization: if main queue has intro and state queue has action with no other items, - // and they're for the same user, combine them into single API call if (mainQueue.length === 1 && stateQueue.length === 1 && mainQueue[0].userId === stateQueue[0].userId && !mainQueue[0].interruptKey) { @@ -291,8 +272,8 @@ async function processQueue() { const combined: QueueItem = { text: `${intro.text} ${action.text}`, userId: intro.userId, - interruptKey: action.interruptKey, // Keep action's interrupt key so rapid switches can interrupt - useDefaultVoice: false, // Combined uses user voice for the intro part + interruptKey: action.interruptKey, + useDefaultVoice: false, }; mainQueue.push(combined); } @@ -308,7 +289,6 @@ async function processQueue() { console.error("TTS Error:", e); } - // Delay between messages - longer for state changes to allow interruption const delay = item.interruptKey ? 800 : 500; setTimeout(() => { isSpeaking = false; @@ -320,10 +300,8 @@ async function processQueue() { function queueSpeak(text: string, userId?: string, interruptKey?: string, queue: "main" | "state" = "main", useDefaultVoice?: boolean) { if (text.trim().length === 0) return; - // Anti-spam: cap queue size const targetQueue = queue === "state" ? stateQueue : mainQueue; if (targetQueue.length >= 5) { - // If queue is full, drop new to stop the spam wave return; } @@ -352,13 +330,12 @@ async function speak(text: string, userId?: string, interruptKey?: string, useDe resolve(); }; - // Helper to play audio and resolve promise when done const playAudio = (url: string) => { const audio = new Audio(url); audio.volume = settings.store.volume; audio.playbackRate = settings.store.rate; audio.onended = onEnd; - audio.onerror = onEnd; // Resolve even on error to unblock queue + audio.onerror = onEnd; currentAudio = audio; currentInterruptKey = interruptKey; currentStop = onEnd; @@ -366,7 +343,6 @@ async function speak(text: string, userId?: string, interruptKey?: string, useDe }; void (async () => { - // Use default voice for universal actions, otherwise user-specific voice const voice = useDefaultVoice ? DEFAULT_VOICE : getVoiceForUser(userId, { @@ -375,16 +351,13 @@ async function speak(text: string, userId?: string, interruptKey?: string, useDe defaultVoice: DEFAULT_VOICE, }); - // Create a unique cache key using the voice and text. const cacheKey = `${voice}_${text}`; - // 1. Check the in-memory cache (fast check) if (ttsCache.has(cacheKey)) { playAudio(ttsCache.get(cacheKey)!); return; } - // 2. Check the persistent IndexedDB cache. try { const cachedBlob = await getCachedVoiceFromDB(cacheKey); if (cachedBlob) { @@ -397,7 +370,6 @@ async function speak(text: string, userId?: string, interruptKey?: string, useDe console.error("Error accessing IndexedDB:", err); } - // 3. Fetch from API try { const response = await fetch(`${API_BASE}/api/generate`, { method: "POST", @@ -415,7 +387,7 @@ async function speak(text: string, userId?: string, interruptKey?: string, useDe if (!response.ok) { console.error(`TTS failed: ${response.status}`); - resolve(); // Skip this message + resolve(); return; } @@ -441,9 +413,6 @@ async function speak(text: string, userId?: string, interruptKey?: string, useDe }); } -// For every user, channelId and oldChannelId will differ when moving channel. -// Only for the local user, channelId and oldChannelId will be the same when moving channel, -// for some ungodly reason let myLastChannelId: string | undefined; type NormalizedVoiceState = { @@ -452,6 +421,7 @@ type NormalizedVoiceState = { streaming: boolean; }; +const lastJoinLeaveTimestamp = new Map(); let trackedChannelId: string | null = null; let baselineReady = false; let baselineUpdateInProgress = false; @@ -464,7 +434,7 @@ function normalizeState(state: VoiceState): NormalizedVoiceState { return { muted: !!(state.mute || state.selfMute), deaf: !!(state.deaf || state.selfDeaf), - streaming: !!((state as any).selfStream || (state as any).stream), + streaming: !!(state.selfStream || state.stream), }; } @@ -480,16 +450,6 @@ function shouldAnnounce(key: string): boolean { type StateActionType = "stream" | "mute" | "deaf"; -/** - * Builds the intro and action segments for state change announcements. - * - * Format: "{USERNAME}" + "{ACTION}" - * Examples: - * - "JohnDoe" + "myooted" / "un-myooted" / "deafind" / "un-deafind" - * - "JohnDoe" + "started streaming" / "stopped streaming" - * - * Uses phonetic spellings for muted/deafened to ensure clear pronunciation. - */ function buildStateSegments( type: StateActionType, isOn: boolean, @@ -505,7 +465,6 @@ function buildStateSegments( }; } - // Mute and deaf - no verb, just name + phonetic action const action = type === "mute" ? (isOn ? "myooted" : "un-myooted") : (isOn ? "deafind" : "un-deafind"); @@ -516,14 +475,6 @@ function buildStateSegments( }; } -/** - * Queues a state change announcement as two parts: - * - Intro (username) goes to mainQueue - won't be interrupted - * - Action (state) goes to stateQueue - can be interrupted by rapid state changes - * - * Uses a shared interrupt key per user so rapid toggles (mute->unmute->mute) - * only announce the final state. - */ function queueStateSplitAnnouncement( userId: string, intro: string, @@ -536,7 +487,6 @@ function queueStateSplitAnnouncement( lastIntroSpokenAt.set(userId, Date.now()); } - // Handle interrupt key for action - remove any pending actions for this user const interruptKey = `${userId}:state:action`; for (let i = stateQueue.length - 1; i >= 0; i--) { if (stateQueue[i].interruptKey === interruptKey) { @@ -545,7 +495,6 @@ function queueStateSplitAnnouncement( } interruptPlayback(interruptKey); - // Add both items to queues BEFORE processing if (shouldQueueIntro) { mainQueue.push({ text: safeIntro, userId, interruptKey: undefined, useDefaultVoice: false }); } @@ -609,7 +558,7 @@ function playSample(tempSettings: any, type: string) { settingsobj[type + "Message"], currentUser.username, "general", - (currentUser as any).globalName ?? currentUser.username, + currentUser.globalName ?? currentUser.username, (myGuildId ? GuildMemberStore.getNick(myGuildId, currentUser.id) : null) ?? currentUser.username, settingsobj.latinOnly ) @@ -715,6 +664,18 @@ const settings = definePluginSettings({ description: "Comma-separated user IDs for whitelist/blacklist. Right-click users to add/remove.", default: "", }, + ignoredUsers: { + type: OptionType.STRING, + description: "Comma-separated user IDs to completely ignore (no join/leave/move/state announcements). Right-click users to add/remove.", + default: "", + }, + joinLeaveTimeout: { + type: OptionType.SLIDER, + description: "Per-user cooldown for join/leave/move announcements (seconds). Prevents spam from rapid rejoiners.", + default: 0, + markers: [0, 5, 10, 15, 30, 60], + stickToMarkers: false, + }, troubleshooting: { type: OptionType.COMPONENT, component: () => , @@ -725,7 +686,6 @@ interface UserContextProps { user: User; } -// Voice selection modal component function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) { const DEFAULT_VALUE = "__default__"; @@ -744,10 +704,8 @@ function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user: setCurrentValue(map.get(user.id) ?? DEFAULT_VALUE); }, [user.id, settings.store.userVoiceMap]); - // Get the display name for preview - const displayName = (user as any).globalName ?? user.username; + const displayName = user.globalName ?? user.username; - // Preview function that speaks the user's name in the selected voice const previewVoice = async (text: string) => { setBusy(true); const voice = currentValue === DEFAULT_VALUE @@ -801,7 +759,6 @@ function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user: onChange={v => setCurrentValue(v as any)} /> - {/* Preview buttons */} Preview how this voice sounds with their name: @@ -869,7 +826,6 @@ function openVoiceSelectModal(user: User) { )); } -// Context menu to assign voice to user const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => { if (!user) return; @@ -879,6 +835,9 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U ? VOICE_OPTIONS.find(v => v.value === currentVoice)?.label ?? currentVoice : "Default"; + const ignoredSet = parseUserIdList(settings.store.ignoredUsers); + const isIgnored = ignoredSet.has(user.id); + const filterMode = settings.store.stateChangeFilterMode ?? "off"; const filterSet = parseStateChangeFilterList(settings.store.stateChangeFilterList); const inFilter = filterSet.has(user.id); @@ -910,6 +869,18 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U label={`Voice: ${voiceLabel}`} action={() => openVoiceSelectModal(user)} /> + { + if (isIgnored) { + settings.store.ignoredUsers = removeUserFromList(settings.store.ignoredUsers, user.id); + } else { + settings.store.ignoredUsers = addUserToList(settings.store.ignoredUsers, user.id); + } + }} + /> 1 && voiceStates.some(s => !("oldChannelId" in (s as any))); if (isBatchUpdate && myChanId) { - // Guild-open / bulk refresh: update baseline silently to avoid spam. await refreshBaseline(myChanId); return; } + const ignoredUsers = parseUserIdList(settings.store.ignoredUsers); + for (const state of voiceStates) { const { userId, channelId } = state; let { oldChannelId } = state; @@ -991,7 +962,6 @@ export default definePlugin({ const affectsMyChannel = channelId === myChanId || oldChannelId === myChanId; if (!isMe && !affectsMyChannel) continue; - // Keep snapshots in sync for join/leave/move without announcing state changes. if (myChanId) { if (oldChannelId !== myChanId && channelId === myChanId) { voiceStateSnapshot.set(userId, normalizeState(state)); @@ -1000,16 +970,34 @@ export default definePlugin({ } } - // Join/leave/move announcements (existing behavior) + const isIgnored = ignoredUsers.has(userId); + const shouldSkipAnnouncement = isIgnored || (isMe && settings.store.ignoreSelf); + const [type, id] = getTypeAndChannelId({ ...state, oldChannelId }, isMe); if (type) { - const template = settings.store[type + "Message"]; - const u = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username; - const displayName = u && ((UserStore.getUser(userId) as any).globalName ?? u); - const nickname = u && ((myGuildId ? GuildMemberStore.getNick(myGuildId, userId) : null) ?? displayName); - const channel = ChannelStore.getChannel(id)?.name ?? "channel"; + if (!shouldSkipAnnouncement) { + const timeout = settings.store.joinLeaveTimeout ?? 0; + let throttled = false; + if (!isMe && timeout > 0) { + const now = Date.now(); + const last = lastJoinLeaveTimestamp.get(userId) ?? 0; + if (now - last < timeout * 1000) { + throttled = true; + } else { + lastJoinLeaveTimestamp.set(userId, now); + } + } - queueSpeak(formatText(template, u, channel, displayName, nickname, settings.store.latinOnly), userId); + if (!throttled) { + const template = settings.store[type + "Message"]; + const u = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username; + const displayName = u && (UserStore.getUser(userId).globalName ?? u); + const nickname = u && ((myGuildId ? GuildMemberStore.getNick(myGuildId, userId) : null) ?? displayName); + const channel = ChannelStore.getChannel(id)?.name ?? "channel"; + + queueSpeak(formatText(template, u, channel, displayName, nickname, settings.store.latinOnly), userId); + } + } if (isMe && (type === "join" || type === "move") && id) { await refreshBaseline(id); @@ -1018,7 +1006,6 @@ export default definePlugin({ continue; } - // State-change announcements (mute/deafen/stream), only when user is in our current VC. if (channelId !== myChanId) continue; if (!baselineReady) continue; @@ -1027,10 +1014,11 @@ export default definePlugin({ voiceStateSnapshot.set(userId, next); if (!prev) continue; + if (shouldSkipAnnouncement) continue; if (!allowStateChange(userId, isMe)) continue; const userObj = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username; - const displayName = userObj && ((UserStore.getUser(userId) as any).globalName ?? userObj); + const displayName = userObj && (UserStore.getUser(userId).globalName ?? userObj); const nickname = userObj && ((myGuildId ? GuildMemberStore.getNick(myGuildId, userId) : null) ?? displayName); const preferredName = nickname || displayName || userObj || (isMe ? "You" : "Someone"); @@ -1042,8 +1030,6 @@ export default definePlugin({ } } - // Deafen takes priority over mute (you're always muted when deafened) - // Only announce mute if deaf state didn't change if (!isMe && settings.store.announceOthersDeafen && prev.deaf !== next.deaf) { const key = `${userId}:deaf`; if (shouldAnnounce(key)) { @@ -1077,7 +1063,6 @@ export default definePlugin({ return ( <> - {/* Author note - pinned at top */}
    {authorAvatar && (
    - {/* Preview sounds section */}
    Preview Sounds {busy && "(playing...)"} diff --git a/src/equicordplugins/vcNarratorCustom/util.ts b/src/equicordplugins/vcNarratorCustom/util.ts index e6fe069b4..5d27f5e3e 100644 --- a/src/equicordplugins/vcNarratorCustom/util.ts +++ b/src/equicordplugins/vcNarratorCustom/util.ts @@ -49,36 +49,15 @@ export const VOICE_OPTIONS = [ { label: "Latin American: Spanish MX Male (es_mx_002)", value: "es_mx_002" }, ]; -/** - * In-memory TTS audio URL cache (`cacheKey` -> `blob:` object URL). - * - * - Cleared on restart. - * - Persistent caching lives in IndexedDB. - * - Call `clearTtsCache()` to clear both memory + persistent caches. - */ export const ttsCache = new Map(); -/** - * Maximum size for the persistent TTS cache stored in IndexedDB. - * - * Implemented as a rolling LRU: when writing new entries, older entries are evicted - * (by least-recent `lastAccess`) until we're back under the limit. - */ export const PERSISTENT_TTS_CACHE_MAX_BYTES = 100 * 1024 * 1024; -/** - * Parses the per-user voice map from settings. - * - * Supported formats: - * - Preferred: `"userId:voiceId,userId2:voiceId2"` (comma-separated) - * - Legacy: `"userId,voiceId\nuserId2,voiceId2"` (newline-separated) - */ export function parseUserVoiceMap(input?: string): Map { const map = new Map(); const trimmed = input?.trim(); if (!trimmed) return map; - // Preferred format (comma-separated pairs) if (trimmed.includes(":") || trimmed.includes("=")) { for (const entry of trimmed.split(",").map(s => s.trim()).filter(Boolean)) { const [userId, voiceId] = entry.split(/[:=]/).map(s => s.trim()); @@ -87,7 +66,6 @@ export function parseUserVoiceMap(input?: string): Map { return map; } - // Legacy format (newline-separated "userId,voiceId") for (const line of trimmed.split(/\n+/)) { const [userId, voiceId] = line.split(",").map(s => s.trim()); if (userId && voiceId) map.set(userId, voiceId); @@ -96,18 +74,12 @@ export function parseUserVoiceMap(input?: string): Map { return map; } -/** - * Serializes a per-user voice map to the preferred storage format. - */ export function serializeUserVoiceMap(map: Map): string { return Array.from(map.entries()) .map(([userId, voiceId]) => `${userId}:${voiceId}`) .join(","); } -/** - * Gets the voice id for a given user id, falling back to the configured/default voice. - */ export function getVoiceForUser( userId: string | undefined, options: { userVoiceMap?: string; customVoice?: string; defaultVoice?: string; } @@ -118,27 +90,46 @@ export function getVoiceForUser( return map.get(userId) ?? defaultVoice; } -/** - * Adds or updates a user voice mapping and returns the updated serialized map. - */ export function upsertUserVoiceMap(userVoiceMap: string | undefined, userId: string, voiceId: string): string { const map = parseUserVoiceMap(userVoiceMap ?? ""); map.set(userId, voiceId); return serializeUserVoiceMap(map); } -/** - * Removes a user voice mapping and returns the updated serialized map. - */ export function removeUserVoiceFromMap(userVoiceMap: string | undefined, userId: string): string { const map = parseUserVoiceMap(userVoiceMap ?? ""); map.delete(userId); return serializeUserVoiceMap(map); } -/** - * Parses the state-change filter list from a comma-separated string. - */ +export function parseUserIdList(input?: string): Set { + const set = new Set(); + const trimmed = input?.trim(); + if (!trimmed) return set; + + for (const entry of trimmed.split(",").map(s => s.trim()).filter(Boolean)) { + set.add(entry); + } + + return set; +} + +export function serializeUserIdList(list: Set): string { + return Array.from(list).join(","); +} + +export function addUserToList(input: string | undefined, userId: string): string { + const set = parseUserIdList(input); + set.add(userId); + return serializeUserIdList(set); +} + +export function removeUserFromList(input: string | undefined, userId: string): string { + const set = parseUserIdList(input); + set.delete(userId); + return serializeUserIdList(set); +} + export function parseStateChangeFilterList(input?: string): Set { const set = new Set(); const trimmed = input?.trim(); @@ -151,34 +142,22 @@ export function parseStateChangeFilterList(input?: string): Set { return set; } -/** - * Serializes the state-change filter list into a comma-separated string. - */ export function serializeStateChangeFilterList(list: Set): string { return Array.from(list).join(","); } -/** - * Adds a user id to the state-change filter list and returns the updated string. - */ export function addUserToStateChangeFilterList(input: string | undefined, userId: string): string { const set = parseStateChangeFilterList(input); set.add(userId); return serializeStateChangeFilterList(set); } -/** - * Removes a user id from the state-change filter list and returns the updated string. - */ export function removeUserFromStateChangeFilterList(input: string | undefined, userId: string): string { const set = parseStateChangeFilterList(input); set.delete(userId); return serializeStateChangeFilterList(set); } -/** - * Sanitizes a string for TTS usage (length cap + character allow list). - */ export function clean(str: string, latinOnly?: boolean) { const replacer = latinOnly ? /[^\p{Script=Latin}\p{Number}\p{Punctuation}\s]/gu @@ -192,9 +171,6 @@ export function clean(str: string, latinOnly?: boolean) { .slice(0, 128); } -/** - * Expands the template string used in announcements. - */ export function formatText( str: string, user: string, @@ -216,41 +192,20 @@ export function formatText( ); } -/** - * IndexedDB database name used for persistent caching. - */ const DB_NAME = "VcNarratorDB"; -/** - * IndexedDB schema version. - * - * v1: `voices` store only - * v2: adds `voices_meta` for rolling size limits / LRU eviction - */ const DB_VERSION = 2; -/** - * Object store holding cached audio blobs (`cacheKey` -> Blob). - */ const VOICES_STORE = "voices"; -/** - * Object store holding metadata (`cacheKey` -> { size, createdAt, lastAccess }). - */ const META_STORE = "voices_meta"; -/** - * Metadata for an entry in the persistent cache. - */ type VoiceMeta = { size: number; createdAt: number; lastAccess: number; }; -/** - * Opens (or creates) the IndexedDB database used for persistent TTS caching. - */ function openDB(): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); @@ -265,12 +220,6 @@ function openDB(): Promise { meta.createIndex("by_lastAccess", "lastAccess"); } - /** - * Migration: older DBs stored only Blobs in `voices` without metadata. - * - * On upgrade to v2, backfill `voices_meta` so size limits and LRU eviction - * can work immediately. - */ if (upgradeTx && (event?.oldVersion ?? 0) < 2) { const now = Date.now(); const voices = upgradeTx.objectStore(VOICES_STORE); @@ -297,9 +246,6 @@ function openDB(): Promise { }); } -/** - * Returns the persistent cache size + entry count (based on IndexedDB metadata). - */ export async function getPersistentTtsCacheStats(): Promise<{ bytes: number; entries: number; }> { const db = await openDB(); return new Promise((resolve, reject) => { @@ -325,14 +271,11 @@ export async function getPersistentTtsCacheStats(): Promise<{ bytes: number; ent }); } -/** - * Clears both the in-memory URL cache and the persistent IndexedDB cache. - */ export async function clearTtsCache(): Promise { for (const url of ttsCache.values()) { try { URL.revokeObjectURL(url); - } catch { /* ignore */ } + } catch { } } ttsCache.clear(); @@ -346,9 +289,6 @@ export async function clearTtsCache(): Promise { }); } -/** - * Enforces the persistent cache size limit by removing least-recently-used entries. - */ async function trimPersistentCacheToMaxBytes(maxBytes = PERSISTENT_TTS_CACHE_MAX_BYTES): Promise { const db = await openDB(); return new Promise((resolve, reject) => { @@ -376,7 +316,7 @@ async function trimPersistentCacheToMaxBytes(maxBytes = PERSISTENT_TTS_CACHE_MAX if (inMemoryUrl) { try { URL.revokeObjectURL(inMemoryUrl); - } catch { /* ignore */ } + } catch { } ttsCache.delete(cacheKey); } @@ -407,9 +347,6 @@ async function trimPersistentCacheToMaxBytes(maxBytes = PERSISTENT_TTS_CACHE_MAX }); } -/** - * Retrieves a cached TTS audio blob from IndexedDB and updates its LRU `lastAccess`. - */ export async function getCachedVoiceFromDB(cacheKey: string): Promise { const db = await openDB(); return new Promise((resolve, reject) => { @@ -439,9 +376,6 @@ export async function getCachedVoiceFromDB(cacheKey: string): Promise { const db = await openDB(); await new Promise((resolve, reject) => { From b140471e20c8bcac162fb4f8d28160caddf992f5 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:54:40 -0400 Subject: [PATCH 12/13] Fix Lint --- src/debug/loadLazyChunks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index a1accee03..74854e882 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -202,7 +202,7 @@ export async function loadLazyChunks() { return !(validChunks.has(id) || invalidChunks.has(id)); }); - await promiseAllSettledBatched(chunksLeft.map(id => queue(async () => { + await promiseAllSettledBatched(chunksLeft.map(id => async () => queue(async () => { const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) .then(t => /importScripts\(|self\.postMessage/.test(t)); From bf7303208c744752c19b44dbb07e355206e5ba52 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:56:23 -0400 Subject: [PATCH 13/13] Revert Prism LazyChunks --- src/debug/loadLazyChunks.ts | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index 74854e882..44e483faf 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -30,18 +30,6 @@ function getWebpackChunkMap() { return v; } -async function promiseAllSettledBatched(tasks: (() => Promise)[], batchSize = 8): Promise { - const results: T[] = []; - for (let i = 0; i < tasks.length; i += batchSize) { - const batch = tasks.slice(i, i + batchSize); - const settled = await Promise.allSettled(batch.map(t => t())); - for (const r of settled) { - if (r.status === "fulfilled") results.push(r.value); - } - } - return results; -} - export async function loadLazyChunks() { const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); const queue = pLimit(50); @@ -73,7 +61,7 @@ export async function loadLazyChunks() { const shouldForceDefer = false; - await promiseAllSettledBatched(Array.from(lazyChunks).map(([, rawChunkIds, entryPoint]) => async () => { + await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { const chunkIds = rawChunkIds ?.matchAll(Webpack.ChunkIdsRegex) .map(m => { @@ -124,9 +112,9 @@ export async function loadLazyChunks() { })); // Loads all found valid chunk groups - await promiseAllSettledBatched( + await Promise.all( Array.from(validChunkGroups) - .map(([chunkIds]) => () => + .map(([chunkIds]) => Promise.all(chunkIds.map(id => wreq.e(id))) ) ); @@ -202,7 +190,7 @@ export async function loadLazyChunks() { return !(validChunks.has(id) || invalidChunks.has(id)); }); - await promiseAllSettledBatched(chunksLeft.map(id => async () => queue(async () => { + await Promise.all(chunksLeft.map(async id => queue(async () => { const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) .then(r => r.text()) .then(t => /importScripts\(|self\.postMessage/.test(t));