MoreUserTags: Fix & Add ability to customize text inside tag (#1077)

Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: ActuallyTheSun <78964224+ActuallyTheSun@users.noreply.github.com>
This commit is contained in:
LordElias 2023-05-14 00:38:49 +02:00 committed by GitHub
parent cd53cf38fe
commit 1bc0678422
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 164 additions and 65 deletions

View file

@ -57,7 +57,7 @@
} }
.vc-text-selectable, .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 */ /* make text selectable, silly discord makes the entirety of settings not selectable */
user-select: text; user-select: text;

View file

@ -17,11 +17,13 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy.js"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { find, findByPropsLazy } from "@webpack"; import { findByPropsLazy, findLazy } from "@webpack";
import { ChannelStore, GuildStore } from "@webpack/common"; 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"; 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"; 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; 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"; const CLYDE_ID = "1081004946872352958";
// PermissionStore.computePermissions is not the same function and doesn't work here // 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<PermissionName, bigint>; const Permissions = findByPropsLazy("SEND_MESSAGES", "VIEW_CREATOR_MONETIZATION_ANALYTICS") as Record<PermissionName, bigint>;
const Tags = proxyLazy(() => find(m => m.Types?.[0] === "BOT").Types) as Record<string, number>; const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot(); const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
@ -81,64 +98,119 @@ const tags: Tag[] = [
permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"] 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 (
<Flex flexDirection="column">
{tags.map(t => (
<Card style={{ padding: "1em 1em 0" }}>
<Forms.FormTitle style={{ width: "fit-content" }}>
<Tooltip text={t.description}>
{({ onMouseEnter, onMouseLeave }) => (
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{t.displayName} Tag <Tag type={Tag.Types[t.name]} />
</div>
)}
</Tooltip>
</Forms.FormTitle>
<TextInput
type="text"
value={tagSettings[t.name]?.text ?? t.displayName}
placeholder={`Text on tag (default: ${t.displayName})`}
onChange={v => {
tagSettings[t.name].text = v;
setValue(tagSettings);
}}
className={Margins.bottom16}
/>
<Switch
value={tagSettings[t.name]?.showInChat ?? true}
onChange={v => {
tagSettings[t.name].showInChat = v;
setValue(tagSettings);
}}
hideBorder
>
Show in messages
</Switch>
<Switch
value={tagSettings[t.name]?.showInNotChat ?? true}
onChange={v => {
tagSettings[t.name].showInNotChat = v;
setValue(tagSettings);
}}
hideBorder
>
Show in member list and profiles
</Switch>
</Card>
))}
</Flex>
);
}
const settings = definePluginSettings({ const settings = definePluginSettings({
dontShowForBots: { 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 type: OptionType.BOOLEAN
}, },
dontShowBotTag: { 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 type: OptionType.BOOLEAN
}, },
...Object.fromEntries(tags.map(({ name, displayName, description }) => [ tagSettings: {
`visibility_${name}`, { type: OptionType.COMPONENT,
description: `Show ${displayName} tags (${description})`, component: SettingsComponent,
type: OptionType.SELECT, description: "fill me",
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"
}
]
}
]))
}); });
export default definePlugin({ export default definePlugin({
name: "MoreUserTags", name: "MoreUserTags",
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)", 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, settings,
patches: [ patches: [
// add tags to the tag list // add tags to the tag list
{ {
find: '.BOT=0]="BOT"', find: '.BOT=0]="BOT"',
replacement: [ 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";/, match: /(\i)\[.\.BOT=0\]="BOT";/,
replace: "$&$1=$self.addTagVariants($1);" replace: "$&$1=$self.addTagVariants($1);"
}, }
]
},
{
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP;",
replacement: [
// make the tag show the right text // 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) => replace: (_, origSwitch, variant, tags, displayedText, strings) =>
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}` `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
}, },
// show OP tags correctly // show OP tags correctly
{ {
match: /(\i)=(\i)===\i\.ORIGINAL_POSTER/, match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/,
replace: "$1=$self.isOPTag($2)" replace: "$1=$self.isOPTag($2)"
}, },
// add HTML data attributes (for easier theming) // add HTML data attributes (for easier theming)
@ -169,15 +241,15 @@ return type!==null?$2.botTag,type"
{ {
find: ".hasAvatarForGuild(null==", find: ".hasAvatarForGuild(null==",
replacement: { replacement: {
match: /\.usernameSection,user/, match: /(?=usernameIcon:)/,
replace: ".usernameSection,moreTags_channelId:arguments[0].channelId,user" replace: "moreTags_channelId:arguments[0].channelId,"
} }
}, },
{ {
find: 'copyMetaData:"User Tag"', find: 'copyMetaData:"User Tag"',
replacement: { replacement: {
match: /discriminatorClass:(.{1,100}),botClass:/, match: /(?=,botClass:)/,
replace: "discriminatorClass:$1,moreTags_channelId:arguments[0].moreTags_channelId,botClass:" replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
} }
}, },
// in profiles // 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[] { getPermissions(user: User, channel: Channel): string[] {
const guild = GuildStore.getGuild(channel?.guild_id); const guild = GuildStore.getGuild(channel?.guild_id);
if (!guild) return []; if (!guild) return [];
@ -202,35 +305,36 @@ return type!==null?$2.botTag,type"
.filter(Boolean); .filter(Boolean);
}, },
addTagVariants(val: any /* i cant think of a good name */) { addTagVariants(tagConstant) {
let i = 100; let i = 100;
tags.forEach(({ name }) => { tags.forEach(({ name }) => {
val[name] = ++i; tagConstant[name] = ++i;
val[i] = name; tagConstant[i] = name;
val[`${name}-BOT`] = ++i; tagConstant[`${name}-BOT`] = ++i;
val[i] = `${name}-BOT`; tagConstant[i] = `${name}-BOT`;
val[`${name}-OP`] = ++i; tagConstant[`${name}-OP`] = ++i;
val[i] = `${name}-OP`; 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<string, string>) { getTagText(passedTagName: string, strings: Record<string, string>) {
if (!passedTagName) return "BOT"; if (!passedTagName) return strings.BOT_TAG_BOT;
const [tagName, variant] = passedTagName.split("-"); const [tagName, variant] = passedTagName.split("-");
const tag = tags.find(({ name }) => tagName === name); 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; 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) { switch (variant) {
case "OP": case "OP":
return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER}${tag.displayName}`; return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER}${tagText}`;
case "BOT": case "BOT":
return `${strings.BOT_TAG_BOT}${tag.displayName}`; return `${strings.BOT_TAG_BOT}${tagText}`;
default: default:
return tag.displayName; return tagText;
} }
}, },
@ -242,12 +346,12 @@ return type!==null?$2.botTag,type"
channel?: Channel & { isForumPost(): boolean; }, channel?: Channel & { isForumPost(): boolean; },
channelId?: string; channelId?: string;
origType?: number; origType?: number;
location: string; location: "chat" | "not-chat";
}): number | null { }): number | null {
if (location === "chat" && user.id === "1") if (location === "chat" && user.id === "1")
return Tags.OFFICIAL; return Tag.Types.OFFICIAL;
if (user.id === CLYDE_ID) if (user.id === CLYDE_ID)
return Tags.AI; return Tag.Types.AI;
let type = typeof origType === "number" ? origType : null; let type = typeof origType === "number" ? origType : null;
@ -258,24 +362,19 @@ return type!==null?$2.botTag,type"
const perms = this.getPermissions(user, channel); const perms = this.getPermissions(user, channel);
for (const tag of tags) { for (const tag of tags) {
switch (settings[`visibility_${tag.name}`]) { if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue;
case "always": if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue;
case location:
break;
default:
continue;
}
if ( if (
tag.permissions?.some(perm => perms.includes(perm)) || tag.permissions?.some(perm => perms.includes(perm)) ||
(tag.condition?.(message!, user, channel)) (tag.condition?.(message!, user, channel))
) { ) {
if (channel.isForumPost() && channel.ownerId === user.id) 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) else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
type = Tags[`${tag.name}-BOT`]; type = Tag.Types[`${tag.name}-BOT`];
else else
type = Tags[tag.name]; type = Tag.Types[tag.name];
break; break;
} }
} }