From 09d58d4747462482a4f3e4cf636c8f74b32b4c8b Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:44:02 -0500 Subject: [PATCH 001/291] t --- crowdin.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..c791867bb --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /i18n/en/ + translation: /i18n/%locale_with_underscore%/%original_file_name% From 8da71948e2c5999f53d2bc3dc1feba9df0b757f3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:45:36 -0500 Subject: [PATCH 002/291] remove mistake --- crowdin.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index c791867bb..000000000 --- a/crowdin.yml +++ /dev/null @@ -1,3 +0,0 @@ -files: - - source: /i18n/en/ - translation: /i18n/%locale_with_underscore%/%original_file_name% From dd19430300daca223781cb4e8130e9a14ca2d79a Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:29:52 -0800 Subject: [PATCH 003/291] Questify: Fix patch. (#525) --- 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 0d6216c61..d50925220 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1251,7 +1251,7 @@ export default definePlugin({ }, { // Add the trigger to the memo for rerendering the progress label. - match: /(\i.intl.string\(\i.\i#{intl::QUESTS_SEE_CODE}\)}\)}},\[)/, + match: /(\)}}},\[)/, replace: "$1questRerenderTrigger," }, { From 38e89b9789f09f32d3969f0f73748b2e275588cc Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:32:11 -0800 Subject: [PATCH 004/291] SMYN: Merge GlobalNicknames. (#524) * Merge plugins. * Lint. --- src/equicordplugins/globalNicknames/index.tsx | 204 ------------- src/plugins/showMeYourName/index.tsx | 269 ++++++++++++------ src/plugins/showMeYourName/style.css | 10 + 3 files changed, 193 insertions(+), 290 deletions(-) delete mode 100644 src/equicordplugins/globalNicknames/index.tsx diff --git a/src/equicordplugins/globalNicknames/index.tsx b/src/equicordplugins/globalNicknames/index.tsx deleted file mode 100644 index e0c71c174..000000000 --- a/src/equicordplugins/globalNicknames/index.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { DataStore } from "@api/index"; -import { definePluginSettings } from "@api/Settings"; -import { Button } from "@components/Button"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Heading } from "@components/Heading"; -import { EquicordDevs } from "@utils/constants"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; -import definePlugin, { OptionType } from "@utils/types"; -import { Message, User } from "@vencord/discord-types"; -import { findComponentByCodeLazy } from "@webpack"; -import { Menu, React, TextInput, UserStore } from "@webpack/common"; - -const UserBoxIcon = findComponentByCodeLazy("0-3-3H5Zm10 6a3"); - -type NicknamesData = Record; - -let nicknames: NicknamesData = {}; - -const settings = definePluginSettings({ - enableMessages: { - type: OptionType.BOOLEAN, - description: "Show global nicknames in chat messages.", - default: true, - restartNeeded: true - }, - enableMemberList: { - type: OptionType.BOOLEAN, - description: "Show global nicknames in the member list.", - default: true, - restartNeeded: true - }, - enableVoiceChannels: { - type: OptionType.BOOLEAN, - description: "Show global nicknames in voice channels.", - default: true, - restartNeeded: true - }, - enableTypingIndicator: { - type: OptionType.BOOLEAN, - description: "Show global nicknames in the typing indicator.", - default: true, - restartNeeded: true - } -}); - -function NicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) { - const [value, setValue] = React.useState(nicknames[user.id] ?? ""); - - return ( - - - - Change Nickname - - - - -
- - Nickname - - -
-
- - - - -
- ); -} - -const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => { - if (!user) return; - - const currentUser = UserStore.getCurrentUser(); - if (!currentUser || user.id === currentUser.id) return; - - children.push( - , - nicknames[user.id] ? ( - { - delete nicknames[user.id]; - await DataStore.set("GlobalNicknames", nicknames); - }} - /> - ) : ( - openModal(props => ( - - - - ))} - /> - ) - ); -}; - -export default definePlugin({ - name: "GlobalNicknames", - description: "Set custom nicknames for any user globally.", - authors: [EquicordDevs.Prism], - dependencies: ["ContextMenuAPI"], - settings, - - patches: [ - { - find: '="SYSTEM_TAG"', - predicate: () => settings.store.enableMessages, - replacement: { - match: /children:(.+?),"data-text":/, - replace: 'children:$self.patchMessageName(arguments[0])??($1),"data-text":' - } - }, - { - find: "let{colorRoleName:", - predicate: () => settings.store.enableMemberList, - replacement: { - match: /,name:(null!=\i\?\i:\i),colorStrings/, - replace: ",name:$self.patchName($1,arguments[0].user)||($1),colorStrings" - } - }, - { - find: ",connectUserDragSource:", - predicate: () => settings.store.enableVoiceChannels, - replacement: { - match: /nick:(\i),collapsed/, - replace: "nick:$self.patchName($1,arguments[0].user)||$1,collapsed" - } - }, - { - find: "#{intl::THREE_USERS_TYPING}", - predicate: () => settings.store.enableTypingIndicator, - replacement: { - match: /(\i\.\i\.getName\(\i\.guild_id,\i\.id),(\i)\)\)/, - replace: "$1,$self.patchTypingName($2)))" - } - } - ], - - async start() { - const data = await DataStore.get("GlobalNicknames"); - nicknames = data ?? {}; - }, - - patchMessageName(props: { message: Message; }): string | null { - const userId = props?.message?.author?.id; - return userId ? nicknames[userId] ?? null : null; - }, - - patchName(_: string, user: User): string | null { - return nicknames[user.id] ?? null; - }, - - patchTypingName(user: User): User { - const nickname = nicknames[user.id]; - if (!nickname) return user; - return { ...user, globalName: nickname, username: nickname } as User; - }, - - contextMenus: { - "user-context": userContextPatch - } -}); diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 6f6234525..30ce3c6e8 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { DataStore } from "@api/index"; import "./style.css"; import { definePluginSettings, Settings } from "@api/Settings"; @@ -11,15 +12,21 @@ import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { GuildMember, Message, User } from "@vencord/discord-types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; -import { ChannelStore, GuildMemberStore, GuildStore, MessageStore, RelationshipStore, StreamerModeStore, useEffect, UserStore } from "@webpack/common"; +import { ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, RelationshipStore, StreamerModeStore, TextInput, useEffect, UserStore, useState } from "@webpack/common"; import { JSX } from "react"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { Button, ErrorBoundary, Heading, TextButton } from "@components/index"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/index"; const wrapEmojis = findByCodeLazy("lastIndex;return"); const AccessibilityStore = findStoreLazy("AccessibilityStore"); const colorPattern = /^#(?:[\da-f]{3}){1,2}$|^#(?:[\da-f]{4}){1,2}$|(rgb|hsl)a?\((\s*-?\d+%?\s*,){2}(\s*-?\d+%?\s*)\)|(rgb|hsl)a?\((\s*-?\d+%?\s*,){3}\s*(0|(0?\.\d+)|1)\)$/iu; const roleColorPattern = /^role((?:\+|-)\d{0,4})?$/iu; const symbolPattern = /^[\p{S}\p{P}]{1,3}$/iu; -const templatePattern = /(?:\{(?:friend|nick|display|user)(?:,\s*(?:friend|nick|display|user))*\})/iu; +const templatePattern = /(?:\{(?:custom|friend|nick|display|user)(?:,\s*(?:custom|friend|nick|display|user))*\})/iu; + +type CustomNicknameData = Record; +let customNicknames: CustomNicknameData = {}; function adjustHex(color: string, percent: number): string { let hex = color.replace("#", ""); @@ -208,8 +215,8 @@ function validTemplate(value: string) { suffix.length > 3 || (!!prefix && !symbolPattern.test(prefix)) || (!!suffix && !symbolPattern.test(suffix)) || - targetProcessedNames.length > 4 || - targetProcessedNames.some(name => !["friend", "nick", "display", "user"].includes(name.trim())) + targetProcessedNames.length > 5 || + targetProcessedNames.some(name => !["custom", "friend", "nick", "display", "user"].includes(name.trim())) ); }); @@ -218,7 +225,13 @@ function validTemplate(value: string) { } } -function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolean, discriminators: boolean): [string | null, string | null, string | null, string | null] { +function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolean, discriminators: boolean): { + username: string | null; + display: string | null; + nick: string | null; + friend: string | null; + custom: string | null; +} { let discriminator: string | null = null; if (discriminators) { @@ -254,7 +267,13 @@ function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolea ? friendName[0] + "..." : friendName as string; - return [username, display, nick, friend]; + const customName: string | null = customNicknames[author.id] || null; + const custom: string | null = !customName ? null + : StreamerModeStore.enabled && (truncateAllNamesWithStreamerMode || customName.toLowerCase() === author.username.toLowerCase()) + ? customName[0] + "..." + : customName; + + return { username, display, nick, friend, custom }; } interface mentionProps { @@ -351,8 +370,8 @@ function renderUsername( const isReaction = isReactionsTooltip || isReactionsPopout; const isVoice = type === "voiceChannel"; - const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "memberList", "profilePopout", "reactions", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]); - const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config; + const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "memberList", "profilePopout", "reactions", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]); + const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config; const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : false); const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d"; @@ -361,14 +380,16 @@ function renderUsername( const resolvedDisplayNameColor = author ? resolveColor(author, displayNameColor.trim(), "", canUseGradient) : null; const resolvedNicknameColor = author ? resolveColor(author, nicknameColor.trim(), "", canUseGradient) : null; const resolvedFriendNameColor = author ? resolveColor(author, friendNameColor.trim(), "", canUseGradient) : null; + const resolvedCustomNameColor = author ? resolveColor(author, customNameColor.trim(), "", canUseGradient) : null; const affixColor = { color: textMutedValue, "-webkit-text-fill-color": textMutedValue, isolation: "isolate", "white-space": "pre", "font-family": "var(--font-primary)", "letter-spacing": "normal" }; - const [username, display, nick, friend] = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators); + const { username, display, nick, friend, custom } = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators); const names: Record = { user: [username, resolvedUsernameColor], display: [display, resolvedDisplayNameColor], nick: [nick, resolvedNicknameColor], friend: [friend, resolvedFriendNameColor], + custom: [custom, resolvedCustomNameColor] }; const outputs: any[] = []; @@ -421,6 +442,7 @@ function renderUsername( let second = outputs.shift(); let third = outputs.shift(); let fourth = outputs.shift(); + let fifth = outputs.shift(); const firstValueWrapped = hookless ? (first.name || "") @@ -438,10 +460,15 @@ function renderUsername( ((fourth ?? {}).name || "") : wrapEmojis((fourth ?? {}).name || ""); + const fifthValueWrapped = hookless ? + ((fifth ?? {}).name || "") + : wrapEmojis((fifth ?? {}).name || ""); + first.wrapped = firstValueWrapped; second && (second.wrapped = secondValueWrapped); third && (third.wrapped = thirdValueWrapped); fourth && (fourth.wrapped = fourthValueWrapped); + fifth && (fifth.wrapped = fifthValueWrapped); if (isMessage && !messages) { return [null, null, null]; @@ -462,10 +489,14 @@ function renderUsername( return [fallbackText, <>{fallbackText}, fallbackText]; } - const prioritizeUsername = (new Set([first, second, third, fourth].filter(Boolean).map(pos => pos.name.toLowerCase())).size > 1); + const prioritizeUsername = (new Set([first, second, third, fourth, fifth].filter(Boolean).map(pos => pos.name.toLowerCase())).size > 1); if (removeDuplicates) { // Remove duplicates from back to front. Prioritize the earlier name, unless it's the username and there's more than one unique option, then prioritize it. + fifth && fourth && fifth.name.toLowerCase() === fourth.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? fourth = null : fifth = null : null; + fifth && third && fifth.name.toLowerCase() === third.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? third = null : fifth = null : null; + fifth && second && fifth.name.toLowerCase() === second.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? second = null : fifth = null : null; + fifth && first && fifth.name.toLowerCase() === first.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? first = null : fifth = null : null; fourth && third && fourth.name.toLowerCase() === third.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? third = null : fourth = null : null; fourth && second && fourth.name.toLowerCase() === second.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? second = null : fourth = null : null; fourth && first && fourth.name.toLowerCase() === first.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? first = null : fourth = null : null; @@ -474,11 +505,12 @@ function renderUsername( second && first && second.name.toLowerCase() === first.name.toLowerCase() ? second.type === "user" && prioritizeUsername ? first = null : second = null : null; } - const remainingNames = [first, second, third, fourth].filter(Boolean); + const remainingNames = [first, second, third, fourth, fifth].filter(Boolean); first = remainingNames.shift(); second = remainingNames.shift(); third = remainingNames.shift(); fourth = remainingNames.shift(); + fifth = remainingNames.shift(); const useTopRoleStyle = isMention || isReactionsPopout; const topRoleStyle = resolveColor(author, "Role", "", canUseGradient); @@ -500,7 +532,8 @@ function renderUsername( const secondDataText = second && shouldAnimateSecondaryNames ? (second.prefix + second.name + second.suffix) : ""; const thirdDataText = third && shouldAnimateSecondaryNames ? (third.prefix + third.name + third.suffix) : ""; const fourthDataText = fourth && shouldAnimateSecondaryNames ? (fourth.prefix + fourth.name + fourth.suffix) : ""; - const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText].filter(Boolean).join(nameSeparator).trim(); + const fifthDataText = fifth && shouldAnimateSecondaryNames ? (fifth.prefix + fifth.name + fifth.suffix) : ""; + const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText, fifthDataText].filter(Boolean).join(nameSeparator).trim(); // Only mentions and reactions popouts should patch in the gradient glow or else a double glow will appear on messages. const hoveringClass = (isHovering ? " show-me-your-name-gradient-hovered" : ""); @@ -512,10 +545,12 @@ function renderUsername( const secondGroupClasses = "show-me-your-name-name-group show-me-your-name-second-name-group"; const thirdGroupClasses = "show-me-your-name-name-group show-me-your-name-third-name-group"; const fourthGroupClasses = "show-me-your-name-name-group show-me-your-name-fourth-name-group"; + const fifthGroupClasses = "show-me-your-name-name-group show-me-your-name-fifth-name-group"; const firstNameClasses = "show-me-your-name-name show-me-your-name-first-name"; const secondNameClasses = "show-me-your-name-name show-me-your-name-second-name"; const thirdNameClasses = "show-me-your-name-name show-me-your-name-third-name"; const fourthNameClasses = "show-me-your-name-name show-me-your-name-fourth-name"; + const fifthNameClasses = "show-me-your-name-name show-me-your-name-fifth-name"; const prefixClasses = "show-me-your-name-affix show-me-your-name-prefix"; const suffixClasses = "show-me-your-name-affix show-me-your-name-suffix"; @@ -549,85 +584,39 @@ function renderUsername( {first.wrapped} )} - {second && ( + {[ + { name: second, dataText: secondDataText, groupClass: secondGroupClasses, nameClass: secondNameClasses, position: "second" }, + { name: third, dataText: thirdDataText, groupClass: thirdGroupClasses, nameClass: thirdNameClasses, position: "third" }, + { name: fourth, dataText: fourthDataText, groupClass: fourthGroupClasses, nameClass: fourthNameClasses, position: "fourth" }, + { name: fifth, dataText: fifthDataText, groupClass: fifthGroupClasses, nameClass: fifthNameClasses, position: "fifth" } + ].map(({ name, dataText, groupClass, nameClass, position }) => name && ( {nameSeparator} - {second.prefix} + {name.prefix} - {second.wrapped} + {name.wrapped} - {second.suffix} + {name.suffix} - )} - {third && ( - - - {nameSeparator} - {third.prefix} - - {third.wrapped} - - {third.suffix} - - )} - {fourth && ( - - - {nameSeparator} - {fourth.prefix} - - {fourth.wrapped} - - {fourth.suffix} - - )} + ))} ); @@ -688,6 +677,98 @@ function removeHoveringReactionPopout(id: string) { settings.store.triggerNameRerender = !settings.store.triggerNameRerender; } +function CustomNicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) { + const [value, setValue] = useState(customNicknames[user.id] ?? ""); + + return ( + + + + {customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"} + + + + + + {"Set a custom SMYN nickname for this user. Make use of it by specifying {custom} in the SMYN template settings."} + +
+ + SMYN Nickname + + + { + setValue(""); + delete customNicknames[user.id]; + await DataStore.set("SMYNCustomNicknames", customNicknames); + settings.store.triggerNameRerender = !settings.store.triggerNameRerender; + }} + > + Reset SMYN Nickname + +
+
+ + + + +
+ ); +} + +const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => { + if (!user) return; + + const currentUser = UserStore.getCurrentUser(); + if (!currentUser || user.id === currentUser.id) return; + + const group = findGroupChildrenByChildId("user-profile", children); + + !group && children.push(); + (group || children).push( + openModal(props => ( + + + + ))} + /> + ); +}; + const settings = definePluginSettings({ messages: { type: OptionType.BOOLEAN, @@ -747,17 +828,17 @@ const settings = definePluginSettings({ ignoreFonts: { type: OptionType.BOOLEAN, default: false, - description: "For the second, third, and fourth names, use GG SANS regardless of the user's custom font.", + description: "For the non-primary names, use Discord's default fonts regardless of the user's custom nitro font.", }, ignoreGradients: { type: OptionType.BOOLEAN, default: true, - description: "For the second, third, and fourth names, if the role has a gradient, ignore it in favor of the value below.", + description: "For the non-primary names, if the role has a gradient, ignore it in favor of the color set below.", }, animateGradients: { type: OptionType.BOOLEAN, default: false, - description: "For the second, third, and fourth names, if the role has a gradient, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.", + description: "For the non-primary names, if the role has a gradient, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.", }, nameSeparator: { type: OptionType.STRING, @@ -766,10 +847,16 @@ const settings = definePluginSettings({ }, includedNames: { type: OptionType.STRING, - description: "The order to display usernames, display names, nicknames, and friend names. Use the following placeholders: {user}, {display}, {nick}, {friend}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {friend, nick, display}. You can have up to three prefixes and three suffixes per name.", - default: "{friend, nick} [{display}] (@{user})", + description: "The order to display usernames, display names, nicknames, friend names, and custom names. Use the following placeholders: {user}, {display}, {nick}, {friend}, {custom}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {custom, friend, nick}. You can have up to three prefixes and three suffixes per name.", + default: "{custom, friend, nick} [{display}] (@{user})", isValid: validTemplate, }, + customNameColor: { + type: OptionType.STRING, + description: "The color to use for the custom name you assigned a user if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")", + default: "Role-25", + isValid: validColor, + }, friendNameColor: { type: OptionType.STRING, description: "The color to use for a friend's nickname if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")", @@ -804,8 +891,9 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ShowMeYourName", - description: "Display any permutation of nicknames, display names, and usernames in chat.", - authors: [Devs.Rini, Devs.TheKodeToad, EquicordDevs.Etorix, Devs.sadan], + description: "Display any permutation of custom nicknames, friend nicknames, server nicknames, display names, and usernames in chat.", + authors: [Devs.Rini, Devs.TheKodeToad, EquicordDevs.Etorix, Devs.sadan, EquicordDevs.Prism], + tags: ["SMYN", "Nicknames", "Custom Nicknames",], isModified: true, settings, @@ -943,6 +1031,15 @@ export default definePlugin({ } ], + async start() { + const data = await DataStore.get("SMYNCustomNicknames"); + customNicknames = data ?? {}; + }, + + contextMenus: { + "user-context": userContextPatch + }, + flux: { RELATIONSHIP_UPDATE(data) { // Allows rerendering when changing friend names. diff --git a/src/plugins/showMeYourName/style.css b/src/plugins/showMeYourName/style.css index 58bd29db7..45cfb18a0 100644 --- a/src/plugins/showMeYourName/style.css +++ b/src/plugins/showMeYourName/style.css @@ -37,4 +37,14 @@ .show-me-your-name-gradient-hovered::after { opacity: .7; transition: opacity .1s ease-in-out +} + +.show-me-your-name-modal-footer-container button { + width: 100%; +} + +.show-me-your-name-reset-button { + margin-top: 8px; + font-size: 14px; + font-weight: 500; } \ No newline at end of file From 526fbb791c9d510e11fb5ad241f5d649766b032b Mon Sep 17 00:00:00 2001 From: 9gvuwu <130901666+9gvuwu@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:34:55 +0100 Subject: [PATCH 005/291] feat: add "Ignore Bots" setting in Boo plugin (#521) * feat: add "Ignore Bots" setting in Boo plugin * t --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/boo/Boo.tsx | 6 +++--- src/equicordplugins/boo/index.tsx | 6 ++++++ src/utils/constants.ts | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/equicordplugins/boo/Boo.tsx b/src/equicordplugins/boo/Boo.tsx index 8e96ae4c1..b195847f9 100644 --- a/src/equicordplugins/boo/Boo.tsx +++ b/src/equicordplugins/boo/Boo.tsx @@ -163,8 +163,8 @@ export function Boo({ channel }: { channel: Channel; }) { return; } - // if exempted, remove from ghost tracking - if (isExempted) { + // if exempted or bot (if setting enabled), remove from ghost tracking + if (isExempted || (settings.store.ignoreBots && lastMessage.author.bot)) { if (countedChannels.has(id)) { countedChannels.delete(id); setBooCount(getBooCount() - 1); @@ -186,7 +186,7 @@ export function Boo({ channel }: { channel: Channel; }) { } }, [state.isCurrentUser, state.isDataProcessed, id, lastMessage?.id]); - if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser || isChannelExempted(id) || isCleared) + if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser || isChannelExempted(id) || isCleared || (settings.store.ignoreBots && lastMessage.author.bot)) return null; if (!settings.store.showDmIcons) return null; diff --git a/src/equicordplugins/boo/index.tsx b/src/equicordplugins/boo/index.tsx index 2abdc1157..788af9592 100644 --- a/src/equicordplugins/boo/index.tsx +++ b/src/equicordplugins/boo/index.tsx @@ -84,6 +84,12 @@ export const settings = definePluginSettings({ description: "Something might happen if you ignore someone for too long...", default: false, restartNeeded: false + }, + ignoreBots: { + type: OptionType.BOOLEAN, + description: "Ignore DMs from bots", + default: true, + restartNeeded: false } }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b5ab319d6..78010ea6c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -632,7 +632,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ alfred: { name: "alfred", id: 1038466644353232967n - } + }, } satisfies Record); export const EquicordDevs = Object.freeze({ @@ -1205,7 +1205,7 @@ export const EquicordDevs = Object.freeze({ name: "neoarz", id: 1015372540937502851n }, - KamiRu:{ + KamiRu: { name: "KamiRu", id: 819191621676695563n }, From 61f6f4c270ab1ef5f5bd62ac84b361b3defad66f Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:00:01 -0500 Subject: [PATCH 006/291] EquicordHelper: Remove old patch --- src/equicordplugins/equicordHelper/index.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index 08c367b7e..ab4f5d054 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -14,12 +14,6 @@ import { PluginButtons } from "./pluginButtons"; import { PluginCards } from "./pluginCards"; const settings = definePluginSettings({ - disableCreateDMButton: { - type: OptionType.BOOLEAN, - description: "Disables the create dm button", - restartNeeded: true, - default: false, - }, disableDMContextMenu: { type: OptionType.BOOLEAN, description: "Disables the DM list context menu in favor of the x button", @@ -59,15 +53,6 @@ export default definePlugin({ } ] }, - // Disable Giant Create DM Button - { - find: ".createDMButtonContainer,", - predicate: () => settings.store.disableCreateDMButton, - replacement: { - match: /"create-dm"\)/, - replace: "$&&&false" - }, - }, // Remove DM Context Menu { find: "#{intl::DM_OPTIONS}", From 722218c120efce29b6457f2d1fdf72c4e87fab4c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:34:55 -0500 Subject: [PATCH 007/291] ShowResourceChannels: Fix Patch --- .../showResourceChannels/index.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/equicordplugins/showResourceChannels/index.ts b/src/equicordplugins/showResourceChannels/index.ts index fbba862ee..d3ca76589 100644 --- a/src/equicordplugins/showResourceChannels/index.ts +++ b/src/equicordplugins/showResourceChannels/index.ts @@ -11,11 +11,15 @@ export default definePlugin({ name: "ShowResourceChannels", description: "shows the channels hidden behind the server resources in the channel list", authors: [EquicordDevs.VillainsRule], - patches: [{ - find: '"should_show_in_recents"', - replacement: [{ - match: /\i\.hideResourceChannels&&/, - replace: "false&&" - }] - }] + patches: [ + { + find: ".GUILD_DIRECTORY:null", + replacement: [ + { + match: /\i\.hideResourceChannels&&/, + replace: "false&&" + } + ] + } + ] }); From 62569ef3bedbee38264c584991870712bcb55094 Mon Sep 17 00:00:00 2001 From: prism Date: Wed, 26 Nov 2025 17:58:42 -0800 Subject: [PATCH 008/291] feat(Alert): use native alert components (#527) --- src/components/Alert.tsx | 57 +++++++++++++++++++ src/components/index.ts | 1 + src/equicordplugins/_misc/styles.css | 11 ---- src/equicordplugins/equicordHelper/index.tsx | 2 - src/equicordplugins/followVoiceUser/index.tsx | 12 ++-- src/equicordplugins/guildTagSettings/index.ts | 2 - .../neverPausePreviews/index.tsx | 12 ++-- src/equicordplugins/platformSpoofer/index.tsx | 12 ++-- .../questCompleter.discordDesktop/index.tsx | 1 - src/equicordplugins/questFocused/index.tsx | 12 ++-- .../universalMention/index.tsx | 6 +- 11 files changed, 81 insertions(+), 47 deletions(-) create mode 100644 src/components/Alert.tsx delete mode 100644 src/equicordplugins/_misc/styles.css diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx new file mode 100644 index 000000000..60c21f448 --- /dev/null +++ b/src/components/Alert.tsx @@ -0,0 +1,57 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { filters, mapMangledModuleLazy } from "@webpack"; +import type { ReactNode } from "react"; + +const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', { + HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO", "ERROR"), + HelpMessage: filters.byCode(".iconDiv") +}); + +export interface AlertProps { + children: ReactNode; + className?: string; +} + +function Warning({ children, className }: AlertProps) { + return ( + + {children} + + ); +} + +function Info({ children, className }: AlertProps) { + return ( + + {children} + + ); +} + +function Error({ children, className }: AlertProps) { + return ( + + {children} + + ); +} + +function Positive({ children, className }: AlertProps) { + return ( + + {children} + + ); +} + +export const Alert = { + Warning, + Info, + Error, + Positive +}; diff --git a/src/components/index.ts b/src/components/index.ts index dd851be5d..a384f977e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +export * from "./Alert"; export * from "./BaseText"; export * from "./Button"; export * from "./Card"; diff --git a/src/equicordplugins/_misc/styles.css b/src/equicordplugins/_misc/styles.css deleted file mode 100644 index 41ce53d89..000000000 --- a/src/equicordplugins/_misc/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -.plugin-warning { - font-size: 16px !important; - background-color: var(--info-warning-background) !important; - color: var(--info-warning-foreground) !important; - border: 1px solid var(--info-warning-foreground) !important; - border-radius: 5px !important; - font-weight: bold; - padding: 6px 10px; - text-align: center; - margin-top: 10px; -} diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index ab4f5d054..e03215075 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -4,8 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "@equicordplugins/_misc/styles.css"; - import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; diff --git a/src/equicordplugins/followVoiceUser/index.tsx b/src/equicordplugins/followVoiceUser/index.tsx index 009009cac..13dabbdf4 100644 --- a/src/equicordplugins/followVoiceUser/index.tsx +++ b/src/equicordplugins/followVoiceUser/index.tsx @@ -4,11 +4,9 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "@equicordplugins/_misc/styles.css"; - import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; -import { Paragraph } from "@components/Paragraph"; +import { Alert } from "@components/Alert"; import { EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { Channel, User, VoiceState } from "@vencord/discord-types"; @@ -79,11 +77,11 @@ export default definePlugin({ description: "Follow a friend in voice chat.", authors: [EquicordDevs.TheArmagan], settings, - settingsAboutComponent: () => <> - + settingsAboutComponent: () => ( + This Plugin is used to follow a Friend/Friends into voice chat(s). - - , + + ), flux: { async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { if (!followedUserInfo) return; diff --git a/src/equicordplugins/guildTagSettings/index.ts b/src/equicordplugins/guildTagSettings/index.ts index 6a1345b83..65e171c06 100644 --- a/src/equicordplugins/guildTagSettings/index.ts +++ b/src/equicordplugins/guildTagSettings/index.ts @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -import "@equicordplugins/_misc/styles.css"; - import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import { Devs } from "@utils/constants"; diff --git a/src/equicordplugins/neverPausePreviews/index.tsx b/src/equicordplugins/neverPausePreviews/index.tsx index b1bef3d4a..6758e933e 100644 --- a/src/equicordplugins/neverPausePreviews/index.tsx +++ b/src/equicordplugins/neverPausePreviews/index.tsx @@ -16,9 +16,7 @@ * along with this program. If not, see . */ -import "@equicordplugins/_misc/styles.css"; - -import { Paragraph } from "@components/Paragraph"; +import { Alert } from "@components/Alert"; import { EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -26,11 +24,11 @@ export default definePlugin({ name: "NeverPausePreviews", description: "Prevents in-call/PiP previews (screenshare, streams, etc) from pausing even if the client loses focus", authors: [EquicordDevs.vappstar], - settingsAboutComponent: () => <> - + settingsAboutComponent: () => ( + This plugin will cause discord to use more resources than normal - - , + + ), patches: [ { find: "streamerPaused()", diff --git a/src/equicordplugins/platformSpoofer/index.tsx b/src/equicordplugins/platformSpoofer/index.tsx index d94b12617..5726fac5b 100644 --- a/src/equicordplugins/platformSpoofer/index.tsx +++ b/src/equicordplugins/platformSpoofer/index.tsx @@ -4,10 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "@equicordplugins/_misc/styles.css"; - import { definePluginSettings } from "@api/Settings"; -import { Paragraph } from "@components/Paragraph"; +import { Alert } from "@components/Alert"; import { EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { UserStore } from "@webpack/common"; @@ -51,11 +49,11 @@ export default definePlugin({ name: "PlatformSpoofer", description: "Spoof what platform or device you're on", authors: [EquicordDevs.Drag], - settingsAboutComponent: () => <> - + settingsAboutComponent: () => ( + We can't guarantee this plugin won't get you warned or banned. - - , + + ), settings: settings, patches: [ { diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index f4183f41a..357ad71bf 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import "@equicordplugins/_misc/styles.css"; import "./style.css"; import { showNotification } from "@api/Notifications"; diff --git a/src/equicordplugins/questFocused/index.tsx b/src/equicordplugins/questFocused/index.tsx index c8469c9b9..695a4f023 100644 --- a/src/equicordplugins/questFocused/index.tsx +++ b/src/equicordplugins/questFocused/index.tsx @@ -4,20 +4,18 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "@equicordplugins/_misc/styles.css"; - -import { Paragraph } from "@components/Paragraph"; +import { Alert } from "@components/Alert"; import { EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; export default definePlugin({ name: "QuestFocused", description: "Prevent the quests player from pausing and possibly skip it all together.", - settingsAboutComponent: () => <> - + settingsAboutComponent: () => ( + You might need to spam left mouse button on the video to skip it. - - , + + ), authors: [EquicordDevs.secp192k1], patches: [ // Block pausing diff --git a/src/equicordplugins/universalMention/index.tsx b/src/equicordplugins/universalMention/index.tsx index 9ee127883..4b3c75f20 100644 --- a/src/equicordplugins/universalMention/index.tsx +++ b/src/equicordplugins/universalMention/index.tsx @@ -5,7 +5,7 @@ */ import { definePluginSettings } from "@api/Settings"; -import { Paragraph } from "@components/Paragraph"; +import { Alert } from "@components/Alert"; import { EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { User } from "@vencord/discord-types"; @@ -41,9 +41,9 @@ export default definePlugin({ description: "Mention any user, regardless of channel access.", settings, settingsAboutComponent: () => ( - + Using Global Mention can cause performance issues and show an absurd amount of users in the autocomplete. - + ), patches: [ From b6d8728b64bfb48a8cc837af069a834d3b04f8f0 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:10:49 -0800 Subject: [PATCH 009/291] SMYN: Fix author undefined. (#528) --- src/plugins/showMeYourName/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 30ce3c6e8..9b747cb5b 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -267,7 +267,7 @@ function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolea ? friendName[0] + "..." : friendName as string; - const customName: string | null = customNicknames[author.id] || null; + const customName: string | null = !author?.id ? null : customNicknames[author.id] || null; const custom: string | null = !customName ? null : StreamerModeStore.enabled && (truncateAllNamesWithStreamerMode || customName.toLowerCase() === author.username.toLowerCase()) ? customName[0] + "..." From b48ce272506f876cf0d6ed3e075f6a003b068415 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 02:36:33 -0500 Subject: [PATCH 010/291] GlobalBadges: rework --- src/api/Badges.ts | 15 ++++ .../globalBadges/badgeComponent.tsx | 79 ----------------- .../globalBadges/badgeModal.tsx | 81 ------------------ src/equicordplugins/globalBadges/index.tsx | 84 ++++++++++++++++--- src/equicordplugins/globalBadges/settings.ts | 6 -- src/equicordplugins/globalBadges/styles.css | 58 ------------- src/equicordplugins/globalBadges/types.ts | 16 ---- src/equicordplugins/globalBadges/utils.ts | 47 ----------- .../iRememberYou/components/ui.tsx | 49 +++++------ src/equicordplugins/iRememberYou/styles.css | 54 ++++++------ src/plugins/_api/badges/index.tsx | 2 +- 11 files changed, 136 insertions(+), 355 deletions(-) delete mode 100644 src/equicordplugins/globalBadges/badgeComponent.tsx delete mode 100644 src/equicordplugins/globalBadges/badgeModal.tsx delete mode 100644 src/equicordplugins/globalBadges/types.ts delete mode 100644 src/equicordplugins/globalBadges/utils.ts diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 82b6a63d4..9ab0f5cb2 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -17,9 +17,12 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; +import globalBadges from "@equicordplugins/globalBadges"; import BadgeAPIPlugin from "@plugins/_api/badges"; import { ComponentType, HTMLProps } from "react"; +import { isPluginEnabled } from "./PluginManager"; + export const enum BadgePosition { START, END @@ -99,6 +102,18 @@ export function _getBadges(args: BadgeUserArgs) { const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); const equicordDonorBadges = BadgeAPIPlugin.getEquicordDonorBadges(args.userId); + const GlobalBadges = isPluginEnabled(globalBadges.name) ? globalBadges.getGlobalBadges(args.userId) : false; + + // do globalbadges first so it shows before the contrib badges but after donor badges + if (GlobalBadges) { + badges.unshift( + ...GlobalBadges.map(badge => ({ + ...args, + ...badge, + })) + ); + } + if (donorBadges) { badges.unshift( ...donorBadges.map(badge => ({ diff --git a/src/equicordplugins/globalBadges/badgeComponent.tsx b/src/equicordplugins/globalBadges/badgeComponent.tsx deleted file mode 100644 index 19c8e8e1f..000000000 --- a/src/equicordplugins/globalBadges/badgeComponent.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { React, Tooltip, UserStore } from "@webpack/common"; -import { JSX } from "react"; - -import { openBadgeModal } from "./badgeModal"; -import { settings } from "./settings"; -import { BadgeCache } from "./types"; -import { cl, fetchBadges } from "./utils"; - -interface BadgeModalItem { - name: string; - rawName: string; -} - -export let badgeData; - -export const BadgeComponent = ({ name, img }: { name: string, img: string; }) => { - return ( - - {(tooltipProps: any) => ( - - )} - - ); -}; - -export const GlobalBadges = ({ userId }: { userId: string; }) => { - const [badges, setBadges] = React.useState({}); - React.useEffect(() => setBadges(fetchBadges(userId) ?? {}), [userId]); - - if (!badges) return null; - const globalBadges: JSX.Element[] = []; - const badgeModal: BadgeModalItem[] = []; - - Object.keys(badges).forEach(mod => { - if (!badges[mod] || !Array.isArray(badges[mod]) || badges[mod].length === 0) return; - - badges[mod].forEach(badge => { - if (!badge || !badge.tooltip || !badge.badge) return; - - const prefix = settings.store.showPrefix ? `(${mod})` : ""; - const suffix = settings.store.showSuffix ? `(${mod})` : ""; - const displayName = `${prefix} ${badge.tooltip} ${suffix}`; - - if (mod === "BadgeVault") { - badge.custom = true; - } - - globalBadges.push(); - badgeModal.push({ - name: displayName, - rawName: badge.tooltip, - ...badge - }); - }); - }); - badgeData = badgeModal; - - return ( -
openBadgeModal(UserStore.getUser(userId))} - > - {globalBadges} -
- ); -}; diff --git a/src/equicordplugins/globalBadges/badgeModal.tsx b/src/equicordplugins/globalBadges/badgeModal.tsx deleted file mode 100644 index 45c94b96c..000000000 --- a/src/equicordplugins/globalBadges/badgeModal.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Button } from "@components/Button"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { HeadingTertiary } from "@components/Heading"; -import { Paragraph } from "@components/Paragraph"; -import { openInviteModal } from "@utils/discord"; -import { ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; -import { User } from "@vencord/discord-types"; -import { React, Tooltip } from "@webpack/common"; - -import { badgeData } from "./badgeComponent"; -import { cl, INVITE_LINK } from "./utils"; - -export const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => { - return ( - <> - - {(tooltipProps: any) => ( - - )} - - - ); -}; - -export function openBadgeModal(user: User) { - const badgeDataElements = badgeData.map(badge => ( - - )); - - openModal(modalprops => ( - - - - - - {user.username} - - - - {badgeDataElements.length ? ( - <> - - {badgeDataElements.length} global badge - {badgeDataElements.length > 1 ? "s" : ""}. - -
- {badgeDataElements} -
- - ) : ( - - No global badges. - - )} -
- - - -
-
- )); -} diff --git a/src/equicordplugins/globalBadges/index.tsx b/src/equicordplugins/globalBadges/index.tsx index 9af75cd04..05c7cb7d5 100644 --- a/src/equicordplugins/globalBadges/index.tsx +++ b/src/equicordplugins/globalBadges/index.tsx @@ -6,23 +6,57 @@ import "./styles.css"; -import { addProfileBadge, BadgePosition, ProfileBadge, removeProfileBadge } from "@api/Badges"; +import { BadgePosition, ProfileBadge } from "@api/Badges"; +import { classNameFactory } from "@api/Styles"; import { Button } from "@components/Button"; +import { BadgeContextMenu } from "@plugins/_api/badges"; import { Devs, EquicordDevs } from "@utils/constants"; import { openInviteModal } from "@utils/discord"; import definePlugin from "@utils/types"; -import { React } from "@webpack/common"; +import { ContextMenuApi, React } from "@webpack/common"; -import { GlobalBadges } from "./badgeComponent"; import { settings } from "./settings"; -import { cl, fetchBadges, INVITE_LINK } from "./utils"; -const Badge: ProfileBadge = { - component: b => , - position: BadgePosition.START, - shouldShow: userInfo => !!Object.keys(fetchBadges(userInfo.userId) ?? {}).length, - key: "GlobalBadges" -}; +let GlobalBadges = {}; +let intervalId: any; +const INVITE_LINK = "kwHCJPxp8t"; +const cl = classNameFactory("vc-global-badges-"); + +async function loadBadges() { + const globalBadges = await fetch("https://badges.equicord.org/users", { cache: "no-cache" }) + .then(r => r.json()); + + const filteredUsers: Record = {}; + + for (const key in globalBadges.users) { + filteredUsers[key] = globalBadges.users[key].map(b => { + if (b.url) { + return { tooltip: b.label, badge: b.url }; + } + return b; + }).filter(b => { + const url = b.badge; + // fix this when creations updates it + return url && + url.startsWith("https://") && + !url.startsWith("https://cdn.discordapp.com") && + !url.startsWith("https://images.equicord.org") && + !(url.startsWith("https://badges.vencord.dev/badges") && !url.startsWith("https://badges.vencord.dev/badges/reviewdb")) && + // !(!settings.store.showAero && url.startsWith(":aero_icon:")) && + // !(!settings.store.showVelocity && url.startsWith("Velocity")) && + // !(!settings.store.showVelocity && url.startsWith("Official Velocity")) && + !(!settings.store.showCustom && url.startsWith("https://gb.obamabot.me")) && + !(!settings.store.showNekocord && url.startsWith("https://nekocord.dev")) && + !(!settings.store.showReviewDB && url.startsWith("https://badges.vencord.dev/badges/reviewdb")) && + !(!settings.store.showAliucord && url.startsWith("https://aliucord.com")) && + !(!settings.store.showRa1ncord && url.startsWith("https://codeberg.org/raincord/badges")) && + !(!settings.store.showEnmity && url.startsWith("https://raw.githubusercontent.com/enmity-mod/badges")); + + }); + } + + GlobalBadges = filteredUsers; +} export default definePlugin({ name: "GlobalBadges", @@ -40,6 +74,32 @@ export default definePlugin({ ), - start: () => addProfileBadge(Badge), - stop: () => removeProfileBadge(Badge), + async start() { + await loadBadges(); + clearInterval(intervalId); + intervalId = setInterval(loadBadges, 1000 * 60 * 30); + }, + async stop() { + clearInterval(intervalId); + }, + get GlobalBadges() { + return GlobalBadges; + }, + getGlobalBadges(userId: string) { + return GlobalBadges[userId]?.map(badge => ({ + iconSrc: badge.badge, + description: badge.tooltip, + position: BadgePosition.START, + props: { + style: { + borderRadius: "50%", + transform: "scale(0.9)" + } + }, + onContextMenu(event, badge) { + ContextMenuApi.openContextMenu(event, () => ); + }, + + } satisfies ProfileBadge)); + } }); diff --git a/src/equicordplugins/globalBadges/settings.ts b/src/equicordplugins/globalBadges/settings.ts index cfcbf3d1b..85e0db7f0 100644 --- a/src/equicordplugins/globalBadges/settings.ts +++ b/src/equicordplugins/globalBadges/settings.ts @@ -78,12 +78,6 @@ export const settings = definePluginSettings({ default: true, restartNeeded: false }, - showReplugged: { - type: OptionType.BOOLEAN, - description: "Show Replugged Badges", - default: true, - restartNeeded: false - }, apiUrl: { type: OptionType.STRING, description: "API to use", diff --git a/src/equicordplugins/globalBadges/styles.css b/src/equicordplugins/globalBadges/styles.css index 3b098133d..9e712cf11 100644 --- a/src/equicordplugins/globalBadges/styles.css +++ b/src/equicordplugins/globalBadges/styles.css @@ -1,61 +1,3 @@ -.vc-global-badges-badges { - align-items: center; - display: flex; - flex-wrap: nowrap; -} - -.vc-global-badges-badge { - width: 20px; - height: 20px; - gap: 1px; -} - -.vc-global-badges-modal-name { - text-transform: none; - flex-grow: 0; - background: var(--background-base-lowest); - border-radius: 0 9999px 9999px 0; - padding: 6px 0.8em 6px 0.5em; - font-size: 20px; - height: 20px; - position: relative; - text-wrap: nowrap; -} - -.vc-global-badges-modal-name::before { - content: ""; - display: block; - position: absolute; - height: 100%; - width: 32px; - background: var(--background-base-lowest); - z-index: -1; - left: -32px; - top: 0; - border-top-left-radius: 9999px; - border-bottom-left-radius: 9999px; -} - -.vc-global-badges-modal-avatar { - height: 48px; - width: 48px; - border-radius: 50%; -} - -.vc-global-badges-modal-description { - font-size: 16px; -} - -.vc-global-badges-modal-text { - margin-left: 8px; -} - -.vc-global-badges-modal-badge { - width: 50px; - height: 50px; - margin: 2px; -} - .vc-global-badges-settings-button { width: 100%; height: 50px; diff --git a/src/equicordplugins/globalBadges/types.ts b/src/equicordplugins/globalBadges/types.ts deleted file mode 100644 index fc06b9265..000000000 --- a/src/equicordplugins/globalBadges/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -export type CustomBadge = { - tooltip: string; - badge: string; - custom?: boolean; -}; - -export interface BadgeCache { - badges: { [mod: string]: CustomBadge[]; }; - expires: number; -} diff --git a/src/equicordplugins/globalBadges/utils.ts b/src/equicordplugins/globalBadges/utils.ts deleted file mode 100644 index 49e348146..000000000 --- a/src/equicordplugins/globalBadges/utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { classNameFactory } from "@api/Styles"; - -import { settings } from "./settings"; -import { BadgeCache } from "./types"; - -const cache = new Map(); -const EXPIRES = 1000 * 60 * 15; - -export const cl = classNameFactory("vc-global-badges-"); -export const INVITE_LINK = "kwHCJPxp8t"; - -export const fetchBadges = (id: string): BadgeCache["badges"] | undefined => { - const cachedValue = cache.get(id); - if (!cache.has(id) || (cachedValue && cachedValue.expires < Date.now())) { - const services: string[] = []; - if (settings.store.showNekocord) services.push("nekocord"); - if (settings.store.showReviewDB) services.push("reviewdb"); - if (settings.store.showAero) services.push("aero"); - if (settings.store.showAliucord) services.push("aliucord"); - if (settings.store.showRa1ncord) services.push("ra1ncord"); - if (settings.store.showVelocity) services.push("velocity"); - if (settings.store.showEnmity) services.push("enmity"); - if (settings.store.showReplugged) services.push("replugged"); - if (settings.store.showCustom) services.push("badgevault"); - - if (services.length === 0) { - cache.set(id, { badges: {}, expires: Date.now() + EXPIRES }); - return {}; - } - - fetch(`${settings.store.apiUrl}${id}?seperated=true&capitalize=true&services=${services.join(",")}`) - .then(res => res.json() as Promise<{ status: number; badges: BadgeCache["badges"]; }>) - .then(body => { - cache.set(id, { badges: body.badges, expires: Date.now() + EXPIRES }); - return body.badges; - }) - .catch(() => null); - } else if (cachedValue) { - return cachedValue.badges; - } -}; diff --git a/src/equicordplugins/iRememberYou/components/ui.tsx b/src/equicordplugins/iRememberYou/components/ui.tsx index 27d530b75..0e3cb03d4 100644 --- a/src/equicordplugins/iRememberYou/components/ui.tsx +++ b/src/equicordplugins/iRememberYou/components/ui.tsx @@ -6,7 +6,7 @@ import { classNameFactory } from "@api/Styles"; import { BaseText } from "@components/BaseText"; -import { Flex } from "@components/Flex"; +import { HeadingPrimary } from "@components/Heading"; import { Paragraph } from "@components/Paragraph"; import { openUserProfile } from "@utils/discord"; import { Avatar, Clickable, React, TextInput, Tooltip, } from "@webpack/common"; @@ -44,11 +44,9 @@ function UsersCollectionRows({ usersCollection }: { usersCollection: Data["users .map(({ name, users }) => ( @@ -59,14 +57,14 @@ function UsersCollectionRows({ usersCollection }: { usersCollection: Data["users function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?: boolean; }) { return ( - - - - openUserProfile(user.id)}> - - - - +
+
+ openUserProfile(user.id)}> + + + + +
{props => @@ -74,15 +72,14 @@ function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?: } - - - - - - {user.id} - - - + + + {user.id} + + +
+
+
); } @@ -94,10 +91,10 @@ function SearchElement({ usersCollection }: { usersCollection: Data["usersCollec
{current && ( - +
{list.filter(user => user.tag.includes(current) || user.username.includes(current)) .map(user => )} - +
)}
); @@ -112,9 +109,9 @@ export function DataUI({ plugin, usersCollection }: { plugin: any; usersCollecti - +
- +
); } diff --git a/src/equicordplugins/iRememberYou/styles.css b/src/equicordplugins/iRememberYou/styles.css index 188f4218a..9eb1a6297 100644 --- a/src/equicordplugins/iRememberYou/styles.css +++ b/src/equicordplugins/iRememberYou/styles.css @@ -5,8 +5,15 @@ flex-direction: column; } +.vc-i-remember-you-header-name { + margin-top: 16px; +} + .vc-i-remember-you-header-btns { - gap: 8px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 350px)); + gap: 10px; + margin-top: 8px; } .vc-i-remember-you-header { @@ -29,49 +36,38 @@ .vc-i-remember-you-user-avatar { cursor: pointer; - margin-right: 30px; + margin-right: 0; } .vc-i-remember-you-user-row { - margin: 0; - width: 100%; - flex-wrap: wrap; - align-items: center; + display: flex; + flex-direction: column; + gap: 6px; + padding: 4px; + align-items: flex-start; } .vc-i-remember-you-user-id { - height: min-content; + align-self: flex-start; opacity: 0.75; + height: min-content; } .vc-i-remember-you-user { - width: 500px; - gap: 250px; + display: flex; + flex-direction: row; align-items: center; - overflow-wrap: break-word; - margin-top: 5px; + gap: 8px; + width: 100%; + margin-top: 0; } .vc-i-remember-you-user-username { margin-top: 2px; } -.vc-i-remember-you-buttons { - margin-top: 10px; -} - -.vc-i-remember-you-buttons-save, -.vc-i-remember-you-buttons-cancel { - margin-left: 490px; - margin-top: 5px; - margin-bottom: 10px; -} - -.vc-i-remember-you-buttons-cancel { - margin-left: 12px; -} - -.vc-i-remember-you-buttons-reset { - cursor: pointer; - margin-left: 12px; +.vc-i-remember-you-user-tooltip { + display: flex; + flex-direction: column; + align-items: flex-start; } \ No newline at end of file diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 873bf6705..354eae6d1 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -100,7 +100,7 @@ async function loadAllBadges(noCache = false) { let intervalId: any; -function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) { +export function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) { return ( Date: Thu, 27 Nov 2025 03:50:14 -0500 Subject: [PATCH 011/291] GlobalBadges: Update Filters & Toolbox Action --- src/equicordplugins/globalBadges/index.tsx | 50 ++++------------- src/equicordplugins/globalBadges/utils.ts | 65 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 src/equicordplugins/globalBadges/utils.ts diff --git a/src/equicordplugins/globalBadges/index.tsx b/src/equicordplugins/globalBadges/index.tsx index 05c7cb7d5..e29794dca 100644 --- a/src/equicordplugins/globalBadges/index.tsx +++ b/src/equicordplugins/globalBadges/index.tsx @@ -13,51 +13,15 @@ import { BadgeContextMenu } from "@plugins/_api/badges"; import { Devs, EquicordDevs } from "@utils/constants"; import { openInviteModal } from "@utils/discord"; import definePlugin from "@utils/types"; -import { ContextMenuApi, React } from "@webpack/common"; +import { ContextMenuApi, React, Toasts } from "@webpack/common"; import { settings } from "./settings"; +import { GlobalBadges, loadBadges } from "./utils"; -let GlobalBadges = {}; let intervalId: any; const INVITE_LINK = "kwHCJPxp8t"; const cl = classNameFactory("vc-global-badges-"); -async function loadBadges() { - const globalBadges = await fetch("https://badges.equicord.org/users", { cache: "no-cache" }) - .then(r => r.json()); - - const filteredUsers: Record = {}; - - for (const key in globalBadges.users) { - filteredUsers[key] = globalBadges.users[key].map(b => { - if (b.url) { - return { tooltip: b.label, badge: b.url }; - } - return b; - }).filter(b => { - const url = b.badge; - // fix this when creations updates it - return url && - url.startsWith("https://") && - !url.startsWith("https://cdn.discordapp.com") && - !url.startsWith("https://images.equicord.org") && - !(url.startsWith("https://badges.vencord.dev/badges") && !url.startsWith("https://badges.vencord.dev/badges/reviewdb")) && - // !(!settings.store.showAero && url.startsWith(":aero_icon:")) && - // !(!settings.store.showVelocity && url.startsWith("Velocity")) && - // !(!settings.store.showVelocity && url.startsWith("Official Velocity")) && - !(!settings.store.showCustom && url.startsWith("https://gb.obamabot.me")) && - !(!settings.store.showNekocord && url.startsWith("https://nekocord.dev")) && - !(!settings.store.showReviewDB && url.startsWith("https://badges.vencord.dev/badges/reviewdb")) && - !(!settings.store.showAliucord && url.startsWith("https://aliucord.com")) && - !(!settings.store.showRa1ncord && url.startsWith("https://codeberg.org/raincord/badges")) && - !(!settings.store.showEnmity && url.startsWith("https://raw.githubusercontent.com/enmity-mod/badges")); - - }); - } - - GlobalBadges = filteredUsers; -} - export default definePlugin({ name: "GlobalBadges", description: "Adds global badges from other client mods", @@ -82,6 +46,16 @@ export default definePlugin({ async stop() { clearInterval(intervalId); }, + toolboxActions: { + async "Refetch Global Badges"() { + await loadBadges(); + Toasts.show({ + id: Toasts.genId(), + message: "Successfully refetched global badges!", + type: Toasts.Type.SUCCESS + }); + } + }, get GlobalBadges() { return GlobalBadges; }, diff --git a/src/equicordplugins/globalBadges/utils.ts b/src/equicordplugins/globalBadges/utils.ts new file mode 100644 index 000000000..cb2bef47b --- /dev/null +++ b/src/equicordplugins/globalBadges/utils.ts @@ -0,0 +1,65 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { settings } from "./settings"; + +export let GlobalBadges = {}; +const serviceMap: Record = { + nekocord: "Nekocord", + reviewdb: "ReviewDB", + aero: "Aero", + aliucord: "Aliucord", + ra1ncord: "Ra1ncord", + velocity: "Velocity", + enmity: "Enmity", + badgevault: "BadgeVault", + vencord: "Vencord", + equicord: "Equicord", +}; + +export async function loadBadges() { + const url = settings.store.apiUrl.endsWith("/") ? settings.store.apiUrl + "users" : settings.store.apiUrl + "/users"; + const globalBadges = await fetch(url, { cache: "no-cache" }).then(r => r.json()); + const filteredUsers: Record = {}; + + for (const key in globalBadges.users) { + filteredUsers[key] = globalBadges.users[key].filter(b => { + const { mod } = b; + if (!mod) return false; + + const blockedMods = ["vencord", "equicord"]; + if (blockedMods.includes(mod)) return false; + + const conditionalMods = { + aero: settings.store.showAero, + velocity: settings.store.showVelocity, + badgevault: settings.store.showCustom, + nekocord: settings.store.showNekocord, + reviewdb: settings.store.showReviewDB, + aliucord: settings.store.showAliucord, + ra1ncord: settings.store.showRa1ncord, + enmity: settings.store.showEnmity + }; + + if (mod in conditionalMods && !conditionalMods[mod]) return false; + + return true; + }).map(b => { + const modFormatted = serviceMap[b.mod]; + const prefix = settings.store.showPrefix ? `${modFormatted} - ` : ""; + const suffix = settings.store.showSuffix ? ` - ${modFormatted}` : ""; + const tooltip = prefix + b.tooltip + suffix; + return { + ...b, + tooltip + }; + }); + } + + GlobalBadges = filteredUsers; +} From 275a5a9edf23e0cfd12e062f944b52d3aa838598 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:00:57 -0500 Subject: [PATCH 012/291] GlobalBadges: Add Badge Modal Back --- .../globalBadges/badgeModal.tsx | 80 +++++++++++++++++++ src/equicordplugins/globalBadges/index.tsx | 12 +-- src/equicordplugins/globalBadges/styles.css | 58 ++++++++++++++ src/equicordplugins/globalBadges/utils.ts | 5 ++ 4 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 src/equicordplugins/globalBadges/badgeModal.tsx diff --git a/src/equicordplugins/globalBadges/badgeModal.tsx b/src/equicordplugins/globalBadges/badgeModal.tsx new file mode 100644 index 000000000..df69a751c --- /dev/null +++ b/src/equicordplugins/globalBadges/badgeModal.tsx @@ -0,0 +1,80 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Button } from "@components/Button"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { HeadingTertiary } from "@components/Heading"; +import { Paragraph } from "@components/Paragraph"; +import { openInviteModal } from "@utils/discord"; +import { ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; +import { User } from "@vencord/discord-types"; +import { React, Tooltip } from "@webpack/common"; + +import { cl, GlobalBadges, INVITE_LINK } from "./utils"; + +export const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => { + return ( + <> + + {(tooltipProps: any) => ( + + )} + + + ); +}; + +export function openBadgeModal(user: User) { + const badgeDataElements = GlobalBadges[user.id].map(badge => ( + + )); + + openModal(modalprops => ( + + + + + + {user.username} + + + + {badgeDataElements.length ? ( + <> + + {badgeDataElements.length} global badge + {badgeDataElements.length > 1 ? "s" : ""}. + +
+ {badgeDataElements} +
+ + ) : ( + + No global badges. + + )} +
+ + + +
+
+ )); +} diff --git a/src/equicordplugins/globalBadges/index.tsx b/src/equicordplugins/globalBadges/index.tsx index e29794dca..7509f151c 100644 --- a/src/equicordplugins/globalBadges/index.tsx +++ b/src/equicordplugins/globalBadges/index.tsx @@ -7,20 +7,18 @@ import "./styles.css"; import { BadgePosition, ProfileBadge } from "@api/Badges"; -import { classNameFactory } from "@api/Styles"; import { Button } from "@components/Button"; import { BadgeContextMenu } from "@plugins/_api/badges"; import { Devs, EquicordDevs } from "@utils/constants"; import { openInviteModal } from "@utils/discord"; import definePlugin from "@utils/types"; -import { ContextMenuApi, React, Toasts } from "@webpack/common"; +import { ContextMenuApi, React, Toasts, UserStore } from "@webpack/common"; +import { openBadgeModal } from "./badgeModal"; import { settings } from "./settings"; -import { GlobalBadges, loadBadges } from "./utils"; +import { cl, GlobalBadges, INVITE_LINK, loadBadges } from "./utils"; let intervalId: any; -const INVITE_LINK = "kwHCJPxp8t"; -const cl = classNameFactory("vc-global-badges-"); export default definePlugin({ name: "GlobalBadges", @@ -73,7 +71,9 @@ export default definePlugin({ onContextMenu(event, badge) { ContextMenuApi.openContextMenu(event, () => ); }, - + onClick() { + return openBadgeModal(UserStore.getUser(userId)); + }, } satisfies ProfileBadge)); } }); diff --git a/src/equicordplugins/globalBadges/styles.css b/src/equicordplugins/globalBadges/styles.css index 9e712cf11..3b098133d 100644 --- a/src/equicordplugins/globalBadges/styles.css +++ b/src/equicordplugins/globalBadges/styles.css @@ -1,3 +1,61 @@ +.vc-global-badges-badges { + align-items: center; + display: flex; + flex-wrap: nowrap; +} + +.vc-global-badges-badge { + width: 20px; + height: 20px; + gap: 1px; +} + +.vc-global-badges-modal-name { + text-transform: none; + flex-grow: 0; + background: var(--background-base-lowest); + border-radius: 0 9999px 9999px 0; + padding: 6px 0.8em 6px 0.5em; + font-size: 20px; + height: 20px; + position: relative; + text-wrap: nowrap; +} + +.vc-global-badges-modal-name::before { + content: ""; + display: block; + position: absolute; + height: 100%; + width: 32px; + background: var(--background-base-lowest); + z-index: -1; + left: -32px; + top: 0; + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; +} + +.vc-global-badges-modal-avatar { + height: 48px; + width: 48px; + border-radius: 50%; +} + +.vc-global-badges-modal-description { + font-size: 16px; +} + +.vc-global-badges-modal-text { + margin-left: 8px; +} + +.vc-global-badges-modal-badge { + width: 50px; + height: 50px; + margin: 2px; +} + .vc-global-badges-settings-button { width: 100%; height: 50px; diff --git a/src/equicordplugins/globalBadges/utils.ts b/src/equicordplugins/globalBadges/utils.ts index cb2bef47b..5351fd2fa 100644 --- a/src/equicordplugins/globalBadges/utils.ts +++ b/src/equicordplugins/globalBadges/utils.ts @@ -6,9 +6,13 @@ import "./styles.css"; +import { classNameFactory } from "@api/Styles"; + import { settings } from "./settings"; export let GlobalBadges = {}; +export const INVITE_LINK = "kwHCJPxp8t"; +export const cl = classNameFactory("vc-global-badges-"); const serviceMap: Record = { nekocord: "Nekocord", reviewdb: "ReviewDB", @@ -56,6 +60,7 @@ export async function loadBadges() { const tooltip = prefix + b.tooltip + suffix; return { ...b, + name: b.tooltip, tooltip }; }); From 57677f2701c30cdb48f9671ea9ca495fbb52353f Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:03:44 -0500 Subject: [PATCH 013/291] GlobalBadges: fix mistake --- src/equicordplugins/globalBadges/badgeModal.tsx | 9 +++++---- src/equicordplugins/globalBadges/styles.css | 12 ------------ src/equicordplugins/globalBadges/utils.ts | 4 ++-- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/equicordplugins/globalBadges/badgeModal.tsx b/src/equicordplugins/globalBadges/badgeModal.tsx index df69a751c..e2c501b41 100644 --- a/src/equicordplugins/globalBadges/badgeModal.tsx +++ b/src/equicordplugins/globalBadges/badgeModal.tsx @@ -15,15 +15,16 @@ import { React, Tooltip } from "@webpack/common"; import { cl, GlobalBadges, INVITE_LINK } from "./utils"; -export const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => { +export const BadgeModalComponent = ({ key, tooltip, badge }: { key: string, tooltip: string, badge: string; }) => { return ( <> - + {(tooltipProps: any) => ( )} @@ -33,7 +34,7 @@ export const BadgeModalComponent = ({ name, img }: { name: string, img: string; export function openBadgeModal(user: User) { const badgeDataElements = GlobalBadges[user.id].map(badge => ( - + )); openModal(modalprops => ( diff --git a/src/equicordplugins/globalBadges/styles.css b/src/equicordplugins/globalBadges/styles.css index 3b098133d..07e07532d 100644 --- a/src/equicordplugins/globalBadges/styles.css +++ b/src/equicordplugins/globalBadges/styles.css @@ -1,15 +1,3 @@ -.vc-global-badges-badges { - align-items: center; - display: flex; - flex-wrap: nowrap; -} - -.vc-global-badges-badge { - width: 20px; - height: 20px; - gap: 1px; -} - .vc-global-badges-modal-name { text-transform: none; flex-grow: 0; diff --git a/src/equicordplugins/globalBadges/utils.ts b/src/equicordplugins/globalBadges/utils.ts index 5351fd2fa..8271166ad 100644 --- a/src/equicordplugins/globalBadges/utils.ts +++ b/src/equicordplugins/globalBadges/utils.ts @@ -13,7 +13,7 @@ import { settings } from "./settings"; export let GlobalBadges = {}; export const INVITE_LINK = "kwHCJPxp8t"; export const cl = classNameFactory("vc-global-badges-"); -const serviceMap: Record = { +export const serviceMap: Record = { nekocord: "Nekocord", reviewdb: "ReviewDB", aero: "Aero", @@ -60,7 +60,7 @@ export async function loadBadges() { const tooltip = prefix + b.tooltip + suffix; return { ...b, - name: b.tooltip, + key: b.tooltip, tooltip }; }); From 45707411cf4eb3baacf06ce337a50c81b69b9795 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:46:34 -0500 Subject: [PATCH 014/291] Questify: Stable comp --- 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 d50925220..85af75edb 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1251,7 +1251,7 @@ export default definePlugin({ }, { // Add the trigger to the memo for rerendering the progress label. - match: /(\)}}},\[)/, + match: /(\i\.intl\.string\(\i\.\i#{intl::QUESTS_SEE_CODE}\)\}\)\}\},\[|\)\}\}\},\[)/, replace: "$1questRerenderTrigger," }, { From b0c74a59bb26826994dcb27149992d0a800cf9a8 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:59:00 -0500 Subject: [PATCH 015/291] ChatBarButtons: Fix Icons + Add AudioProcessor Def --- src/api/PluginManager.ts | 19 ++++++++--- src/equicordplugins/customSounds/index.tsx | 5 ++- src/equicordplugins/ingtoninator/index.tsx | 29 ++++++++++++---- .../invisibleChat.desktop/index.tsx | 34 ++++++++++++------- .../messageFetchTimer/index.tsx | 31 +++++++++++------ src/equicordplugins/sekaiStickers/index.tsx | 5 ++- src/equicordplugins/signature/index.tsx | 14 +++++++- src/equicordplugins/talkInReverse/index.tsx | 13 ++++++- src/plugins/silentTyping/index.tsx | 14 ++++++-- src/utils/types.ts | 4 +++ 10 files changed, 126 insertions(+), 42 deletions(-) diff --git a/src/api/PluginManager.ts b/src/api/PluginManager.ts index 2d57a4e59..9acfc94ee 100644 --- a/src/api/PluginManager.ts +++ b/src/api/PluginManager.ts @@ -40,6 +40,7 @@ import Plugins from "~plugins"; export { Plugins as plugins }; import { traceFunction } from "../debug/Tracer"; +import { addAudioProcessor, removeAudioProcessor } from "./AudioPlayer"; const logger = new Logger("PluginManager", "#a6d189"); @@ -187,7 +188,9 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: const { name, commands, contextMenus, managedStyle, userProfileBadges, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, - renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, renderNicknameIcon + renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, + // Custom + renderNicknameIcon, audioProcessor } = p; if (p.start) { @@ -241,13 +244,16 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: // @ts-expect-error: legacy code doesn't have icon else if (renderChatBarButton) addChatBarButton(name, renderChatBarButton); if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator); - if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon); if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration); if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory); if (messagePopoverButton) addMessagePopoverButton(name, messagePopoverButton.render, messagePopoverButton.icon); // @ts-expect-error: legacy code doesn't have icon else if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton); + // Custom + if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon); + if (audioProcessor) addAudioProcessor(name, audioProcessor); + return true; }, p => `startPlugin ${p.name}`); @@ -255,7 +261,9 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu const { name, commands, contextMenus, managedStyle, userProfileBadges, onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, - renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, renderNicknameIcon + renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, + // Custom + renderNicknameIcon, audioProcessor } = p; if (p.stop) { @@ -305,11 +313,14 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu if (chatBarButton || renderChatBarButton) removeChatBarButton(name); if (renderMemberListDecorator) removeMemberListDecorator(name); - if (renderNicknameIcon) removeNicknameIcon(name); if (renderMessageDecoration) removeMessageDecoration(name); if (renderMessageAccessory) removeMessageAccessory(name); if (messagePopoverButton || renderMessagePopoverButton) removeMessagePopoverButton(name); + // Custom + if (renderNicknameIcon) removeNicknameIcon(name); + if (audioProcessor) removeAudioProcessor(name); + return true; }, p => `stopPlugin ${p.name}`); diff --git a/src/equicordplugins/customSounds/index.tsx b/src/equicordplugins/customSounds/index.tsx index 0df8ae15c..ecdac9e7d 100644 --- a/src/equicordplugins/customSounds/index.tsx +++ b/src/equicordplugins/customSounds/index.tsx @@ -6,7 +6,7 @@ import "./styles.css"; -import { addAudioProcessor, AudioProcessor, PreprocessAudioData, removeAudioProcessor } from "@api/AudioPlayer"; +import { AudioProcessor, PreprocessAudioData } from "@api/AudioPlayer"; import { get as getFromDataStore } from "@api/DataStore"; import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; @@ -399,10 +399,10 @@ export default definePlugin({ settings, getCustomSoundURL, startAt: StartAt.Init, + audioProcessor: getCustomSoundURL, async start() { console.log("[CustomSounds] Plugin starting..."); - addAudioProcessor("CustomSounds", this.getCustomSoundURL); try { await preloadDataURIs(); @@ -413,7 +413,6 @@ export default definePlugin({ }, stop() { - removeAudioProcessor("CustomSounds"); console.log("[CustomSounds] Plugin stopped"); } }); diff --git a/src/equicordplugins/ingtoninator/index.tsx b/src/equicordplugins/ingtoninator/index.tsx index 0a777f340..83fc16875 100644 --- a/src/equicordplugins/ingtoninator/index.tsx +++ b/src/equicordplugins/ingtoninator/index.tsx @@ -79,24 +79,39 @@ const IngtoninatorButton: ChatBarButtonFactory = ({ isMainChat }) => { onClick={toggle} > {isEnabled ? ( - - - + enabledIcon() ) : ( - - - + disabledIcon() )} ); }; +function enabledIcon() { + return ( + + + + ); +} + +function disabledIcon() { + return ( + + + + ); +} + export default definePlugin({ name: "Ingtoninator", description: "Suffixes 'ington' to a random word in your message", authors: [EquicordDevs.zyqunix], settings, - renderChatBarButton: IngtoninatorButton, + chatBarButton: { + icon: disabledIcon, + render: IngtoninatorButton + }, start() { addMessagePreSendListener(handleMessage); }, diff --git a/src/equicordplugins/invisibleChat.desktop/index.tsx b/src/equicordplugins/invisibleChat.desktop/index.tsx index 37a14d4d2..4b3235534 100644 --- a/src/equicordplugins/invisibleChat.desktop/index.tsx +++ b/src/equicordplugins/invisibleChat.desktop/index.tsx @@ -65,7 +65,7 @@ function Indicator() { } -const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { +const ChatBarRender: ChatBarButtonFactory = ({ isMainChat }) => { if (!isMainChat) return null; return ( @@ -76,20 +76,26 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { "aria-haspopup": "dialog", }} > - - - + {ChatBarIcon()} ); }; +function ChatBarIcon() { + return ( + + + + ); +} + const settings = definePluginSettings({ savedPasswords: { type: OptionType.STRING, @@ -149,6 +155,11 @@ export default definePlugin({ reporterTestable: ReporterTestable.Patches, settings, + chatBarButton: { + icon: ChatBarIcon, + render: ChatBarRender + }, + patches: [ { // Indicator @@ -165,7 +176,6 @@ export default definePlugin({ const { default: StegCloak } = await getStegCloak(); steggo = new StegCloak(true, false); }, - renderChatBarButton: ChatBarIcon, messagePopoverButton: { icon: PopOverIcon, render(message) { diff --git a/src/equicordplugins/messageFetchTimer/index.tsx b/src/equicordplugins/messageFetchTimer/index.tsx index 40a6a7bff..93640440b 100644 --- a/src/equicordplugins/messageFetchTimer/index.tsx +++ b/src/equicordplugins/messageFetchTimer/index.tsx @@ -72,14 +72,7 @@ const FetchTimeButton: ChatBarButtonFactory = ({ isMainChat }) => { alignItems: "center", gap: "4px" }}> - - - + {FetchTimeIcon()} { ); }; +function FetchTimeIcon() { + return ( + + + + ); +} + function formatTimeAgo(timestamp: Date): string { const now = new Date(); const diff = now.getTime() - timestamp.getTime(); @@ -145,6 +151,11 @@ export default definePlugin({ authors: [EquicordDevs.GroupXyz], settings, + chatBarButton: { + icon: FetchTimeIcon, + render: FetchTimeButton + }, + start() { FluxDispatcher.subscribe("CHANNEL_SELECT", handleChannelSelect); FluxDispatcher.subscribe("LOAD_MESSAGES_SUCCESS", handleMessageLoad); @@ -164,7 +175,5 @@ export default definePlugin({ currentFetch = null; channelTimings.clear(); currentChannelId = null; - }, - - renderChatBarButton: FetchTimeButton, + } }); diff --git a/src/equicordplugins/sekaiStickers/index.tsx b/src/equicordplugins/sekaiStickers/index.tsx index bfbc07feb..08cc3058f 100644 --- a/src/equicordplugins/sekaiStickers/index.tsx +++ b/src/equicordplugins/sekaiStickers/index.tsx @@ -36,7 +36,10 @@ export default definePlugin({ authors: [Devs.MaiKokain], dependencies: ["ChatInputButtonAPI"], settings, - renderChatBarButton: SekaiStickerChatButton, + chatBarButton: { + icon: kanadeSvg, + render: SekaiStickerChatButton + }, async start() { const fonts = [{ name: "YurukaStd", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/47a2ca33b8cb35f59800e8faad48980e4ce5ea71/src/fonts/YurukaStd.woff2" }, { name: "SSFangTangTi", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/ShangShouFangTangTi.woff2" }]; if (!IS_FONTS_LOADED) { diff --git a/src/equicordplugins/signature/index.tsx b/src/equicordplugins/signature/index.tsx index 69d428d8f..5eb1d6b22 100644 --- a/src/equicordplugins/signature/index.tsx +++ b/src/equicordplugins/signature/index.tsx @@ -70,6 +70,14 @@ const SignatureToggle: ChatBarButtonFactory = ({ isMainChat }) => { ); }; +function SignatureIcon() { + return ( + + + + ); +} + const handleMessage = ((channelId, msg) => { if (!settings.store.isEnabled) { msg.content = msg.content; @@ -103,7 +111,6 @@ export default definePlugin({ description: "Automated fingerprint/end text", authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.KrystalSkull], dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], - renderChatBarButton: SignatureToggle, start: () => { if (settings.store.isEnabled) true; @@ -115,6 +122,11 @@ export default definePlugin({ }, + chatBarButton: { + icon: SignatureIcon, + render: SignatureToggle + }, + settings, contextMenus: { diff --git a/src/equicordplugins/talkInReverse/index.tsx b/src/equicordplugins/talkInReverse/index.tsx index 7ecf97e5a..de5bc0477 100644 --- a/src/equicordplugins/talkInReverse/index.tsx +++ b/src/equicordplugins/talkInReverse/index.tsx @@ -54,10 +54,21 @@ const ReverseMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => { ); }; +function ReverseMessageIcon() { + return ( + + + + ); +} + export default definePlugin({ name: "TalkInReverse", authors: [EquicordDevs.Tolgchu], description: "Reverses the message content before sending it.", dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], - renderChatBarButton: ReverseMessageToggle, + chatBarButton: { + icon: ReverseMessageIcon, + render: ReverseMessageToggle + } }); diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index c4b973ed1..4b0c41a8d 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -201,6 +201,14 @@ const SilentTypingChatToggle: ChatBarButtonFactory = ({ channel, type }) => { ); }; +function SilentTypingChatIcon() { + return ( + + + + ); +} + function getEffectiveList(): string[] { if (settings.store.defaultHidden) { if (!settings.store.disabledLocations) { @@ -298,6 +306,10 @@ export default definePlugin({ contextMenus: { "textarea-context": ChatBarContextCheckbox }, + chatBarButton: { + icon: SilentTypingChatIcon, + render: SilentTypingChatToggle + }, patches: [ { @@ -425,6 +437,4 @@ export default definePlugin({ if (checkEnabled(channelId)) return; FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId }); }, - - renderChatBarButton: SilentTypingChatToggle, }); diff --git a/src/utils/types.ts b/src/utils/types.ts index d882dd785..a03ab10b5 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { AudioProcessor } from "@api/AudioPlayer"; import type { ProfileBadge } from "@api/Badges"; import type { ChatBarButtonData, ChatBarButtonFactory } from "@api/ChatButtons"; import type { NavContextMenuPatchCallback } from "@api/ContextMenu"; @@ -195,7 +196,10 @@ export interface PluginDef { renderMessageDecoration?: MessageDecorationFactory; renderMemberListDecorator?: MemberListDecoratorFactory; + + // Custom renderNicknameIcon?: NicknameIconFactory; + audioProcessor?: AudioProcessor; // TODO: Remove eventually /** From 4850a6401ff92b0a261a3e54dab21541b5ca72d2 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:00:48 -0500 Subject: [PATCH 016/291] Fix Requires --- src/api/PluginManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/PluginManager.ts b/src/api/PluginManager.ts index 9acfc94ee..d5b01f39d 100644 --- a/src/api/PluginManager.ts +++ b/src/api/PluginManager.ts @@ -365,9 +365,12 @@ export const initPluginManager = onlyOnce(function init() { if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI"); if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI"); if (p.messagePopoverButton || p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI"); - if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI"); if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI"); + // Custom + if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI"); + if (p.audioProcessor) neededApiPlugins.add("AudioPlayerAPI"); + for (const key of pluginKeysToBind) { p[key] &&= p[key].bind(p) as any; } From 0c0498e531f75b16ddb8c5ec9af608fb9633d566 Mon Sep 17 00:00:00 2001 From: prism Date: Thu, 27 Nov 2025 13:47:59 -0800 Subject: [PATCH 017/291] feat(HeaderBarAPI): Add API to register header bar buttons (#530) * feat(HeaderBarAPI): Add API to register header bar buttons * bleh * t * t * a * t --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- src/api/HeaderBar.tsx | 62 +++++++++++++++ src/api/PluginManager.ts | 18 +++-- src/api/index.ts | 6 ++ src/equicordplugins/baseDecoder/index.tsx | 1 - src/equicordplugins/customSounds/index.tsx | 5 +- src/equicordplugins/dontFilterMe/index.tsx | 46 +++++------- src/equicordplugins/equicordToolbox/index.tsx | 35 +++------ src/equicordplugins/findReply/index.tsx | 2 - src/equicordplugins/holyNotes/index.tsx | 48 ++---------- src/equicordplugins/husk/index.tsx | 1 - .../components/LogsButton.tsx | 25 +------ .../messageLoggerEnhanced/index.tsx | 30 +------- src/equicordplugins/moreUserTags/index.tsx | 1 - src/equicordplugins/noBulletPoints/index.tsx | 13 +--- src/equicordplugins/polishWording/index.ts | 6 +- .../questCompleter.discordDesktop/index.tsx | 75 +++---------------- src/equicordplugins/sekaiStickers/index.tsx | 1 - .../showBadgesInChat/index.tsx | 9 +-- src/equicordplugins/signature/index.tsx | 21 ++---- src/equicordplugins/soggy/index.tsx | 28 +------ .../soundBoardLogger/index.tsx | 33 +------- .../soundBoardLogger/settings.tsx | 9 --- .../splitLargeMessages/index.ts | 9 +-- src/equicordplugins/talkInReverse/index.tsx | 2 +- src/equicordplugins/translatePlus/index.tsx | 1 - src/plugins/_api/headerBar.ts | 24 ++++++ src/plugins/platformIndicators/index.tsx | 52 +++---------- src/plugins/silentTyping/index.tsx | 1 - src/plugins/userVoiceShow/index.tsx | 36 ++++----- src/utils/types.ts | 4 +- 30 files changed, 204 insertions(+), 400 deletions(-) create mode 100644 src/api/HeaderBar.tsx create mode 100644 src/plugins/_api/headerBar.ts diff --git a/src/api/HeaderBar.tsx b/src/api/HeaderBar.tsx new file mode 100644 index 000000000..04d740f8c --- /dev/null +++ b/src/api/HeaderBar.tsx @@ -0,0 +1,62 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { useEffect, useState } from "@webpack/common"; +import type { JSX } from "react"; + +export type HeaderBarButtonFactory = () => JSX.Element | null; + +interface HeaderBarButton { + render: HeaderBarButtonFactory; + priority: number; +} + +export const buttons = new Map(); + +const listeners = new Set<() => void>(); + +/** + * Add a button to the header bar. + * @param id Unique identifier for the button. + * @param render Function that renders the button component. + * @param priority Higher priority = more to the right. Default is 0. + */ +export function addHeaderBarButton(id: string, render: HeaderBarButtonFactory, priority = 0) { + buttons.set(id, { render, priority }); + listeners.forEach(l => l()); +} + +/** + * Remove a button from the header bar. + * @param id The identifier of the button to remove. + */ +export function removeHeaderBarButton(id: string) { + buttons.delete(id); + listeners.forEach(l => l()); +} + +function HeaderBarButtons() { + const [, forceUpdate] = useState(0); + + useEffect(() => { + const listener = () => forceUpdate(n => n + 1); + listeners.add(listener); + return () => { listeners.delete(listener); }; + }, []); + + const sorted = Array.from(buttons).sort(([, a], [, b]) => a.priority - b.priority); + + return sorted.map(([id, { render: Button }]) => ( + + - )} - - - ); - }, - renderNavBar(e) { - if (Array.isArray(e.toolbar)) - return e.toolbar.unshift( - - - - ); - - e.toolbar = [ - - - , - e.toolbar, - ]; - } + renderHeaderBarButton: ToolBarHeader }); diff --git a/src/equicordplugins/sekaiStickers/index.tsx b/src/equicordplugins/sekaiStickers/index.tsx index 08cc3058f..af1d6c1af 100644 --- a/src/equicordplugins/sekaiStickers/index.tsx +++ b/src/equicordplugins/sekaiStickers/index.tsx @@ -34,7 +34,6 @@ export default definePlugin({ name: "SekaiStickers", description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka", authors: [Devs.MaiKokain], - dependencies: ["ChatInputButtonAPI"], settings, chatBarButton: { icon: kanadeSvg, diff --git a/src/equicordplugins/showBadgesInChat/index.tsx b/src/equicordplugins/showBadgesInChat/index.tsx index ec23fde67..4d672e144 100644 --- a/src/equicordplugins/showBadgesInChat/index.tsx +++ b/src/equicordplugins/showBadgesInChat/index.tsx @@ -4,7 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; import { Devs, EquicordDevs } from "@utils/constants"; import { isEquicordPluginDev, isPluginDev } from "@utils/misc"; import definePlugin from "@utils/types"; @@ -142,12 +141,8 @@ export default definePlugin({ name: "ShowBadgesInChat", authors: [Devs.Inbestigator, EquicordDevs.KrystalSkull], description: "Shows the message author's badges beside their name in chat.", - dependencies: ["MessageDecorationsAPI"], settings, - start: () => { - addMessageDecoration("vc-show-badges-in-chat", props => props.message?.author ? : null); - }, - stop: () => { - removeMessageDecoration("vc-show-badges-in-chat"); + renderMessageDecoration(props) { + return props.message?.author ? : null; } }); diff --git a/src/equicordplugins/signature/index.tsx b/src/equicordplugins/signature/index.tsx index 5eb1d6b22..7c5630650 100644 --- a/src/equicordplugins/signature/index.tsx +++ b/src/equicordplugins/signature/index.tsx @@ -7,7 +7,6 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -78,14 +77,6 @@ function SignatureIcon() { ); } -const handleMessage = ((channelId, msg) => { - if (!settings.store.isEnabled) { - msg.content = msg.content; - } else { - msg.content = textProcessing(msg.content); - } -}); - const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => { const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]); if (!contextMenu) return; @@ -110,16 +101,18 @@ export default definePlugin({ name: "Signature", description: "Automated fingerprint/end text", authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.KrystalSkull], - dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], - + onBeforeMessageSend(channelId, msg) { + if (!settings.store.isEnabled) { + msg.content = msg.content; + } else { + msg.content = textProcessing(msg.content); + } + }, start: () => { if (settings.store.isEnabled) true; - addMessagePreSendListener(handleMessage); }, stop: () => { if (settings.store.isEnabled) false; - removeMessagePreSendListener(handleMessage); - }, chatBarButton: { diff --git a/src/equicordplugins/soggy/index.tsx b/src/equicordplugins/soggy/index.tsx index edd0361e2..caf1b4659 100644 --- a/src/equicordplugins/soggy/index.tsx +++ b/src/equicordplugins/soggy/index.tsx @@ -6,7 +6,6 @@ import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer"; import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { EquicordDevs } from "@utils/constants"; import { ModalProps, ModalRoot, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; @@ -143,15 +142,7 @@ export default definePlugin({ authors: [EquicordDevs.sliwka], settings, dependencies: ["AudioPlayerAPI"], - patches: [ - { - find: ".controlButtonWrapper,", - replacement: { - match: /(function \i\(\i\){)(.{1,200}toolbar.{1,450}mobileToolbar)/, - replace: "$1$self.addIconToToolBar(arguments[0]);$2" - } - } - ], + renderHeaderBarButton: SoggyButton, start() { assignBoop(settings.store.boopLink, settings.store.boopVolume * 100); @@ -162,21 +153,4 @@ export default definePlugin({ boopSound?.delete(); song?.delete(); }, - - // taken from message logger lol - addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) { - if (Array.isArray(e.toolbar)) - return e.toolbar.unshift( - - - - ); - - e.toolbar = [ - - - , - e.toolbar, - ]; - }, }); diff --git a/src/equicordplugins/soundBoardLogger/index.tsx b/src/equicordplugins/soundBoardLogger/index.tsx index 41aa76180..5d95a2f2d 100644 --- a/src/equicordplugins/soundBoardLogger/index.tsx +++ b/src/equicordplugins/soundBoardLogger/index.tsx @@ -4,14 +4,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { disableStyle, enableStyle } from "@api/Styles"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; import { FluxDispatcher } from "@webpack/common"; -import { CharBarButton, ChatBarIcon } from "./components/Icons"; import { OpenSBLogsButton } from "./components/SoundBoardLog"; import settings from "./settings"; import { updateLoggedSounds } from "./store"; @@ -22,33 +19,9 @@ export default definePlugin({ name: "SoundBoardLogger", authors: [Devs.Moxxie, EquicordDevs.Fres, Devs.amy, Devs.thororen], description: "Logs all soundboards that are played in a voice chat and allows you to download them", - dependencies: ["AudioPlayerAPI", "ChatInputButtonAPI"], - patches: [ - { - predicate: () => settings.store.IconLocation === "toolbar", - find: ".controlButtonWrapper,", - replacement: { - match: /(function \i\(\i\){)(.{1,500}toolbar.{1,500}mobileToolbar)/, - replace: "$1$self.addSBIconToToolBar(arguments[0]);$2" - } - } - ], + dependencies: ["AudioPlayerAPI"], settings, - addSBIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) { - if (Array.isArray(e.toolbar)) - return e.toolbar.unshift( - - - - ); - - e.toolbar = [ - - - , - e.toolbar, - ]; - }, + renderHeaderBarButton: OpenSBLogsButton, start() { enableStyle(styles); FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", async sound => { @@ -56,10 +29,8 @@ export default definePlugin({ await updateLoggedSounds(sound); getListeners().forEach(cb => cb()); }); - if (settings.store.IconLocation === "chat") addChatBarButton("vc-soundlog-button", CharBarButton, ChatBarIcon); }, stop() { disableStyle(styles); - if (settings.store.IconLocation === "chat") removeChatBarButton("vc-soundlog-button"); } }); diff --git a/src/equicordplugins/soundBoardLogger/settings.tsx b/src/equicordplugins/soundBoardLogger/settings.tsx index 6c9453077..75d211c5d 100644 --- a/src/equicordplugins/soundBoardLogger/settings.tsx +++ b/src/equicordplugins/soundBoardLogger/settings.tsx @@ -85,15 +85,6 @@ const settings = definePluginSettings({ default: 0.5, markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] }, - IconLocation: { - description: "choose where to show the SoundBoard Log icon (requires restart)", - type: OptionType.SELECT, - options: [ - { label: "Toolbar", value: "toolbar", default: true }, - { label: "Chat input", value: "chat" } - ], - restartNeeded: true - }, }); export default settings; diff --git a/src/equicordplugins/splitLargeMessages/index.ts b/src/equicordplugins/splitLargeMessages/index.ts index 50dadff56..0e52af7f4 100644 --- a/src/equicordplugins/splitLargeMessages/index.ts +++ b/src/equicordplugins/splitLargeMessages/index.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents"; +import { MessageSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { EquicordDevs } from "@utils/constants"; import { getCurrentChannel, sendMessage } from "@utils/discord"; @@ -117,16 +117,11 @@ export default definePlugin({ name: "SplitLargeMessages", description: "Splits large messages into multiple to fit Discord's message limit.", authors: [EquicordDevs.Reycko], - dependencies: ["MessageEventsAPI"], settings, + onBeforeMessageSend: listener, start() { if (settings.store.maxLength === 0) maxLength = autoMaxLength(); - addMessagePreSendListener(listener); - }, - - stop() { - removeMessagePreSendListener(listener); }, patches: [ diff --git a/src/equicordplugins/talkInReverse/index.tsx b/src/equicordplugins/talkInReverse/index.tsx index de5bc0477..c469c9b31 100644 --- a/src/equicordplugins/talkInReverse/index.tsx +++ b/src/equicordplugins/talkInReverse/index.tsx @@ -66,7 +66,7 @@ export default definePlugin({ name: "TalkInReverse", authors: [EquicordDevs.Tolgchu], description: "Reverses the message content before sending it.", - dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], + dependencies: ["MessageEventsAPI"], chatBarButton: { icon: ReverseMessageIcon, render: ReverseMessageToggle diff --git a/src/equicordplugins/translatePlus/index.tsx b/src/equicordplugins/translatePlus/index.tsx index 2457d0877..78e48b120 100644 --- a/src/equicordplugins/translatePlus/index.tsx +++ b/src/equicordplugins/translatePlus/index.tsx @@ -46,7 +46,6 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => export default definePlugin({ name: "Translate+", description: "Vencord's translate plugin but with support for artistic languages!", - dependencies: ["MessageAccessoriesAPI"], authors: [Devs.Ven, EquicordDevs.Prince527], settings, contextMenus: { diff --git a/src/plugins/_api/headerBar.ts b/src/plugins/_api/headerBar.ts new file mode 100644 index 000000000..20af4ecb4 --- /dev/null +++ b/src/plugins/_api/headerBar.ts @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "HeaderBarAPI", + description: "API to add buttons to the header bar.", + authors: [EquicordDevs.Prism], + + patches: [ + { + find: '?"BACK_FORWARD_NAVIGATION":', + replacement: { + match: /(?<=\i\.Fragment,\{children:\[.{1,500}className:\i\}\))(?=\])/, + replace: ",...Vencord.Api.HeaderBar._addButtons()" + } + } + ] +}); diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index 9d1541dbc..17d44cd45 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -18,9 +18,6 @@ import "./style.css"; -import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; -import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; -import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons"; import { definePluginSettings, migratePluginSetting, Settings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; @@ -192,49 +189,22 @@ const PlatformIndicator = ({ user, isProfile, isMessage, isMemberList }: Platfor ); }; -function toggleMemberListDecorators(enabled: boolean) { - if (enabled) { - addMemberListDecorator("PlatformIndicators", props => ); - } else { - removeMemberListDecorator("PlatformIndicators"); - } -} - -function toggleNicknameIcons(enabled: boolean) { - if (enabled) { - addNicknameIcon("PlatformIndicators", props => , 1); - } else { - removeNicknameIcon("PlatformIndicators"); - } -} - -function toggleMessageDecorators(enabled: boolean) { - if (enabled) { - addMessageDecoration("PlatformIndicators", props => ); - } else { - removeMessageDecoration("PlatformIndicators"); - } -} - migratePluginSetting("PlatformIndicators", "profiles", "badges"); const settings = definePluginSettings({ list: { type: OptionType.BOOLEAN, description: "Show indicators in the member list", default: true, - onChange: toggleMemberListDecorators }, profiles: { type: OptionType.BOOLEAN, description: "Show indicators in user profiles", default: true, - onChange: toggleNicknameIcons }, messages: { type: OptionType.BOOLEAN, description: "Show indicators inside messages", default: true, - onChange: toggleMessageDecorators }, colorMobileIndicator: { type: OptionType.BOOLEAN, @@ -278,19 +248,21 @@ export default definePlugin({ name: "PlatformIndicators", description: "Adds platform indicators (Desktop, Mobile, Web...) to users", authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven], - dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"], settings, - - start() { - if (settings.store.list) toggleMemberListDecorators(true); - if (settings.store.profiles) toggleNicknameIcons(true); - if (settings.store.messages) toggleMessageDecorators(true); + renderNicknameIcon(props) { + if (!settings.store.profiles) return null; + return ( + + ); }, + renderMemberListDecorator(props) { + if (!settings.store.list) return null; + return ; - stop() { - if (settings.store.list) toggleMemberListDecorators(false); - if (settings.store.profiles) toggleNicknameIcons; - if (settings.store.messages) toggleMessageDecorators(false); + }, + renderMessageDecoration(props) { + if (!settings.store.messages) return null; + return ; }, patches: [ diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index 4b0c41a8d..8637889ac 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -296,7 +296,6 @@ export default definePlugin({ name: "SilentTyping", authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.Etorix], description: "Hide your typing indicator from chat.", - dependencies: ["ChatInputButtonAPI"], isModified: true, settings, diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx index 5f1a05e54..70c22cf59 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -18,9 +18,6 @@ import "./style.css"; -import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; -import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; -import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons"; import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -52,9 +49,22 @@ export default definePlugin({ name: "UserVoiceShow", description: "Shows an indicator when a user is in a Voice Channel", authors: [Devs.Nuckyz, Devs.LordElias, EquicordDevs.omaw], - dependencies: ["NicknameIconsAPI", "MemberListDecoratorsAPI", "MessageDecorationsAPI"], settings, + renderNicknameIcon({ userId }) { + if (!settings.store.showInUserProfileModal) return null; + return ( + + ); + }, + renderMemberListDecorator({ user }) { + if (!settings.store.showInMemberList) return null; + return user == null ? null : ; + }, + renderMessageDecoration({ message }) { + if (!settings.store.showInMessages) return null; + return message?.author == null ? null : ; + }, patches: [ // Friends List { @@ -67,23 +77,5 @@ export default definePlugin({ } ], - start() { - if (settings.store.showInUserProfileModal) { - addNicknameIcon("UserVoiceShow", ({ userId }) => ); - } - if (settings.store.showInMemberList) { - addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : ); - } - if (settings.store.showInMessages) { - addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : ); - } - }, - - stop() { - removeNicknameIcon("UserVoiceShow"); - removeMemberListDecorator("UserVoiceShow"); - removeMessageDecoration("UserVoiceShow"); - }, - VoiceChannelIndicator }); diff --git a/src/utils/types.ts b/src/utils/types.ts index a03ab10b5..14b6f8327 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -20,6 +20,7 @@ import { AudioProcessor } from "@api/AudioPlayer"; import type { ProfileBadge } from "@api/Badges"; import type { ChatBarButtonData, ChatBarButtonFactory } from "@api/ChatButtons"; import type { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { HeaderBarButtonFactory } from "@api/HeaderBar"; import type { MemberListDecoratorFactory } from "@api/MemberListDecorators"; import type { MessageAccessoryFactory } from "@api/MessageAccessories"; import type { MessageDecorationFactory } from "@api/MessageDecorations"; @@ -199,7 +200,8 @@ export interface PluginDef { // Custom renderNicknameIcon?: NicknameIconFactory; - audioProcessor?: AudioProcessor; + renderHeaderBarButton?: HeaderBarButtonFactory; + onAudioProcessor?: AudioProcessor; // TODO: Remove eventually /** From d8b8e365cfae1a240fd2c1a4126986387e842293 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:52:33 -0500 Subject: [PATCH 018/291] onAudioProcessor -> audioProcessor --- src/api/PluginManager.ts | 12 ++++++------ src/equicordplugins/customSounds/index.tsx | 2 +- src/utils/types.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/api/PluginManager.ts b/src/api/PluginManager.ts index 0c9e00911..c1e4cdb2a 100644 --- a/src/api/PluginManager.ts +++ b/src/api/PluginManager.ts @@ -191,7 +191,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, // Custom - renderNicknameIcon, renderHeaderBarButton, onAudioProcessor + renderNicknameIcon, renderHeaderBarButton, audioProcessor } = p; if (p.start) { @@ -254,7 +254,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: // Custom if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon); if (renderHeaderBarButton) addHeaderBarButton(name, renderHeaderBarButton); - if (onAudioProcessor) addAudioProcessor(name, onAudioProcessor); + if (audioProcessor) addAudioProcessor(name, audioProcessor); return true; }, p => `startPlugin ${p.name}`); @@ -265,7 +265,7 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu onBeforeMessageEdit, onBeforeMessageSend, onMessageClick, renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, // Custom - renderNicknameIcon, renderHeaderBarButton, onAudioProcessor + renderNicknameIcon, renderHeaderBarButton, audioProcessor } = p; if (p.stop) { @@ -322,7 +322,7 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu // Custom if (renderNicknameIcon) removeNicknameIcon(name); if (renderHeaderBarButton) removeHeaderBarButton(name); - if (onAudioProcessor) removeAudioProcessor(name); + if (audioProcessor) removeAudioProcessor(name); return true; }, p => `stopPlugin ${p.name}`); @@ -335,7 +335,7 @@ export const initPluginManager = onlyOnce(function init() { "onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick", "renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton", // Custom - "renderNicknameIcon", "renderHeaderBarButton", "onAudioProcessor" + "renderNicknameIcon", "renderHeaderBarButton" ]; const neededApiPlugins = new Set(); @@ -375,7 +375,7 @@ export const initPluginManager = onlyOnce(function init() { // Custom if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI"); if (p.renderHeaderBarButton) neededApiPlugins.add("HeaderBarAPI"); - if (p.onAudioProcessor) neededApiPlugins.add("AudioPlayerAPI"); + if (p.audioProcessor) neededApiPlugins.add("AudioPlayerAPI"); for (const key of pluginKeysToBind) { p[key] &&= p[key].bind(p) as any; diff --git a/src/equicordplugins/customSounds/index.tsx b/src/equicordplugins/customSounds/index.tsx index ee66cb6be..38e593432 100644 --- a/src/equicordplugins/customSounds/index.tsx +++ b/src/equicordplugins/customSounds/index.tsx @@ -396,7 +396,7 @@ export default definePlugin({ authors: [Devs.ScattrdBlade, Devs.TheKodeToad], settings, startAt: StartAt.Init, - onAudioProcessor: getCustomSoundURL, + audioProcessor: getCustomSoundURL, async start() { console.log("[CustomSounds] Plugin starting..."); diff --git a/src/utils/types.ts b/src/utils/types.ts index 14b6f8327..f70902291 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -201,7 +201,7 @@ export interface PluginDef { // Custom renderNicknameIcon?: NicknameIconFactory; renderHeaderBarButton?: HeaderBarButtonFactory; - onAudioProcessor?: AudioProcessor; + audioProcessor?: AudioProcessor; // TODO: Remove eventually /** From 34c724e3ed6092175fca8f7a918efbcbb28d4211 Mon Sep 17 00:00:00 2001 From: creations Date: Fri, 28 Nov 2025 23:22:53 -0500 Subject: [PATCH 019/291] add firefox to publish (#531) * add firefox to publish * add sources * rename sources --- .github/workflows/publish.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index add75c8a8..a69c9bccc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -77,13 +77,18 @@ jobs: if: steps.check.outputs.release == 'true' run: pnpm buildWebStandalone + - name: Create sources zip + if: steps.check.outputs.release == 'true' + run: zip -r dist/extension-sources.zip . -x "node_modules/*" -x "dist/*" -x ".git/*" + - name: Publish extension if: steps.check.outputs.release == 'true' - run: | - cd dist/browser/chromium-unpacked - pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish + run: pnpx publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip dist/extension-sources.zip env: - EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} + FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }} + FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }} From 9d11a50e6603251295d404c0d998dc701cf395d7 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 28 Nov 2025 23:24:49 -0500 Subject: [PATCH 020/291] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df95c1c8b..5d123d02e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "equicord", "private": "true", - "version": "1.13.6.2", + "version": "1.13.6.3", "description": "The other cutest Discord client mod", "homepage": "https://github.com/Equicord/Equicord#readme", "bugs": { From 421e016bc2ec2fc666c1fcc21f4d99f29ffcf17c Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:19:26 -0500 Subject: [PATCH 021/291] Update Crowdin configuration file --- crowdin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index d0e747bd0..8bd95226c 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,3 @@ files: - - source: /i18n/en-US/*.json + - source: /i18n/en-US/ translation: /i18n/%locale%/%original_file_name% From 3d8f74836d39a3ef241229006c70bbe557ca9a21 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:21:18 -0500 Subject: [PATCH 022/291] Update Crowdin configuration file --- crowdin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index 8bd95226c..d0e747bd0 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,3 @@ files: - - source: /i18n/en-US/ + - source: /i18n/en-US/*.json translation: /i18n/%locale%/%original_file_name% From 475f8af89d8613c6f737ef2164045d420cfad71a Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:06:47 -0500 Subject: [PATCH 023/291] AccountPanelServerProfile: Fix AlwaysExpandProfiles Conflict --- .../alwaysExpandProfiles/index.ts | 6 +- src/equicordplugins/videoSpeed/index.tsx | 96 ------------------- .../accountPanelServerProfile/index.tsx | 6 +- 3 files changed, 8 insertions(+), 100 deletions(-) delete mode 100644 src/equicordplugins/videoSpeed/index.tsx diff --git a/src/equicordplugins/alwaysExpandProfiles/index.ts b/src/equicordplugins/alwaysExpandProfiles/index.ts index 785fefb0d..e7fa58419 100644 --- a/src/equicordplugins/alwaysExpandProfiles/index.ts +++ b/src/equicordplugins/alwaysExpandProfiles/index.ts @@ -17,13 +17,13 @@ export default definePlugin({ { find: '"view-profile"', replacement: { - match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200})return/, - replace: "$1return $self.openUserProfile($2);" + match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200}return)/, + replace: "$1 $self.openUserModal($2);" }, all: true }, ], - openUserProfile(user: User) { + openUserModal(user: User) { openUserProfile(user.id); } }); diff --git a/src/equicordplugins/videoSpeed/index.tsx b/src/equicordplugins/videoSpeed/index.tsx deleted file mode 100644 index 448d9ae71..000000000 --- a/src/equicordplugins/videoSpeed/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; -import { Tooltip, useRef, useState } from "@webpack/common"; - -const settings = definePluginSettings( - { - preservePitch: { - type: OptionType.BOOLEAN, - description: "Should pitch be preserved when changing speed?", - default: false, - restartNeeded: true - }, - }); - -export default definePlugin({ - name: "VideoSpeed", - description: "Allows you to change the speed of videos", - authors: [Devs.Samwich], - settings, - patches: [ - { - find: /\.VIDEO\?\i\.videoControls:/, - replacement: { - match: /children:\[this\.renderPlayIcon\(\),.{0,200}\.setDurationRef}\),/, - replace: "$&$self.SpeedButton()," - }, - }, - ], - SpeedButton: ErrorBoundary.wrap(() => { - const elementRef = useRef(null); - - const [speed, setSpeed] = useState(1); - - const speedPaths = - { - 1.5: "M320 48a48 48 0 1 1 96 0a48 48 0 1 1-96 0m-115.5 73.3c-5.4-2.5-11.7-1.9-16.4 1.7l-40.9 30.7c-14.1 10.6-34.2 7.7-44.8-6.4s-7.7-34.2 6.4-44.8l40.9-30.7c23.7-17.8 55.3-21 82.1-8.4l90.4 42.5c29.1 13.7 36.8 51.6 15.2 75.5L299.1 224h97.4c30.3 0 53 27.7 47.1 57.4l-28.2 140.9c-3.5 17.3-20.3 28.6-37.7 25.1s-28.6-20.3-25.1-37.7L377 288h-70.3c8.6 19.6 13.3 41.2 13.3 64c0 88.4-71.6 160-160 160S0 440.4 0 352s71.6-160 160-160c11.1 0 22 1.1 32.4 3.3l54.2-54.2zM160 448a96 96 0 1 0 0-192a96 96 0 1 0 0 192", - 1.25: "M320 48a48 48 0 1 0-96 0a48 48 0 1 0 96 0M125.7 175.5c9.9-9.9 23.4-15.5 37.5-15.5c1.9 0 3.8.1 5.6.3L137.6 254c-9.3 28 1.7 58.8 26.8 74.5l86.2 53.9l-25.4 88.8c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l28.7-100.4c5.9-20.6-2.6-42.6-20.7-53.9L238 299l30.9-82.4l5.1 12.3c15 35.8 49.9 59.1 88.7 59.1H384c17.7 0 32-14.3 32-32s-14.3-32-32-32h-21.3c-12.9 0-24.6-7.8-29.5-19.7l-6.3-15c-14.6-35.1-44.1-61.9-80.5-73.1l-48.7-15C186.6 97.8 175 96 163.3 96c-31 0-60.8 12.3-82.7 34.3l-23.2 23.1c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l23.1-23.1zM91.2 352H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h69.6c19 0 36.2-11.2 43.9-28.5l11.5-25.9l-9.5-6a95.394 95.394 0 0 1-37.9-44.9z", - 1: "M112 48a48 48 0 1 1 96 0a48 48 0 1 1-96 0m40 304v128c0 17.7-14.3 32-32 32s-32-14.3-32-32V256.9l-28.6 47.6c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6h29.7c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V352z", - 0.5: "M368 32c41.7 0 75.9 31.8 79.7 72.5l85.6 26.3c25.4 7.8 42.8 31.3 42.8 57.9c0 21.8-11.7 41.9-30.7 52.7l-144.6 82.1l92.5 92.5H544c17.7 0 32 14.3 32 32s-14.3 32-32 32h-64c-8.5 0-16.6-3.4-22.6-9.4L346.9 360.2c11.7-36 3.2-77.1-25.4-105.7c-40.6-40.6-106.3-40.6-146.9-.1l-73.6 70c-6.4 6.1-6.7 16.2-.6 22.6s16.2 6.6 22.6.6l73.8-70.2l.1-.1l.1-.1c3.5-3.5 7.3-6.6 11.3-9.2c27.9-18.5 65.9-15.4 90.5 9.2c24.7 24.7 27.7 62.9 9 90.9c-2.6 3.8-5.6 7.5-9 10.9l-37 37H352c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-35.3 0-64-28.7-64-64C0 249.6 127 112.9 289.3 97.5C296.2 60.2 328.8 32 368 32m0 104a24 24 0 1 0 0-48a24 24 0 1 0 0 48" - }; - - return
- - {tooltipProps => ( -
{ - console.log(e); - let newSpeed; - switch (speed) { - case 1: - newSpeed = 1.25; - break; - case 1.25: - newSpeed = 1.5; - break; - case 1.5: - newSpeed = 0.5; - break; - case 0.5: - newSpeed = 1; - break; - } - const parent = e.currentTarget.parentNode!.parentNode!.parentNode!; - - // this works with audio too as it is but it doesnt always select the right audio tag - const media = parent.querySelector("video")! /* ?? document.querySelector("source")?.parentElement*/; - console.log(media); - media.playbackRate = newSpeed; - media.preservesPitch = settings.store.preservePitch; - setSpeed(newSpeed); - }} - > - -
- ) - } -
-
; - }, { noop: true }) -}); diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index 57144ec08..df3205967 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -4,14 +4,16 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { isPluginEnabled } from "@api/PluginManager"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; +import alwaysExpandProfiles from "@equicordplugins/alwaysExpandProfiles"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; -import { ContextMenuApi, Menu } from "@webpack/common"; +import { ContextMenuApi, Menu, UserStore } from "@webpack/common"; interface UserProfileProps { popoutProps: Record; @@ -37,6 +39,8 @@ const AccountPanelContextMenu = ErrorBoundary.wrap(() => { label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"} disabled={getCurrentChannel()?.getGuildId() == null} action={e => { + const user = UserStore.getCurrentUser(); + if (isPluginEnabled(alwaysExpandProfiles.name)) return alwaysExpandProfiles.openUserModal(user); openAlternatePopout = true; accountPanelRef.current?.click(); }} From 97950239065e5a9bcf350a1d19abcf2d363217f3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:10:49 -0500 Subject: [PATCH 024/291] AccountPanelServerProfile: Format --- src/plugins/accountPanelServerProfile/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index df3205967..cbf23bbab 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -39,8 +39,10 @@ const AccountPanelContextMenu = ErrorBoundary.wrap(() => { label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"} disabled={getCurrentChannel()?.getGuildId() == null} action={e => { - const user = UserStore.getCurrentUser(); - if (isPluginEnabled(alwaysExpandProfiles.name)) return alwaysExpandProfiles.openUserModal(user); + if (isPluginEnabled(alwaysExpandProfiles.name)) { + const user = UserStore.getCurrentUser(); + return alwaysExpandProfiles.openUserModal(user); + } openAlternatePopout = true; accountPanelRef.current?.click(); }} From ea5e7224bcc666c790855ae5d7f199844999ca25 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:33:12 -0500 Subject: [PATCH 025/291] Fix All Patches --- src/equicordplugins/questify/index.tsx | 2 +- src/equicordplugins/questify/utils/components.tsx | 4 ++-- src/equicordplugins/unreadBadgeCount/index.tsx | 2 +- src/plugins/decor/ui/index.ts | 2 +- src/plugins/noUnblockToJump/index.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 85af75edb..bf6af35d4 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1073,7 +1073,7 @@ export default definePlugin({ }, { // Adds a maxDigits prop to the LowerBadge component which allows for not truncating, or for truncating at a specific threshold. - find: "STATUS_DANGER,disableColor", + find: '"renderBadgeCount"])', group: true, replacement: [ { diff --git a/src/equicordplugins/questify/utils/components.tsx b/src/equicordplugins/questify/utils/components.tsx index 9b14f999a..ff61c28ef 100644 --- a/src/equicordplugins/questify/utils/components.tsx +++ b/src/equicordplugins/questify/utils/components.tsx @@ -14,8 +14,8 @@ import { formatLowerBadge } from "./misc"; // GuildlessServerListItem's built-in pill does not support unread state. export const GuildlessServerListItemComponent = findComponentByCodeLazy("tooltip:", "lowerBadgeSize:"); export const GuildedServerListItemPillComponent = findComponentByCodeLazy('"pill":"empty"'); -export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,disableColor:"); -export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,shape:", "iconBadge,"); +export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("numberBadge", '"renderBadgeCount"'); +export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("iconBadge", ".ROUND,disableColor"); export const RadioGroup = findComponentByCodeLazy(',["label","description"'); export const QuestTile = findComponentByCodeLazy(".rowIndex,trackGuildAndChannelMetadata"); diff --git a/src/equicordplugins/unreadBadgeCount/index.tsx b/src/equicordplugins/unreadBadgeCount/index.tsx index c28dd7139..e1ee1d4e9 100644 --- a/src/equicordplugins/unreadBadgeCount/index.tsx +++ b/src/equicordplugins/unreadBadgeCount/index.tsx @@ -14,7 +14,7 @@ import { ReadStateStore, useStateFromStores } from "@webpack/common"; const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); const JoinedThreadsStore = findStoreLazy("JoinedThreadsStore"); -const NumberBadge = findComponentByCodeLazy("numberBadge", ".STATUS_DANGER,"); +const NumberBadge = findComponentByCodeLazy("numberBadge", '"renderBadgeCount"'); const settings = definePluginSettings({ showOnMutedChannels: { diff --git a/src/plugins/decor/ui/index.ts b/src/plugins/decor/ui/index.ts index ec9a9e74f..52f6df2cb 100644 --- a/src/plugins/decor/ui/index.ts +++ b/src/plugins/decor/ui/index.ts @@ -10,5 +10,5 @@ import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack"; export const cl = classNameFactory("vc-decor-"); export const DecorationModalStyles = findByPropsLazy("modalPreview", "modalCloseButton", "spinner", "modal"); -export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["initialSelectedDecoration:", /initialSelectedDecoration:\i,.{0,300}Promise\.all/]); +export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["initialSelectedDecoration:", /initialSelectedDecoration:\i,.{0,200}onCloseModal:\i/]); export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]); diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index b36a2e24d..5d876fc7b 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::UNIGNORE_TO_JUMP_BODY}", replacement: { - match: /return \i\.\i\.isBlockedForMessage\(/, + match: /if\(\i\.\i\.isBlockedForMessage\(/, replace: "return true;$&" } } From db6b92c226eb067e4380ac82864ee05fd20c7b33 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:35:50 -0500 Subject: [PATCH 026/291] NoUnblockToJump: Update Patch For Stable --- src/plugins/noUnblockToJump/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index 5d876fc7b..d0921324c 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::UNIGNORE_TO_JUMP_BODY}", replacement: { - match: /if\(\i\.\i\.isBlockedForMessage\(/, + match: /(if\(|return )\i\.\i\.isBlockedForMessage\(/, replace: "return true;$&" } } From e3745f2ed1192a9268044e597c1ff49634291f94 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:45:24 -0500 Subject: [PATCH 027/291] bump version + edge publishing --- .github/workflows/publish.yml | 6 +++++- package.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a69c9bccc..23499757e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -83,7 +83,8 @@ jobs: - name: Publish extension if: steps.check.outputs.release == 'true' - run: pnpx publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip dist/extension-sources.zip + run: pnpx publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip dist/extension-sources.zip --edge-zip dist/extension-chrome.zip + env: CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} @@ -92,3 +93,6 @@ jobs: FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }} FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }} + EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} + EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} + EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }} diff --git a/package.json b/package.json index 5d123d02e..6a3f58794 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "equicord", "private": "true", - "version": "1.13.6.3", + "version": "1.13.6.4", "description": "The other cutest Discord client mod", "homepage": "https://github.com/Equicord/Equicord#readme", "bugs": { From 18513870a221d28dbe5d5ef6b0db91c9fe289ba2 Mon Sep 17 00:00:00 2001 From: prism Date: Tue, 2 Dec 2025 22:28:01 +0100 Subject: [PATCH 028/291] UI: cleanup and migration (#532) * cook * minor changes * h * t * b * revert * updates --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- src/components/Alert.tsx | 20 +++++---- src/components/ThemeSettings/ThemesTab.tsx | 5 ++- src/components/ThemeSettings/UserCSSModal.tsx | 4 +- src/components/settings/DonateButton.tsx | 42 ++++++------------- src/components/settings/SpecialCard.css | 1 + .../tabs/changelog/NewPluginsSection.tsx | 7 ++-- .../settings/tabs/changelog/index.tsx | 28 +++++-------- .../settings/tabs/plugins/PluginModal.tsx | 16 +++---- .../settings/tabs/plugins/styles.css | 2 +- .../tabs/sync/BackupAndRestoreTab.tsx | 29 +++++++------ .../settings/tabs/sync/CloudTab.tsx | 23 +++++----- .../settings/tabs/updater/Components.tsx | 7 ++-- .../settings/tabs/updater/index.tsx | 7 ++-- .../settings/tabs/vencord/DonateButton.tsx | 4 +- .../tabs/vencord/NotificationSettings.tsx | 3 +- .../settings/tabs/vencord/VencordTab.css | 5 +++ .../settings/tabs/vencord/index.tsx | 33 ++++++++------- src/plugins/_api/badges/modals.tsx | 6 +-- 18 files changed, 114 insertions(+), 128 deletions(-) diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx index 60c21f448..8e83f5738 100644 --- a/src/components/Alert.tsx +++ b/src/components/Alert.tsx @@ -5,7 +5,7 @@ */ import { filters, mapMangledModuleLazy } from "@webpack"; -import type { ReactNode } from "react"; +import type { ComponentType, CSSProperties, ReactNode } from "react"; const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', { HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO", "ERROR"), @@ -15,35 +15,37 @@ const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positi export interface AlertProps { children: ReactNode; className?: string; + style?: CSSProperties; + icon?: ComponentType<{ className?: string; color?: string; }>; } -function Warning({ children, className }: AlertProps) { +function Warning({ children, className, style, icon }: AlertProps) { return ( - + {children} ); } -function Info({ children, className }: AlertProps) { +function Info({ children, className, style, icon }: AlertProps) { return ( - + {children} ); } -function Error({ children, className }: AlertProps) { +function Error({ children, className, style, icon }: AlertProps) { return ( - + {children} ); } -function Positive({ children, className }: AlertProps) { +function Positive({ children, className, style, icon }: AlertProps) { return ( - + {children} ); diff --git a/src/components/ThemeSettings/ThemesTab.tsx b/src/components/ThemeSettings/ThemesTab.tsx index 279b9d862..f283e48e3 100644 --- a/src/components/ThemeSettings/ThemesTab.tsx +++ b/src/components/ThemeSettings/ThemesTab.tsx @@ -21,6 +21,7 @@ import "./themesStyles.css"; import { isPluginEnabled } from "@api/PluginManager"; import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { Button } from "@components/Button"; import { Card } from "@components/Card"; import { Divider } from "@components/Divider"; import { ErrorCard } from "@components/ErrorCard"; @@ -43,7 +44,7 @@ import { getThemeInfo, stripBOM, type UserThemeHeader } from "@utils/themes/bd"; import { usercssParse } from "@utils/themes/usercss"; import { getStylusWebStoreUrl } from "@utils/web"; import { findLazy } from "@webpack"; -import { Alerts, Button, React, showToast, TabBar, TextInput, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common"; +import { Alerts, React, showToast, TabBar, TextInput, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; import type { UserstyleHeader } from "usercss-meta"; @@ -599,7 +600,7 @@ export function CspErrorCard() { {i !== 0 && }
{url} -
diff --git a/src/components/ThemeSettings/UserCSSModal.tsx b/src/components/ThemeSettings/UserCSSModal.tsx index c98c3deef..b4f3e4812 100644 --- a/src/components/ThemeSettings/UserCSSModal.tsx +++ b/src/components/ThemeSettings/UserCSSModal.tsx @@ -90,8 +90,8 @@ function ResetButton({ themeSettings, themeId, close, onReset }: ResetButtonProp onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} - onClick={async () => { - await close(); // close the modal first to stop rendering + onClick={() => { + close(); // close the modal first to stop rendering delete themeSettings[themeId]; onReset(); }}> diff --git a/src/components/settings/DonateButton.tsx b/src/components/settings/DonateButton.tsx index eb2a91e25..cc455305f 100644 --- a/src/components/settings/DonateButton.tsx +++ b/src/components/settings/DonateButton.tsx @@ -16,41 +16,24 @@ * along with this program. If not, see . */ +import { Button } from "@components/Button"; import { Heart } from "@components/Heart"; import { openInviteModal } from "@utils/discord"; import { ButtonProps } from "@vencord/discord-types"; -import { Button, showToast } from "@webpack/common"; - -export function VCDonateButton({ - look = Button.Looks.LINK, - color = Button.Colors.TRANSPARENT, - ...props -}: Partial) { - return ( - - ); -} +import { showToast } from "@webpack/common"; export function DonateButton({ - look = Button.Looks.LINK, - color = Button.Colors.TRANSPARENT, + equicord = false, ...props -}: Partial) { +}: Partial & { equicord?: boolean; }) { + const link = equicord ? "https://github.com/sponsors/thororen1234" : "https://github.com/sponsors/Vendicated"; return ( {!Settings.ignoreResetWarning && ( )} diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx index a01cbee6a..ad77e4bdf 100644 --- a/src/components/settings/tabs/sync/CloudTab.tsx +++ b/src/components/settings/tabs/sync/CloudTab.tsx @@ -19,6 +19,7 @@ import { useSettings } from "@api/Settings"; import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup"; import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync"; +import { Button } from "@components/Button"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Divider } from "@components/Divider"; import { FormSwitch } from "@components/FormSwitch"; @@ -28,7 +29,7 @@ import { Link } from "@components/Link"; import { Paragraph } from "@components/Paragraph"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { Margins } from "@utils/margins"; -import { Alerts, Button, Tooltip, useState } from "@webpack/common"; +import { Alerts, Tooltip, useState } from "@webpack/common"; function validateUrl(url: string) { try { @@ -60,7 +61,7 @@ function SettingsSyncSection() { />
- - diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx index 8e3757c96..b56419dad 100644 --- a/src/components/settings/tabs/updater/Components.tsx +++ b/src/components/settings/tabs/updater/Components.tsx @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { Button } from "@components/Button"; import { Card } from "@components/Card"; import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; @@ -13,7 +14,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { changes, checkForUpdates, update, updateError } from "@utils/updater"; -import { Alerts, Button, React, Toasts, useState } from "@webpack/common"; +import { Alerts, React, Toasts, useState } from "@webpack/common"; import { runWithDispatch } from "./runWithDispatch"; @@ -95,7 +96,7 @@ export function Updatable(props: CommonProps) { {isOutdated && ( )} } +
+ ))} + + + ); +}, { noop: true }); + +const settings = definePluginSettings({ + patterns: { + type: OptionType.COMPONENT, + description: "URL patterns to highlight using glob patterns.", + default: [] as PatternEntry[], + component: PatternsComponent + }, + boldUrls: { + type: OptionType.BOOLEAN, + description: "Make highlighted URLs bold.", + default: false + }, + highlightEmbeds: { + type: OptionType.BOOLEAN, + description: "Also highlight URLs in embed content.", + default: false + } +}); + +function globToRegex(glob: string): RegExp { + const escaped = glob + .replace(/[.+^${}()|[\]\\]/g, "\\$&") + .replace(/\*/g, ".*") + .replace(/\?/g, "."); + return new RegExp(escaped, "i"); +} + +function getMatchingPattern(url: string): PatternEntry | null { + for (const entry of settings.store.patterns) { + const pattern = entry.pattern.trim(); + if (!pattern) continue; + if (globToRegex(pattern).test(url)) return entry; + } + return null; +} + +export default definePlugin({ + name: "UrlHighlighter", + description: "Highlights URLs in messages that match your patterns.", + authors: [EquicordDevs.Prism], + settings, + + patches: [ + { + find: '"messageId","channelId"', + replacement: { + match: /,children:null!=\i\?\i:\i/, + replace: "$&,...$self.getProps(arguments[0])" + } + } + ], + + getProps(props: { href?: string; title?: string; }) { + if (!props.href) return {}; + if (!settings.store.highlightEmbeds && props.href !== props.title) return {}; + + const match = getMatchingPattern(props.href); + if (!match) return {}; + + return { + className: cl(settings.store.boldUrls ? "marked" : "highlight"), + style: { "--vc-url-hl-color": match.color } as React.CSSProperties + }; + } +}); diff --git a/src/equicordplugins/urlHighlighter/styles.css b/src/equicordplugins/urlHighlighter/styles.css new file mode 100644 index 000000000..6f70231b4 --- /dev/null +++ b/src/equicordplugins/urlHighlighter/styles.css @@ -0,0 +1,47 @@ +.vc-url-hl-highlight { + color: var(--vc-url-hl-color) !important; +} + +.vc-url-hl-marked { + color: var(--vc-url-hl-color) !important; + font-weight: 600; +} + +.vc-url-hl-pattern-wrapper { + display: grid; + grid-template-columns: 1fr auto auto; + gap: 8px; + align-items: center; + padding-bottom: 12px; +} + +.vc-url-hl-color-picker { + display: flex; + align-items: center; +} + +.vc-url-hl-color-picker [class^="swatch"] { + width: 32px; + height: 32px; + border-radius: 4px; +} + +.vc-url-hl-remove-button { + width: 36px !important; + height: 36px !important; + min-width: 36px !important; + min-height: 36px !important; + padding: 0 !important; + box-sizing: border-box; +} + +.vc-url-hl-add-button { + display: flex; + align-items: center; + gap: 2px; +} + +.vc-url-hl-icon { + width: 20px; + height: 20px; +} From 614c3b820d4ce0bb30af72204b8ac7740c6a4f16 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:52:58 -0500 Subject: [PATCH 045/291] Fix Patches + Update Contrib Card --- src/components/settings/DonateButton.tsx | 6 ++-- src/components/settings/SpecialCard.css | 25 ++++++++++++++++ .../settings/tabs/vencord/DonateButton.tsx | 13 +++++--- .../settings/tabs/vencord/index.tsx | 30 +++++++++---------- src/plugins/_api/badges/modals.tsx | 4 +-- src/plugins/roleColorEverywhere/index.tsx | 2 +- src/plugins/showMeYourName/index.tsx | 2 +- 7 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/components/settings/DonateButton.tsx b/src/components/settings/DonateButton.tsx index f1fc23a41..e7c562df7 100644 --- a/src/components/settings/DonateButton.tsx +++ b/src/components/settings/DonateButton.tsx @@ -18,6 +18,7 @@ import { Button } from "@components/Button"; import { Heart } from "@components/Heart"; +import { OpenExternalIcon } from "@components/Icons"; import { openInviteModal } from "@utils/discord"; import { ButtonProps } from "@vencord/discord-types"; import { showToast } from "@webpack/common"; @@ -48,7 +49,7 @@ export function InviteButton({ return ( ); } diff --git a/src/components/settings/SpecialCard.css b/src/components/settings/SpecialCard.css index c7af4a914..7c2e51c89 100644 --- a/src/components/settings/SpecialCard.css +++ b/src/components/settings/SpecialCard.css @@ -1,13 +1,38 @@ +.vc-contrib-button, +.vc-invite-button, .vc-donate-button { overflow: visible !important; +} + +.vc-invite-link { + width: 16px; + height: 16px; + margin-left: 8px; + flex-shrink: 0; +} + +.vc-contrib-button { + overflow: visible !important; + margin-top: 1em; color: black; } +.vc-contrib-github { + width: 24px; + height: 24px; + flex-shrink: 0; + transition: transform 0.3s; +} + +.vc-invite-button .vc-invite-link, +.vc-contrib-button .vc-contrib-github, .vc-donate-button .vc-heart-icon { transition: transform 0.3s; margin-right: 0.5em; } +.vc-invite-button:hover .vc-invite-link, +.vc-contrib-github:hover .vc-contrib-github, .vc-donate-button:hover .vc-heart-icon { transform: scale(1.1); z-index: 10; diff --git a/src/components/settings/tabs/vencord/DonateButton.tsx b/src/components/settings/tabs/vencord/DonateButton.tsx index 9b529bd45..c78c1bf0a 100644 --- a/src/components/settings/tabs/vencord/DonateButton.tsx +++ b/src/components/settings/tabs/vencord/DonateButton.tsx @@ -4,7 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { DonateButton } from "@components/settings/DonateButton"; +import { Flex } from "@components/Flex"; +import { DonateButton, InviteButton } from "@components/settings/DonateButton"; import BadgeAPI from "@plugins/_api/badges"; import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants"; import { GuildMemberStore } from "@webpack/common"; @@ -16,8 +17,12 @@ export const isDonor = (userId: string) => !!( export function DonateButtonComponent() { return ( - + + + + ); } diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx index 89d9cf9c1..579ae4a5d 100644 --- a/src/components/settings/tabs/vencord/index.tsx +++ b/src/components/settings/tabs/vencord/index.tsx @@ -17,7 +17,7 @@ import { FormSwitch } from "@components/FormSwitch"; import { Heading } from "@components/Heading"; import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons"; import { Paragraph } from "@components/Paragraph"; -import { DonateButton, InviteButton, openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings"; +import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings"; import { QuickAction, QuickActionCard } from "@components/settings/QuickAction"; import { SpecialCard } from "@components/settings/SpecialCard"; import { gitRemote } from "@shared/vencordUserAgent"; @@ -28,6 +28,7 @@ import { relaunch } from "@utils/native"; import { GuildMemberStore, React, Select, UserStore } from "@webpack/common"; import BadgeAPI from "plugins/_api/badges"; +import { DonateButtonComponent } from "./DonateButton"; import { openNotificationSettingsModal } from "./NotificationSettings"; const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; @@ -156,9 +157,18 @@ function EquicordSettings() { cardImage={COZY_CONTRIB_IMAGE} backgroundImage={CONTRIB_BACKGROUND_IMAGE} backgroundColor="#EDCC87" - buttonTitle="See what you've contributed to" - buttonOnClick={() => openContributorModal(user)} - /> + > + + )}
@@ -330,18 +340,6 @@ function EquicordSettings() { ); } -function DonateButtonComponent() { - return ( - - - - - ); -} - export default wrapTab(EquicordSettings, "Equicord Settings"); export function isEquicordDonor(userId: string): boolean { diff --git a/src/plugins/_api/badges/modals.tsx b/src/plugins/_api/badges/modals.tsx index 8890f86f7..644057b54 100644 --- a/src/plugins/_api/badges/modals.tsx +++ b/src/plugins/_api/badges/modals.tsx @@ -57,7 +57,7 @@ export function VencordDonorModal() { - + @@ -113,7 +113,7 @@ export function EquicordDonorModal() { - + diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 779fc895d..c5d047179 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -135,7 +135,7 @@ export default definePlugin({ }, // Reaction List { - find: ".reactorDefault", + find: ".tagFaded:null", replacement: { match: /tag:"strong"(?=.{0,50}\i\.name)(?<=onContextMenu:.{0,15}\((\i),(\i),\i\).+?)/, replace: "$&,style:$self.getColorStyle($2?.id,$1?.channel?.id)" diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index eab8a0259..48798d7ab 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -1016,7 +1016,7 @@ export default definePlugin({ }, { // Track hovering over reaction popouts. - match: /(return\(0,\i.\i\)\(\i.\i,{className:\i.reactorDefault,)(onContextMenu)/, + match: /(return\(0,\i.\i\)\(\i.\i,{className:\i.reactor,)(onContextMenu)/, replace: "$1onMouseEnter:()=>{$self.addHoveringReactionPopout(arguments[0].user.id)},onMouseLeave:()=>{$self.removeHoveringReactionPopout(arguments[0].user.id)},$2" } ] From faf342312e3796c014aa3d0388df358332e7114c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:10:42 -0500 Subject: [PATCH 046/291] CSS: Fix Contrib Coloring On Hover --- src/components/settings/SpecialCard.css | 18 ++++++++++++++++-- .../settings/tabs/vencord/DonateButton.tsx | 4 ++-- src/plugins/_api/badges/modals.tsx | 4 ++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/settings/SpecialCard.css b/src/components/settings/SpecialCard.css index 7c2e51c89..083cb1d67 100644 --- a/src/components/settings/SpecialCard.css +++ b/src/components/settings/SpecialCard.css @@ -1,7 +1,15 @@ -.vc-contrib-button, .vc-invite-button, .vc-donate-button { overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #c3a3ce 15%, pink); +} + +.vc-invite-button:hover, +.vc-donate-button:hover { + overflow: visible !important; + color: black; + background-color: color-mix(in srgb, hotpink 15%, pink); } .vc-invite-link { @@ -15,6 +23,12 @@ overflow: visible !important; margin-top: 1em; color: black; + background-color: color-mix(in srgb, #EDCC87 95%, black); +} + +.vc-contrib-button:hover { + color: black; + background-color: color-mix(in srgb, #EDCC87 90%, black); } .vc-contrib-github { @@ -32,7 +46,7 @@ } .vc-invite-button:hover .vc-invite-link, -.vc-contrib-github:hover .vc-contrib-github, +.vc-contrib-button:hover .vc-contrib-github, .vc-donate-button:hover .vc-heart-icon { transform: scale(1.1); z-index: 10; diff --git a/src/components/settings/tabs/vencord/DonateButton.tsx b/src/components/settings/tabs/vencord/DonateButton.tsx index c78c1bf0a..c9f0ebce7 100644 --- a/src/components/settings/tabs/vencord/DonateButton.tsx +++ b/src/components/settings/tabs/vencord/DonateButton.tsx @@ -20,9 +20,9 @@ export function DonateButtonComponent() { + style={{ marginTop: "1em" }} /> + style={{ marginTop: "1em" }} /> ); } diff --git a/src/plugins/_api/badges/modals.tsx b/src/plugins/_api/badges/modals.tsx index 644057b54..8890f86f7 100644 --- a/src/plugins/_api/badges/modals.tsx +++ b/src/plugins/_api/badges/modals.tsx @@ -57,7 +57,7 @@ export function VencordDonorModal() { - + @@ -113,7 +113,7 @@ export function EquicordDonorModal() { - + From ce04141b7a04d6fe9ec3888cc12f60e0acfb612b Mon Sep 17 00:00:00 2001 From: OMeta <93689229+OMetaVR@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:07:01 -0500 Subject: [PATCH 047/291] feat(plugin): ScheduledMessages (#534) * scheduledMessages plugin developed and created * some fixes * Updated buttons to use @components/button * webpack message model patch added, fixed media display/persistence, added day counter, and improve rate limit handling * Refactored codebase of plugin thanks to Prism and found a new method for handling reactions * now using isPrivate() for DM checks, and isMultiUserDM check removed because of redundancy, simplified into isDM and isGroupDM for 1:1 DMs and group DMs respectively. Now using discord's channel.isPrivate() method as opposed to the isDM, isGroupDM and isMultiUserDM checks * I did as I was told --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- .../components/ChatBarButton.tsx | 54 ++ .../scheduledMessages/components/Icons.tsx | 57 ++ .../components/MessageAccessory.tsx | 39 ++ .../components/ScheduleTimeModal.tsx | 155 +++++ .../components/ViewScheduledModal.tsx | 111 ++++ .../scheduledMessages/index.tsx | 181 ++++++ .../scheduledMessages/styles.css | 276 +++++++++ .../scheduledMessages/types.ts | 57 ++ .../scheduledMessages/utils.ts | 567 ++++++++++++++++++ 9 files changed, 1497 insertions(+) create mode 100644 src/equicordplugins/scheduledMessages/components/ChatBarButton.tsx create mode 100644 src/equicordplugins/scheduledMessages/components/Icons.tsx create mode 100644 src/equicordplugins/scheduledMessages/components/MessageAccessory.tsx create mode 100644 src/equicordplugins/scheduledMessages/components/ScheduleTimeModal.tsx create mode 100644 src/equicordplugins/scheduledMessages/components/ViewScheduledModal.tsx create mode 100644 src/equicordplugins/scheduledMessages/index.tsx create mode 100644 src/equicordplugins/scheduledMessages/styles.css create mode 100644 src/equicordplugins/scheduledMessages/types.ts create mode 100644 src/equicordplugins/scheduledMessages/utils.ts diff --git a/src/equicordplugins/scheduledMessages/components/ChatBarButton.tsx b/src/equicordplugins/scheduledMessages/components/ChatBarButton.tsx new file mode 100644 index 000000000..a7035cb62 --- /dev/null +++ b/src/equicordplugins/scheduledMessages/components/ChatBarButton.tsx @@ -0,0 +1,54 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; +import { useEffect, useState } from "@webpack/common"; + +import { CalendarIcon } from "./Icons"; +import { openViewScheduledModal } from "./ViewScheduledModal"; + +export let isScheduleModeEnabled = false; + +const stateUpdaters = new Set<(value: boolean) => void>(); + +export function setScheduleModeEnabled(value: boolean): void { + isScheduleModeEnabled = value; + stateUpdaters.forEach(updater => updater(value)); +} + +export const ScheduledMessagesButton: ChatBarButtonFactory = ({ isMainChat }) => { + const [enabled, setEnabled] = useState(isScheduleModeEnabled); + + useEffect(() => { + stateUpdaters.add(setEnabled); + return () => { stateUpdaters.delete(setEnabled); }; + }, []); + + if (!isMainChat) return null; + + const toggleScheduleMode = () => { + const newValue = !enabled; + setScheduleModeEnabled(newValue); + }; + + const tooltip = enabled + ? "Schedule Mode ON (click to disable, right-click for list)" + : "Schedule Mode OFF (click to enable, right-click for list)"; + + return ( + { + e.preventDefault(); + openViewScheduledModal(); + }} + buttonProps={{ "aria-haspopup": "dialog" }} + > + + + ); +}; diff --git a/src/equicordplugins/scheduledMessages/components/Icons.tsx b/src/equicordplugins/scheduledMessages/components/Icons.tsx new file mode 100644 index 000000000..1dc4c4e03 --- /dev/null +++ b/src/equicordplugins/scheduledMessages/components/Icons.tsx @@ -0,0 +1,57 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +interface IconProps { + width?: number | string; + height?: number | string; + className?: string; + color?: string; +} + +export function CalendarIcon({ width = 20, height = 20, className, color }: IconProps) { + return ( + + ); +} + +export function TimerIcon({ width = 20, height = 20, className }: IconProps) { + return ( + + ); +} + +export function ErrorIcon() { + return ( + + + + ); +} diff --git a/src/equicordplugins/scheduledMessages/components/MessageAccessory.tsx b/src/equicordplugins/scheduledMessages/components/MessageAccessory.tsx new file mode 100644 index 000000000..fe69de80c --- /dev/null +++ b/src/equicordplugins/scheduledMessages/components/MessageAccessory.tsx @@ -0,0 +1,39 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; + +import { ExtendedMessage } from "../types"; +import { phantomMessageMap } from "../utils"; +import { TimerIcon } from "./Icons"; + +const cl = classNameFactory("vc-scheduled-msg-"); + +export function MessageAccessory({ message }: { message: ExtendedMessage; }) { + const data = phantomMessageMap.get(message?.id) ?? message?.scheduledMessageData; + if (!data) return null; + + const { scheduledTime } = data; + const timeLeft = scheduledTime - Date.now(); + + let timeLeftStr = ""; + if (timeLeft > 0) { + const mins = Math.floor(timeLeft / 60000); + const hrs = Math.floor(mins / 60); + const days = Math.floor(hrs / 24); + + timeLeftStr = days > 0 ? ` (${days}d ${hrs % 24}h remaining)` + : hrs > 0 ? ` (${hrs}h ${mins % 60}m remaining)` + : ` (${mins}m remaining)`; + } + + return ( +
+ + Scheduled for {new Date(scheduledTime).toLocaleString()}{timeLeftStr} +
+ ); +} diff --git a/src/equicordplugins/scheduledMessages/components/ScheduleTimeModal.tsx b/src/equicordplugins/scheduledMessages/components/ScheduleTimeModal.tsx new file mode 100644 index 000000000..4b540e0fa --- /dev/null +++ b/src/equicordplugins/scheduledMessages/components/ScheduleTimeModal.tsx @@ -0,0 +1,155 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { Button } from "@components/Button"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Heading } from "@components/Heading"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; +import { findByPropsLazy } from "@webpack"; +import { ChannelStore, DraftType, showToast, TextInput, Toasts, UploadManager, useState } from "@webpack/common"; + +import { ScheduledAttachment } from "../types"; +import { addScheduledMessage, getChannelDisplayInfo } from "../utils"; +import { ErrorIcon } from "./Icons"; + +const cl = classNameFactory("vc-scheduled-msg-"); +const ComponentDispatch = findByPropsLazy("dispatchToLastSubscribed"); + +function ScheduleTimeModalInner({ channelId, content, attachments, rootProps, close }: { + channelId: string; + content: string; + attachments?: ScheduledAttachment[]; + rootProps: ModalProps; + close: () => void; +}) { + const [scheduleType, setScheduleType] = useState<"delay" | "time">("delay"); + const [delayMinutes, setDelayMinutes] = useState("5"); + const [scheduledDateTime, setScheduledDateTime] = useState(""); + const [error, setError] = useState(""); + + const { name, avatar } = getChannelDisplayInfo(channelId); + const channel = ChannelStore.getChannel(channelId); + if (!channel) return null; + + const isDM = channel.isPrivate(); + + const handleSchedule = async () => { + let scheduledTime: number; + + if (scheduleType === "delay") { + const minutes = parseInt(delayMinutes, 10); + if (isNaN(minutes) || minutes < 1) { + setError("Please enter a valid delay (minimum 1 minute)"); + return; + } + scheduledTime = Date.now() + minutes * 60 * 1000; + } else { + const dateTime = new Date(scheduledDateTime).getTime(); + if (isNaN(dateTime) || dateTime <= Date.now()) { + setError("Please select a future date and time"); + return; + } + scheduledTime = dateTime; + } + + const result = await addScheduledMessage(channelId, content, scheduledTime, attachments); + + if (result.success) { + ComponentDispatch.dispatchToLastSubscribed("CLEAR_TEXT"); + UploadManager.clearAll(channelId, DraftType.ChannelMessage); + showToast("Message scheduled!", Toasts.Type.SUCCESS); + close(); + } else { + setError(result.error ?? "Failed to schedule message"); + } + }; + + return ( + + + Schedule Message + + + + +
+ {avatar && } + + Scheduling for: {isDM ? name : `#${name}`} + +
+ + Schedule Type +
+ + +
+ + {scheduleType === "delay" ? ( + <> + Delay (minutes) + + + ) : ( + <> + Date & Time + setScheduledDateTime(e.target.value)} + min={new Date().toISOString().slice(0, 16)} + /> + + )} + + {error && ( +
+ + {error} +
+ )} +
+ + + + + +
+ ); +} + +export const ScheduleTimeModal = ErrorBoundary.wrap(ScheduleTimeModalInner, { noop: true }); + +export function openScheduleTimeModal(channelId: string, content: string, attachments?: ScheduledAttachment[]): void { + const key = openModal(props => ( + closeModal(key)} + /> + )); +} diff --git a/src/equicordplugins/scheduledMessages/components/ViewScheduledModal.tsx b/src/equicordplugins/scheduledMessages/components/ViewScheduledModal.tsx new file mode 100644 index 000000000..eb6204967 --- /dev/null +++ b/src/equicordplugins/scheduledMessages/components/ViewScheduledModal.tsx @@ -0,0 +1,111 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { Button } from "@components/Button"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Heading } from "@components/Heading"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; +import { ChannelStore, showToast, Toasts, useState } from "@webpack/common"; + +import { clearAllScheduledMessages, getChannelDisplayInfo, getScheduledMessages, removeScheduledMessage } from "../utils"; +import { CalendarIcon, TimerIcon } from "./Icons"; + +const cl = classNameFactory("vc-scheduled-msg-"); + +interface ViewScheduledModalProps { + rootProps: ModalProps; + close: () => void; +} + +function ViewScheduledModalInner({ rootProps, close }: ViewScheduledModalProps) { + const [messages, setMessages] = useState(getScheduledMessages()); + + const handleDelete = async (id: string) => { + await removeScheduledMessage(id); + setMessages(getScheduledMessages()); + showToast("Scheduled message removed", Toasts.Type.SUCCESS); + }; + + const handleClearAll = async () => { + await clearAllScheduledMessages(); + setMessages([]); + showToast("All scheduled messages cleared", Toasts.Type.SUCCESS); + }; + + return ( + + + Scheduled Messages + {messages.length > 0 && ( + + )} + + + + + {!messages.length ? ( +
+ + No scheduled messages +
+ ) : ( +
+ {messages.map(msg => { + const { name, avatar } = getChannelDisplayInfo(msg.channelId); + const channel = ChannelStore.getChannel(msg.channelId); + if (!channel) return null; + + const isDM = channel.isPrivate(); + const displayContent = msg.content.length > 200 + ? msg.content.slice(0, 200) + "..." + : msg.content; + + return ( +
+
+
+ {avatar && } + + {isDM ? name : `#${name}`} + +
+
+ + {new Date(msg.scheduledTime).toLocaleString()} +
+
{displayContent}
+
+ +
+ ); + })} +
+ )} +
+ + + + +
+ ); +} + +export const ViewScheduledModal = ErrorBoundary.wrap(ViewScheduledModalInner, { noop: true }); + +export function openViewScheduledModal(): void { + const key = openModal(props => ( + closeModal(key)} /> + )); +} diff --git a/src/equicordplugins/scheduledMessages/index.tsx b/src/equicordplugins/scheduledMessages/index.tsx new file mode 100644 index 000000000..320058c83 --- /dev/null +++ b/src/equicordplugins/scheduledMessages/index.tsx @@ -0,0 +1,181 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { MessageOptions } from "@api/MessageEvents"; +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; +import definePlugin, { OptionType } from "@utils/types"; + +import { isScheduleModeEnabled, ScheduledMessagesButton, setScheduleModeEnabled } from "./components/ChatBarButton"; +import { CalendarIcon } from "./components/Icons"; +import { MessageAccessory } from "./components/MessageAccessory"; +import { openScheduleTimeModal } from "./components/ScheduleTimeModal"; +import { openViewScheduledModal } from "./components/ViewScheduledModal"; +import { FluxReactionEvent, ScheduledAttachment } from "./types"; +import { + cleanupAllPhantomMessages, + handleReactionAdd, + handleReactionRemove, + isPhantomMessage, + loadScheduledMessages, + recreatePhantomMessages, + resyncPhantomReactions, + startScheduler, + stopScheduler +} from "./utils"; + +const logger = new Logger("ScheduledMessages"); + +export const settings = definePluginSettings({ + maxMessagesPerMinute: { + type: OptionType.SLIDER, + description: "Max scheduled messages per channel that can fire in the same minute.", + markers: [1, 2, 3, 4, 5], + default: 3, + stickToMarkers: true + }, + checkIntervalSeconds: { + type: OptionType.SLIDER, + description: "How often to check for messages to send (seconds).", + markers: [5, 10, 15, 30, 60], + default: 10, + stickToMarkers: true, + onChange: () => { + stopScheduler(); + startScheduler(); + } + }, + showNotifications: { + type: OptionType.BOOLEAN, + description: "Show toast notifications when messages are sent.", + default: true + }, + showPhantomMessages: { + type: OptionType.BOOLEAN, + description: "Show scheduled messages as phantom messages in chat.", + default: true + } +}); + +function handleReactionEvent(event: FluxReactionEvent): void { + const { messageId, channelId, emoji } = event; + + logger.info("handleReactionEvent:", { type: event.type, messageId, optimistic: event.optimistic, emoji: emoji?.name }); + + if (!messageId || !channelId || !emoji) { + logger.warn("handleReactionEvent: Missing required fields"); + return; + } + + const isPhantom = isPhantomMessage(messageId); + logger.info("handleReactionEvent: isPhantomMessage =", isPhantom); + + if (!isPhantom) return; + + if (event.optimistic) { + // User-initiated reaction change - update our data + logger.info("handleReactionEvent: Processing optimistic", event.type); + if (event.type === "MESSAGE_REACTION_ADD") { + handleReactionAdd(messageId, channelId, emoji); + } else if (event.type === "MESSAGE_REACTION_REMOVE") { + handleReactionRemove(messageId, channelId, emoji); + } + } else { + logger.info("handleReactionEvent: Non-optimistic event, scheduling resync"); + setTimeout(() => { + resyncPhantomReactions(messageId, channelId); + }, 50); + } +} + +export default definePlugin({ + name: "ScheduledMessages", + description: "Schedule messages to be sent at a specific time or after a delay.", + authors: [EquicordDevs.mmeta, EquicordDevs.Prism], + settings, + + flux: { + MESSAGE_REACTION_ADD: handleReactionEvent, + MESSAGE_REACTION_REMOVE: handleReactionEvent + }, + + chatBarButton: { + icon: CalendarIcon, + render: ScheduledMessagesButton + }, + + toolboxActions: { + "View Scheduled Messages": openViewScheduledModal + }, + + patches: [ + { + find: "}addReaction(", + replacement: { + match: /this\.customRenderedContent=(\i)\.customRenderedContent,/, + replace: "$&this.scheduledMessageData=$1.scheduledMessageData," + } + } + ], + + renderMessageAccessory(props: Record) { + return ; + }, + + async onBeforeMessageSend(channelId: string, messageObj: { content: string; }, options: MessageOptions) { + if (!isScheduleModeEnabled) return; + if (!messageObj.content.trim() && !options.uploads?.length) return; + + setScheduleModeEnabled(false); + + let attachments: ScheduledAttachment[] | undefined; + + if (options.uploads?.length) { + attachments = []; + + for (const upload of options.uploads) { + const { file } = upload.item; + + try { + const base64 = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result as string; + resolve(result.split(",")[1] ?? result); + }; + reader.onerror = () => reject(reader.error); + reader.readAsDataURL(file); + }); + + attachments.push({ + filename: upload.filename, + data: base64, + type: file.type + }); + } catch { + continue; + } + } + } + + openScheduleTimeModal(channelId, messageObj.content, attachments); + return { cancel: true }; + }, + + async start() { + await loadScheduledMessages(); + startScheduler(); + recreatePhantomMessages(); + }, + + stop() { + stopScheduler(); + cleanupAllPhantomMessages(); + } +}); diff --git a/src/equicordplugins/scheduledMessages/styles.css b/src/equicordplugins/scheduledMessages/styles.css new file mode 100644 index 000000000..24478f6fe --- /dev/null +++ b/src/equicordplugins/scheduledMessages/styles.css @@ -0,0 +1,276 @@ +/* Modal Header */ +.vc-scheduled-msg-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + gap: 16px; +} + +.vc-scheduled-msg-modal-title { + flex: 1; + margin: 0 !important; + color: var(--header-primary); + font-size: 20px; + font-weight: 600; +} + +/* Modal Content */ +.vc-scheduled-msg-modal-content { + padding: 0 16px 16px; +} + +/* Modal Footer */ +.vc-scheduled-msg-modal-footer { + display: flex; + flex-direction: row-reverse; + justify-content: flex-start; + align-items: center; + gap: 8px; + padding: 16px; +} + +/* Channel Info */ +.vc-scheduled-msg-channel-info { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 16px; + padding: 12px; + background: var(--background-secondary); + border-radius: 8px; +} + +.vc-scheduled-msg-channel-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +/* Dark theme channel text */ +.theme-dark .vc-scheduled-msg-channel-text { + color: #dbdee1 !important; +} + +.theme-dark .vc-scheduled-msg-channel-text strong { + color: #f2f3f5 !important; +} + +/* Light theme channel text */ +.theme-light .vc-scheduled-msg-channel-text { + color: #313338 !important; +} + +.theme-light .vc-scheduled-msg-channel-text strong { + color: #060607 !important; +} + +/* Field Labels */ +.vc-scheduled-msg-field-label { + margin: 16px 0 8px !important; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +/* Dark theme field labels */ +.theme-dark .vc-scheduled-msg-field-label { + color: #b5bac1 !important; +} + +/* Light theme field labels */ +.theme-light .vc-scheduled-msg-field-label { + color: #4e5058 !important; +} + +/* Schedule Type Buttons */ +.vc-scheduled-msg-schedule-type-buttons { + display: flex; + gap: 8px; +} + +/* DateTime Input */ +.vc-scheduled-msg-datetime-input { + width: 100%; + padding: 10px; + border: none; + border-radius: 3px; + background: var(--input-background); + font-size: 16px; +} + +.vc-scheduled-msg-datetime-input:focus { + outline: none; +} + +/* Dark theme datetime */ +.theme-dark .vc-scheduled-msg-datetime-input { + color: #dbdee1 !important; + color-scheme: dark; +} + +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-calendar-picker-indicator { + filter: invert(1); +} + +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-fields-wrapper, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-text, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-month-field, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-day-field, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-year-field, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-hour-field, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-minute-field, +.theme-dark .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-ampm-field { + color: #dbdee1 !important; +} + +/* Light theme datetime */ +.theme-light .vc-scheduled-msg-datetime-input { + color: #313338 !important; + color-scheme: light; +} + +.theme-light .vc-scheduled-msg-datetime-input::-webkit-calendar-picker-indicator { + filter: none; +} + +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-fields-wrapper, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-text, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-month-field, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-day-field, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-year-field, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-hour-field, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-minute-field, +.theme-light .vc-scheduled-msg-datetime-input::-webkit-datetime-edit-ampm-field { + color: #313338 !important; +} + +/* Error Display */ +.vc-scheduled-msg-error { + display: flex; + align-items: center; + gap: 10px; + margin-top: 16px; + padding: 12px; + background: var(--info-danger-background); + border-radius: 5px; + color: var(--text-normal); + font-size: 14px; +} + +.vc-scheduled-msg-error svg { + flex-shrink: 0; + color: var(--status-danger); +} + +/* Empty State */ +.vc-scheduled-msg-empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 48px 32px; + color: var(--text-muted); +} + +.vc-scheduled-msg-empty-state svg { + opacity: 0.5; +} + +/* Message List */ +.vc-scheduled-msg-message-list { + display: flex; + flex-direction: column; + gap: 12px; + max-height: 400px; + overflow-y: auto; +} + +.vc-scheduled-msg-message-item { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + padding: 12px; + background: var(--background-secondary); + border-radius: 8px; +} + +.vc-scheduled-msg-message-info { + flex: 1; + min-width: 0; +} + +.vc-scheduled-msg-message-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.vc-scheduled-msg-message-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +.vc-scheduled-msg-message-channel { + font-weight: 600; + color: var(--header-primary); +} + +.vc-scheduled-msg-message-time { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-muted); + margin-bottom: 8px; +} + +.vc-scheduled-msg-message-content { + overflow-wrap: break-word; + white-space: pre-wrap; +} + +/* Dark theme message content */ +.theme-dark .vc-scheduled-msg-message-content { + color: #dbdee1 !important; +} + +/* Light theme message content */ +.theme-light .vc-scheduled-msg-message-content { + color: #313338 !important; +} + +/* Phantom Message Styles */ +.vc-scheduled-msg-phantom { + opacity: 0.5 !important; +} + +.vc-scheduled-msg-phantom:hover { + opacity: 0.7 !important; +} + +/* Scheduled message accessory */ +.vc-scheduled-msg-accessory { + display: flex; + align-items: center; + gap: 6px; + margin-top: 4px; + padding: 4px 8px; + background: var(--background-secondary); + border-radius: 4px; + font-size: 12px; + color: var(--text-muted); + pointer-events: none; + width: fit-content; +} \ No newline at end of file diff --git a/src/equicordplugins/scheduledMessages/types.ts b/src/equicordplugins/scheduledMessages/types.ts new file mode 100644 index 000000000..4e7bdc30f --- /dev/null +++ b/src/equicordplugins/scheduledMessages/types.ts @@ -0,0 +1,57 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Message } from "@vencord/discord-types"; + +export interface ScheduledReaction { + emoji: { + id: string | null; + name: string; + animated?: boolean; + }; + count: number; +} + +export interface ScheduledAttachment { + filename: string; + data: string; + type: string; +} + +export interface ScheduledMessage { + id: string; + channelId: string; + content: string; + scheduledTime: number; + createdAt: number; + reactions?: ScheduledReaction[]; + attachments?: ScheduledAttachment[]; +} + +export interface PhantomMessageData { + scheduledTime: number; + messageId: string; + channelId: string; +} + +export interface FluxReactionEvent { + type: string; + messageId?: string; + channelId?: string; + emoji?: { + id: string | null; + name: string; + animated?: boolean; + }; + optimistic?: boolean; +} + +export interface ExtendedMessage extends Message { + scheduledMessageData?: { + scheduledTime: number; + messageId: string; + }; +} diff --git a/src/equicordplugins/scheduledMessages/utils.ts b/src/equicordplugins/scheduledMessages/utils.ts new file mode 100644 index 000000000..c3b1dca97 --- /dev/null +++ b/src/equicordplugins/scheduledMessages/utils.ts @@ -0,0 +1,567 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import * as DataStore from "@api/DataStore"; +import { Logger } from "@utils/Logger"; +import { CloudUpload, Message } from "@vencord/discord-types"; +import { CloudUploadPlatform } from "@vencord/discord-types/enums"; +import { findLazy } from "@webpack"; +import { ChannelStore, Constants, FluxDispatcher, GuildStore, IconUtils, MessageActions, MessageStore, RestAPI, showToast, SnowflakeUtils, Toasts, UserStore } from "@webpack/common"; + +import { settings } from "."; +import { PhantomMessageData, ScheduledAttachment, ScheduledMessage, ScheduledReaction } from "./types"; + +const logger = new Logger("ScheduledMessages"); + +const CloudUploader = findLazy(m => m.prototype?.trackUploadFinished) as typeof CloudUpload; + +const STORAGE_KEY = "ScheduledMessages_queue"; + +let scheduledMessages: ScheduledMessage[] = []; +let checkInterval: ReturnType | null = null; +let isProcessingMessages = false; + +export const phantomMessageMap = new Map(); +const pendingReactions = new Map(); + +const recentReactionChanges = new Map(); +const REACTION_COOLDOWN_MS = 2000; + +const pendingRecreations = new Map>(); +const RECREATE_DEBOUNCE_MS = 300; + +export async function loadScheduledMessages(): Promise { + const saved = await DataStore.get(STORAGE_KEY); + scheduledMessages = Array.isArray(saved) ? saved : []; +} + +async function saveScheduledMessages(): Promise { + await DataStore.set(STORAGE_KEY, scheduledMessages); +} + +export function getScheduledMessages(): ScheduledMessage[] { + return [...scheduledMessages]; +} + +export function getChannelDisplayInfo(channelId: string): { name: string; avatar: string; } { + const channel = ChannelStore.getChannel(channelId); + if (!channel) return { name: "Unknown", avatar: "" }; + + if (channel.isDM()) { + const user = channel.recipients?.[0] ? UserStore.getUser(channel.recipients[0]) : null; + return user + ? { name: user.globalName ?? user.username, avatar: IconUtils.getUserAvatarURL(user, true, 64) } + : { name: "DM", avatar: "" }; + } + + if (channel.isGroupDM() || channel.isMultiUserDM()) { + return { name: channel.name || "Group DM", avatar: IconUtils.getChannelIconURL(channel) ?? "" }; + } + + const guild = GuildStore.getGuild(channel.guild_id); + return { + name: channel.name || "Channel", + avatar: guild ? IconUtils.getGuildIconURL({ id: guild.id, icon: guild.icon, canAnimate: true, size: 512 }) ?? "" : "" + }; +} + + +function getImageDimensions(dataUrl: string): Promise<{ width: number; height: number; }> { + return new Promise(resolve => { + const img = new Image(); + img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight }); + img.onerror = () => resolve({ width: 400, height: 300 }); + img.src = dataUrl; + }); +} + +function getVideoPreview(dataUrl: string): Promise<{ width: number; height: number; previewUrl: string; } | null> { + return new Promise(resolve => { + const video = document.createElement("video"); + video.preload = "metadata"; + video.muted = true; + video.playsInline = true; + + const timeout = setTimeout(() => { video.src = ""; resolve(null); }, 5000); + + video.onloadeddata = () => { clearTimeout(timeout); video.currentTime = 0; }; + video.onerror = () => { clearTimeout(timeout); resolve(null); }; + + video.onseeked = () => { + const canvas = document.createElement("canvas"); + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + const ctx = canvas.getContext("2d"); + + if (!ctx) { resolve(null); video.src = ""; return; } + + ctx.drawImage(video, 0, 0); + const size = Math.min(canvas.width, canvas.height) * 0.2; + const cx = canvas.width / 2, cy = canvas.height / 2; + + ctx.fillStyle = "rgba(0, 0, 0, 0.6)"; + ctx.beginPath(); + ctx.arc(cx, cy, size, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = "white"; + ctx.beginPath(); + ctx.moveTo(cx - size * 0.3, cy - size * 0.4); + ctx.lineTo(cx - size * 0.3, cy + size * 0.4); + ctx.lineTo(cx + size * 0.5, cy); + ctx.closePath(); + ctx.fill(); + + resolve({ width: video.videoWidth, height: video.videoHeight, previewUrl: canvas.toDataURL("image/png") }); + video.src = ""; + }; + + video.src = dataUrl; + }); +} + +async function buildPhantomAttachments(attachments: ScheduledAttachment[]) { + const result: { id: string; filename: string; size: number; content_type: string; url?: string; proxy_url?: string; width?: number; height?: number; }[] = []; + + for (let idx = 0; idx < attachments.length; idx++) { + const att = attachments[idx]; + const dataUrl = `data:${att.type};base64,${att.data}`; + const attachment: typeof result[0] = { + id: String(idx), + filename: att.filename, + size: Math.ceil(att.data.length * 0.75), + content_type: att.type + }; + + if (att.type.startsWith("image/")) { + const dims = await getImageDimensions(dataUrl); + Object.assign(attachment, { url: dataUrl, proxy_url: dataUrl, ...dims }); + } else if (["video/mp4", "video/webm", "video/ogg"].includes(att.type)) { + const preview = await getVideoPreview(dataUrl); + if (preview) { + Object.assign(attachment, { + content_type: "image/png", + url: preview.previewUrl, + proxy_url: preview.previewUrl, + width: preview.width, + height: preview.height, + filename: att.filename.replace(/\.[^.]+$/, "_preview.png") + }); + } + } + + result.push(attachment); + } + + return result; +} + +export async function createPhantomMessage(msg: ScheduledMessage): Promise { + if (!settings.store.showPhantomMessages) return; + + const currentUser = UserStore.getCurrentUser(); + if (!currentUser) return; + + const messageId = `scheduled-${msg.id}`; + + phantomMessageMap.set(messageId, { scheduledTime: msg.scheduledTime, messageId: msg.id, channelId: msg.channelId }); + if (msg.reactions?.length) pendingReactions.set(msg.id, [...msg.reactions]); + + const attachments = msg.attachments?.length ? await buildPhantomAttachments(msg.attachments) : []; + + const initialReactions = (msg.reactions ?? []).map(r => ({ + emoji: r.emoji, + count: r.count, + count_details: { burst: 0, normal: r.count }, + me: true, + me_burst: false, + burst_count: 0, + burst_colors: [], + burst_me: false + })); + + const messagesLoaded = MessageStore.hasPresent(msg.channelId) + ? Promise.resolve() + : MessageActions.fetchMessages({ channelId: msg.channelId }); + + messagesLoaded.then(() => { + FluxDispatcher.dispatch({ + type: "MESSAGE_CREATE", + channelId: msg.channelId, + message: { + id: messageId, + channel_id: msg.channelId, + author: { + id: currentUser.id, + username: currentUser.username, + discriminator: currentUser.discriminator || "0", + avatar: currentUser.avatar, + global_name: currentUser.globalName ?? null, + bot: false + }, + content: msg.content, + timestamp: new Date().toISOString(), + edited_timestamp: null, + tts: false, + mention_everyone: false, + mentions: [], + mention_roles: [], + attachments, + embeds: [], + pinned: false, + type: 0, + flags: 0, + components: [], + reactions: initialReactions, + nonce: messageId, + scheduledMessageData: { scheduledTime: msg.scheduledTime, messageId: msg.id } + }, + optimistic: true, + sendMessageOptions: {}, + isPushNotification: false + }); + + applyPhantomClassToMessage(msg.channelId, messageId); + }).catch(() => { }); +} + +function applyPhantomClassToMessage(channelId: string, messageId: string): void { + const tryApply = (retries = 0) => { + const el = document.getElementById(`chat-messages-${channelId}-${messageId}`); + if (el) { + el.classList.add("vc-scheduled-msg-phantom"); + return true; + } + if (retries < 20) { + setTimeout(() => tryApply(retries + 1), 100 + retries * 50); + } + return false; + }; + + setTimeout(() => tryApply(), 50); +} + +function removePhantomMessage(msg: ScheduledMessage): void { + const messageId = `scheduled-${msg.id}`; + phantomMessageMap.delete(messageId); + FluxDispatcher.dispatch({ type: "MESSAGE_DELETE", channelId: msg.channelId, id: messageId, mlDeleted: true }); +} + +function updatePhantomReactions(messageId: string, channelId: string, reactions: ScheduledReaction[]): void { + const existingTimeout = pendingRecreations.get(messageId); + if (existingTimeout) { + clearTimeout(existingTimeout); + } + + const timeout = setTimeout(() => { + pendingRecreations.delete(messageId); + doRecreatePhantomMessage(messageId, channelId, reactions); + }, RECREATE_DEBOUNCE_MS); + + pendingRecreations.set(messageId, timeout); +} + +function doRecreatePhantomMessage(messageId: string, channelId: string, reactions: ScheduledReaction[]): void { + logger.info("doRecreatePhantomMessage: Recreating phantom message with", reactions.length, "reactions"); + + const phantomData = phantomMessageMap.get(messageId); + if (!phantomData) { + logger.warn("doRecreatePhantomMessage: No phantom data found"); + return; + } + + const msg = scheduledMessages.find(m => m.id === phantomData.messageId); + if (!msg) { + logger.warn("doRecreatePhantomMessage: No scheduled message found"); + return; + } + + FluxDispatcher.dispatch({ + type: "MESSAGE_DELETE", + channelId, + id: messageId, + mlDeleted: true + }); + + setTimeout(() => { + msg.reactions = reactions; + createPhantomMessage(msg); + }, 50); +} + + +async function uploadAttachment(channelId: string, att: ScheduledAttachment): Promise<{ id: string; filename: string; uploaded_filename: string; } | null> { + return new Promise(resolve => { + const bytes = Uint8Array.from(atob(att.data), c => c.charCodeAt(0)); + const file = new File([bytes], att.filename, { type: att.type }); + const upload = new CloudUploader({ file, platform: CloudUploadPlatform.WEB }, channelId); + + upload.on("complete", () => resolve({ id: "", filename: upload.filename, uploaded_filename: upload.uploadedFilename })); + upload.on("error", () => resolve(null)); + upload.upload(); + }); +} + +async function postMessage(channelId: string, content: string, attachments?: { id: string; filename: string; uploaded_filename: string; }[]): Promise { + await RestAPI.post({ + url: Constants.Endpoints.MESSAGES(channelId), + body: { + content, + nonce: SnowflakeUtils.fromTimestamp(Date.now()), + ...(attachments?.length ? { channel_id: channelId, sticker_ids: [], type: 0, attachments } : {}) + } + }); +} + +async function addReactionsToMessage(channelId: string, messageId: string, reactions: ScheduledReaction[]): Promise { + for (const reaction of reactions) { + const emojiStr = reaction.emoji.id + ? `${reaction.emoji.name}:${reaction.emoji.id}` + : encodeURIComponent(reaction.emoji.name); + + for (let attempt = 0; attempt < 5; attempt++) { + try { + await RestAPI.put({ url: `/channels/${channelId}/messages/${messageId}/reactions/${emojiStr}/@me` }); + break; + } catch (e) { + const err = e as { status?: number; body?: { retry_after?: number; }; }; + if (err.status === 429 || err.body?.retry_after) { + await new Promise(r => setTimeout(r, (err.body?.retry_after ?? 1) * 1000 + 100)); + } else if (err.status === 404) { + await new Promise(r => setTimeout(r, 1000 * (attempt + 1))); + } else { + break; + } + } + } + await new Promise(r => setTimeout(r, 350)); + } +} + +async function sendScheduledMessage(msg: ScheduledMessage): Promise { + try { + if (!ChannelStore.getChannel(msg.channelId)) return false; + + const reactions = pendingReactions.get(msg.id) ?? msg.reactions ?? []; + removePhantomMessage(msg); + pendingReactions.delete(msg.id); + + if (msg.attachments?.length) { + const uploaded = (await Promise.all(msg.attachments.map((att, i) => + uploadAttachment(msg.channelId, att).then(r => r ? { ...r, id: String(i) } : null) + ))).filter(Boolean) as { id: string; filename: string; uploaded_filename: string; }[]; + + await postMessage(msg.channelId, msg.content, uploaded.length ? uploaded : undefined); + } else { + await postMessage(msg.channelId, msg.content); + } + + if (reactions.length) { + await new Promise(r => setTimeout(r, 1500)); + const msgArray = (MessageStore.getMessages(msg.channelId) as { _array?: Message[]; })?._array ?? []; + const currentUserId = UserStore.getCurrentUser()?.id; + + for (let i = msgArray.length - 1; i >= Math.max(0, msgArray.length - 10); i--) { + const m = msgArray[i]; + if (m?.author?.id === currentUserId && m?.content === msg.content && !m?.id?.startsWith("scheduled-")) { + await addReactionsToMessage(msg.channelId, m.id, reactions); + break; + } + } + } + + if (settings.store.showNotifications) { + showToast(`Scheduled message sent to ${getChannelDisplayInfo(msg.channelId).name}`, Toasts.Type.SUCCESS); + } + return true; + } catch { + if (settings.store.showNotifications) showToast("Failed to send scheduled message", Toasts.Type.FAILURE); + return false; + } +} + +export async function addScheduledMessage( + channelId: string, + content: string, + scheduledTime: number, + attachments?: ScheduledAttachment[] +): Promise<{ success: boolean; error?: string; }> { + const minuteStart = Math.floor(scheduledTime / 60000) * 60000; + const count = scheduledMessages.filter(m => + m.channelId === channelId && m.scheduledTime >= minuteStart && m.scheduledTime < minuteStart + 60000 + ).length; + + if (count >= settings.store.maxMessagesPerMinute) { + return { success: false, error: `Maximum of ${settings.store.maxMessagesPerMinute} messages per channel per minute reached` }; + } + + const newMessage: ScheduledMessage = { + id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, + channelId, + content, + scheduledTime, + createdAt: Date.now(), + attachments + }; + + scheduledMessages.push(newMessage); + scheduledMessages.sort((a, b) => a.scheduledTime - b.scheduledTime); + await saveScheduledMessages(); + createPhantomMessage(newMessage); + + return { success: true }; +} + +export async function removeScheduledMessage(id: string): Promise { + const msg = scheduledMessages.find(m => m.id === id); + if (msg) removePhantomMessage(msg); + scheduledMessages = scheduledMessages.filter(m => m.id !== id); + await saveScheduledMessages(); +} + +export async function clearAllScheduledMessages(): Promise { + for (const msg of scheduledMessages) { + removePhantomMessage(msg); + } + scheduledMessages = []; + await saveScheduledMessages(); +} + +async function checkAndSendMessages(): Promise { + if (isProcessingMessages) return; + isProcessingMessages = true; + + try { + const now = Date.now(); + const dueMessages = scheduledMessages.filter(m => m.scheduledTime <= now); + + for (const msg of dueMessages) { + await removeScheduledMessage(msg.id); + await sendScheduledMessage(msg); + } + } finally { + isProcessingMessages = false; + } +} + +export function startScheduler(): void { + if (checkInterval) return; + checkAndSendMessages(); + checkInterval = setInterval(checkAndSendMessages, settings.store.checkIntervalSeconds * 1000); +} + +export function stopScheduler(): void { + if (checkInterval) { clearInterval(checkInterval); checkInterval = null; } +} + +export async function recreatePhantomMessages(): Promise { + for (const msg of scheduledMessages) await createPhantomMessage(msg); +} + +export function cleanupAllPhantomMessages(): void { + for (const msg of scheduledMessages) removePhantomMessage(msg); +} + +function modifyReaction(messageId: string, channelId: string, emoji: { id: string | null; name: string; animated?: boolean; }, delta: number): void { + logger.info("modifyReaction called:", { messageId, emoji: emoji.name, delta }); + + const phantomData = phantomMessageMap.get(messageId); + if (!phantomData) { + logger.warn("modifyReaction: No phantom data found for messageId =", messageId); + return; + } + + const msg = scheduledMessages.find(m => m.id === phantomData.messageId); + if (!msg) { + logger.warn("modifyReaction: No scheduled message found for id =", phantomData.messageId); + return; + } + + const reactions = pendingReactions.get(phantomData.messageId) ?? []; + const idx = reactions.findIndex(r => r.emoji.name === emoji.name && r.emoji.id === emoji.id); + + logger.info("modifyReaction: Current reactions count =", reactions.length, "found at idx =", idx); + + if (delta > 0) { + if (idx >= 0) reactions[idx].count += delta; + else reactions.push({ emoji: { id: emoji.id ?? null, name: emoji.name, animated: emoji.animated }, count: 1 }); + } else if (idx >= 0) { + reactions[idx].count += delta; + if (reactions[idx].count <= 0) reactions.splice(idx, 1); + } + + pendingReactions.set(phantomData.messageId, reactions); + msg.reactions = reactions; + saveScheduledMessages(); + + logger.info("modifyReaction: Updated reactions count =", reactions.length, "calling updatePhantomReactions"); + updatePhantomReactions(messageId, channelId, reactions); +} + +function getReactionKey(messageId: string, emoji: { id: string | null; name: string; }): string { + return `${messageId}:${emoji.name}:${emoji.id ?? ""}`; +} + +export function handleReactionAdd(messageId: string, channelId: string, emoji: { id: string | null; name: string; animated?: boolean; }): void { + const key = getReactionKey(messageId, emoji); + const recent = recentReactionChanges.get(key); + const now = Date.now(); + + if (recent && recent.action === "add" && now - recent.timestamp < REACTION_COOLDOWN_MS) { + logger.info("handleReactionAdd: Ignoring duplicate ADD within cooldown", { messageId, emoji: emoji.name }); + return; + } + + if (recent && recent.action === "remove" && now - recent.timestamp < REACTION_COOLDOWN_MS) { + logger.info("handleReactionAdd: Ignoring Discord revert (ADD after our REMOVE)", { messageId, emoji: emoji.name }); + resyncPhantomReactions(messageId, channelId); + return; + } + + logger.info("handleReactionAdd: Processing", { messageId, emoji: emoji.name }); + recentReactionChanges.set(key, { action: "add", timestamp: now }); + modifyReaction(messageId, channelId, emoji, 1); +} + +export function handleReactionRemove(messageId: string, channelId: string, emoji: { id: string | null; name: string; }): void { + const key = getReactionKey(messageId, emoji); + const recent = recentReactionChanges.get(key); + const now = Date.now(); + + if (recent && recent.action === "remove" && now - recent.timestamp < REACTION_COOLDOWN_MS) { + logger.info("handleReactionRemove: Ignoring duplicate REMOVE within cooldown", { messageId, emoji: emoji.name }); + return; + } + + if (recent && recent.action === "add" && now - recent.timestamp < REACTION_COOLDOWN_MS) { + logger.info("handleReactionRemove: Ignoring Discord revert (REMOVE after our ADD)", { messageId, emoji: emoji.name }); + resyncPhantomReactions(messageId, channelId); + return; + } + + logger.info("handleReactionRemove: Processing", { messageId, emoji: emoji.name }); + recentReactionChanges.set(key, { action: "remove", timestamp: now }); + modifyReaction(messageId, channelId, emoji, -1); +} + +export function isPhantomMessage(messageId: string): boolean { + return phantomMessageMap.has(messageId); +} + +export function resyncPhantomReactions(messageId: string, channelId: string): void { + logger.info("resyncPhantomReactions:", { messageId }); + + const phantomData = phantomMessageMap.get(messageId); + if (!phantomData) { + logger.warn("resyncPhantomReactions: No phantom data for messageId =", messageId); + return; + } + + const reactions = pendingReactions.get(phantomData.messageId) ?? []; + logger.info("resyncPhantomReactions: Found", reactions.length, "reactions to sync"); + + updatePhantomReactions(messageId, channelId, reactions); +} From 7427447b65989bbae4caacfa6feab5c9fbce1e52 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:21:12 -0500 Subject: [PATCH 048/291] Quoter: Push to the end on contextmenu --- src/equicordplugins/quoter/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/equicordplugins/quoter/index.tsx b/src/equicordplugins/quoter/index.tsx index dd8d0eef7..153bdb4af 100644 --- a/src/equicordplugins/quoter/index.tsx +++ b/src/equicordplugins/quoter/index.tsx @@ -4,7 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { BaseText } from "@components/BaseText"; import { FormSwitch } from "@components/FormSwitch"; @@ -84,9 +83,7 @@ export default definePlugin({ /> ); - const group = findGroupChildrenByChildId("copy-text", children); - if (!group) children.push(buttonElement); - else group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement); + children.push(buttonElement); } } }); From 6b5e31ff3b2bc71f5ee9a4f642a48aaff0206d7c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:27:09 -0500 Subject: [PATCH 049/291] ScheduledMessages: Fix Patch --- src/equicordplugins/scheduledMessages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/scheduledMessages/index.tsx b/src/equicordplugins/scheduledMessages/index.tsx index 320058c83..640170b80 100644 --- a/src/equicordplugins/scheduledMessages/index.tsx +++ b/src/equicordplugins/scheduledMessages/index.tsx @@ -118,7 +118,7 @@ export default definePlugin({ { find: "}addReaction(", replacement: { - match: /this\.customRenderedContent=(\i)\.customRenderedContent,/, + match: /this\.channel_id=(\i)\.channel_id,/, replace: "$&this.scheduledMessageData=$1.scheduledMessageData," } } From b5241907634f344eddd229fb3e0629e00285c676 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:04:57 -0500 Subject: [PATCH 050/291] Drop VCSupport for direct implementation --- src/equicordplugins/equicordHelper/index.tsx | 31 +++++++++++++++++++- src/equicordplugins/vcSupport/index.ts | 25 ---------------- src/plugins/_core/supportHelper.tsx | 29 +----------------- 3 files changed, 31 insertions(+), 54 deletions(-) delete mode 100644 src/equicordplugins/vcSupport/index.ts diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index e03215075..303ed429a 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -5,12 +5,16 @@ */ import { definePluginSettings } from "@api/Settings"; -import { Devs, EquicordDevs } from "@utils/constants"; +import { Devs, EquicordDevs, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_SUPPORT_CHANNEL_IDS } from "@utils/constants"; +import { isAnyPluginDev } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { Alerts, NavigationRouter, UserStore } from "@webpack/common"; import { PluginButtons } from "./pluginButtons"; import { PluginCards } from "./pluginCards"; +let clicked = false; + const settings = definePluginSettings({ disableDMContextMenu: { type: OptionType.BOOLEAN, @@ -89,6 +93,7 @@ export default definePlugin({ }, predicate: () => settings.store.noMirroredCamera }, + // Remove Activity Section above Member List { find: ".MEMBERLIST_CONTENT_FEED_TOGGLED,", predicate: () => settings.store.removeActivitySection, @@ -97,6 +102,7 @@ export default definePlugin({ replace: "true||$&" }, }, + // Always show open legacy settings ...[ ".DEVELOPER_SECTION,", '"LegacySettingsSidebarItem"' @@ -117,5 +123,28 @@ export default definePlugin({ ); + }, + flux: { + async CHANNEL_SELECT({ channelId }) { + const isSupportChannel = SUPPORT_CHANNEL_IDS.includes(channelId); + if (!isSupportChannel) return; + + const selfId = UserStore.getCurrentUser()?.id; + if (!selfId || isAnyPluginDev(selfId)) return; + if (VC_SUPPORT_CHANNEL_IDS.includes(channelId) && !clicked) { + return Alerts.show({ + title: "Vencord Support Channel Warning", + body: "Before asking for help. Check updates and if this issue is actually caused by Equicord!", + confirmText: "Equicord Support", + onConfirm() { + NavigationRouter.transitionTo(`/channels/${GUILD_ID}/${SUPPORT_CHANNEL_ID}`); + }, + cancelText: "Okay continue", + onCancel() { + clicked = true; + }, + }); + } + }, } }); diff --git a/src/equicordplugins/vcSupport/index.ts b/src/equicordplugins/vcSupport/index.ts deleted file mode 100644 index d26b94868..000000000 --- a/src/equicordplugins/vcSupport/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Devs, EquicordDevs } from "@utils/constants"; -import { isAnyPluginDev } from "@utils/misc"; -import definePlugin from "@utils/types"; -import { UserStore } from "@webpack/common"; - -export default definePlugin({ - name: "VCSupport", - description: "Wumpus Dance + Support Warnings", - authors: [Devs.thororen, EquicordDevs.coolesding], - required: true, - start() { - const selfId = UserStore.getCurrentUser()?.id; - if (isAnyPluginDev(selfId)) { - Vencord.Settings.plugins.VCSupport.enabled = false; - } else { - Vencord.Settings.plugins.VCSupport.enabled = true; - } - } -}); diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 2b6a4ad1c..c158a9d9f 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -29,7 +29,7 @@ import { Paragraph } from "@components/Paragraph"; import { openSettingsTabModal, UpdaterTab } from "@components/settings"; import { platformName } from "@equicordplugins/equicordHelper/utils"; import { gitHash, gitHashShort } from "@shared/vencordUserAgent"; -import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_IDS, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants"; +import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; @@ -181,8 +181,6 @@ const settings = definePluginSettings({}).withPrivateSettings<{ dismissedDevBuildWarning?: boolean; }>(); -let clicked = false; - export default definePlugin({ name: "SupportHelper", required: true, @@ -227,31 +225,6 @@ export default definePlugin({ const selfId = UserStore.getCurrentUser()?.id; if (!selfId || isAnyPluginDev(selfId)) return; - if (VC_SUPPORT_CHANNEL_IDS.includes(channelId) && Vencord.Plugins.isPluginEnabled("VCSupport") && !clicked) { - return Alerts.show({ - title: "You are entering the support channel!", - body:
- -
- -
- Before you ask for help, - Check for updates and if this - issue could be caused by Equicord! -
, - confirmText: "Go to Equicord Support", - onConfirm() { - clicked = true; - VencordNative.native.openExternal("https://equicord.org/discord"); - }, - cancelText: "Okay continue", - onCancel() { - clicked = true; - }, - }); - } if (!IS_UPDATER_DISABLED) { await checkForUpdatesOnce().catch(() => { }); From d8fe9f0c76426ac681eca5cfcd7222f00d3a5f6c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 5 Dec 2025 21:44:29 +0100 Subject: [PATCH 051/291] fix some text being wrong colour --- src/components/Heading.css | 2 +- src/components/settings/QuickAction.css | 2 +- src/components/settings/tabs/plugins/components/styles.css | 4 ++-- src/plugins/serverListIndicators/index.tsx | 4 ++-- src/plugins/spotifyControls/spotifyStyles.css | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Heading.css b/src/components/Heading.css index 2b101250c..3eddc40e7 100644 --- a/src/components/Heading.css +++ b/src/components/Heading.css @@ -7,7 +7,7 @@ .vc-h3, .vc-h4, .vc-h5 { - color: var(--header-secondary); + color: var(--header-primary); } .vc-h1 { diff --git a/src/components/settings/QuickAction.css b/src/components/settings/QuickAction.css index 8b94bd2d4..ba582106b 100644 --- a/src/components/settings/QuickAction.css +++ b/src/components/settings/QuickAction.css @@ -15,7 +15,7 @@ .vc-settings-quickActions-pill { all: unset; background: var(--button-secondary-background); - color: var(--header-secondary); + color: var(--text-default); display: flex; align-items: center; gap: 0.5em; diff --git a/src/components/settings/tabs/plugins/components/styles.css b/src/components/settings/tabs/plugins/components/styles.css index 9d72554e3..100c1d4c6 100644 --- a/src/components/settings/tabs/plugins/components/styles.css +++ b/src/components/settings/tabs/plugins/components/styles.css @@ -27,9 +27,9 @@ } .vc-plugins-setting-description { - color: var(--header-secondary); + color: var(--text-default); } .vc-plugins-setting-error { color: var(--text-danger); -} +} \ No newline at end of file diff --git a/src/plugins/serverListIndicators/index.tsx b/src/plugins/serverListIndicators/index.tsx index 7648bc2d0..17a11a37d 100644 --- a/src/plugins/serverListIndicators/index.tsx +++ b/src/plugins/serverListIndicators/index.tsx @@ -55,7 +55,7 @@ function FriendsIndicator() { width: "100%", fontSize: "12px", fontWeight: "600", - color: "var(--header-secondary)", + color: "var(--text-default)", textTransform: "uppercase", textAlign: "center", }}> @@ -79,7 +79,7 @@ function ServersIndicator() { width: "100%", fontSize: "12px", fontWeight: "600", - color: "var(--header-secondary)", + color: "var(--text-default)", textTransform: "uppercase", textAlign: "center", }}> diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index e9dfeffde..03cdfbe0b 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -149,7 +149,7 @@ } .vc-spotify-comma { - color: var(--header-secondary); + color: var(--text-default); } .vc-spotify-artist[role="link"]:hover, From d96f414d3eb060c47c1b4158081a4d491ab5552c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:59:37 -0500 Subject: [PATCH 052/291] UserPFP: Hide DB Change if on Standalone --- src/equicordplugins/userpfp/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/equicordplugins/userpfp/index.tsx b/src/equicordplugins/userpfp/index.tsx index 332bf023e..10fd0967a 100644 --- a/src/equicordplugins/userpfp/index.tsx +++ b/src/equicordplugins/userpfp/index.tsx @@ -34,8 +34,12 @@ const settings = definePluginSettings({ description: "URL to load database from", type: OptionType.STRING, default: "https://userpfp.github.io/UserPFP/source/data.json", + hidden: !IS_DEV, isValid: (value => { - if (!value) return false; + if (!value) { + value = "https://userpfp.github.io/UserPFP/source/data.json"; + return false; + } return true; }) } From b8ab6a74702c32c504963085c37a5684c8ca58da Mon Sep 17 00:00:00 2001 From: VV <44335189+vvareo@users.noreply.github.com> Date: Sat, 6 Dec 2025 11:32:51 +1100 Subject: [PATCH 053/291] support new Settings UI (#3803) Co-authored-by: Vendicated --- src/components/Icons.tsx | 98 +++++++ src/components/settings/tabs/BaseTab.tsx | 9 +- .../settings/tabs/patchHelper/index.tsx | 2 +- .../settings/tabs/plugins/index.tsx | 2 +- .../tabs/sync/BackupAndRestoreTab.tsx | 2 +- .../settings/tabs/sync/CloudTab.tsx | 6 +- src/components/settings/tabs/themes/index.tsx | 4 +- .../settings/tabs/updater/index.tsx | 4 +- .../settings/tabs/vencord/index.tsx | 2 +- src/plugins/_core/settings.tsx | 256 +++++++++++++++--- src/utils/constants.ts | 4 + 11 files changed, 333 insertions(+), 56 deletions(-) diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 40cb15c9d..eede9559e 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -444,3 +444,101 @@ export function PlaceholderIcon(props: IconProps) { ); } + +export function MainSettingsIcon(props: IconProps) { + return ( + + + + ); +} + +export function PluginsIcon(props: IconProps) { + return ( + + + + ); +} + +export function CloudIcon(props: IconProps) { + return ( + + + + ); +} + +export function BackupRestoreIcon(props: IconProps) { + return ( + + + + ); +} + +export function UpdaterIcon(props: IconProps) { + return ( + + + + ); +} + +export function PatchHelperIcon(props: IconProps) { + return ( + + + + ); +} + +export function VesktopSettingsIcon(props: IconProps) { + return ( + + + + ); +} diff --git a/src/components/settings/tabs/BaseTab.tsx b/src/components/settings/tabs/BaseTab.tsx index 5d9914c3d..b4c0ebb53 100644 --- a/src/components/settings/tabs/BaseTab.tsx +++ b/src/components/settings/tabs/BaseTab.tsx @@ -16,20 +16,15 @@ * along with this program. If not, see . */ -import { BaseText } from "@components/BaseText"; import ErrorBoundary from "@components/ErrorBoundary"; import { handleComponentFailed } from "@components/handleComponentFailed"; -import { Margins } from "@utils/margins"; import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { onlyOnce } from "@utils/onlyOnce"; import type { ComponentType, PropsWithChildren } from "react"; -export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) { +export function SettingsTab({ children }: PropsWithChildren) { return ( -
- {title} - {children} -
+
{children}
); } diff --git a/src/components/settings/tabs/patchHelper/index.tsx b/src/components/settings/tabs/patchHelper/index.tsx index 2d33a0293..bd99f5c8b 100644 --- a/src/components/settings/tabs/patchHelper/index.tsx +++ b/src/components/settings/tabs/patchHelper/index.tsx @@ -104,7 +104,7 @@ function PatchHelper() { } return ( - + Full patch + diff --git a/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx index e4e8b001f..447c1f442 100644 --- a/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx +++ b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx @@ -27,7 +27,7 @@ import { Button, Text } from "@webpack/common"; function BackupAndRestoreTab() { return ( - + Warning diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx index 73526bcdb..bab67a7fd 100644 --- a/src/components/settings/tabs/sync/CloudTab.tsx +++ b/src/components/settings/tabs/sync/CloudTab.tsx @@ -96,10 +96,8 @@ function CloudTab() { const settings = useSettings(["cloud.authenticated", "cloud.url"]); return ( - +
- Cloud Settings - Vencord comes with a cloud integration that adds goodies like settings sync across devices. It respects your privacy, and @@ -118,7 +116,7 @@ function CloudTab() { settings.cloud.authenticated = v; }} /> - Backend URL + Backend URL Which backend to use when using cloud integrations. diff --git a/src/components/settings/tabs/themes/index.tsx b/src/components/settings/tabs/themes/index.tsx index 7d2234459..2bc5e81eb 100644 --- a/src/components/settings/tabs/themes/index.tsx +++ b/src/components/settings/tabs/themes/index.tsx @@ -37,7 +37,7 @@ function ThemesTab() { const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); return ( - + + Themes are not supported on the Userscript! diff --git a/src/components/settings/tabs/updater/index.tsx b/src/components/settings/tabs/updater/index.tsx index 935f66125..c238d0772 100644 --- a/src/components/settings/tabs/updater/index.tsx +++ b/src/components/settings/tabs/updater/index.tsx @@ -44,9 +44,7 @@ function Updater() { }; return ( - - Updater Settings - + + {isDonor(user?.id) ? ( . */ -import { Settings } from "@api/Settings"; +import { definePluginSettings } from "@api/Settings"; +import { BackupRestoreIcon, CloudIcon, MainSettingsIcon, PaintbrushIcon, PatchHelperIcon, PlaceholderIcon, PluginsIcon, UpdaterIcon, VesktopSettingsIcon } from "@components/Icons"; import { BackupAndRestoreTab, CloudTab, PatchHelperTab, PluginsTab, ThemesTab, UpdaterTab, VencordTab } from "@components/settings/tabs"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; -import definePlugin, { OptionType } from "@utils/types"; +import { isTruthy } from "@utils/guards"; +import definePlugin, { IconProps, OptionType } from "@utils/types"; +import { waitFor } from "@webpack"; import { React } from "@webpack/common"; +import type { ComponentType, PropsWithChildren, ReactNode } from "react"; import gitHash from "~git-hash"; -type SectionType = "HEADER" | "DIVIDER" | "CUSTOM"; -type SectionTypes = Record; +let LayoutTypes = { + SECTION: 1, + SIDEBAR_ITEM: 2, + PANEL: 3, + PANE: 4 +}; +waitFor(["SECTION", "SIDEBAR_ITEM", "PANEL", "PANE"], v => LayoutTypes = v); + +const FallbackSectionTypes = { + HEADER: "HEADER", + DIVIDER: "DIVIDER", + CUSTOM: "CUSTOM" +}; +type SectionTypes = typeof FallbackSectionTypes; + +type SettingsLocation = + | "top" + | "aboveNitro" + | "belowNitro" + | "aboveActivity" + | "belowActivity" + | "bottom"; + +interface SettingsLayoutNode { + type: number; + key?: string; + legacySearchKey?: string; + useLabel?(): string; + useTitle?(): string; + buildLayout?(): SettingsLayoutNode[]; + icon?(): ReactNode; + render?(): ReactNode; +} + +interface EntryOptions { + key: string, + title: string, + panelTitle?: string, + Component: ComponentType<{}>, + Icon: ComponentType; +} +interface SettingsLayoutBuilder { + key?: string; + buildLayout(): SettingsLayoutNode[]; +} + +const settings = definePluginSettings({ + settingsLocation: { + type: OptionType.SELECT, + description: "Where to put the Vencord settings section", + options: [ + { label: "At the very top", value: "top" }, + { label: "Above the Nitro section", value: "aboveNitro", default: true }, + { label: "Below the Nitro section", value: "belowNitro" }, + { label: "Above Activity Settings", value: "aboveActivity" }, + { label: "Below Activity Settings", value: "belowActivity" }, + { label: "At the very bottom", value: "bottom" }, + ] as { label: string; value: SettingsLocation; default?: boolean; }[] + } +}); export default definePlugin({ name: "Settings", @@ -34,19 +96,28 @@ export default definePlugin({ authors: [Devs.Ven, Devs.Megu], required: true, + settings, + patches: [ { find: ".versionHash", replacement: [ { - match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, - replace: (m, component, props) => { + match: /\.compactInfo.+?(?=null!=(\i)&&(.{0,20}\i\.Text.{0,200}?,children:).{0,15}?("span"),({className:\i\.versionHash,children:\["Build Override: ",\1\.id\]\})\)\}\))/, + replace: (m, _buildOverride, makeRow, component, props) => { props = props.replace(/children:\[.+\]/, ""); - return `${m},$self.makeInfoElements(${component}, ${props})`; + return `${m},$self.makeInfoElements(${component},${props}).map(e=>${makeRow}e})),`; } }, { - match: /copyValue:\i\.join\(" "\)/, + match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, + replace: (m, component, props) => { + props = props.replace(/children:\[.+\]/, ""); + return `${m},$self.makeInfoElements(${component},${props})`; + } + }, + { + match: /copyValue:\i\.join\(" "\)/g, replace: "$& + $self.getInfoString()" } ] @@ -73,15 +144,147 @@ export default definePlugin({ } }, { - find: "2025-09-user-settings-redesign-1", + find: ".buildLayout().map", replacement: { - match: /enabled:![01],showLegacyOpen:/g, - replace: "enabled:false,showLegacyOpen:" + match: /(\i)\.buildLayout\(\)(?=\.map)/, + replace: "$self.buildLayout($1)" } } ], + buildEntry(options: EntryOptions): SettingsLayoutNode { + const { key, title, panelTitle = title, Component, Icon } = options; + + return ({ + key, + type: LayoutTypes.SIDEBAR_ITEM, + legacySearchKey: title.toUpperCase(), + useTitle: () => title, + icon: () => , + buildLayout: () => [ + { + key: key + "_panel", + type: LayoutTypes.PANEL, + useTitle: () => panelTitle, + buildLayout: () => [ + { + key: key + "_pane", + type: LayoutTypes.PANE, + buildLayout: () => [], + render: () => , + useTitle: () => panelTitle + } + ] + } + ] + }); + }, + + buildLayout(originalLayoutBuilder: SettingsLayoutBuilder) { + const layout = originalLayoutBuilder.buildLayout(); + if (originalLayoutBuilder.key !== "$Root") return layout; + if (!Array.isArray(layout)) return layout; + + if (layout.some(s => s?.key === "vencord_section")) return layout; + + const { buildEntry } = this; + + const vencordEntries: SettingsLayoutNode[] = [ + buildEntry({ + key: "vencord_main", + title: "Vencord", + panelTitle: "Vencord Settings", + Component: VencordTab, + Icon: MainSettingsIcon + }), + buildEntry({ + key: "vencord_plugins", + title: "Plugins", + Component: PluginsTab, + Icon: PluginsIcon + }), + buildEntry({ + key: "vencord_themes", + title: "Themes", + Component: ThemesTab, + Icon: PaintbrushIcon + }), + !IS_UPDATER_DISABLED && UpdaterTab && buildEntry({ + key: "vencord_updater", + title: "Updater", + panelTitle: "Vencord Updater", + Component: UpdaterTab, + Icon: UpdaterIcon + }), + buildEntry({ + key: "vencord_cloud", + title: "Cloud", + panelTitle: "Vencord Cloud", + Component: CloudTab, + Icon: CloudIcon + }), + buildEntry({ + key: "vencord_backup_restore", + title: "Backup & Restore", + Component: BackupAndRestoreTab, + Icon: BackupRestoreIcon + }), + IS_DEV && PatchHelperTab && buildEntry({ + key: "vencord_patch_helper", + title: "Patch Helper", + Component: PatchHelperTab, + Icon: PatchHelperIcon + }), + ...this.customEntries.map(buildEntry), + // TODO: Remove deprecated customSections in a future update + ...this.customSections.map((func, i) => { + const { section, element, label } = func(FallbackSectionTypes); + if (Object.values(FallbackSectionTypes).includes(section)) return null; + + return buildEntry({ + key: `vencord_deprecated_custom_${section}`, + title: label, + Component: element, + Icon: section === "Vesktop" ? VesktopSettingsIcon : PlaceholderIcon + }); + }) + ].filter(isTruthy); + + const vencordSection: SettingsLayoutNode = { + key: "vencord_section", + type: LayoutTypes.SECTION, + useLabel: () => "Vencord", + buildLayout: () => vencordEntries + }; + + const { settingsLocation } = settings.store; + + const places: Record = { + top: "user_section", + aboveNitro: "billing_section", + belowNitro: "billing_section", + aboveActivity: "activity_section", + belowActivity: "activity_section", + bottom: "logout_section" + }; + + const key = places[settingsLocation] ?? places.top; + let idx = layout.findIndex(s => typeof s?.key === "string" && s.key === key); + + if (idx === -1) { + idx = 2; + } else if (settingsLocation.startsWith("below")) { + idx += 1; + } + + layout.splice(idx, 0, vencordSection); + + return layout; + }, + + /** @deprecated Use customEntries */ customSections: [] as ((SectionTypes: SectionTypes) => any)[], + customEntries: [] as EntryOptions[], makeSettingsCategories(SectionTypes: SectionTypes) { return [ @@ -139,12 +342,12 @@ export default definePlugin({ ].filter(Boolean); }, - isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) { - const firstChild = settings?.[0]; + isRightSpot({ header, settings: s }: { header?: string; settings?: string[]; }) { + const firstChild = s?.[0]; // lowest two elements... sanity backup if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true; - const { settingsLocation } = Settings.plugins.Settings; + const { settingsLocation } = settings.store; if (settingsLocation === "bottom") return firstChild === "LOGOUT"; if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG"; @@ -152,7 +355,7 @@ export default definePlugin({ if (!header) return; try { - const names = { + const names: Record, string> = { top: getIntlMessage("USER_SETTINGS"), aboveNitro: getIntlMessage("BILLING_SETTINGS"), belowNitro: getIntlMessage("APP_SETTINGS"), @@ -182,31 +385,12 @@ export default definePlugin({ return (...args: any[]) => { const elements = originalHook(...args); if (!this.patchedSettings.has(elements)) - elements.unshift(...this.makeSettingsCategories({ - HEADER: "HEADER", - DIVIDER: "DIVIDER", - CUSTOM: "CUSTOM" - })); + elements.unshift(...this.makeSettingsCategories(FallbackSectionTypes)); return elements; }; }, - options: { - settingsLocation: { - type: OptionType.SELECT, - description: "Where to put the Vencord settings section", - options: [ - { label: "At the very top", value: "top" }, - { label: "Above the Nitro section", value: "aboveNitro", default: true }, - { label: "Below the Nitro section", value: "belowNitro" }, - { label: "Above Activity Settings", value: "aboveActivity" }, - { label: "Below Activity Settings", value: "belowActivity" }, - { label: "At the very bottom", value: "bottom" }, - ] - }, - }, - get electronVersion() { return VencordNative.native.getVersions().electron || window.legcord?.electron || null; }, @@ -245,7 +429,7 @@ export default definePlugin({ return "\n" + this.getInfoRows().join("\n"); }, - makeInfoElements(Component: React.ComponentType, props: React.PropsWithChildren) { + makeInfoElements(Component: ComponentType, props: PropsWithChildren) { return this.getInfoRows().map((text, i) => {text} ); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 737c5eb9a..e864c651e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -612,6 +612,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ alfred: { name: "alfred", id: 1038466644353232967n + }, + vv: { + name: "VV", + id: 254866377087778816n } } satisfies Record); From b07db6c50016778a160f7cd2cd0466ef66f67c37 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 5 Dec 2025 16:37:42 -0800 Subject: [PATCH 054/291] SendTimestamps: support s and S timestamp formats (#3792) --- src/plugins/sendTimestamps/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx index a136e9c8f..579656c45 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -48,7 +48,7 @@ function parseTime(time: string) { return ``; } -const Formats = ["", "t", "T", "d", "D", "f", "F", "R"] as const; +const Formats = ["", "t", "T", "d", "D", "f", "F", "s", "S", "R"] as const; type Format = typeof Formats[number]; const cl = classNameFactory("vc-st-"); From 3ea80c254ae3676828fd4dc31e30aed831182848 Mon Sep 17 00:00:00 2001 From: nin0 Date: Fri, 5 Dec 2025 19:45:04 -0500 Subject: [PATCH 055/291] LastFmRPC: ignore custom status (#3814) --- src/plugins/lastfmRichPresence/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx index fdfa3f3ed..b6164b196 100644 --- a/src/plugins/lastfmRichPresence/index.tsx +++ b/src/plugins/lastfmRichPresence/index.tsx @@ -243,7 +243,7 @@ export default definePlugin({ async getActivity(): Promise { if (settings.store.hideWithActivity) { - if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.application_id !== DISCORD_APP_ID)) { + if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.application_id !== DISCORD_APP_ID && a.type !== ActivityType.CUSTOM_STATUS)) { return null; } } From 192d6a2237a4aa3138872ce244b1274de6c794cf Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:33:17 -0500 Subject: [PATCH 056/291] ShikiCodeBlocks: Update assets (#3785) Co-authored-by: V --- .../shikiCodeblocks.desktop/api/languages.ts | 31 +++++++++++-------- .../shikiCodeblocks.desktop/api/themes.ts | 10 ++++-- .../shikiCodeblocks.desktop/devicon.css | 2 +- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/plugins/shikiCodeblocks.desktop/api/languages.ts b/src/plugins/shikiCodeblocks.desktop/api/languages.ts index baf7a5481..140d9c530 100644 --- a/src/plugins/shikiCodeblocks.desktop/api/languages.ts +++ b/src/plugins/shikiCodeblocks.desktop/api/languages.ts @@ -18,11 +18,12 @@ import { ILanguageRegistration } from "@vap/shiki"; -export const VPC_REPO = "Vap0r1ze/vapcord"; -export const VPC_REPO_COMMIT = "4d0e4b420fb1e4358852bbd18c804a6f5e54c0d7"; -export const vpcRepoAssets = `https://raw.githubusercontent.com/${VPC_REPO}/${VPC_REPO_COMMIT}/assets/shiki-codeblocks`; -export const vpcRepoGrammar = (fileName: string) => `${vpcRepoAssets}/${fileName}`; -export const vpcRepoLanguages = `${vpcRepoAssets}/languages.json`; +import { SHIKI_REPO, SHIKI_REPO_COMMIT } from "./themes"; + +export const JSON_REPO = "Vencord/ShikiPluginAssets"; +export const JSON_REPO_COMMIT = "75d69df9fdf596a31eef8b7f6f891231a6feab44"; +export const JSON_URL = `https://cdn.jsdelivr.net/gh/${JSON_REPO}@${JSON_REPO_COMMIT}/grammars.json`; +export const shikiRepoGrammar = (name: string) => `https://cdn.jsdelivr.net/gh/${SHIKI_REPO}@${SHIKI_REPO_COMMIT}/packages/tm-grammars/grammars/${name}.json`; export interface Language { name: string; @@ -36,22 +37,26 @@ export interface Language { } export interface LanguageJson { name: string; - id: string; - fileName: string; - devicon?: string; + displayName: string; scopeName: string; + devicon?: string; aliases?: string[]; } export const languages: Record = {}; export const loadLanguages = async () => { - const langsJson: LanguageJson[] = await fetch(vpcRepoLanguages).then(res => res.ok ? res.json() : []); + const langsJson: LanguageJson[] = await fetch(JSON_URL).then(res => res.ok ? res.json() : []); const loadedLanguages = Object.fromEntries( - langsJson.map(lang => [lang.id, { - ...lang, - grammarUrl: vpcRepoGrammar(lang.fileName), - }]) + langsJson.map(lang => { + const { name, displayName, ...rest } = lang; + return [name, { + ...rest, + id: name, + name: displayName, + grammarUrl: shikiRepoGrammar(name), + }]; + }) ); Object.assign(languages, loadedLanguages); }; diff --git a/src/plugins/shikiCodeblocks.desktop/api/themes.ts b/src/plugins/shikiCodeblocks.desktop/api/themes.ts index bc1dd184d..4c2f8b682 100644 --- a/src/plugins/shikiCodeblocks.desktop/api/themes.ts +++ b/src/plugins/shikiCodeblocks.desktop/api/themes.ts @@ -19,8 +19,8 @@ import { IShikiTheme } from "@vap/shiki"; export const SHIKI_REPO = "shikijs/textmate-grammars-themes"; -export const SHIKI_REPO_COMMIT = "2d87559c7601a928b9f7e0f0dda243d2fb6d4499"; -export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/tm-themes/themes/${name}.json`; +export const SHIKI_REPO_COMMIT = "bc5436518111d87ea58eb56d97b3f9bec30e6b83"; +export const shikiRepoTheme = (name: string) => `https://cdn.jsdelivr.net/gh/${SHIKI_REPO}@${SHIKI_REPO_COMMIT}/packages/tm-themes/themes/${name}.json`; export const themes = { // Default @@ -48,6 +48,12 @@ export const themes = { GithubLightDefault: shikiRepoTheme("github-light-default"), GithubLightHighContrast: shikiRepoTheme("github-light-high-contrast"), GithubLight: shikiRepoTheme("github-light"), + GruvBoxDarkHard: shikiRepoTheme("gruvbox-dark-hard"), + GruvBoxDarkMedium: shikiRepoTheme("gruvbox-dark-medium"), + GruvBoxDarkSoft: shikiRepoTheme("gruvbox-dark-soft"), + GruvBoxLightHard: shikiRepoTheme("gruvbox-light-hard"), + GruvBoxLightMedium: shikiRepoTheme("gruvbox-light-medium"), + GruvBoxLightSoft: shikiRepoTheme("gruvbox-light-soft"), Houston: shikiRepoTheme("houston"), KanagawaDragon: shikiRepoTheme("kanagawa-dragon"), KanagawaLotus: shikiRepoTheme("kanagawa-lotus"), diff --git a/src/plugins/shikiCodeblocks.desktop/devicon.css b/src/plugins/shikiCodeblocks.desktop/devicon.css index ed1014e38..be0893f09 100644 --- a/src/plugins/shikiCodeblocks.desktop/devicon.css +++ b/src/plugins/shikiCodeblocks.desktop/devicon.css @@ -1 +1 @@ -@import url("https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css"); +@import url("https://cdn.jsdelivr.net/gh/devicons/devicon@v2.17.0/devicon.min.css"); \ No newline at end of file From 9aa3f380333e9bf9f170126c8bd7de94400c4e61 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 6 Dec 2025 02:41:37 +0100 Subject: [PATCH 057/291] PreviewMessage, SendTimestamps: add chat bar button in split view --- src/api/ChatButtons.tsx | 5 +++-- src/plugins/previewMessage/index.tsx | 7 +++---- src/plugins/sendTimestamps/index.tsx | 4 ++-- src/plugins/translate/TranslateIcon.tsx | 4 ++-- src/plugins/translate/settings.ts | 5 ----- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 52052a528..c2a1a1982 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -79,7 +79,7 @@ export interface ChatBarProps { }; } -export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; +export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; isAnyChat: boolean; }) => JSX.Element | null; export type ChatBarButtonData = { render: ChatBarButtonFactory; /** @@ -98,13 +98,14 @@ const logger = new Logger("ChatButtons"); function VencordChatBarButtons(props: ChatBarProps) { const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements; + const { analyticsName } = props.type; return ( <> {Array.from(ChatBarButtonMap) .filter(([key]) => chatBarButtons[key]?.enabled !== false) .map(([key, { render: Button }]) => ( logger.error(`Failed to render ${key}`, e.error)}> - - Repository - - {repoPending ? ( - repo - ) : repoErr ? ( - "Failed to retrieve - check console" - ) : ( - - {repo.split("/").slice(-2).join("/")} - - )}{" "} - (Current:{" "} - - {gitHashShort} - - ) - - -
- - - {changelogHistory.length > 0 && ( - <> - - - - )} -
+ {changelogHistory.length > 0 && ( + <> + + + + )} + +
{error && ( diff --git a/src/components/settings/tabs/changelog/styles.css b/src/components/settings/tabs/changelog/styles.css index 9a33fe5a4..ce4fdcb1c 100644 --- a/src/components/settings/tabs/changelog/styles.css +++ b/src/components/settings/tabs/changelog/styles.css @@ -76,7 +76,6 @@ } .vc-changelog-controls { - margin-bottom: 1em; display: flex; align-items: center; flex-wrap: wrap; From 51fd7986c653bbd6d286ee64cd20438c0585bdd0 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:49:53 -0500 Subject: [PATCH 063/291] QuestCompleter: Fix Conflict --- .../fixFileExtensions/index.tsx | 6 +++--- .../questCompleter.discordDesktop/index.tsx | 18 ++++++++++++++---- src/plugins/showMeYourName/index.tsx | 6 ++++-- src/plugins/usrbg/index.tsx | 6 ++++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/equicordplugins/fixFileExtensions/index.tsx b/src/equicordplugins/fixFileExtensions/index.tsx index c1dfc3a93..be4517b24 100644 --- a/src/equicordplugins/fixFileExtensions/index.tsx +++ b/src/equicordplugins/fixFileExtensions/index.tsx @@ -4,8 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Settings } from "@api/Settings"; -import { tarExtMatcher } from "@plugins/anonymiseFileNames"; +import { isPluginEnabled } from "@api/PluginManager"; +import anonymiseFileNames, { tarExtMatcher } from "@plugins/anonymiseFileNames"; import { Devs } from "@utils/constants"; import definePlugin, { ReporterTestable } from "@utils/types"; import { CloudUpload } from "@vencord/discord-types"; @@ -40,7 +40,7 @@ export default definePlugin({ replace: "$&$1.forEach($self.fixExt);" } ], - predicate: () => !Settings.plugins.AnonymiseFileNames.enabled, + predicate: () => !isPluginEnabled(anonymiseFileNames.name), }, ], fixExt(upload: CloudUpload) { diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index b3bd6852d..9e48675db 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -20,7 +20,9 @@ import "./style.css"; import { HeaderBarButton } from "@api/HeaderBar"; import { showNotification } from "@api/Notifications"; +import { isPluginEnabled } from "@api/PluginManager"; import { definePluginSettings } from "@api/Settings"; +import questify from "@equicordplugins/questify"; import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; @@ -223,10 +225,18 @@ export default definePlugin({ patches: [ { find: ".platformSelectorPrimary,", - replacement: { - match: /(?<=questId:(\i\.id).*?"secondary",)disabled:!0/, - replace: "onClick:()=>$self.mobileQuestPatch($1)" - }, + replacement: [ + { + match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",)disabled:!0/, + replace: "onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id),", + predicate: () => !isPluginEnabled(questify.name), + }, + { + match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",.{0,150}fullWidth:!0)/, + replace: ",onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)", + predicate: () => isPluginEnabled(questify.name), + }, + ], }, ], mobileQuestPatch(questId) { diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 48798d7ab..29e998867 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -8,8 +8,10 @@ import "./style.css"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { DataStore } from "@api/index"; -import { definePluginSettings, Settings } from "@api/Settings"; +import { isPluginEnabled } from "@api/PluginManager"; +import { definePluginSettings } from "@api/Settings"; import { Button, ErrorBoundary, Heading, TextButton } from "@components/index"; +import mentionAvatars from "@plugins/mentionAvatars"; import { Devs, EquicordDevs } from "@utils/constants"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/index"; import definePlugin, { OptionType } from "@utils/types"; @@ -934,7 +936,7 @@ export default definePlugin({ { match: /(?<=onContextMenu:\i\},\i\),{children:)/, replace: "showMeYourNameMention??", - predicate: () => !Settings.plugins.MentionAvatars.enabled, + predicate: () => !isPluginEnabled(mentionAvatars.name), } ] }, diff --git a/src/plugins/usrbg/index.tsx b/src/plugins/usrbg/index.tsx index 2d97b02ef..d11455a01 100644 --- a/src/plugins/usrbg/index.tsx +++ b/src/plugins/usrbg/index.tsx @@ -18,9 +18,11 @@ import "./styles.css"; -import { definePluginSettings, Settings } from "@api/Settings"; +import { isPluginEnabled } from "@api/PluginManager"; +import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Button } from "@components/Button"; +import fullVcPfp from "@equicordplugins/fullVcPfp"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -67,7 +69,7 @@ export default definePlugin({ }, { find: "\"data-selenium-video-tile\":", - predicate: () => !Settings.plugins.FullVCPFP.enabled && settings.store.voiceBackground, + predicate: () => !isPluginEnabled(fullVcPfp.name) && settings.store.voiceBackground, replacement: [ { match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, From 49cbe431c8ab59885401d443465f30e6124f5bce Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:52:03 -0500 Subject: [PATCH 064/291] QuestCompleter: Fix Patch --- src/equicordplugins/questCompleter.discordDesktop/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index 9e48675db..9ddcd2de9 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -228,7 +228,7 @@ export default definePlugin({ replacement: [ { match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",)disabled:!0/, - replace: "onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id),", + replace: "onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)", predicate: () => !isPluginEnabled(questify.name), }, { From 219695393c263608c14eb97cad74e386fcb09756 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:55:15 -0500 Subject: [PATCH 065/291] QuestCompleter: Take 3? --- .../questCompleter.discordDesktop/index.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index 9ddcd2de9..ee675aba6 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -225,18 +225,19 @@ export default definePlugin({ patches: [ { find: ".platformSelectorPrimary,", - replacement: [ - { - match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",)disabled:!0/, - replace: "onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)", - predicate: () => !isPluginEnabled(questify.name), - }, - { - match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",.{0,150}fullWidth:!0)/, - replace: ",onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)", - predicate: () => isPluginEnabled(questify.name), - }, - ], + predicate: () => !isPluginEnabled(questify.name), + replacement: { + match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",)disabled:!0/, + replace: "onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)" + }, + }, + { + find: ".platformSelectorPrimary,", + predicate: () => isPluginEnabled(questify.name), + replacement: { + match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",.{0,150}fullWidth:!0)/, + replace: ",onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)" + }, }, ], mobileQuestPatch(questId) { From 5825c01ed1fd5e54ae7f5f8be920c975f34f3c25 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:00:56 -0500 Subject: [PATCH 066/291] QuestCompleter: Drop Mobile Quest Support --- .../questCompleter.discordDesktop/index.tsx | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index ee675aba6..fc76d698a 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -20,9 +20,7 @@ import "./style.css"; import { HeaderBarButton } from "@api/HeaderBar"; import { showNotification } from "@api/Notifications"; -import { isPluginEnabled } from "@api/PluginManager"; import { definePluginSettings } from "@api/Settings"; -import questify from "@equicordplugins/questify"; import { Devs } from "@utils/constants"; import { getTheme, Theme } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; @@ -34,8 +32,6 @@ const ApplicationStreamingStore = findStoreLazy("ApplicationStreamingStore"); const RunningGameStore = findStoreLazy("RunningGameStore"); const QuestsStore = findByPropsLazy("getQuest"); -let questIdCheck = 0; - function ToolBarHeader() { return ( !isPluginEnabled(questify.name), - replacement: { - match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",)disabled:!0/, - replace: "onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)" - }, - }, - { - find: ".platformSelectorPrimary,", - predicate: () => isPluginEnabled(questify.name), - replacement: { - match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",.{0,150}fullWidth:!0)/, - replace: ",onClick:()=>$self.mobileQuestPatch(arguments[0]?.quest?.id)" - }, - }, - ], - mobileQuestPatch(questId) { - if (questId === questIdCheck) return; - questIdCheck = questId; - Vencord.Webpack.Common.RestAPI.post({ - url: `/quests/${questId}/enroll`, - body: { - location: 11 - } - }); - }, headerBarButton: { icon: QuestIcon, render: ToolBarHeader From 06e72225475c9964532d20adcbb7414c7841d31f Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:13:34 -0800 Subject: [PATCH 067/291] Questify: Fix patches and add setting to hide the sponsored Quest banner. (#540) * Fix patches. * Better. * Add new setting to hide the sponsored Quest banner. * Revert some patches. --- src/equicordplugins/questify/index.tsx | 29 +++++++++++++++++++---- src/equicordplugins/questify/settings.tsx | 14 ++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index d9ea16c02..654dfbb28 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -175,6 +175,18 @@ function shouldHideDirectMessagesTab(): boolean { return disableQuestsDirectMessagesTab || disableQuestsEverything; } +function shouldHideSponsoredQuestBanner(): boolean { + const { + disableQuestsPageSponsoredBanner, + disableQuestsEverything + } = settings.use([ + "disableQuestsPageSponsoredBanner", + "disableQuestsEverything" + ]); + + return disableQuestsPageSponsoredBanner || disableQuestsEverything; +} + function shouldHideBadgeOnUserProfiles(): boolean { const { disableQuestsBadgeOnUserProfiles, @@ -816,7 +828,7 @@ function getQuestPanelOverride(): Quest | null { const timeRemaining = interval.duration - interval.progress; // 3 second buffer to account for per-second rerendering - // which could cause flickering if multiple quests were + // which could cause flickering if multiple Quests were // started at the same time. if (timeRemaining < (closestTimeRemaining - 3)) { closestTimeRemaining = timeRemaining; @@ -908,6 +920,7 @@ export default definePlugin({ shouldHideDirectMessagesTab, shouldPreventFetchingQuests, shouldHideBadgeOnUserProfiles, + shouldHideSponsoredQuestBanner, shouldHideGiftInventoryRelocationNotice, shouldHideFriendsListActiveNowPromotion, shouldHideMembersListActivelyPlayingIcon, @@ -952,6 +965,14 @@ export default definePlugin({ } ] }, + { + // Hides the sponsored banner on the Quests page. + find: "assetSponsorImage.url,", + replacement: { + match: /(?<=campaignId]\),)/, + replace: "$self.shouldHideSponsoredQuestBanner()?null:" + } + }, { // Hides the Quest icon from members list items when // a user is playing a game tied to an active Quest. @@ -1150,7 +1171,7 @@ export default definePlugin({ { // Run Questify's sort function every time due to hook requirements but return // early if not applicable. If the sort method is set to "Questify", replace the - // quests with the sorted ones. Also, setup a trigger to rerender the memo. + // Quests with the sorted ones. Also, setup a trigger to rerender the memo. match: /(?<=function \i\((\i),\i\){let \i=\i.useRef.{0,100}?;)(return \i.useMemo\(\(\)=>{)/, replace: "const questRerenderTrigger=$self.useQuestRerender();const questifySorted=$self.sortQuests($1,arguments[1].sortMethod!==\"questify\");$2if(arguments[1].sortMethod===\"questify\"){$1=questifySorted;};" }, @@ -1278,7 +1299,7 @@ export default definePlugin({ replacement: [ { // Initial and subsequent select drop down for picking or changing a platform. - match: /(#{intl::QUEST_MULTIPLATFORM_SELECT_SUBTITLE}.{0,50}select:)(\i)(,serialize:\i=>{)/g, + match: /(select:)(\i)(,serialize:\i=>{)/g, replace: "$1(platform)=>{$self.processQuestForAutoComplete(arguments[0].quest),$2(platform)}$3" }, { @@ -1287,7 +1308,7 @@ export default definePlugin({ // The "Quest Accepted" text is changed to "Resume" if the Quest is in progress but not active. // Then, when the Quest Accepted button is clicked, resume the automatic completion of the // Quest and disable the button again. - match: /(?<=fullWidth:!0}\)}\):.{0,200}?secondary",)disabled:!0,text:(.{0,30}?#{intl::QUEST_ACCEPTED}\)),/, + match: /(?<=secondary",)disabled:!0,text:(\i\.intl\.string\(\i\.\i#{intl::QUEST_ACCEPTED}\)),/, replace: "...$self.getQuestAcceptedButtonProps(arguments[0].quest,$1)," }, { diff --git a/src/equicordplugins/questify/settings.tsx b/src/equicordplugins/questify/settings.tsx index 6b763aeda..9cc5ea498 100644 --- a/src/equicordplugins/questify/settings.tsx +++ b/src/equicordplugins/questify/settings.tsx @@ -368,6 +368,7 @@ function validateDisableQuestSetting() { disableQuestsDiscoveryTab, disableQuestsFetchingQuests, disableQuestsDirectMessagesTab, + disableQuestsPageSponsoredBanner, disableQuestsPopupAboveAccountPanel, disableQuestsBadgeOnUserProfiles, disableQuestsGiftInventoryRelocationNotice, @@ -378,6 +379,7 @@ function validateDisableQuestSetting() { "disableQuestsDiscoveryTab", "disableQuestsFetchingQuests", "disableQuestsDirectMessagesTab", + "disableQuestsPageSponsoredBanner", "disableQuestsPopupAboveAccountPanel", "disableQuestsBadgeOnUserProfiles", "disableQuestsGiftInventoryRelocationNotice", @@ -385,7 +387,7 @@ function validateDisableQuestSetting() { "disableMembersListActivelyPlayingIcon" ]); - if (disableQuestsDiscoveryTab || disableQuestsDirectMessagesTab || disableQuestsFetchingQuests || disableQuestsPopupAboveAccountPanel || disableQuestsBadgeOnUserProfiles || disableQuestsGiftInventoryRelocationNotice || disableFriendsListActiveNowPromotion || disableMembersListActivelyPlayingIcon) { + if (disableQuestsDiscoveryTab || disableQuestsDirectMessagesTab || disableQuestsPageSponsoredBanner || disableQuestsFetchingQuests || disableQuestsPopupAboveAccountPanel || disableQuestsBadgeOnUserProfiles || disableQuestsGiftInventoryRelocationNotice || disableFriendsListActiveNowPromotion || disableMembersListActivelyPlayingIcon) { settings.store.disableQuestsEverything = false; } } @@ -673,6 +675,7 @@ function DisableQuestsSetting(): JSX.Element { disableQuestsDiscoveryTab, disableQuestsFetchingQuests, disableQuestsDirectMessagesTab, + disableQuestsPageSponsoredBanner, disableQuestsPopupAboveAccountPanel, disableQuestsBadgeOnUserProfiles, disableQuestsGiftInventoryRelocationNotice, @@ -687,6 +690,7 @@ function DisableQuestsSetting(): JSX.Element { "disableQuestsDiscoveryTab", "disableQuestsFetchingQuests", "disableQuestsDirectMessagesTab", + "disableQuestsPageSponsoredBanner", "disableQuestsPopupAboveAccountPanel", "disableQuestsBadgeOnUserProfiles", "disableQuestsGiftInventoryRelocationNotice", @@ -706,6 +710,7 @@ function DisableQuestsSetting(): JSX.Element { { label: "Disable Popup Above User Panel", value: "popup", selected: disableQuestsPopupAboveAccountPanel, type: "disable" }, { label: "Disable Discovery Tab Relocation Notice", value: "discovery", selected: disableQuestsDiscoveryTab, type: "disable" }, { label: "Disable Gift Inventory Relocation Notice", value: "inventory", selected: disableQuestsGiftInventoryRelocationNotice, type: "disable" }, + { label: "Disable Sponsored Banner on Quests Page", value: "sponsored-banner", selected: disableQuestsPageSponsoredBanner, type: "disable" }, { label: "Disable Friends List Active Now Promotion", value: "friends-list", selected: disableFriendsListActiveNowPromotion, type: "disable" }, { label: "Disable Members List Actively Playing Icon", value: "members-list", selected: disableMembersListActivelyPlayingIcon, type: "disable" }, { label: "Make Mobile Quests Desktop Compatible", value: "mobile-desktop-compatible", selected: makeMobileQuestsDesktopCompatible, type: "modification" }, @@ -735,6 +740,7 @@ function DisableQuestsSetting(): JSX.Element { settings.store.disableQuestsDiscoveryTab = enabledValues.includes("discovery"); settings.store.disableQuestsFetchingQuests = enabledValues.includes("fetching"); settings.store.disableQuestsDirectMessagesTab = enabledValues.includes("dms"); + settings.store.disableQuestsPageSponsoredBanner = enabledValues.includes("sponsored-banner"); settings.store.disableQuestsPopupAboveAccountPanel = enabledValues.includes("popup"); settings.store.disableQuestsBadgeOnUserProfiles = enabledValues.includes("badge"); settings.store.disableQuestsGiftInventoryRelocationNotice = enabledValues.includes("inventory"); @@ -1622,6 +1628,12 @@ export const settings = definePluginSettings({ default: false, hidden: true }, + disableQuestsPageSponsoredBanner: { + type: OptionType.BOOLEAN, + description: "Disable the sponsored banner on the Quest page.", + default: false, + hidden: true + }, disableQuestsPopupAboveAccountPanel: { type: OptionType.BOOLEAN, description: "Disable the Quest popup above your account panel.", From b78dd0c6a5a4a5da19145ebfe879e5ba77c597f6 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:21:19 -0500 Subject: [PATCH 068/291] ShowBadgesInChat: Remove Dead Badge --- src/equicordplugins/showBadgesInChat/index.tsx | 1 - src/equicordplugins/showBadgesInChat/settings.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/equicordplugins/showBadgesInChat/index.tsx b/src/equicordplugins/showBadgesInChat/index.tsx index 4d672e144..fcde80777 100644 --- a/src/equicordplugins/showBadgesInChat/index.tsx +++ b/src/equicordplugins/showBadgesInChat/index.tsx @@ -27,7 +27,6 @@ const discordBadges: readonly [number, string, string][] = Object.freeze([ [8, "HypeSquad Balance", "3aa41de486fa12454c3761e8e223442e"], [3, "Discord Bug Hunter", "2717692c7dca7289b35297368a940dd0"], [14, "Discord Bug Hunter", "848f79194d4be5ff5f81505cbd0ce1e6"], - [22, "Active Developer", "6bdc42827a38498929a4920da12695d9"], [17, "Early Verified Bot Developer", "6df5892e0f35b051f8b61eace34f4967"], [9, "Early Supporter", "7060786766c9c840eb3019e725d2b358"], [18, "Moderator Programs Alumni", "fee1624003e2fee35cb398e125dc479b"] diff --git a/src/equicordplugins/showBadgesInChat/settings.tsx b/src/equicordplugins/showBadgesInChat/settings.tsx index 2ce348699..bdf289a5e 100644 --- a/src/equicordplugins/showBadgesInChat/settings.tsx +++ b/src/equicordplugins/showBadgesInChat/settings.tsx @@ -97,7 +97,7 @@ const BadgeSettings = () => { { src: "https://equicord.org/assets/favicon.png", shown: settings.store.showEquicordContributor, title: "Equicord contributor badge", key: "EquicordContributer", position: settings.store.EquicordContributorPosition }, { src: "https://cdn.discordapp.com/emojis/1026533070955872337.png", shown: settings.store.showVencordDonor, title: "Vencord donor badges", key: "VencordDonor", position: settings.store.VencordDonorPosition }, { src: "https://cdn.discordapp.com/emojis/1092089799109775453.png", shown: settings.store.showVencordContributor, title: "Vencord contributor badge", key: "VencordContributer", position: settings.store.VencordContributorPosition }, - { src: "https://cdn.discordapp.com/badge-icons/bf01d1073931f921909045f3a39fd264.png", shown: settings.store.showDiscordProfile, title: "Discord profile badges (HypeSquad, Discord Staff, Active Developer, etc.)", key: "DiscordProfile", position: settings.store.DiscordProfilePosition }, + { src: "https://cdn.discordapp.com/badge-icons/bf01d1073931f921909045f3a39fd264.png", shown: settings.store.showDiscordProfile, title: "Discord profile badges (HypeSquad, Discord Staff, Orbs Apprentice, etc.)", key: "DiscordProfile", position: settings.store.DiscordProfilePosition }, { src: "https://cdn.discordapp.com/badge-icons/2ba85e8026a8614b640c2837bcdfe21b.png", shown: settings.store.showDiscordNitro, title: "Nitro badge", key: "DiscordNitro", position: settings.store.DiscordNitroPosition } ]); From 9c8e13562031a7ee773dceb57f25283f9c6f7af8 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:25:26 -0500 Subject: [PATCH 069/291] ShowBadgesInChat: Replace Orbs Badge Text --- src/equicordplugins/showBadgesInChat/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/showBadgesInChat/settings.tsx b/src/equicordplugins/showBadgesInChat/settings.tsx index bdf289a5e..1d1fb75c1 100644 --- a/src/equicordplugins/showBadgesInChat/settings.tsx +++ b/src/equicordplugins/showBadgesInChat/settings.tsx @@ -97,7 +97,7 @@ const BadgeSettings = () => { { src: "https://equicord.org/assets/favicon.png", shown: settings.store.showEquicordContributor, title: "Equicord contributor badge", key: "EquicordContributer", position: settings.store.EquicordContributorPosition }, { src: "https://cdn.discordapp.com/emojis/1026533070955872337.png", shown: settings.store.showVencordDonor, title: "Vencord donor badges", key: "VencordDonor", position: settings.store.VencordDonorPosition }, { src: "https://cdn.discordapp.com/emojis/1092089799109775453.png", shown: settings.store.showVencordContributor, title: "Vencord contributor badge", key: "VencordContributer", position: settings.store.VencordContributorPosition }, - { src: "https://cdn.discordapp.com/badge-icons/bf01d1073931f921909045f3a39fd264.png", shown: settings.store.showDiscordProfile, title: "Discord profile badges (HypeSquad, Discord Staff, Orbs Apprentice, etc.)", key: "DiscordProfile", position: settings.store.DiscordProfilePosition }, + { src: "https://cdn.discordapp.com/badge-icons/bf01d1073931f921909045f3a39fd264.png", shown: settings.store.showDiscordProfile, title: "Discord profile badges (HypeSquad, Discord Staff, Early Supporter, etc.)", key: "DiscordProfile", position: settings.store.DiscordProfilePosition }, { src: "https://cdn.discordapp.com/badge-icons/2ba85e8026a8614b640c2837bcdfe21b.png", shown: settings.store.showDiscordNitro, title: "Nitro badge", key: "DiscordNitro", position: settings.store.DiscordNitroPosition } ]); From 527743b147b0aa084ae1618eb968b5db8b96596b Mon Sep 17 00:00:00 2001 From: prism Date: Sat, 6 Dec 2025 14:24:20 +0100 Subject: [PATCH 070/291] feat(IconViewer): add hover effects to icon grid (#542) --- src/equicordplugins/iconViewer/IconsTab.css | 136 ++++++++++++-------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/src/equicordplugins/iconViewer/IconsTab.css b/src/equicordplugins/iconViewer/IconsTab.css index 1b2cb336e..b5d6206b9 100644 --- a/src/equicordplugins/iconViewer/IconsTab.css +++ b/src/equicordplugins/iconViewer/IconsTab.css @@ -1,115 +1,137 @@ .vc-icon-modal-codeblock { - margin-left: 10%; - margin-top: 30px; + margin-left: 10%; + margin-top: 30px; } .vc-icon-icon { - margin-left: 5%; + margin-left: 5%; + color: var(--interactive-normal); + transition: transform 0.15s ease, color 0.15s ease; +} + +.vc-icon-icon path, +.vc-icon-icon polygon, +.vc-icon-icon circle, +.vc-icon-icon rect { + fill: var(--interactive-normal); + transition: fill 0.15s ease; +} + +.vc-icon-container:hover .vc-icon-icon { + color: var(--interactive-active); + transform: scale(1.020); +} + +.vc-icon-container:hover .vc-icon-icon path, +.vc-icon-container:hover .vc-icon-icon polygon, +.vc-icon-container:hover .vc-icon-icon circle, +.vc-icon-container:hover .vc-icon-icon rect { + fill: var(--interactive-active); } .vc-icon-modal-main-container { - display: flex; + display: flex; } .vc-ic-unordered-list li { - margin-left: 5%; - list-style: disc; + margin-left: 5%; + list-style: disc; } .vc-icon-other-icon-sizes { - height: 32px; - display: flex; - margin-top: 15%; - margin-left: 5%; + height: 32px; + display: flex; + margin-top: 15%; + margin-left: 5%; } .vc-ic-icon-modal-root { - height: 450px; - width: 700px; + height: 450px; + width: 700px; } .vc-icons-tab-grid-container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); - gap: 8px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); + gap: 8px; } .vc-icon-modal-size-ex-icon { - margin-right: 5%; + margin-right: 5%; } .vc-icon-modal-icon { - height: 164px; - width: 164px; + height: 164px; + width: 164px; } .vc-icon-tab-search-bar-grid { - display: grid; - height: 50px; - gap: 10px; - grid-template-columns: 1fr 10px; + display: grid; + height: 50px; + gap: 10px; + grid-template-columns: 1fr 10px; } .vc-icon-display-box { - height: 164px; - width: 164px; - margin-top: 5%; - margin-left: 15%; - background-image: repeating-linear-gradient(45deg, - #ffffff1a 0, - #ffffff1a 10px, - #0000001a 10px, - #0000001a 20px); - border-radius: 10px; + height: 164px; + width: 164px; + margin-top: 5%; + margin-left: 15%; + background-image: repeating-linear-gradient(45deg, + #ffffff1a 0, + #ffffff1a 10px, + #0000001a 10px, + #0000001a 20px); + border-radius: 10px; } .vc-icon-container { - margin-top: 5px; - padding: 15px; - border-radius: 5px; - border: 3px solid transparent; - box-sizing: border-box; + margin-top: 5px; + padding: 15px; + border-radius: 5px; + border: 3px solid transparent; + box-sizing: border-box; } .vc-icon-container:hover { - border-radius: 5px; - border: 3px solid var(--background-base-low); - box-sizing: border-box; + border-radius: 5px; + border: 3px solid var(--background-base-low); + box-sizing: border-box; } .vc-icon-title { - font-size: 0.8em; - margin-top: 0; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + font-size: 0.8em; + margin-top: 0; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .vc-icon-modal-color-tooltip:hover { - background-color: var(--info-help-background); + background-color: var(--info-help-background); } .vc-save-modal { - margin-top: 10%; - display: grid; - grid-template-columns: auto 1fr; - gap: 10px; + margin-top: 10%; + display: grid; + grid-template-columns: auto 1fr; + gap: 10px; } .vc-save-select-option-1 { - margin-bottom: 5%; + margin-bottom: 5%; } .vc-save-select-option-2 { - margin-top: 5%; + margin-top: 5%; } .vc-ic-modals-root { - border-radius: 25px; + border-radius: 25px; } .vc-ic-modals-footer { - border-bottom-left-radius: 25px; - border-bottom-right-radius: 25px; -} \ No newline at end of file + border-bottom-left-radius: 25px; + border-bottom-right-radius: 25px; +} From 4f23a0449ee6c300a4082b47c7241fa9031f6f0f Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 12:33:57 -0500 Subject: [PATCH 071/291] ChatButtons: Rename Menu item --- src/api/ChatButtons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index c2a1a1982..f4871141d 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -174,7 +174,7 @@ addContextMenuPatch("textarea-context", (children, args) => { if (idx === -1) return; group.splice(idx, 0, - + {buttons.map(([id]) => ( Date: Sat, 6 Dec 2025 14:54:13 -0500 Subject: [PATCH 072/291] MessageClickActions: allow deleting one's own user app messages (#3806) --- .../src/common/messages/Message.d.ts | 15 ++++++++++++++- src/plugins/messageClickActions/index.ts | 10 ++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts index c2af24962..4a6f3e8b5 100644 --- a/packages/discord-types/src/common/messages/Message.d.ts +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -2,7 +2,7 @@ import { CommandOption } from './Commands'; import { User, UserJSON } from '../User'; import { Embed, EmbedJSON } from './Embed'; import { DiscordRecord } from "../Record"; -import { MessageFlags, MessageType, StickerFormatType } from "../../../enums"; +import { ApplicationIntegrationType, MessageFlags, MessageType, StickerFormatType } from "../../../enums"; /* * TODO: looks like discord has moved over to Date instead of Moment; @@ -71,6 +71,19 @@ export class Message extends DiscordRecord { type: number; version: string; }[]; + interactionMetadata?: { + id: string; + type: number; + name?: string; + command_type?: number; + ephemerality_reason?: number; + user: User; + authorizing_integration_owners: Record; + original_response_message_id?: string; + interacted_message_id?: string; + target_user?: User; + target_message_id?: string; + }; interactionError: unknown[]; isSearchHit: boolean; loggingName: unknown; diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 6404bd6f3..b9eb718d1 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -21,9 +21,9 @@ import { definePluginSettings } from "@api/Settings"; import NoReplyMentionPlugin from "@plugins/noReplyMention"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { MessageFlags } from "@vencord/discord-types/enums"; +import { ApplicationIntegrationType, MessageFlags } from "@vencord/discord-types/enums"; import { findByPropsLazy } from "@webpack"; -import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; +import { AuthenticationStore, FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, WindowStore } from "@webpack/common"; const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const EditStore = findByPropsLazy("isEditing", "isEditingAny"); @@ -76,7 +76,9 @@ export default definePlugin({ }, onMessageClick(msg, channel, event) { - const isMe = msg.author.id === UserStore.getCurrentUser().id; + const myId = AuthenticationStore.getId(); + const isMe = msg.author.id === myId; + const isSelfInvokedUserApp = msg.interactionMetadata?.authorizing_integration_owners[ApplicationIntegrationType.USER_INSTALL] === myId; if (!isDeletePressed) { if (event.detail < 2) return; if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return; @@ -106,7 +108,7 @@ export default definePlugin({ showMentionToggle: channel.guild_id !== null }); } - } else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) { + } else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel) || isSelfInvokedUserApp)) { if (msg.deleted) { FluxDispatcher.dispatch({ type: "MESSAGE_DELETE", From 45ba1350340e11102701b991d2be4c1b5201b16a Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 6 Dec 2025 21:11:41 +0100 Subject: [PATCH 073/291] LastFM: add api key guide back. our shared key may get ratelimited --- src/plugins/lastfmRichPresence/index.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx index b6164b196..4848b7438 100644 --- a/src/plugins/lastfmRichPresence/index.tsx +++ b/src/plugins/lastfmRichPresence/index.tsx @@ -17,6 +17,11 @@ */ import { definePluginSettings } from "@api/Settings"; +import { LinkButton } from "@components/Button"; +import { Card } from "@components/Card"; +import { Heading } from "@components/Heading"; +import { Margins } from "@components/margins"; +import { Paragraph } from "@components/Paragraph"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; @@ -61,6 +66,10 @@ function setActivity(activity: Activity | null) { } const settings = definePluginSettings({ + apiKey: { + description: "Custom Last.fm API key. Not required but highly recommended to avoid rate limiting with our shared key", + type: OptionType.STRING, + }, username: { description: "Last.fm username", type: OptionType.STRING, @@ -165,10 +174,6 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true, }, - apiKey: { - description: "Custom Last.fm API key. You shouldn't need to set this", - type: OptionType.STRING, - }, }); export default definePlugin({ @@ -178,6 +183,16 @@ export default definePlugin({ settings, + settingsAboutComponent() { + return ( + + How to create an API key + Set Application name and Application description to anything and leave the rest blank. + Create API Key + + ); + }, + start() { this.updatePresence(); this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000); From fa46c4c4d2f19114bc87842ae46b988e39e6a0e7 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 6 Dec 2025 21:33:58 +0100 Subject: [PATCH 074/291] settings sync: disallow possibly insecure settings realistically this shouldn't matter too much, but still good to have --- src/api/SettingsSync/offline.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/api/SettingsSync/offline.ts b/src/api/SettingsSync/offline.ts index aced11e81..5ff509caf 100644 --- a/src/api/SettingsSync/offline.ts +++ b/src/api/SettingsSync/offline.ts @@ -24,6 +24,21 @@ const toastFailure = (err: any) => const logger = new Logger("SettingsSync:Offline", "#39b7e0"); +function isSafeObject(obj: any) { + if (obj == null || typeof obj !== "object") return true; + + for (const key in obj) { + if (["__proto__", "constructor", "prototype"].includes(key)) { + return false; + } + if (!isSafeObject(obj[key])) { + return false; + } + } + + return true; +} + export async function importSettings(data: string) { try { var parsed = JSON.parse(data); @@ -32,6 +47,9 @@ export async function importSettings(data: string) { throw new Error("Failed to parse JSON: " + String(err)); } + if (!isSafeObject(parsed)) + throw new Error("Unsafe Settings"); + if ("settings" in parsed && "quickCss" in parsed) { Object.assign(PlainSettings, parsed.settings); await VencordNative.settings.set(parsed.settings); From ed51391efca92578aec94bed18baeaf4deb69452 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 6 Dec 2025 21:43:25 +0100 Subject: [PATCH 075/291] fix ShowHiddenChannels patch --- src/plugins/showHiddenChannels/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 5f83a3f5d..2cbd9552a 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -457,11 +457,11 @@ export default definePlugin({ // Filter hidden channels from GuildChannelStore.getChannels unless told otherwise match: /(?<=getChannels\(\i)(\){.*?)return (.+?)}/, replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??arguments[0]==="@favorites");}` - } + }, ] }, { - find: "#{intl::FORM_LABEL_MUTED}", + find: ".invitesDisabledTooltip", replacement: { // Make GuildChannelStore.getChannels return hidden channels match: /(?<=getChannels\(\i)(?=\))/, From 09a9ebd4096ab3709948326cae7f2c9a9b6529d0 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Sat, 6 Dec 2025 12:53:51 -0800 Subject: [PATCH 076/291] Improve patch for sponsor banner. (#541) --- src/equicordplugins/questify/index.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 654dfbb28..07ab803ad 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -967,11 +967,18 @@ export default definePlugin({ }, { // Hides the sponsored banner on the Quests page. - find: "assetSponsorImage.url,", - replacement: { - match: /(?<=campaignId]\),)/, - replace: "$self.shouldHideSponsoredQuestBanner()?null:" - } + find: "{isInDiscoverQuestHomeTab:", + group: true, + replacement: [ + { + match: /(?<=resetSortingFiltering\(\)},\[\]\);)/, + replace: "const shouldHideSponsoredQuestBanner=$self.shouldHideSponsoredQuestBanner();" + }, + { + match: /(?<=if\(null!=\i\))return(.{0,60}?}\))/, + replace: "if(!shouldHideSponsoredQuestBanner)return $1" + } + ] }, { // Hides the Quest icon from members list items when From 960f49bab683d23f716462768dbb1f431cd9edc5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 6 Dec 2025 21:58:13 +0100 Subject: [PATCH 077/291] bump to 1.13.7 --- package.json | 2 +- packages/vencord-types/package.json | 2 +- src/webpack/common/utils.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e267b04c1..f2dd8870c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.13.6", + "version": "1.13.7", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/packages/vencord-types/package.json b/packages/vencord-types/package.json index ae12354cd..7fd3345f8 100644 --- a/packages/vencord-types/package.json +++ b/packages/vencord-types/package.json @@ -1,7 +1,7 @@ { "name": "@vencord/types", "private": false, - "version": "1.13.2", + "version": "1.13.7", "description": "", "types": "index.d.ts", "scripts": { diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 0223c2e74..ff4c0b29a 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -18,6 +18,7 @@ import type * as t from "@vencord/discord-types"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; +import type * as TSPattern from "ts-pattern"; export let FluxDispatcher: t.FluxDispatcher; waitFor(["dispatch", "subscribe"], m => { @@ -47,7 +48,7 @@ export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYea export const hljs: typeof import("highlight.js").default = findByPropsLazy("highlight", "registerLanguage"); -export const { match, P }: Pick = mapMangledModuleLazy("@ts-pattern/matcher", { +export const { match, P }: { match: typeof TSPattern["match"], P: typeof TSPattern["P"]; } = mapMangledModuleLazy("@ts-pattern/matcher", { match: filters.byCode("return new"), P: filters.byProps("when") }); From a296025551d87e1df137313708149950882cf741 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 17:35:35 -0500 Subject: [PATCH 078/291] settingsrouter: update for v2 Co-Authored-By: prism <69634294+imjustprism@users.noreply.github.com> --- src/Vencord.ts | 6 +- src/api/SettingsSync/cloudSync.ts | 4 +- .../commandPalette/registry.tsx | 62 +++++++------------ src/equicordplugins/iconViewer/index.tsx | 4 +- .../keyboardNavigation/commands.tsx | 16 ++--- .../keyboardNavigation/index.tsx | 16 ++--- src/equicordplugins/themeLibrary/index.tsx | 4 +- src/plugins/webKeybinds.web/index.ts | 4 +- 8 files changed, 51 insertions(+), 65 deletions(-) diff --git a/src/Vencord.ts b/src/Vencord.ts index 3f1576f69..8fb08db06 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -44,7 +44,7 @@ import { localStorage } from "./utils/localStorage"; import { relaunch } from "./utils/native"; import { checkForUpdates, update, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; -import { SettingsRouter } from "./webpack/common"; +import { openUserSettingsPanel } from "./webpack/common"; import { patches } from "./webpack/patchWebpack"; if (IS_REPORTER) { @@ -61,7 +61,7 @@ async function syncSettings() { title: "Cloud Settings", body: "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)", color: "var(--yellow-360)", - onClick: () => SettingsRouter.open("VencordCloud") + onClick: () => openUserSettingsPanel("equicord_cloud") }); // Disable cloud sync globally Settings.cloud.authenticated = false; @@ -80,7 +80,7 @@ async function syncSettings() { body: "We've noticed you have cloud integrations enabled in another client! Due to limitations, you will " + "need to re-authenticate to continue using them. Click here to go to the settings page to do so!", color: "var(--yellow-360)", - onClick: () => SettingsRouter.open("EquicordCloud") + onClick: () => openUserSettingsPanel("equicord_cloud") }); return; } diff --git a/src/api/SettingsSync/cloudSync.ts b/src/api/SettingsSync/cloudSync.ts index c454eea96..14c41f4a7 100644 --- a/src/api/SettingsSync/cloudSync.ts +++ b/src/api/SettingsSync/cloudSync.ts @@ -8,7 +8,7 @@ import { showNotification } from "@api/Notifications"; import { PlainSettings, Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; import { relaunch } from "@utils/native"; -import { SettingsRouter } from "@webpack/common"; +import { openUserSettingsPanel } from "@webpack/common"; import { deflateSync, inflateSync } from "fflate"; import { deauthorizeCloud, getCloudAuth, getCloudUrl } from "./cloudSetup"; @@ -79,7 +79,7 @@ export async function getCloudSettings(shouldNotify = true, force = false) { title: "Cloud Settings", body: "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)", color: "var(--yellow-360)", - onClick: () => SettingsRouter.open("VencordCloud") + onClick: () => openUserSettingsPanel("equicord_cloud") }); // Disable cloud sync globally Settings.cloud.authenticated = false; diff --git a/src/equicordplugins/commandPalette/registry.tsx b/src/equicordplugins/commandPalette/registry.tsx index 18b73bce4..e3bf857c7 100644 --- a/src/equicordplugins/commandPalette/registry.tsx +++ b/src/equicordplugins/commandPalette/registry.tsx @@ -13,7 +13,7 @@ import { toggleEnabled } from "@equicordplugins/equicordHelper/utils"; import type { Plugin } from "@utils/types"; import { changes, checkForUpdates } from "@utils/updater"; import { findByPropsLazy, findStoreLazy } from "@webpack"; -import { ChannelActionCreators, ChannelRouter, ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, MediaEngineStore, React, ReadStateUtils, SelectedChannelStore, SelectedGuildStore, SettingsRouter, StreamerModeStore, Toasts, useEffect, UserStore, VoiceActions } from "@webpack/common"; +import { ChannelActionCreators, ChannelRouter, ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, MediaEngineStore, openUserSettingsPanel, React, ReadStateUtils, SelectedChannelStore, SelectedGuildStore, StreamerModeStore, Toasts, useEffect, UserStore, VoiceActions } from "@webpack/common"; import type { FC, ReactElement, ReactNode } from "react"; import { Settings } from "Vencord"; @@ -1270,7 +1270,7 @@ const BUILT_IN_COMMANDS: CommandEntry[] = [ keywords: ["settings", "equicord"], categoryId: DEFAULT_CATEGORY_ID, tags: [TAG_NAVIGATION, TAG_CORE], - handler: () => SettingsRouter.open("EquicordSettings") + handler: () => openUserSettingsPanel("equicord_main") }, { id: "open-plugin-settings", @@ -1278,7 +1278,7 @@ const BUILT_IN_COMMANDS: CommandEntry[] = [ keywords: ["settings", "plugins"], categoryId: DEFAULT_CATEGORY_ID, tags: [TAG_NAVIGATION, TAG_PLUGINS], - handler: () => SettingsRouter.open("EquicordPlugins") + handler: () => openUserSettingsPanel("equicord_plugins") }, { id: "reload-windows", @@ -1292,15 +1292,15 @@ const BUILT_IN_COMMANDS: CommandEntry[] = [ ]; const DISCORD_SETTINGS_COMMANDS: Array<{ id: string; label: string; route: string; keywords: string[]; description?: string; }> = [ - { id: "settings-account", label: "Open My Account", route: "My Account", keywords: ["account", "profile"] }, - { id: "settings-privacy", label: "Open Data & Privacy", route: "Data & Privacy", keywords: ["privacy", "safety", "data"] }, - { id: "settings-notifications", label: "Open Notifications", route: "Notifications", keywords: ["notifications"] }, - { id: "settings-voice", label: "Open Voice & Video", route: "Voice & Video", keywords: ["voice", "video", "audio"] }, - { id: "settings-text", label: "Open Text & Images", route: "Text & Images", keywords: ["text", "images"] }, - { id: "settings-appearance", label: "Open Appearance", route: "Appearance", keywords: ["appearance", "theme"] }, - { id: "settings-accessibility", label: "Open Accessibility", route: "Accessibility", keywords: ["accessibility"] }, - { id: "settings-keybinds", label: "Open Keybinds", route: "Keybinds", keywords: ["keybinds", "shortcuts"] }, - { id: "settings-advanced", label: "Open Advanced", route: "Advanced", keywords: ["advanced"] } + { id: "settings-account", label: "Open My Account", route: "my_account", keywords: ["account", "profile"] }, + { id: "settings-privacy", label: "Open Data & Privacy", route: "data_and_privacy", keywords: ["privacy", "safety", "data"] }, + { id: "settings-notifications", label: "Open Notifications", route: "legacy_notifications_settings", keywords: ["notifications"] }, + { id: "settings-voice", label: "Open Voice & Video", route: "voice_and_video", keywords: ["voice", "video", "audio"] }, + { id: "settings-text", label: "Open Text & Images", route: "text_and_images", keywords: ["text", "images"] }, + { id: "settings-appearance", label: "Open Appearance", route: "appearance", keywords: ["appearance", "theme"] }, + { id: "settings-accessibility", label: "Open Accessibility", route: "accessibility", keywords: ["accessibility"] }, + { id: "settings-keybinds", label: "Open Keybinds", route: "keybinds", keywords: ["keybinds", "shortcuts"] }, + { id: "settings-advanced", label: "Open Advanced", route: "advanced", keywords: ["advanced"] } ]; const settingsCommandsById = new Map(); @@ -1686,7 +1686,7 @@ function registerUpdateCommands() { keywords: ["updates", "changelog"], categoryId: "updates", tags: [TAG_DEVELOPER, TAG_NAVIGATION], - handler: () => SettingsRouter.open("EquicordChangelog") + handler: () => openUserSettingsPanel("equicord_changelog") }); } @@ -1705,33 +1705,17 @@ function registerDiscordSettingsCommands() { categoryId: "discord-settings", tags: [TAG_NAVIGATION], handler: async () => { - const fallbackRoute = command.id === "settings-privacy" ? "Privacy & Safety" : null; - - const attemptOpen = (route: string) => { - const result = SettingsRouter.open(route); - if (result && typeof (result as Promise).then === "function") { - return (result as Promise).then(() => undefined); - } - return Promise.resolve(); - }; + const fallbackRoute = command.id === "settings-privacy" ? "privacy_and_safety" : null; try { - await new Promise((resolve, reject) => { - requestAnimationFrame(() => { - attemptOpen(command.route) - .catch(error => { - if (fallbackRoute) { - return attemptOpen(fallbackRoute); - } - throw error; - }) - .then(() => resolve()) - .catch(reject); - }); - }); + await openUserSettingsPanel(command.route); } catch (error) { - console.error("CommandPalette", "Failed to open Discord settings", command.route, error); - showToast(`Unable to open ${command.label}.`, Toasts.Type.FAILURE); + if (fallbackRoute) { + await openUserSettingsPanel(fallbackRoute); + } else { + console.error("CommandPalette", "Failed to open Discord settings", command.route, error); + showToast(`Unable to open ${command.label}.`, Toasts.Type.FAILURE); + } } } }); @@ -2022,7 +2006,7 @@ async function executeCustomCommand(command: CustomCommandDefinition) { await runCommandById(action.commandId, new Set([command.id])); break; case "settings": - SettingsRouter.open(action.route); + openUserSettingsPanel(action.route); break; case "url": { const external = (window as any)?.DiscordNative?.app?.openExternalURL; @@ -2687,7 +2671,7 @@ function registerCustomizationCommands() { keywords: ["theme", "library"], categoryId: DEFAULT_CATEGORY_ID, tags: [TAG_CUSTOMIZATION, TAG_NAVIGATION], - handler: () => SettingsRouter.open("EquicordThemes") + handler: () => openUserSettingsPanel("equicord_themes") }); } diff --git a/src/equicordplugins/iconViewer/index.tsx b/src/equicordplugins/iconViewer/index.tsx index 30d295d4c..3651d21dc 100644 --- a/src/equicordplugins/iconViewer/index.tsx +++ b/src/equicordplugins/iconViewer/index.tsx @@ -8,7 +8,7 @@ import { MagnifyingGlassIcon } from "@components/Icons"; import SettingsPlugin from "@plugins/_core/settings"; import { EquicordDevs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; -import { SettingsRouter } from "@webpack/common"; +import { openUserSettingsPanel } from "@webpack/common"; import IconsTab from "./IconsTab"; import { SettingsAbout } from "./subComponents"; @@ -22,7 +22,7 @@ export default definePlugin({ startAt: StartAt.WebpackReady, toolboxActions: { "Open Icons Tab"() { - SettingsRouter.open("VencordDiscordIcons"); + openUserSettingsPanel("icon_viewer"); }, }, settingsAboutComponent: SettingsAbout, diff --git a/src/equicordplugins/keyboardNavigation/commands.tsx b/src/equicordplugins/keyboardNavigation/commands.tsx index 72e065042..6cf9bffe9 100644 --- a/src/equicordplugins/keyboardNavigation/commands.tsx +++ b/src/equicordplugins/keyboardNavigation/commands.tsx @@ -10,7 +10,7 @@ import { gitHashShort } from "@shared/vencordUserAgent"; import { copyToClipboard } from "@utils/clipboard"; import { relaunch, showItemInFolder } from "@utils/native"; import { checkForUpdates, getRepo } from "@utils/updater"; -import { GuildStore, NavigationRouter, SettingsRouter, Toasts } from "@webpack/common"; +import { GuildStore, NavigationRouter, openUserSettingsPanel, Toasts } from "@webpack/common"; import gitRemote from "~git-remote"; import Plugins from "~plugins"; @@ -26,12 +26,12 @@ export interface ButtonAction { } export const actions: ButtonAction[] = [ - { id: "openEquicordSettings", label: "Open Equicord tab", callback: async () => await SettingsRouter.open("EquicordSettings"), registrar: "Equicord" }, - { id: "openPluginSettings", label: "Open Plugin tab", callback: () => SettingsRouter.open("EquicordPlugins"), registrar: "Equicord" }, - { id: "openThemesSettings", label: "Open Themes tab", callback: () => SettingsRouter.open("EquicordThemes"), registrar: "Equicord" }, - { id: "openUpdaterSettings", label: "Open Updater tab", callback: () => SettingsRouter.open("EquicordUpdater"), registrar: "Equicord" }, - { id: "openEquicordCloudSettings", label: "Open Cloud tab", callback: () => SettingsRouter.open("EquicordCloud"), registrar: "Equicord" }, - { id: "openBackupSettings", label: "Open Backup & Restore tab", callback: () => SettingsRouter.open("EquicordSettingsSync"), registrar: "Equicord" }, + { id: "openEquicordSettings", label: "Open Equicord tab", callback: async () => await openUserSettingsPanel("equicord_main"), registrar: "Equicord" }, + { id: "openPluginSettings", label: "Open Plugin tab", callback: () => openUserSettingsPanel("equicord_plugins"), registrar: "Equicord" }, + { id: "openThemesSettings", label: "Open Themes tab", callback: () => openUserSettingsPanel("equicord_themes"), registrar: "Equicord" }, + { id: "openUpdaterSettings", label: "Open Updater tab", callback: () => openUserSettingsPanel("equicord_updater"), registrar: "Equicord" }, + { id: "openEquicordCloudSettings", label: "Open Cloud tab", callback: () => openUserSettingsPanel("equicord_cloud"), registrar: "Equicord" }, + { id: "openBackupSettings", label: "Open Backup & Restore tab", callback: () => openUserSettingsPanel("equicord_backup_restore"), registrar: "Equicord" }, { id: "restartClient", label: "Restart Client", callback: () => relaunch(), registrar: "Equicord" }, { id: "openQuickCSSFile", label: "Open Quick CSS File", callback: () => VencordNative.quickCss.openEditor(), registrar: "Equicord" }, { id: "openSettingsFolder", label: "Open Settings Folder", callback: async () => showItemInFolder(await VencordNative.settings.getSettingsDir()), registrar: "Equicord" }, @@ -140,7 +140,7 @@ export const actions: ButtonAction[] = [ permanent: true, noPersist: true, onClick() { - SettingsRouter.open("EquicordUpdater"); + openUserSettingsPanel("equicord_updater"); } }), 10_000); } else { diff --git a/src/equicordplugins/keyboardNavigation/index.tsx b/src/equicordplugins/keyboardNavigation/index.tsx index 00405614f..75bb65d7e 100644 --- a/src/equicordplugins/keyboardNavigation/index.tsx +++ b/src/equicordplugins/keyboardNavigation/index.tsx @@ -9,7 +9,7 @@ import { classNameFactory } from "@api/Styles"; import { Devs } from "@utils/constants"; import { closeAllModals } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { SettingsRouter, useState } from "@webpack/common"; +import { openUserSettingsPanel, useState } from "@webpack/common"; import { registerAction } from "./commands"; import { openCommandPalette } from "./components/CommandPalette"; @@ -94,12 +94,14 @@ export default definePlugin({ start() { document.addEventListener("keydown", this.event); - registerAction({ - id: "openDevSettings", - label: "Open Dev tab", - callback: () => SettingsRouter.open("EquicordPatchHelper"), - registrar: "Equicord" - }); + if (IS_DEV) { + registerAction({ + id: "openDevSettings", + label: "Open Dev tab", + callback: () => openUserSettingsPanel("equicord_patch_helper"), + registrar: "Equicord" + }); + } }, stop() { diff --git a/src/equicordplugins/themeLibrary/index.tsx b/src/equicordplugins/themeLibrary/index.tsx index 17edca5c3..380ebcc5f 100644 --- a/src/equicordplugins/themeLibrary/index.tsx +++ b/src/equicordplugins/themeLibrary/index.tsx @@ -8,7 +8,7 @@ import { ColorPaletteIcon } from "@components/Icons"; import SettingsPlugin from "@plugins/_core/settings"; import { EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { SettingsRouter } from "@webpack/common"; +import { openUserSettingsPanel } from "@webpack/common"; import { settings } from "./utils/settings"; @@ -19,7 +19,7 @@ export default definePlugin({ settings, toolboxActions: { "Open Theme Library": () => { - SettingsRouter.open("ThemeLibrary"); + openUserSettingsPanel("theme_library"); }, }, diff --git a/src/plugins/webKeybinds.web/index.ts b/src/plugins/webKeybinds.web/index.ts index 6c69cb470..8ee938230 100644 --- a/src/plugins/webKeybinds.web/index.ts +++ b/src/plugins/webKeybinds.web/index.ts @@ -19,7 +19,7 @@ import { Devs, IS_MAC } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common"; +import { ComponentDispatch, FluxDispatcher, NavigationRouter, openUserSettingsPanel, SelectedGuildStore } from "@webpack/common"; const KeyBinds = findByPropsLazy("JUMP_TO_GUILD", "SERVER_NEXT"); @@ -55,7 +55,7 @@ export default definePlugin({ break; case ",": e.preventDefault(); - SettingsRouter.open("My Account"); + openUserSettingsPanel("my_account"); break; default: if (e.key >= "1" && e.key <= "9") { From 7098f41c5bbe76edd62151042e4bd7c7d15ac2f6 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 6 Dec 2025 23:43:30 +0100 Subject: [PATCH 079/291] fix Vencord missing from Settings right click menu & BetterSettings --- src/plugins/_core/settings.tsx | 23 ++++++++- .../betterSettings/fullHeightContext.css | 10 ++++ src/plugins/betterSettings/index.tsx | 51 ++++++++++++------- 3 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 src/plugins/betterSettings/fullHeightContext.css diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index fe3c792ac..63a85c72c 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -149,6 +149,13 @@ export default definePlugin({ match: /(\i)\.buildLayout\(\)(?=\.map)/, replace: "$self.buildLayout($1)" } + }, + { + find: "getWebUserSettingFromSection", + replacement: { + match: /new Map\(\[(?=\[.{0,10}\.ACCOUNT,.{0,10}\.ACCOUNT_PANEL)/, + replace: "new Map([...$self.getSettingsSectionMappings()," + } } ], @@ -180,6 +187,18 @@ export default definePlugin({ }); }, + getSettingsSectionMappings() { + return [ + ["VencordSettings", "vencord_main_panel"], + ["VencordPlugins", "vencord_plugins_panel"], + ["VencordThemes", "vencord_themes_panel"], + ["VencordUpdater", "vencord_updater_panel"], + ["VencordCloud", "vencord_cloud_panel"], + ["VencordBackupAndRestore", "vencord_backup_restore_panel"], + ["VencordPatchHelper", "vencord_patch_helper_panel"] + ]; + }, + buildLayout(originalLayoutBuilder: SettingsLayoutBuilder) { const layout = originalLayoutBuilder.buildLayout(); if (originalLayoutBuilder.key !== "$Root") return layout; @@ -294,7 +313,7 @@ export default definePlugin({ className: "vc-settings-header" }, { - section: "settings/tabs", + section: "VencordSettings", label: "Vencord", element: VencordTab, className: "vc-settings" @@ -324,7 +343,7 @@ export default definePlugin({ className: "vc-cloud" }, { - section: "settings/tabsSync", + section: "VencordBackupAndRestore", label: "Backup & Restore", element: BackupAndRestoreTab, className: "vc-backup-restore" diff --git a/src/plugins/betterSettings/fullHeightContext.css b/src/plugins/betterSettings/fullHeightContext.css new file mode 100644 index 000000000..97069dc9c --- /dev/null +++ b/src/plugins/betterSettings/fullHeightContext.css @@ -0,0 +1,10 @@ +/* + * Discord has dumb max height logic for their context menus. + * If a context menu is at the bottom of the screen, its submenus are capped to its max height and can't even grow upwards + * We unset the variable they use to cap height. This allows submenus to grow as tall as they want + */ + +#user-settings-cog, +[aria-activedescendant^="user-settings-cog"] { + --reference-position-layer-max-height: initial !important; +} \ No newline at end of file diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index ab2700e7c..c3432ab87 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -5,16 +5,17 @@ */ import { definePluginSettings } from "@api/Settings"; -import { classNameFactory } from "@api/Styles"; +import { classNameFactory, disableStyle, enableStyle } from "@api/Styles"; import { buildPluginMenuEntries, buildThemeMenuEntries } from "@plugins/vencordToolbox/menu"; import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { waitFor } from "@webpack"; import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common"; import type { HTMLAttributes, ReactElement } from "react"; +import fullHeightStyle from "./fullHeightContext.css?managed"; + type SettingsEntry = { section: string, label: string; }; const cl = classNameFactory(""); @@ -42,6 +43,11 @@ const settings = definePluginSettings({ } }); +interface TransformedSettingsEntry { + section: string; + items: SettingsEntry[]; +} + interface LayerProps extends HTMLAttributes { mode: "SHOWN" | "HIDDEN"; baseLayer?: boolean; @@ -81,6 +87,15 @@ export default definePlugin({ authors: [Devs.Kyuuhachi], settings, + start() { + if (settings.store.organizeMenu) + enableStyle(fullHeightStyle); + }, + + stop() { + disableStyle(fullHeightStyle); + }, + patches: [ { find: "this.renderArtisanalHack()", @@ -124,8 +139,8 @@ export default definePlugin({ find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", replacement: [ { - match: /=\[\];if\((\i)(?=\.forEach)/, - replace: "=$self.wrapMap([]);if($self.transformSettingsEntries($1)", + match: /=\[\];if\((\i)(?=\.forEach.{0,100}"logout"!==\i.{0,30}(\i)\.get\(\i\))/, + replace: "=$self.wrapMap([]);if($self.transformSettingsEntries($1,$2)", predicate: () => settings.store.organizeMenu }, { @@ -154,36 +169,34 @@ export default definePlugin({ return ; }, - transformSettingsEntries(list: SettingsEntry[]) { - const items = [{ label: null as string | null, items: [] as SettingsEntry[] }]; + transformSettingsEntries(list: SettingsEntry[], keyMap: Map) { + const items = [] as TransformedSettingsEntry[]; for (const item of list) { if (item.section === "HEADER") { - items.push({ label: item.label, items: [] }); - } else if (item.section === "DIVIDER") { - items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] }); - } else { - items.at(-1)!.items.push(item); + keyMap.set(item.label, item.label); + items.push({ section: item.label, items: [] }); + } else if (item.section !== "DIVIDER" && keyMap.has(item.section)) { + items.at(-1)?.items.push(item); } } return items; }, - wrapMap(toWrap: any[]) { - const otherOptions = getIntlMessage("OTHER_OPTIONS"); + wrapMap(toWrap: TransformedSettingsEntry[]) { // @ts-expect-error toWrap.map = function (render: (item: SettingsEntry) => ReactElement) { return this - .filter(a => a.items.length > 0 && a.label !== otherOptions) - .map(({ label, items }) => { + .filter(a => a.items.length > 0) + .map(({ section, items }) => { const children = items.map(render); - if (label) { + if (section) { return ( {children} From 448ea1110dd36de97e4278ea97f0b7b845a9b54e Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 17:49:06 -0500 Subject: [PATCH 080/291] text-danger was removed --- src/components/Button.css | 2 +- src/components/ThemeSettings/ThemesTab.tsx | 2 +- src/components/settings/tabs/patchHelper/FullPatchInput.tsx | 2 +- src/components/settings/tabs/patchHelper/PatchPreview.tsx | 2 +- src/components/settings/tabs/plugins/components/styles.css | 2 +- src/components/settings/tabs/plugins/styles.css | 4 ++-- .../channelTabs/components/KeybindSettings.tsx | 2 +- src/equicordplugins/channelTabs/style.css | 2 +- src/equicordplugins/commandPalette/style.css | 2 +- src/equicordplugins/discordDevBanner/styles.css | 4 ++-- src/equicordplugins/gifCollections/style.css | 4 ++-- src/equicordplugins/githubRepos/index.tsx | 2 +- src/equicordplugins/githubRepos/styles.css | 2 +- src/equicordplugins/iconViewer/saveModal.tsx | 2 +- src/equicordplugins/soundBoardLogger/settings.tsx | 4 ++-- src/plugins/customRPC/settings.css | 2 +- src/plugins/textReplace/index.tsx | 2 +- src/plugins/voiceMessages/styles.css | 4 ++-- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/Button.css b/src/components/Button.css index b60123bb8..debf21e8a 100644 --- a/src/components/Button.css +++ b/src/components/Button.css @@ -191,7 +191,7 @@ } .vc-text-btn-danger { - color: var(--text-feedback-critical, var(--text-danger)); + color: var(--text-feedback-critical, var(--text-feedback-critical)); } .vc-text-btn-link { diff --git a/src/components/ThemeSettings/ThemesTab.tsx b/src/components/ThemeSettings/ThemesTab.tsx index 57315dfb4..b662a4e46 100644 --- a/src/components/ThemeSettings/ThemesTab.tsx +++ b/src/components/ThemeSettings/ThemesTab.tsx @@ -82,7 +82,7 @@ function Validator({ link, onValidate }: { link: string; onValidate: (valid: boo : "Valid!"; return {text}; } diff --git a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx index 370ccf799..7599b9faf 100644 --- a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx +++ b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx @@ -78,7 +78,7 @@ export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacemen onChange={setPatch} onBlur={update} /> - {error !== "" && {error}} + {error !== "" && {error}} ); } diff --git a/src/components/settings/tabs/patchHelper/PatchPreview.tsx b/src/components/settings/tabs/patchHelper/PatchPreview.tsx index 69f4a44a0..00f7f0597 100644 --- a/src/components/settings/tabs/patchHelper/PatchPreview.tsx +++ b/src/components/settings/tabs/patchHelper/PatchPreview.tsx @@ -142,7 +142,7 @@ export function PatchPreview({ module, match, replacement, setReplacementError } )} {compileResult && ( - + {compileResult[1]} )} diff --git a/src/components/settings/tabs/plugins/components/styles.css b/src/components/settings/tabs/plugins/components/styles.css index 100c1d4c6..c1d55b0a8 100644 --- a/src/components/settings/tabs/plugins/components/styles.css +++ b/src/components/settings/tabs/plugins/components/styles.css @@ -31,5 +31,5 @@ } .vc-plugins-setting-error { - color: var(--text-danger); + color: var(--text-feedback-critical); } \ No newline at end of file diff --git a/src/components/settings/tabs/plugins/styles.css b/src/components/settings/tabs/plugins/styles.css index 951e82655..05db9d626 100644 --- a/src/components/settings/tabs/plugins/styles.css +++ b/src/components/settings/tabs/plugins/styles.css @@ -159,7 +159,7 @@ } .text-danger { - color: var(--text-danger) !important; + color: var(--text-feedback-critical) !important; font-size: 1.2rem; font-weight: bold; } @@ -224,7 +224,7 @@ /* Specific to disable all modal */ .warning-text { - color: var(--text-danger) !important; + color: var(--text-feedback-critical) !important; font-size: 1.2rem !important; } diff --git a/src/equicordplugins/channelTabs/components/KeybindSettings.tsx b/src/equicordplugins/channelTabs/components/KeybindSettings.tsx index ccb53ade5..4331adfcd 100644 --- a/src/equicordplugins/channelTabs/components/KeybindSettings.tsx +++ b/src/equicordplugins/channelTabs/components/KeybindSettings.tsx @@ -120,7 +120,7 @@ function KeybindInput({ label, description, settingKey, enabledKey }: KeybindInp {description} {!isEnabled && ( - + This shortcut is currently disabled )} diff --git a/src/equicordplugins/channelTabs/style.css b/src/equicordplugins/channelTabs/style.css index 9d3be766d..02fde82df 100644 --- a/src/equicordplugins/channelTabs/style.css +++ b/src/equicordplugins/channelTabs/style.css @@ -1171,7 +1171,7 @@ } .channelTabs-keybind-conflict { - color: var(--text-danger); + color: var(--text-feedback-critical); font-size: 12px; font-weight: 500; margin-top: 2px; diff --git a/src/equicordplugins/commandPalette/style.css b/src/equicordplugins/commandPalette/style.css index d71c30a2f..f69647887 100644 --- a/src/equicordplugins/commandPalette/style.css +++ b/src/equicordplugins/commandPalette/style.css @@ -769,7 +769,7 @@ } .vc-cp-keybind-conflict { - color: var(--text-danger); + color: var(--text-feedback-critical); font-size: 12px; font-weight: 500; margin-top: 2px; diff --git a/src/equicordplugins/discordDevBanner/styles.css b/src/equicordplugins/discordDevBanner/styles.css index 2c89b08b6..1f0eb4b66 100644 --- a/src/equicordplugins/discordDevBanner/styles.css +++ b/src/equicordplugins/discordDevBanner/styles.css @@ -5,7 +5,7 @@ .vc-discord-dev-banner-error { margin-top: 8px; - color: var(--text-danger); + color: var(--text-feedback-critical); } .vc-discord-dev-banner-icons { @@ -27,4 +27,4 @@ border-radius: 12px; outline: none; margin-top: 12px; -} +} \ No newline at end of file diff --git a/src/equicordplugins/gifCollections/style.css b/src/equicordplugins/gifCollections/style.css index 5dcd8cbbe..813535744 100644 --- a/src/equicordplugins/gifCollections/style.css +++ b/src/equicordplugins/gifCollections/style.css @@ -148,13 +148,13 @@ } .input-warning { - outline: 1px solid var(--text-danger); + outline: 1px solid var(--text-feedback-critical); } .warning-text { margin-top: 16px; font-size: 14px; - color: var(--text-danger); + color: var(--text-feedback-critical); } .collection-buttons { diff --git a/src/equicordplugins/githubRepos/index.tsx b/src/equicordplugins/githubRepos/index.tsx index aad63fd03..c528b97f3 100644 --- a/src/equicordplugins/githubRepos/index.tsx +++ b/src/equicordplugins/githubRepos/index.tsx @@ -53,7 +53,7 @@ const ProfilePopoutComponent = ErrorBoundary.wrap( }, { noop: true, - fallback: () => + fallback: () => Error, Failed to render GithubRepos } diff --git a/src/equicordplugins/githubRepos/styles.css b/src/equicordplugins/githubRepos/styles.css index dfa286105..214dab195 100644 --- a/src/equicordplugins/githubRepos/styles.css +++ b/src/equicordplugins/githubRepos/styles.css @@ -216,7 +216,7 @@ } .vc-github-repos-error { - color: var(--text-danger); + color: var(--text-feedback-critical); } .vc-github-repos-link { diff --git a/src/equicordplugins/iconViewer/saveModal.tsx b/src/equicordplugins/iconViewer/saveModal.tsx index 6c038ffd4..24064e3de 100644 --- a/src/equicordplugins/iconViewer/saveModal.tsx +++ b/src/equicordplugins/iconViewer/saveModal.tsx @@ -69,7 +69,7 @@ export function SelectComponent({ option, onChange, onError, className }: IDivEl isSelected={v => v === state} serialize={v => String(v)} /> - {error && {error}} + {error && {error}} ); } diff --git a/src/equicordplugins/soundBoardLogger/settings.tsx b/src/equicordplugins/soundBoardLogger/settings.tsx index 75d211c5d..7fbcff37a 100644 --- a/src/equicordplugins/soundBoardLogger/settings.tsx +++ b/src/equicordplugins/soundBoardLogger/settings.tsx @@ -57,8 +57,8 @@ const settings = definePluginSettings({ onChange={handleChange} placeholder={"Enter a number"} /> - {shouldShowWarning && Warning! Setting the number to a lower value will reset the log!} - {errorMessage && {errorMessage}} + {shouldShowWarning && Warning! Setting the number to a lower value will reset the log!} + {errorMessage && {errorMessage}}
); } diff --git a/src/plugins/customRPC/settings.css b/src/plugins/customRPC/settings.css index 766009eae..0fe9ae66f 100644 --- a/src/plugins/customRPC/settings.css +++ b/src/plugins/customRPC/settings.css @@ -27,5 +27,5 @@ } .vc-customRPC-settings-error { - color: var(--text-danger); + color: var(--text-feedback-critical); } \ No newline at end of file diff --git a/src/plugins/textReplace/index.tsx b/src/plugins/textReplace/index.tsx index 341489739..e22bcf1c8 100644 --- a/src/plugins/textReplace/index.tsx +++ b/src/plugins/textReplace/index.tsx @@ -95,7 +95,7 @@ function renderFindError(find: string) { return null; } catch (e) { return ( - + {String(e)} ); diff --git a/src/plugins/voiceMessages/styles.css b/src/plugins/voiceMessages/styles.css index cb8a7a233..9e42350e1 100644 --- a/src/plugins/voiceMessages/styles.css +++ b/src/plugins/voiceMessages/styles.css @@ -50,5 +50,5 @@ } .vc-vmsg-error { - color: var(--text-danger); -} + color: var(--text-feedback-critical); +} \ No newline at end of file From cad91b72b1235ebb8616b8cf686e8be9d1877076 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:43:51 -0500 Subject: [PATCH 081/291] ThemesTab: Remove Semicolon --- src/components/ThemeSettings/ThemesTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ThemeSettings/ThemesTab.tsx b/src/components/ThemeSettings/ThemesTab.tsx index b662a4e46..a4a151496 100644 --- a/src/components/ThemeSettings/ThemesTab.tsx +++ b/src/components/ThemeSettings/ThemesTab.tsx @@ -469,7 +469,7 @@ function ThemesTab() { })); return ( - <>; + <>
Online Themes From f50bac6277b3da5817614ef5f71e0c484f357092 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 19:34:36 -0500 Subject: [PATCH 082/291] Add Old Settings v1 sections back --- src/equicordplugins/iRememberYou/index.tsx | 22 ++++++++++++++------ src/equicordplugins/iconViewer/index.tsx | 24 +++++++++++++++------- src/equicordplugins/themeLibrary/index.tsx | 22 ++++++++++++++------ 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/equicordplugins/iRememberYou/index.tsx b/src/equicordplugins/iRememberYou/index.tsx index 92f26f485..02851a6e5 100644 --- a/src/equicordplugins/iRememberYou/index.tsx +++ b/src/equicordplugins/iRememberYou/index.tsx @@ -24,14 +24,22 @@ export default definePlugin({ patches: [], async start() { - const customEntriesSections = SettingsPlugin.customEntries; - customEntriesSections.push({ - key: "i_remember_you", + const { customEntries, customSections } = SettingsPlugin; + + customEntries.push({ + key: "equicord_i_remember_you", title: "I Remember You", Component: () => , Icon: EyeIcon }); + customSections.push(() => ({ + section: "EquicordIRememberYou", + label: "IRememberYou", + element: () => , + id: "IRememberYou" + })); + const data = (this.dataManager = await new Data().withStart()); await data.initializeUsersCollection(); @@ -45,9 +53,11 @@ export default definePlugin({ stop() { const dataManager = this.dataManager as Data; - const customEntriesSections = SettingsPlugin.customEntries; - const i = customEntriesSections.findIndex(entry => entry.key === "i_remember_you"); - if (i !== -1) customEntriesSections.splice(i, 1); + const { customEntries, customSections } = SettingsPlugin; + const entry = customEntries.findIndex(entry => entry.key === "equicord_i_remember_you"); + const section = customSections.findIndex(section => section({} as any).id === "IRememberYou"); + if (entry !== -1) customEntries.splice(entry, 1); + if (section !== -1) customSections.splice(section, 1); removeMessagePreSendListener(dataManager._onMessagePreSend_preSend); clearInterval(dataManager._storageAutoSaveProtocol_interval); diff --git a/src/equicordplugins/iconViewer/index.tsx b/src/equicordplugins/iconViewer/index.tsx index 3651d21dc..738d9c248 100644 --- a/src/equicordplugins/iconViewer/index.tsx +++ b/src/equicordplugins/iconViewer/index.tsx @@ -22,23 +22,33 @@ export default definePlugin({ startAt: StartAt.WebpackReady, toolboxActions: { "Open Icons Tab"() { - openUserSettingsPanel("icon_viewer"); + openUserSettingsPanel("equicord_icon_viewer"); }, }, settingsAboutComponent: SettingsAbout, start() { - const customEntriesSections = SettingsPlugin.customEntries; + const { customEntries, customSections } = SettingsPlugin; - customEntriesSections.push({ - key: "icon_viewer", + customEntries.push({ + key: "equicord_icon_viewer", title: "Icon Finder", Component: IconsTab, Icon: MagnifyingGlassIcon }); + + customSections.push(() => ({ + section: "EquicordDiscordIcons", + label: "Icon Finder", + element: IconsTab, + className: "vc-discord-icons", + id: "IconViewer" + })); }, stop() { - const customEntriesSections = SettingsPlugin.customEntries; - const i = customEntriesSections.findIndex(entry => entry.key === "icon_viewer"); - if (i !== -1) customEntriesSections.splice(i, 1); + const { customEntries, customSections } = SettingsPlugin; + const entry = customEntries.findIndex(entry => entry.key === "equicord_icon_viewer"); + const section = customSections.findIndex(section => section({} as any).id === "IconViewer"); + if (entry !== -1) customEntries.splice(entry, 1); + if (section !== -1) customSections.splice(section, 1); }, }); diff --git a/src/equicordplugins/themeLibrary/index.tsx b/src/equicordplugins/themeLibrary/index.tsx index 380ebcc5f..fdf8d81cd 100644 --- a/src/equicordplugins/themeLibrary/index.tsx +++ b/src/equicordplugins/themeLibrary/index.tsx @@ -24,19 +24,29 @@ export default definePlugin({ }, start() { - const customEntriesSections = SettingsPlugin.customEntries; + const { customEntries, customSections } = SettingsPlugin; - customEntriesSections.push({ - key: "theme_library", + customEntries.push({ + key: "equicord_theme_library", title: "Theme Library", Component: require("./components/ThemeTab").default, Icon: ColorPaletteIcon }); + + customSections.push(() => ({ + section: "EquicordThemeLibrary", + label: "Theme Library", + searchableTitles: ["Theme Library"], + element: require("./components/ThemeTab").default, + id: "ThemeLibrary", + })); }, stop() { - const customEntriesSections = SettingsPlugin.customEntries; - const i = customEntriesSections.findIndex(entry => entry.key === "theme_library"); - if (i !== -1) customEntriesSections.splice(i, 1); + const { customEntries, customSections } = SettingsPlugin; + const entry = customEntries.findIndex(entry => entry.key === "equicord_theme_library"); + const section = customSections.findIndex(section => section({} as any).id === "ThemeLibrary"); + if (entry !== -1) customEntries.splice(entry, 1); + if (section !== -1) customSections.splice(section, 1); }, }); From b8d73f518529986310714c9e69bd79e006e2d292 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 19:43:44 -0500 Subject: [PATCH 083/291] bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8723705fb..2636a7da4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "equicord", "private": "true", - "version": "1.13.7", + "version": "1.13.7.1", "description": "The other cutest Discord client mod", "homepage": "https://github.com/Equicord/Equicord#readme", "bugs": { @@ -121,4 +121,4 @@ "engines": { "node": ">=18" } -} +} \ No newline at end of file From bd5b89376eaab4893269bfe6c4c22b346eb21899 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 19:48:08 -0500 Subject: [PATCH 084/291] this mistake made me a failure --- src/equicordplugins/selfForward/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/selfForward/index.ts b/src/equicordplugins/selfForward/index.ts index 50b5eb0f9..74c9902bc 100644 --- a/src/equicordplugins/selfForward/index.ts +++ b/src/equicordplugins/selfForward/index.ts @@ -9,7 +9,7 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "SelfForward", - description: "adds the current channel to the forward list popup", + description: "Adds the current channel to the forward list popup", authors: [EquicordDevs.VillainsRule], patches: [ { From fbc74ce8c895fed601599572a2833995aded4d2d Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 20:34:59 -0500 Subject: [PATCH 085/291] Settings: Remove migration for customsections --- src/plugins/_core/settings.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index a6496440f..ade1b10fa 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -5,7 +5,7 @@ */ import { definePluginSettings } from "@api/Settings"; -import { BackupRestoreIcon, CloudIcon, LogIcon, MainSettingsIcon, PaintbrushIcon, PatchHelperIcon, PlaceholderIcon, PluginsIcon, UpdaterIcon, VesktopSettingsIcon } from "@components/Icons"; +import { BackupRestoreIcon, CloudIcon, LogIcon, MainSettingsIcon, PaintbrushIcon, PatchHelperIcon, PluginsIcon, UpdaterIcon, VesktopSettingsIcon } from "@components/Icons"; import { BackupAndRestoreTab, ChangelogTab, @@ -207,6 +207,7 @@ export default definePlugin({ ["EquicordCloud", "equicord_cloud_panel"], ["EquicordBackupAndRestore", "equicord_backup_restore_panel"], ["EquicordPatchHelper", "equicord_patch_helper_panel"], + ["Equibop", "EquibopSettings", "equicord_equibop_settings_panel"], ["EquicordDiscordIcons", "equicord_icon_viewer"], ["EquicordThemeLibrary", "equicord_theme_library"], ["EquicordIRememberYou", "equicord_i_remember_you"], @@ -278,13 +279,13 @@ export default definePlugin({ // TODO: Remove deprecated customSections in a future update ...this.customSections.map((func, i) => { const { section, element, label } = func(FallbackSectionTypes); - if (Object.values(FallbackSectionTypes).includes(section)) return null; + if (section !== "Equibop") return null; return buildEntry({ key: `equicord_deprecated_custom_${section}`, title: label, Component: element, - Icon: section === "Vesktop" ? VesktopSettingsIcon : PlaceholderIcon + Icon: VesktopSettingsIcon }); }) ].filter(isTruthy); From 708fb53a60e893aaa0712d96b99db1ace23f952c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 20:55:59 -0500 Subject: [PATCH 086/291] JellyfinRichPresence: Remove Deprecated Setting --- src/equicordplugins/jellyfinRichPresence/index.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/equicordplugins/jellyfinRichPresence/index.tsx b/src/equicordplugins/jellyfinRichPresence/index.tsx index 7e07a6388..0508337c3 100644 --- a/src/equicordplugins/jellyfinRichPresence/index.tsx +++ b/src/equicordplugins/jellyfinRichPresence/index.tsx @@ -107,11 +107,6 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: false, }, - showTMDBButton: { - description: "Show TheMovieDB button in Rich Presence", - type: OptionType.BOOLEAN, - default: true, - }, overrideRichPresenceType: { description: "Override the rich presence type", type: OptionType.SELECT, From c710fffa2f133ee8e1771947509228646ee6d861 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:48:54 -0500 Subject: [PATCH 087/291] Fix Donor Card Colors --- src/components/settings/DonateButton.tsx | 6 +++-- src/components/settings/SpecialCard.css | 26 ++++++++++++++++--- .../settings/tabs/vencord/DonateButton.tsx | 4 ++- .../settings/tabs/vencord/index.tsx | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/components/settings/DonateButton.tsx b/src/components/settings/DonateButton.tsx index e7c562df7..4a8e96dc8 100644 --- a/src/components/settings/DonateButton.tsx +++ b/src/components/settings/DonateButton.tsx @@ -25,6 +25,7 @@ import { showToast } from "@webpack/common"; export function DonateButton({ equicord = false, + className, ...props }: Partial & { equicord?: boolean; }) { const link = equicord ? "https://github.com/sponsors/thororen1234" : "https://github.com/sponsors/Vendicated"; @@ -35,7 +36,7 @@ export function DonateButton({ size="medium" type="button" onClick={() => VencordNative.native.openExternal(link)} - className="vc-donate-button" + className={className || "vc-donate-button"} > Donate @@ -44,6 +45,7 @@ export function DonateButton({ } export function InviteButton({ + className, ...props }: Partial) { return ( @@ -58,7 +60,7 @@ export function InviteButton({ showToast("Invalid or expired invite"), ); }} - className="vc-invite-button" + className={className || "vc-donate-button"} > Invite diff --git a/src/components/settings/SpecialCard.css b/src/components/settings/SpecialCard.css index 083cb1d67..c58c5044d 100644 --- a/src/components/settings/SpecialCard.css +++ b/src/components/settings/SpecialCard.css @@ -2,14 +2,28 @@ .vc-donate-button { overflow: visible !important; color: black; - background-color: color-mix(in srgb, #c3a3ce 15%, pink); + background-color: color-mix(in srgb, #ED87A9 25%, pink); } .vc-invite-button:hover, .vc-donate-button:hover { overflow: visible !important; color: black; - background-color: color-mix(in srgb, hotpink 15%, pink); + background-color: color-mix(in srgb, #ED87A9 50%, pink); +} + +.vc-invite-support-button, +.vc-donate-support-button { + overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #c3a3ce 25%, rgb(240, 178, 240)); +} + +.vc-invite-support-button:hover, +.vc-donate-support-button:hover { + overflow: visible !important; + color: black; + background-color: color-mix(in srgb, #c3a3ce 50%, rgb(240, 178, 240)); } .vc-invite-link { @@ -40,14 +54,18 @@ .vc-invite-button .vc-invite-link, .vc-contrib-button .vc-contrib-github, -.vc-donate-button .vc-heart-icon { +.vc-donate-button .vc-heart-icon, +.vc-invite-support-button .vc-invite-link, +.vc-donate-support-button .vc-heart-icon { transition: transform 0.3s; margin-right: 0.5em; } .vc-invite-button:hover .vc-invite-link, .vc-contrib-button:hover .vc-contrib-github, -.vc-donate-button:hover .vc-heart-icon { +.vc-donate-button:hover .vc-heart-icon, +.vc-invite-support-button:hover .vc-invite-link, +.vc-donate-support-button:hover .vc-heart-icon { transform: scale(1.1); z-index: 10; position: relative; diff --git a/src/components/settings/tabs/vencord/DonateButton.tsx b/src/components/settings/tabs/vencord/DonateButton.tsx index c9f0ebce7..0d63f2b87 100644 --- a/src/components/settings/tabs/vencord/DonateButton.tsx +++ b/src/components/settings/tabs/vencord/DonateButton.tsx @@ -15,13 +15,15 @@ export const isDonor = (userId: string) => !!( || GuildMemberStore?.getMember(VC_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) ); -export function DonateButtonComponent() { +export function DonateButtonComponent({ donated = false }) { return ( ); diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx index e8ff3e8aa..a34a2ee64 100644 --- a/src/components/settings/tabs/vencord/index.tsx +++ b/src/components/settings/tabs/vencord/index.tsx @@ -136,7 +136,7 @@ function EquicordSettings() { backgroundImage={DONOR_BACKGROUND_IMAGE} backgroundColor="#ED87A9" > - + ) : ( Date: Sun, 7 Dec 2025 13:45:17 -0500 Subject: [PATCH 088/291] SpotifyActivityToggle: toolbox option --- .../index.tsx | 15 ++- .../spotifyActivityToggle/index.tsx | 100 +++++++++++++----- 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx b/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx index bb8ddabc7..cd144a4c1 100644 --- a/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx +++ b/src/equicordplugins/clipsEnhancements.discordDesktop/index.tsx @@ -5,13 +5,11 @@ */ import { definePluginSettings } from "@api/Settings"; -import { Link } from "@components/Link"; +import { Button } from "@components/Button"; import { Devs } from "@utils/constants"; -import { ModalAPI } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { Activity, SelectOption } from "@vencord/discord-types"; -import { findStoreLazy } from "@webpack"; -import { PresenceStore, UserStore } from "@webpack/common"; +import { openUserSettingsPanel, PresenceStore, UserStore } from "@webpack/common"; const settings = definePluginSettings({ richPresenceTagging: { @@ -29,14 +27,13 @@ const settings = definePluginSettings({ component: () => { return ( <> - { - ModalAPI.closeAllModals(); + openUserSettingsPanel("clips"); }} > Change FPS and duration options in Clips settings! - + ); } @@ -73,7 +70,7 @@ export default definePlugin({ patchTimeslots(timeslots: SelectOption[]) { const newTimeslots = [...timeslots]; - const extraTimeslots = [3, 5, 7, 10]; + const extraTimeslots = [3, 5, 7, 10, 15, 20, 25, 30]; extraTimeslots.forEach(timeslot => newTimeslots.push({ value: timeslot * 60000, label: `${timeslot} Minutes` })); diff --git a/src/equicordplugins/spotifyActivityToggle/index.tsx b/src/equicordplugins/spotifyActivityToggle/index.tsx index ab59fd249..1e74cf634 100644 --- a/src/equicordplugins/spotifyActivityToggle/index.tsx +++ b/src/equicordplugins/spotifyActivityToggle/index.tsx @@ -4,11 +4,17 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { isPluginEnabled } from "@api/PluginManager"; +import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; +import equicordToolbox from "@equicordplugins/equicordToolbox"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { findComponentByCodeLazy } from "@webpack"; -import { Constants, React, RestAPI, useEffect, useState } from "@webpack/common"; +import { Constants, Menu, React, RestAPI, useEffect, useState } from "@webpack/common"; + +let spotifyId: string | null = null; +let showActivity: boolean = false; const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON"); @@ -34,41 +40,31 @@ function makeSpotifyIcon(enabled: boolean) { ); } -function SpotifyActivityToggleButton() { - const [loading, setLoading] = useState(true); - const [id, setId] = useState(null); - const [showActivity, setShowActivity] = useState(null); +function SpotifyActivityToggleButton(props: { nameplate?: any; }) { + const { location } = settings.use(["location"]); + const [showActivityState, setShowActivityState] = useState(showActivity); useEffect(() => { - (async () => { - const { body } = await RestAPI.get({ - url: Constants.Endpoints.CONNECTIONS, - }); - if (!body) return; - - const spotifyConn = body.find(c => c.type === "spotify"); - if (spotifyConn) { - setId(spotifyConn.id); - setShowActivity(spotifyConn.show_activity); - } - setLoading(false); - })(); + setShowActivityState(showActivity); }, []); - if (loading || !id || showActivity === null) return null; + if (showActivity === null) return null; + if (!spotifyId || location !== "PANEL" && isPluginEnabled(equicordToolbox.name)) return null; return ( + Fetch Changes + + Check the repository for new commits, plugin updates, and code changes. This will compare your current version with the latest available and show you what's new. + - {changelogHistory.length > 0 && ( - <> - - - - )} - - +
+ + + {changelogHistory.length > 0 && ( + <> + + + + )} +
{error && ( - -

{error}

- + + {error} + Make sure you have an internet connection and try again. )} - + - {/* Current Changes Section */} - {hasCurrentChanges ? ( -
- - Recent Changes - + Repository + + This is the GitHub repository where Equicord fetches updates from. + + + {repoPending ? ( + repo + ) : repoErr ? ( + "Failed to retrieve - check console" + ) : ( + + {repo.split("/").slice(-2).join("/")} + + )} + {" "}() + + + {hasCurrentChanges && ( + <> + + + Recent Changes + + These are the new commits and plugin updates since your last version. You can see what features were added, bugs were fixed, and which plugins received updates. + - {/* New Plugins Section */} {newPlugins.length > 0 && (
{ - // Handle plugin toggle if needed - }} + onPluginToggle={() => { }} />
)} - {/* Updated Plugins Section */} {updatedPlugins.length > 0 && (
@@ -608,12 +594,10 @@ function ChangelogContent() {
)} - {/* Code Changes */} {changelog.length > 0 && (
- Code Changes ({changelog.length}{" "} - {changelog.length === 1 ? "commit" : "commits"}) + Code Changes ({changelog.length} {changelog.length === 1 ? "commit" : "commits"})
{changelog.map(entry => ( @@ -627,34 +611,28 @@ function ChangelogContent() {
)} -
- ) : ( - !isLoading && - !error && ( - - - No commits available ahead of your current version. - Click "Fetch from Repository" to check for new - changes. - - - ) + + )} + + {!hasCurrentChanges && !isLoading && !error && ( + <> + + Recent Changes + + No commits available ahead of your current version. Click "Fetch from Repository" to check for new changes. + + )} - {/* History Section */} {showHistory && changelogHistory.length > 0 && ( -
- - - Update Logs ({changelogHistory.length}{" "} - {changelogHistory.length === 1 ? "log" : "logs"}) + <> + + + + Update Logs ({changelogHistory.length} {changelogHistory.length === 1 ? "log" : "logs"}) - Previous update sessions with their commit history and - plugin changes. + A history of your previous update sessions with their commit history and plugin changes. Click on a log to expand it and see the details.
@@ -678,11 +656,7 @@ function ChangelogContent() { await loadChangelogHistory(); setExpandedLogs( new Set( - Array.from( - expandedLogs, - ).filter( - id => id !== logId, - ), + Array.from(expandedLogs).filter(id => id !== logId), ), ); Toasts.show({ @@ -690,8 +664,7 @@ function ChangelogContent() { id: Toasts.genId(), type: Toasts.Type.SUCCESS, options: { - position: - Toasts.Position.BOTTOM, + position: Toasts.Position.BOTTOM, }, }); }, @@ -700,7 +673,7 @@ function ChangelogContent() { /> ))}
-
+ )} ); diff --git a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx index 7599b9faf..3943dac3a 100644 --- a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx +++ b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx @@ -5,7 +5,6 @@ */ import { Paragraph } from "@components/Paragraph"; -import { Margins } from "@utils/margins"; import { Patch, ReplaceFn } from "@utils/types"; import { TextArea, useEffect, useRef, useState } from "@webpack/common"; @@ -69,9 +68,6 @@ export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacemen return ( <> - - Paste your full JSON patch here to fill out the fields -