From cf28c65374ea9ac2c71358640b9d979221360b40 Mon Sep 17 00:00:00 2001 From: Grzesiek11 Date: Mon, 27 Jan 2025 03:34:00 +0100 Subject: [PATCH 01/10] Add IrcColors plugin (#2048) Co-authored-by: V --- src/plugins/ircColors/README.md | 17 ++++++ src/plugins/ircColors/index.ts | 94 +++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/plugins/ircColors/README.md create mode 100644 src/plugins/ircColors/index.ts diff --git a/src/plugins/ircColors/README.md b/src/plugins/ircColors/README.md new file mode 100644 index 000000000..9d9c7634b --- /dev/null +++ b/src/plugins/ircColors/README.md @@ -0,0 +1,17 @@ +# IrcColors + +Makes username colors in chat unique, like in IRC clients + +![Chat with IrcColors and Compact++ enabled](https://github.com/Vendicated/Vencord/assets/33988779/88e05c0b-a60a-4d10-949e-8b46e1d7226c) + +Improves chat readability by assigning every user an unique nickname color, +making distinguishing between different users easier. Inspired by the feature +in many IRC clients, such as HexChat or WeeChat. + +Keep in mind this overrides role colors in chat, so if you wish to know +someone's role color without checking their profile, enable the role dot: go to +**User Settings**, **Accessibility** and switch **Role Colors** to **Show role +colors next to names**. + +Created for use with the [Compact++](https://gitlab.com/Grzesiek11/compactplusplus-discord-theme) +theme. diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts new file mode 100644 index 000000000..d5cc5f3e4 --- /dev/null +++ b/src/plugins/ircColors/index.ts @@ -0,0 +1,94 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +// Compute a 64-bit FNV-1a hash of the passed data +function hash(id: bigint) { + const fnvPrime = 1099511628211n; + const offsetBasis = 14695981039346656037n; + + let result = offsetBasis; + for (let i = 7n; i >= 0n; i--) { + result ^= (id >> (8n * i)) & 0xffn; + result = (result * fnvPrime) % 2n ** 32n; + } + + return result; +} + +// Calculate a CSS color string based on the user ID +function calculateNameColorForUser(id: bigint) { + const idHash = hash(id); + + return `hsl(${idHash % 360n}, 100%, ${settings.store.lightness}%)`; +} + +const settings = definePluginSettings({ + lightness: { + description: "Lightness, in %. Change if the colors are too light or too dark. Reopen the chat to apply.", + type: OptionType.NUMBER, + default: 70, + }, + memberListColors: { + description: "Replace role colors in the member list", + restartNeeded: true, + type: OptionType.BOOLEAN, + default: true, + }, +}); + +export default definePlugin({ + name: "IrcColors", + description: "Makes username colors in chat unique, like in IRC clients", + authors: [Devs.Grzesiek11], + patches: [ + { + find: '="SYSTEM_TAG"', + replacement: { + match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/, + replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},", + }, + }, + { + find: ".NameWithRole,{roleName:", + replacement: { + match: /(?<=color:)null!=.{0,50}?(?=,)/, + replace: "$self.calculateNameColorForListContext(arguments[0])", + }, + predicate: () => settings.store.memberListColors, + }, + ], + settings, + calculateNameColorForMessageContext(context: any) { + const id = context?.message?.author?.id; + if (id == null) { + return null; + } + return calculateNameColorForUser(BigInt(id)); + }, + calculateNameColorForListContext(context: any) { + const id = context?.user?.id; + if (id == null) { + return null; + } + return calculateNameColorForUser(BigInt(id)); + }, +}); From f29662c5b315a3579e14bcfb062d96bf047b7d39 Mon Sep 17 00:00:00 2001 From: vishnyanetchereshnya <151846235+vishnyanetchereshnya@users.noreply.github.com> Date: Mon, 27 Jan 2025 06:12:26 +0300 Subject: [PATCH 02/10] feat(ViewRaw): add View Role option (#3083) Co-authored-by: v --- src/plugins/betterRoleContext/index.tsx | 30 ++++++++++++------------- src/plugins/viewRaw/index.tsx | 27 ++++++++++++++++++---- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index 1029c07e2..afef63908 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -83,7 +83,7 @@ export default definePlugin({ if (!role) return; if (role.colorString) { - children.push( + children.unshift( { + await GuildSettingsActions.open(guild.id, "ROLES"); + GuildSettingsActions.selectRole(id); + }} + icon={PencilIcon} + /> + ); + } + if (role.icon) { children.push( { - await GuildSettingsActions.open(guild.id, "ROLES"); - GuildSettingsActions.selectRole(id); - }} - icon={PencilIcon} - /> - ); - } } } }); diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index b45919a21..ddcbd3b46 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -22,12 +22,12 @@ import { CodeBlock } from "@components/CodeBlock"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; +import { getCurrentGuild, getIntlMessage } from "@utils/discord"; import { Margins } from "@utils/margins"; import { copyWithToast } from "@utils/misc"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common"; +import { Button, ChannelStore, Forms, GuildStore, Menu, Text } from "@webpack/common"; import { Message } from "discord-types/general"; @@ -118,7 +118,7 @@ const settings = definePluginSettings({ } }); -function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback { +function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel"): NavContextMenuPatchCallback { return (children, props) => { const value = props[name.toLowerCase()]; if (!value) return; @@ -144,6 +144,23 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenu }; } +const devContextCallback: NavContextMenuPatchCallback = (children, { id }: { id: string; }) => { + const guild = getCurrentGuild(); + if (!guild) return; + + const role = GuildStore.getRole(guild.id, id); + if (!role) return; + + children.push( + openViewRawModal(JSON.stringify(role, null, 4), "Role")} + icon={CopyIcon} + /> + ); +}; + export default definePlugin({ name: "ViewRaw", description: "Copy and view the raw content/data of any message, channel or guild", @@ -152,10 +169,12 @@ export default definePlugin({ contextMenus: { "guild-context": MakeContextCallback("Guild"), + "guild-settings-role-context": MakeContextCallback("Role"), "channel-context": MakeContextCallback("Channel"), "thread-context": MakeContextCallback("Channel"), "gdm-context": MakeContextCallback("Channel"), - "user-context": MakeContextCallback("User") + "user-context": MakeContextCallback("User"), + "dev-context": devContextCallback }, renderMessagePopoverButton(msg) { From 3350922c09ea1e9ad0f4f4f40320e7e80b7ce940 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 27 Jan 2025 04:25:37 +0100 Subject: [PATCH 03/10] LastFmRPC: Add option to hide if there is another presence closes #2866 Co-Authored-By: 54ac --- src/plugins/lastfm/index.tsx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/plugins/lastfm/index.tsx b/src/plugins/lastfm/index.tsx index 02fd694f8..77fa27841 100644 --- a/src/plugins/lastfm/index.tsx +++ b/src/plugins/lastfm/index.tsx @@ -86,7 +86,7 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f"; const logger = new Logger("LastFMRichPresence"); -const presenceStore = findByPropsLazy("getLocalPresence"); +const PresenceStore = findByPropsLazy("getLocalPresence"); async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; @@ -124,6 +124,11 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true, }, + hideWithActivity: { + description: "Hide Last.fm presence if you have any other presence", + type: OptionType.BOOLEAN, + default: false, + }, statusName: { description: "custom status text", type: OptionType.STRING, @@ -274,12 +279,16 @@ export default definePlugin({ }, async getActivity(): Promise { + if (settings.store.hideWithActivity) { + if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) { + return null; + } + } + if (settings.store.hideWithSpotify) { - for (const activity of presenceStore.getActivities()) { - if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) { - // there is already music status because of Spotify or richerCider (probably more) - return null; - } + if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== applicationId)) { + // there is already music status because of Spotify or richerCider (probably more) + return null; } } From c4f8221f75157c6f23f4aaba22b17e06a785917d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:30:11 -0300 Subject: [PATCH 04/10] IrcColors: Make lightness apply without restart --- src/plugins/ircColors/index.ts | 53 ++++++++++++++-------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index d5cc5f3e4..208b327e9 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -17,33 +17,22 @@ */ import { definePluginSettings } from "@api/Settings"; +import { hash as h64 } from "@intrnl/xxhash64"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; - -// Compute a 64-bit FNV-1a hash of the passed data -function hash(id: bigint) { - const fnvPrime = 1099511628211n; - const offsetBasis = 14695981039346656037n; - - let result = offsetBasis; - for (let i = 7n; i >= 0n; i--) { - result ^= (id >> (8n * i)) & 0xffn; - result = (result * fnvPrime) % 2n ** 32n; - } - - return result; -} +import { useMemo } from "@webpack/common"; // Calculate a CSS color string based on the user ID -function calculateNameColorForUser(id: bigint) { - const idHash = hash(id); +function calculateNameColorForUser(id: string) { + const { lightness } = settings.use(["lightness"]); + const idHash = useMemo(() => h64(id), [id]); - return `hsl(${idHash % 360n}, 100%, ${settings.store.lightness}%)`; + return `hsl(${idHash % 360n}, 100%, ${lightness}%)`; } const settings = definePluginSettings({ lightness: { - description: "Lightness, in %. Change if the colors are too light or too dark. Reopen the chat to apply.", + description: "Lightness, in %. Change if the colors are too light or too dark", type: OptionType.NUMBER, default: 70, }, @@ -51,44 +40,46 @@ const settings = definePluginSettings({ description: "Replace role colors in the member list", restartNeeded: true, type: OptionType.BOOLEAN, - default: true, - }, + default: true + } }); export default definePlugin({ name: "IrcColors", description: "Makes username colors in chat unique, like in IRC clients", authors: [Devs.Grzesiek11], + settings, + patches: [ { find: '="SYSTEM_TAG"', replacement: { match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/, - replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},", - }, + replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])}," + } }, { - find: ".NameWithRole,{roleName:", + find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /(?<=color:)null!=.{0,50}?(?=,)/, - replace: "$self.calculateNameColorForListContext(arguments[0])", + match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/, + replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor` }, - predicate: () => settings.store.memberListColors, - }, + predicate: () => settings.store.memberListColors + } ], - settings, + calculateNameColorForMessageContext(context: any) { const id = context?.message?.author?.id; if (id == null) { return null; } - return calculateNameColorForUser(BigInt(id)); + return calculateNameColorForUser(id); }, calculateNameColorForListContext(context: any) { const id = context?.user?.id; if (id == null) { return null; } - return calculateNameColorForUser(BigInt(id)); - }, + return calculateNameColorForUser(id); + } }); From ceba9776c4be12288dbf618b5bc36e0e41d3f70f Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 27 Jan 2025 20:44:54 +0100 Subject: [PATCH 05/10] Delete MoreUserTags for now because it's unstable This plugin is written in a way that makes it susceptible to crashes. This is not the first time it has caused crashes and will not be the last. A rewrite is necessary to make it more robust --- src/plugins/moreUserTags/index.tsx | 372 ----------------------------- 1 file changed, 372 deletions(-) delete mode 100644 src/plugins/moreUserTags/index.tsx diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx deleted file mode 100644 index 8029b4833..000000000 --- a/src/plugins/moreUserTags/index.tsx +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/Settings"; -import { Flex } from "@components/Flex"; -import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; -import { Margins } from "@utils/margins"; -import definePlugin, { OptionType } from "@utils/types"; -import { findByCodeLazy, findLazy } from "@webpack"; -import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip } from "@webpack/common"; -import type { Permissions, RC } from "@webpack/types"; -import type { Channel, Guild, Message, User } from "discord-types/general"; - -interface Tag { - // name used for identifying, must be alphanumeric + underscores - name: string; - // name shown on the tag itself, can be anything probably; automatically uppercase'd - displayName: string; - description: string; - permissions?: Permissions[]; - condition?(message: Message | null, user: User, channel: Channel): boolean; -} - -interface TagSetting { - text: string; - showInChat: boolean; - showInNotChat: boolean; -} -interface TagSettings { - WEBHOOK: TagSetting, - OWNER: TagSetting, - ADMINISTRATOR: TagSetting, - MODERATOR_STAFF: TagSetting, - MODERATOR: TagSetting, - VOICE_MODERATOR: TagSetting, - TRIAL_MODERATOR: TagSetting, - [k: string]: TagSetting; -} - -// PermissionStore.computePermissions will not work here since it only gets permissions for the current user -const computePermissions: (options: { - user?: { id: string; } | string | null; - context?: Guild | Channel | null; - overwrites?: Channel["permissionOverwrites"] | null; - checkElevated?: boolean /* = true */; - excludeGuildPermissions?: boolean /* = false */; -}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()"); - -const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record; }; - -const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot(); - -const tags: Tag[] = [ - { - name: "WEBHOOK", - displayName: "Webhook", - description: "Messages sent by webhooks", - condition: isWebhook - }, { - name: "OWNER", - displayName: "Owner", - description: "Owns the server", - condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id - }, { - name: "ADMINISTRATOR", - displayName: "Admin", - description: "Has the administrator permission", - permissions: ["ADMINISTRATOR"] - }, { - name: "MODERATOR_STAFF", - displayName: "Staff", - description: "Can manage the server, channels or roles", - permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"] - }, { - name: "MODERATOR", - displayName: "Mod", - description: "Can manage messages or kick/ban people", - permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"] - }, { - name: "VOICE_MODERATOR", - displayName: "VC Mod", - description: "Can manage voice chats", - permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"] - }, { - name: "CHAT_MODERATOR", - displayName: "Chat Mod", - description: "Can timeout people", - permissions: ["MODERATE_MEMBERS"] - } -]; -const defaultSettings = Object.fromEntries( - tags.map(({ name, displayName }) => [name, { text: displayName, showInChat: true, showInNotChat: true }]) -) as TagSettings; - -function SettingsComponent() { - const tagSettings = settings.store.tagSettings ??= defaultSettings; - - return ( - - {tags.map(t => ( - - - - {({ onMouseEnter, onMouseLeave }) => ( -
- {t.displayName} Tag -
- )} -
-
- - tagSettings[t.name].text = v} - className={Margins.bottom16} - /> - - tagSettings[t.name].showInChat = v} - hideBorder - > - Show in messages - - - tagSettings[t.name].showInNotChat = v} - hideBorder - > - Show in member list and profiles - -
- ))} -
- ); -} - -const settings = definePluginSettings({ - dontShowForBots: { - description: "Don't show extra tags for bots (excluding webhooks)", - type: OptionType.BOOLEAN - }, - dontShowBotTag: { - description: "Only show extra tags for bots / Hide [BOT] text", - type: OptionType.BOOLEAN - }, - tagSettings: { - type: OptionType.COMPONENT, - component: SettingsComponent, - description: "fill me" - } -}); - -export default definePlugin({ - name: "MoreUserTags", - description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)", - authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN], - settings, - patches: [ - // add tags to the tag list - { - find: ".ORIGINAL_POSTER=", - replacement: { - match: /(?=(\i)\[\i\.BOT)/, - replace: "$self.genTagTypes($1);" - } - }, - { - find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}", - replacement: [ - // make the tag show the right text - { - match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/, - replace: (_, origSwitch, variant, tags, displayedText, originalText) => - `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}` - }, - // show OP tags correctly - { - match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/, - replace: "$1=$self.isOPTag($2)" - }, - // add HTML data attributes (for easier theming) - { - match: /.botText,children:(\i)}\)]/, - replace: "$&,'data-tag':$1.toLowerCase()" - } - ], - }, - // in messages - { - find: ".Types.ORIGINAL_POSTER", - replacement: { - match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/, - replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null" - } - }, - // in the member list - { - find: "#{intl::GUILD_OWNER}),children:", - replacement: { - match: /(?\i)=\(null==.{0,100}\.BOT;return null!=(?\i)&&\i\.bot/, - replace: "$ = $self.getTag({user: $, channel: arguments[0].channel, origType: $.bot ? 0 : null, location: 'not-chat' }); return typeof $ === 'number'" - } - }, - // pass channel id down props to be used in profiles - { - find: ".hasAvatarForGuild(null==", - replacement: { - match: /(?=usernameIcon:)/, - replace: "moreTags_channelId:arguments[0].channelId," - } - }, - { - find: "#{intl::USER_PROFILE_PRONOUNS}", - replacement: { - match: /(?=,hideBotTag:!0)/, - replace: ",moreTags_channelId:arguments[0].moreTags_channelId" - } - }, - // in profiles - { - find: ",overrideDiscriminator:", - group: true, - replacement: [ - { - // prevent channel id from getting ghosted - // it's either this or extremely long lookbehind - match: /user:\i,nick:\i,/, - replace: "$&moreTags_channelId," - }, { - match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g, - replace: ",botType:$self.getTag({user:$3,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),botVerified:$2," - } - ] - }, - ], - - start() { - settings.store.tagSettings ??= defaultSettings; - - // newly added field might be missing from old users - settings.store.tagSettings.CHAT_MODERATOR ??= { - text: "Chat Mod", - showInChat: true, - showInNotChat: true - }; - }, - - getPermissions(user: User, channel: Channel): string[] { - const guild = GuildStore.getGuild(channel?.guild_id); - if (!guild) return []; - - const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites }); - return Object.entries(PermissionsBits) - .map(([perm, permInt]) => - permissions & permInt ? perm : "" - ) - .filter(Boolean); - }, - - genTagTypes(obj) { - let i = 100; - tags.forEach(({ name }) => { - obj[name] = ++i; - obj[i] = name; - obj[`${name}-BOT`] = ++i; - obj[i] = `${name}-BOT`; - obj[`${name}-OP`] = ++i; - obj[i] = `${name}-OP`; - }); - }, - - isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]), - - getTagText(passedTagName: string, originalText: string) { - try { - const [tagName, variant] = passedTagName.split("-"); - if (!passedTagName) return getIntlMessage("APP_TAG"); - const tag = tags.find(({ name }) => tagName === name); - if (!tag) return getIntlMessage("APP_TAG"); - if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG"); - - const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName; - switch (variant) { - case "OP": - return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")} • ${tagText}`; - case "BOT": - return `${getIntlMessage("APP_TAG")} • ${tagText}`; - default: - return tagText; - } - } catch { - return originalText; - } - }, - - getTag({ - message, user, channelId, origType, location, channel - }: { - message?: Message, - user: User & { isClyde(): boolean; }, - channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; }, - channelId?: string; - origType?: number; - location: "chat" | "not-chat"; - }): number | null { - if (!user) - return null; - if (location === "chat" && user.id === "1") - return Tag.Types.OFFICIAL; - if (user.isClyde()) - return Tag.Types.AI; - - let type = typeof origType === "number" ? origType : null; - - channel ??= ChannelStore.getChannel(channelId!) as any; - if (!channel) return type; - - const settings = this.settings.store; - const perms = this.getPermissions(user, channel); - - for (const tag of tags) { - if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue; - if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue; - - // If the owner tag is disabled, and the user is the owner of the guild, - // avoid adding other tags because the owner will always match the condition for them - if ( - tag.name !== "OWNER" && - GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id && - (location === "chat" && !settings.tagSettings.OWNER.showInChat) || - (location === "not-chat" && !settings.tagSettings.OWNER.showInNotChat) - ) continue; - - if ( - tag.permissions?.some(perm => perms.includes(perm)) || - (tag.condition?.(message!, user, channel)) - ) { - if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id) - type = Tag.Types[`${tag.name}-OP`]; - else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag) - type = Tag.Types[`${tag.name}-BOT`]; - else - type = Tag.Types[tag.name]; - break; - } - } - return type; - } -}); From ea1e96185b1f1a613a2100c1d7899603790dc862 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 27 Jan 2025 20:54:16 +0100 Subject: [PATCH 06/10] MessageLatency: ErrorBoundary should be noop --- src/plugins/messageLatency/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index e4e5b8771..1bc18580e 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -162,7 +162,7 @@ export default definePlugin({ } ; - }); + }, { noop: true }); }, Icon({ delta, fill, props }: { From 21ded874a3e94415639ee14b3e8716d894e3456a Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 27 Jan 2025 20:47:27 -0300 Subject: [PATCH 07/10] Settings API: Add utility to migrate a setting --- src/api/Settings.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index c99d030d0..8c05d9bb3 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -220,6 +220,19 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) { } } +export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) { + const { plugins } = SettingsStore.plain; + + if ( + plugins[pluginName][newSetting] != null || + plugins[pluginName][oldSetting] == null + ) return; + + plugins[pluginName][newSetting] = plugins[pluginName][oldSetting]; + delete plugins[pluginName][oldSetting]; + SettingsStore.markAsChanged(); +} + export function definePluginSettings< Def extends SettingsDefinition, Checks extends SettingsChecks, From f43baddc550dfd30c1a6b44f7e42544b03084a5a Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Tue, 28 Jan 2025 01:57:16 +0000 Subject: [PATCH 08/10] NoBlockedMessages: Add ignored messages (#3126) --- src/plugins/noBlockedMessages/index.ts | 59 +++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index 48ca63d18..95b53c6b3 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { Settings } from "@api/Settings"; +import { definePluginSettings, migratePluginSetting } from "@api/Settings"; import { Devs } from "@utils/constants"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { Logger } from "@utils/Logger"; @@ -32,10 +32,29 @@ interface MessageDeleteProps { collapsedReason: () => any; } +// Remove this migration once enough time has passed +migratePluginSetting("NoBlockedMessages", "ignoreBlockedMessages", "ignoreMessages"); +const settings = definePluginSettings({ + ignoreMessages: { + description: "Completely ignores incoming messages from blocked and ignored (if enabled) users", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: true + }, + applyToIgnoredUsers: { + description: "Additionally apply to 'ignored' users", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: false + } +}); + export default definePlugin({ name: "NoBlockedMessages", - description: "Hides all blocked messages from chat completely.", - authors: [Devs.rushii, Devs.Samu], + description: "Hides all blocked/ignored messages from chat completely", + authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365], + settings, + patches: [ { find: "#{intl::BLOCKED_MESSAGES_HIDE}", @@ -51,38 +70,40 @@ export default definePlugin({ '"ReadStateStore"' ].map(find => ({ find, - predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true, + predicate: () => settings.store.ignoreMessages, replacement: [ { match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/, - replace: (_, _funcName, props) => `if($self.isBlocked(${props}.message))return;` + replace: (_, _funcName, props) => `if($self.shouldIgnoreMessage(${props}.message))return;` } ] })) ], - options: { - ignoreBlockedMessages: { - description: "Completely ignores (recent) incoming messages from blocked users (locally).", - type: OptionType.BOOLEAN, - default: false, - restartNeeded: true, - }, - }, - isBlocked(message: Message) { + shouldIgnoreMessage(message: Message) { try { - return RelationshipStore.isBlocked(message.author.id); + if (RelationshipStore.isBlocked(message.author.id)) { + return true; + } + return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id); } catch (e) { - new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e); + new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e); + return false; } }, - shouldHide(props: MessageDeleteProps) { + shouldHide(props: MessageDeleteProps): boolean { try { - return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")](); + const collapsedReason = props.collapsedReason(); + const blockedReason = i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")](); + const ignoredReason = settings.store.applyToIgnoredUsers + ? i18n.t[runtimeHashMessageKey("IGNORED_MESSAGE_COUNT")]() + : null; + + return collapsedReason === blockedReason || collapsedReason === ignoredReason; } catch (e) { console.error(e); + return false; } - return false; } }); From cdc756193e93914d3f644f52ecc4d65dbd02d5a9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 28 Jan 2025 01:13:36 -0300 Subject: [PATCH 09/10] Settings API: Fix erroring if plugin settings don't exist --- src/api/Settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 8c05d9bb3..262722b14 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -224,8 +224,8 @@ export function migratePluginSetting(pluginName: string, oldSetting: string, new const { plugins } = SettingsStore.plain; if ( - plugins[pluginName][newSetting] != null || - plugins[pluginName][oldSetting] == null + plugins?.[pluginName]?.[oldSetting] == null || + plugins[pluginName][newSetting] != null ) return; plugins[pluginName][newSetting] = plugins[pluginName][oldSetting]; From 33d4f13a242fb4b5124010546773a7bda3d9d962 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 29 Jan 2025 01:04:36 -0300 Subject: [PATCH 10/10] Fix everything broken by recent Discord update (#3177) Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com> Co-authored-by: Vendicated --- src/api/ChatButtons.tsx | 4 +- src/api/ContextMenu.ts | 8 +- src/api/Settings.ts | 12 +- src/debug/runReporter.ts | 15 +- src/plugins/_api/badges/index.tsx | 18 +-- src/plugins/_api/chatButtons.ts | 16 ++- src/plugins/_api/contextMenu.ts | 20 ++- src/plugins/_api/menuItemDemangler.ts | 68 +++++++++ src/plugins/_core/settings.tsx | 2 +- src/plugins/betterFolders/index.tsx | 4 +- src/plugins/betterSessions/index.tsx | 4 +- src/plugins/betterSettings/index.tsx | 4 +- src/plugins/consoleJanitor/index.ts | 4 +- src/plugins/consoleShortcuts/index.ts | 37 ++++- src/plugins/ctrlEnterSend/index.ts | 6 +- src/plugins/fakeNitro/index.tsx | 8 +- src/plugins/fullSearchContext/index.tsx | 4 +- src/plugins/gameActivityToggle/index.tsx | 2 +- src/plugins/iLoveSpam/index.ts | 2 +- src/plugins/ignoreActivities/index.tsx | 2 +- src/plugins/implicitRelationships/index.ts | 2 +- src/plugins/mentionAvatars/index.tsx | 2 +- src/plugins/messageLatency/index.tsx | 4 +- src/plugins/messageLogger/index.tsx | 2 +- src/plugins/openInApp/index.ts | 4 +- src/plugins/permissionFreeWill/index.ts | 4 +- .../pinDms/components/CreateCategoryModal.tsx | 4 +- src/plugins/platformIndicators/index.tsx | 8 +- src/plugins/showHiddenChannels/index.tsx | 22 +-- src/plugins/showTimeoutDuration/index.tsx | 4 +- src/plugins/typingIndicator/index.tsx | 4 +- src/plugins/userVoiceShow/components.tsx | 2 +- src/plugins/vencordToolbox/index.tsx | 4 +- src/plugins/viewIcons/index.tsx | 8 +- src/utils/modal.tsx | 81 ++++++----- src/webpack/common/components.ts | 130 +++++++++--------- src/webpack/common/menu.ts | 20 ++- src/webpack/common/types/components.d.ts | 3 - src/webpack/common/types/iconNames.d.ts | 14 -- src/webpack/patchWebpack.ts | 40 ++++-- src/webpack/webpack.ts | 31 +++-- 41 files changed, 389 insertions(+), 244 deletions(-) create mode 100644 src/plugins/_api/menuItemDemangler.ts delete mode 100644 src/webpack/common/types/iconNames.d.ts diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index c24e3886f..6f4285ff5 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -9,7 +9,7 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; import { waitFor } from "@webpack"; -import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; +import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; @@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {