diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index f7d75e6c..26988fdb 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -57,7 +57,7 @@ } .vc-text-selectable, -.vc-text-selectable :not(a, button, a *, button *) { +.vc-text-selectable :not(a, button, a *, button *, input, input *) { /* make text selectable, silly discord makes the entirety of settings not selectable */ user-select: text; diff --git a/src/plugins/moreUserTags.ts b/src/plugins/moreUserTags.tsx similarity index 55% rename from src/plugins/moreUserTags.ts rename to src/plugins/moreUserTags.tsx index 90662b0c..dbe8cc29 100644 --- a/src/plugins/moreUserTags.ts +++ b/src/plugins/moreUserTags.tsx @@ -17,11 +17,13 @@ */ import { definePluginSettings } from "@api/Settings"; +import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import { proxyLazy } from "@utils/lazy.js"; +import { Margins } from "@utils/margins"; import definePlugin, { OptionType } from "@utils/types"; -import { find, findByPropsLazy } from "@webpack"; -import { ChannelStore, GuildStore } from "@webpack/common"; +import { findByPropsLazy, findLazy } from "@webpack"; +import { Card, ChannelStore, Forms, GuildStore, Switch, TextInput, Tooltip, useState } from "@webpack/common"; +import { RC } from "@webpack/types"; import { Channel, Message, User } from "discord-types/general"; type PermissionName = "CREATE_INSTANT_INVITE" | "KICK_MEMBERS" | "BAN_MEMBERS" | "ADMINISTRATOR" | "MANAGE_CHANNELS" | "MANAGE_GUILD" | "CHANGE_NICKNAME" | "MANAGE_NICKNAMES" | "MANAGE_ROLES" | "MANAGE_WEBHOOKS" | "MANAGE_GUILD_EXPRESSIONS" | "CREATE_GUILD_EXPRESSIONS" | "VIEW_AUDIT_LOG" | "VIEW_CHANNEL" | "VIEW_GUILD_ANALYTICS" | "VIEW_CREATOR_MONETIZATION_ANALYTICS" | "MODERATE_MEMBERS" | "SEND_MESSAGES" | "SEND_TTS_MESSAGES" | "MANAGE_MESSAGES" | "EMBED_LINKS" | "ATTACH_FILES" | "READ_MESSAGE_HISTORY" | "MENTION_EVERYONE" | "USE_EXTERNAL_EMOJIS" | "ADD_REACTIONS" | "USE_APPLICATION_COMMANDS" | "MANAGE_THREADS" | "CREATE_PUBLIC_THREADS" | "CREATE_PRIVATE_THREADS" | "USE_EXTERNAL_STICKERS" | "SEND_MESSAGES_IN_THREADS" | "CONNECT" | "SPEAK" | "MUTE_MEMBERS" | "DEAFEN_MEMBERS" | "MOVE_MEMBERS" | "USE_VAD" | "PRIORITY_SPEAKER" | "STREAM" | "USE_EMBEDDED_ACTIVITIES" | "USE_SOUNDBOARD" | "USE_EXTERNAL_SOUNDS" | "REQUEST_TO_SPEAK" | "MANAGE_EVENTS" | "CREATE_EVENTS"; @@ -36,6 +38,21 @@ interface Tag { 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, + [k: string]: TagSetting; +} + const CLYDE_ID = "1081004946872352958"; // PermissionStore.computePermissions is not the same function and doesn't work here @@ -44,7 +61,7 @@ const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") }; const Permissions = findByPropsLazy("SEND_MESSAGES", "VIEW_CREATOR_MONETIZATION_ANALYTICS") as Record; -const Tags = proxyLazy(() => find(m => m.Types?.[0] === "BOT").Types) as Record; +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(); @@ -81,64 +98,119 @@ const tags: Tag[] = [ permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"] } ]; +const defaultSettings = Object.fromEntries( + tags.map(({ name, displayName }) => [name, { text: displayName, showInChat: true, showInNotChat: true }]) +) as TagSettings; + +function SettingsComponent(props: { setValue(v: any): void; }) { + settings.store.tagSettings ??= defaultSettings; + + const [tagSettings, setTagSettings] = useState(settings.store.tagSettings as TagSettings); + const setValue = (v: TagSettings) => { + setTagSettings(v); + props.setValue(v); + }; + + return ( + + {tags.map(t => ( + + + + {({ onMouseEnter, onMouseLeave }) => ( +
+ {t.displayName} Tag +
+ )} +
+
+ + { + tagSettings[t.name].text = v; + setValue(tagSettings); + }} + className={Margins.bottom16} + /> + + { + tagSettings[t.name].showInChat = v; + setValue(tagSettings); + }} + hideBorder + > + Show in messages + + + { + tagSettings[t.name].showInNotChat = v; + setValue(tagSettings); + }} + hideBorder + > + Show in member list and profiles + +
+ ))} +
+ ); +} const settings = definePluginSettings({ dontShowForBots: { - description: "Don't show tags (not including the webhook tag) for bots", + description: "Don't show extra tags for bots (excluding webhooks)", type: OptionType.BOOLEAN }, dontShowBotTag: { - description: "Don't show [BOT] text for bots with other tags (verified bots will still have checkmark)", + description: "Only show extra tags for bots / Hide [BOT] text", type: OptionType.BOOLEAN }, - ...Object.fromEntries(tags.map(({ name, displayName, description }) => [ - `visibility_${name}`, { - description: `Show ${displayName} tags (${description})`, - type: OptionType.SELECT, - options: [ - { - label: "Always", - value: "always", - default: true - }, { - label: "Only in chat", - value: "chat" - }, { - label: "Only in member list and profiles", - value: "not-chat" - }, { - label: "Never", - value: "never" - } - ] - } - ])) + 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], + authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias], settings, patches: [ // add tags to the tag list { find: '.BOT=0]="BOT"', replacement: [ - // add tags to the exported tags list (the Tags variable here) + // add tags to the exported tags list (Tag.Types) { match: /(\i)\[.\.BOT=0\]="BOT";/, replace: "$&$1=$self.addTagVariants($1);" - }, + } + ] + }, + { + find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP;", + replacement: [ // make the tag show the right text { - match: /(switch\((\i)\){.+?)case (\i)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/, + match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/, replace: (_, origSwitch, variant, tags, displayedText, strings) => `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}` }, // show OP tags correctly { - match: /(\i)=(\i)===\i\.ORIGINAL_POSTER/, + match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/, replace: "$1=$self.isOPTag($2)" }, // add HTML data attributes (for easier theming) @@ -169,15 +241,15 @@ return type!==null?$2.botTag,type" { find: ".hasAvatarForGuild(null==", replacement: { - match: /\.usernameSection,user/, - replace: ".usernameSection,moreTags_channelId:arguments[0].channelId,user" + match: /(?=usernameIcon:)/, + replace: "moreTags_channelId:arguments[0].channelId," } }, { find: 'copyMetaData:"User Tag"', replacement: { - match: /discriminatorClass:(.{1,100}),botClass:/, - replace: "discriminatorClass:$1,moreTags_channelId:arguments[0].moreTags_channelId,botClass:" + match: /(?=,botClass:)/, + replace: ",moreTags_channelId:arguments[0].moreTags_channelId" } }, // in profiles @@ -190,6 +262,37 @@ return type!==null?$2.botTag,type" }, ], + start() { + if (settings.store.tagSettings) return; + // @ts-ignore + if (!settings.store.visibility_WEBHOOK) settings.store.tagSettings = defaultSettings; + else { + const newSettings = { ...defaultSettings }; + Object.entries(Vencord.PlainSettings.plugins.MoreUserTags).forEach(([name, value]) => { + const [setting, tag] = name.split("_"); + if (setting === "visibility") { + switch (value) { + case "always": + // its the default + break; + case "chat": + newSettings[tag].showInNotChat = false; + break; + case "not-chat": + newSettings[tag].showInChat = false; + break; + case "never": + newSettings[tag].showInChat = false; + newSettings[tag].showInNotChat = false; + break; + } + } + settings.store.tagSettings = newSettings; + delete Vencord.Settings.plugins.MoreUserTags[name]; + }); + } + }, + getPermissions(user: User, channel: Channel): string[] { const guild = GuildStore.getGuild(channel?.guild_id); if (!guild) return []; @@ -202,35 +305,36 @@ return type!==null?$2.botTag,type" .filter(Boolean); }, - addTagVariants(val: any /* i cant think of a good name */) { + addTagVariants(tagConstant) { let i = 100; tags.forEach(({ name }) => { - val[name] = ++i; - val[i] = name; - val[`${name}-BOT`] = ++i; - val[i] = `${name}-BOT`; - val[`${name}-OP`] = ++i; - val[i] = `${name}-OP`; + tagConstant[name] = ++i; + tagConstant[i] = name; + tagConstant[`${name}-BOT`] = ++i; + tagConstant[i] = `${name}-BOT`; + tagConstant[`${name}-OP`] = ++i; + tagConstant[i] = `${name}-OP`; }); - return val; + return tagConstant; }, - isOPTag: (tag: number) => tag === Tags.ORIGINAL_POSTER || tags.some(t => tag === Tags[`${t.name}-OP`]), + isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]), getTagText(passedTagName: string, strings: Record) { - if (!passedTagName) return "BOT"; + if (!passedTagName) return strings.BOT_TAG_BOT; const [tagName, variant] = passedTagName.split("-"); const tag = tags.find(({ name }) => tagName === name); - if (!tag) return "BOT"; + if (!tag) return strings.BOT_TAG_BOT; if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return strings.BOT_TAG_BOT; + const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName; switch (variant) { case "OP": - return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER} • ${tag.displayName}`; + return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER} • ${tagText}`; case "BOT": - return `${strings.BOT_TAG_BOT} • ${tag.displayName}`; + return `${strings.BOT_TAG_BOT} • ${tagText}`; default: - return tag.displayName; + return tagText; } }, @@ -242,12 +346,12 @@ return type!==null?$2.botTag,type" channel?: Channel & { isForumPost(): boolean; }, channelId?: string; origType?: number; - location: string; + location: "chat" | "not-chat"; }): number | null { if (location === "chat" && user.id === "1") - return Tags.OFFICIAL; + return Tag.Types.OFFICIAL; if (user.id === CLYDE_ID) - return Tags.AI; + return Tag.Types.AI; let type = typeof origType === "number" ? origType : null; @@ -258,24 +362,19 @@ return type!==null?$2.botTag,type" const perms = this.getPermissions(user, channel); for (const tag of tags) { - switch (settings[`visibility_${tag.name}`]) { - case "always": - case location: - break; - default: - continue; - } + if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue; + if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue; if ( tag.permissions?.some(perm => perms.includes(perm)) || (tag.condition?.(message!, user, channel)) ) { if (channel.isForumPost() && channel.ownerId === user.id) - type = Tags[`${tag.name}-OP`]; + type = Tag.Types[`${tag.name}-OP`]; else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag) - type = Tags[`${tag.name}-BOT`]; + type = Tag.Types[`${tag.name}-BOT`]; else - type = Tags[tag.name]; + type = Tag.Types[tag.name]; break; } }