diff --git a/src/plugins/pronoundb/api.ts b/src/plugins/pronoundb/api.ts deleted file mode 100644 index 228217965..000000000 --- a/src/plugins/pronoundb/api.ts +++ /dev/null @@ -1,172 +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 { getCurrentChannel } from "@utils/discord"; -import { useAwaiter } from "@utils/react"; -import { findStoreLazy } from "@webpack"; -import { UserProfileStore, UserStore } from "@webpack/common"; - -import { settings } from "./settings"; -import { PronounMapping, Pronouns, PronounsCache, PronounSets, PronounsFormat, PronounSource, PronounsResponse } from "./types"; - -const UserSettingsAccountStore = findStoreLazy("UserSettingsAccountStore"); - -const EmptyPronouns = { pronouns: undefined, source: "", hasPendingPronouns: false } as const satisfies Pronouns; - -type RequestCallback = (pronounSets?: PronounSets) => void; - -const pronounCache: Record = {}; -const requestQueue: Record = {}; -let isProcessing = false; - -async function processQueue() { - if (isProcessing) return; - isProcessing = true; - - let ids = Object.keys(requestQueue); - while (ids.length > 0) { - const idsChunk = ids.splice(0, 50); - const pronouns = await bulkFetchPronouns(idsChunk); - - for (const id of idsChunk) { - const callbacks = requestQueue[id]; - for (const callback of callbacks) { - callback(pronouns[id]?.sets); - } - - delete requestQueue[id]; - } - - ids = Object.keys(requestQueue); - await new Promise(r => setTimeout(r, 2000)); - } - - isProcessing = false; -} - -function fetchPronouns(id: string): Promise { - return new Promise(resolve => { - if (pronounCache[id] != null) { - resolve(extractPronouns(pronounCache[id].sets)); - return; - } - - function handlePronouns(pronounSets?: PronounSets) { - const pronouns = extractPronouns(pronounSets); - resolve(pronouns); - } - - if (requestQueue[id] != null) { - requestQueue[id].push(handlePronouns); - return; - } - - requestQueue[id] = [handlePronouns]; - processQueue(); - }); -} - -async function bulkFetchPronouns(ids: string[]): Promise { - const params = new URLSearchParams(); - params.append("platform", "discord"); - params.append("ids", ids.join(",")); - - try { - const req = await fetch("https://pronoundb.org/api/v2/lookup?" + String(params), { - method: "GET", - headers: { - "Accept": "application/json", - "X-PronounDB-Source": "WebExtension/0.14.5" - } - }); - - if (!req.ok) throw new Error(`Status ${req.status}`); - const res: PronounsResponse = await req.json(); - - Object.assign(pronounCache, res); - return res; - - } catch (e) { - console.error("PronounDB request failed:", e); - const dummyPronouns: PronounsResponse = Object.fromEntries(ids.map(id => [id, { sets: {} }])); - - Object.assign(pronounCache, dummyPronouns); - return dummyPronouns; - } -} - -function extractPronouns(pronounSets?: PronounSets): string | undefined { - if (pronounSets == null) return undefined; - if (pronounSets.en == null) return PronounMapping.unspecified; - - const pronouns = pronounSets.en; - if (pronouns.length === 0) return PronounMapping.unspecified; - - const { pronounsFormat } = settings.store; - - if (pronouns.length > 1) { - const pronounString = pronouns.map(p => p[0].toUpperCase() + p.slice(1)).join("/"); - return pronounsFormat === PronounsFormat.Capitalized ? pronounString : pronounString.toLowerCase(); - } - - const pronoun = pronouns[0]; - // For capitalized pronouns or special codes (any, ask, avoid), we always return the normal (capitalized) string - if (pronounsFormat === PronounsFormat.Capitalized || ["any", "ask", "avoid", "other", "unspecified"].includes(pronoun)) { - return PronounMapping[pronoun]; - } else { - return PronounMapping[pronoun].toLowerCase(); - } -} - -function getDiscordPronouns(id: string, useGlobalProfile: boolean = false): string | undefined { - const globalPronouns = UserProfileStore.getUserProfile(id)?.pronouns; - if (useGlobalProfile) return globalPronouns; - - return UserProfileStore.getGuildMemberProfile(id, getCurrentChannel()?.guild_id)?.pronouns || globalPronouns; -} - -export function useFormattedPronouns(id: string, useGlobalProfile: boolean = false): Pronouns { - const discordPronouns = getDiscordPronouns(id, useGlobalProfile)?.trim().replace(/\n+/g, ""); - const hasPendingPronouns = UserSettingsAccountStore.getPendingPronouns() != null; - - const [pronouns] = useAwaiter(() => fetchPronouns(id)); - - if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns) { - return { pronouns: discordPronouns, source: "Discord", hasPendingPronouns }; - } - - if (pronouns != null && pronouns !== PronounMapping.unspecified) { - return { pronouns, source: "PronounDB", hasPendingPronouns }; - } - - return { pronouns: discordPronouns, source: "Discord", hasPendingPronouns }; -} - -export function useProfilePronouns(id: string, useGlobalProfile: boolean = false): Pronouns { - try { - const pronouns = useFormattedPronouns(id, useGlobalProfile); - - if (!settings.store.showInProfile) return EmptyPronouns; - if (!settings.store.showSelf && id === UserStore.getCurrentUser()?.id) return EmptyPronouns; - - return pronouns; - } catch (e) { - console.error(e); - return EmptyPronouns; - } -} diff --git a/src/plugins/pronoundb/components/PronounsAboutComponent.tsx b/src/plugins/pronoundb/components/PronounsAboutComponent.tsx deleted file mode 100644 index 255c6d35b..000000000 --- a/src/plugins/pronoundb/components/PronounsAboutComponent.tsx +++ /dev/null @@ -1,41 +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 { Link } from "@components/Link"; -import { Forms, React } from "@webpack/common"; - -export default function PronounsAboutComponent() { - return ( - - More Information - To add your own pronouns, visit{" "} - pronoundb.org - - - - The two pronoun formats are lowercase and capitalized. Example: -
    -
  • Lowercase: they/them
  • -
  • Capitalized: They/Them
  • -
- Text like "Ask me my pronouns" or "Any pronouns" will always be capitalized.

- You can also configure whether or not to display pronouns for the current user (since you probably already know them) -
-
- ); -} diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts deleted file mode 100644 index 511aeb1c2..000000000 --- a/src/plugins/pronoundb/index.ts +++ /dev/null @@ -1,78 +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 "./styles.css"; - -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -import { useProfilePronouns } from "./api"; -import PronounsAboutComponent from "./components/PronounsAboutComponent"; -import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } from "./components/PronounsChatComponent"; -import { settings } from "./settings"; - -export default definePlugin({ - name: "PronounDB", - authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven, Devs.Elvyra], - description: "Adds pronouns to user messages using pronoundb", - patches: [ - { - find: "showCommunicationDisabledStyles", - replacement: [ - // Add next to username (compact mode) - { - match: /("span",{id:\i,className:\i,children:\i}\))/, - replace: "$1, $self.CompactPronounsChatComponentWrapper(arguments[0])" - }, - // Patch the chat timestamp element (normal mode) - { - match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/, - replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]" - } - ] - }, - - { - find: ".Messages.USER_PROFILE_PRONOUNS", - group: true, - replacement: [ - { - match: /\.PANEL},/, - replace: "$&{pronouns:vcPronoun,source:vcPronounSource,hasPendingPronouns:vcHasPendingPronouns}=$self.useProfilePronouns(arguments[0].user?.id)," - }, - { - match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/, - replace: '$&+(vcPronoun==null||vcHasPendingPronouns?"":` (${vcPronounSource})`)' - }, - { - match: /(\.pronounsText.+?children:)(\i)/, - replace: "$1(vcPronoun==null||vcHasPendingPronouns)?$2:vcPronoun" - } - ] - } - ], - - settings, - - settingsAboutComponent: PronounsAboutComponent, - - // Re-export the components on the plugin object so it is easily accessible in patches - PronounsChatComponentWrapper, - CompactPronounsChatComponentWrapper, - useProfilePronouns -}); diff --git a/src/plugins/pronoundb/styles.css b/src/plugins/pronoundb/styles.css deleted file mode 100644 index a7d9eb9e5..000000000 --- a/src/plugins/pronoundb/styles.css +++ /dev/null @@ -1,9 +0,0 @@ -.vc-pronoundb-compact { - display: none; -} - -[class*="compact"] .vc-pronoundb-compact { - display: inline-block; - margin-left: -2px; - margin-right: 0.25rem; -} diff --git a/src/plugins/pronoundb/types.ts b/src/plugins/pronoundb/types.ts deleted file mode 100644 index 66bb13f02..000000000 --- a/src/plugins/pronoundb/types.ts +++ /dev/null @@ -1,63 +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 . -*/ - -export interface UserProfileProps { - userId: string; -} - -export interface UserProfilePronounsProps { - currentPronouns: string | null; - hidePersonalInformation: boolean; -} - -export type PronounSets = Record; -export type PronounsResponse = Record; - -export interface PronounsCache { - sets?: PronounSets; -} - -export const PronounMapping = { - he: "He/Him", - it: "It/Its", - she: "She/Her", - they: "They/Them", - any: "Any pronouns", - other: "Other pronouns", - ask: "Ask me my pronouns", - avoid: "Avoid pronouns, use my name", - unspecified: "No pronouns specified.", -} as const satisfies Record; - -export type PronounCode = keyof typeof PronounMapping; - -export interface Pronouns { - pronouns?: string; - source: string; - hasPendingPronouns: boolean; -} - -export const enum PronounsFormat { - Lowercase = "LOWERCASE", - Capitalized = "CAPITALIZED" -} - -export const enum PronounSource { - PreferPDB, - PreferDiscord -} diff --git a/src/plugins/pronoundb/components/PronounsChatComponent.tsx b/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx similarity index 68% rename from src/plugins/pronoundb/components/PronounsChatComponent.tsx rename to src/plugins/userMessagesPronouns/PronounsChatComponent.tsx index 46c8a8a16..c2d7be2e8 100644 --- a/src/plugins/pronoundb/components/PronounsChatComponent.tsx +++ b/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx @@ -16,22 +16,22 @@ * along with this program. If not, see . */ +import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { findByPropsLazy } from "@webpack"; -import { UserStore } from "@webpack/common"; +import { i18n, Tooltip, UserStore } from "@webpack/common"; import { Message } from "discord-types/general"; -import { useFormattedPronouns } from "../api"; -import { settings } from "../settings"; +import { settings } from "./settings"; +import { useFormattedPronouns } from "./utils"; const styles: Record = findByPropsLazy("timestampInline"); +const MessageDisplayCompact = getUserSettingLazy("textAndImages", "messageDisplayCompact")!; const AUTO_MODERATION_ACTION = 24; function shouldShow(message: Message): boolean { - if (!settings.store.showInMessages) - return false; if (message.author.bot || message.author.system || message.type === AUTO_MODERATION_ACTION) return false; if (!settings.store.showSelf && message.author.id === UserStore.getCurrentUser().id) @@ -40,6 +40,21 @@ function shouldShow(message: Message): boolean { return true; } +function PronounsChatComponent({ message }: { message: Message; }) { + const pronouns = useFormattedPronouns(message.author.id); + + return pronouns && ( + + {tooltipProps => ( + • {pronouns} + )} + + ); +} + export const PronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => { return shouldShow(message) ? @@ -47,27 +62,11 @@ export const PronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { m }, { noop: true }); export const CompactPronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => { - return shouldShow(message) - ? - : null; -}, { noop: true }); - -function PronounsChatComponent({ message }: { message: Message; }) { - const { pronouns } = useFormattedPronouns(message.author.id); - - return pronouns && ( - • {pronouns} - ); -} - -export const CompactPronounsChatComponent = ErrorBoundary.wrap(({ message }: { message: Message; }) => { - const { pronouns } = useFormattedPronouns(message.author.id); - - return pronouns && ( - • {pronouns} - ); + const compact = MessageDisplayCompact.useSetting(); + + if (!compact || !shouldShow(message)) { + return null; + } + + return ; }, { noop: true }); diff --git a/src/plugins/userMessagesPronouns/README.md b/src/plugins/userMessagesPronouns/README.md new file mode 100644 index 000000000..455f9c443 --- /dev/null +++ b/src/plugins/userMessagesPronouns/README.md @@ -0,0 +1,5 @@ +User Messages Pronouns + +Adds pronouns to chat user messages + +![](https://github.com/user-attachments/assets/34dc373d-faf4-4420-b49b-08b2647baa3b) diff --git a/src/plugins/userMessagesPronouns/index.ts b/src/plugins/userMessagesPronouns/index.ts new file mode 100644 index 000000000..27b162b90 --- /dev/null +++ b/src/plugins/userMessagesPronouns/index.ts @@ -0,0 +1,54 @@ +/* + * 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 { migratePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } from "./PronounsChatComponent"; +import { settings } from "./settings"; + +migratePluginSettings("UserMessagesPronouns", "PronounDB"); +export default definePlugin({ + name: "UserMessagesPronouns", + authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven, Devs.Elvyra], + description: "Adds pronouns to chat user messages", + settings, + + patches: [ + { + find: "showCommunicationDisabledStyles", + replacement: { + // Add next to timestamp (normal mode) + match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/, + replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]" + } + }, + { + find: '="SYSTEM_TAG"', + replacement: { + // Add next to username (compact mode) + match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, + replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0])," + } + } + ], + + PronounsChatComponentWrapper, + CompactPronounsChatComponentWrapper, +}); diff --git a/src/plugins/pronoundb/settings.ts b/src/plugins/userMessagesPronouns/settings.ts similarity index 60% rename from src/plugins/pronoundb/settings.ts rename to src/plugins/userMessagesPronouns/settings.ts index ebacfbc88..bbbd50852 100644 --- a/src/plugins/pronoundb/settings.ts +++ b/src/plugins/userMessagesPronouns/settings.ts @@ -19,7 +19,10 @@ import { definePluginSettings } from "@api/Settings"; import { OptionType } from "@utils/types"; -import { PronounsFormat, PronounSource } from "./types"; +export const enum PronounsFormat { + Lowercase = "LOWERCASE", + Capitalized = "CAPITALIZED" +} export const settings = definePluginSettings({ pronounsFormat: { @@ -37,34 +40,9 @@ export const settings = definePluginSettings({ } ] }, - pronounSource: { - type: OptionType.SELECT, - description: "Where to source pronouns from", - options: [ - { - label: "Prefer PronounDB, fall back to Discord", - value: PronounSource.PreferPDB, - default: true - }, - { - label: "Prefer Discord, fall back to PronounDB (might lead to inconsistency between pronouns in chat and profile)", - value: PronounSource.PreferDiscord - } - ] - }, showSelf: { type: OptionType.BOOLEAN, - description: "Enable or disable showing pronouns for the current user", - default: true - }, - showInMessages: { - type: OptionType.BOOLEAN, - description: "Show in messages", - default: true - }, - showInProfile: { - type: OptionType.BOOLEAN, - description: "Show in profile", + description: "Enable or disable showing pronouns for yourself", default: true } }); diff --git a/src/plugins/userMessagesPronouns/utils.ts b/src/plugins/userMessagesPronouns/utils.ts new file mode 100644 index 000000000..18a227721 --- /dev/null +++ b/src/plugins/userMessagesPronouns/utils.ts @@ -0,0 +1,35 @@ +/* + * 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 { getCurrentChannel } from "@utils/discord"; +import { UserProfileStore, useStateFromStores } from "@webpack/common"; + +import { PronounsFormat, settings } from "./settings"; + +function useDiscordPronouns(id: string, useGlobalProfile: boolean = false): string | undefined { + const globalPronouns: string | undefined = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(id)?.pronouns); + const guildPronouns: string | undefined = useStateFromStores([UserProfileStore], () => UserProfileStore.getGuildMemberProfile(id, getCurrentChannel()?.getGuildId())?.pronouns); + + if (useGlobalProfile) return globalPronouns; + return guildPronouns || globalPronouns; +} + +export function useFormattedPronouns(id: string, useGlobalProfile: boolean = false) { + const pronouns = useDiscordPronouns(id, useGlobalProfile)?.trim().replace(/\n+/g, ""); + return settings.store.pronounsFormat === PronounsFormat.Lowercase ? pronouns?.toLowerCase() : pronouns; +}