diff --git a/src/plugins/pronoundb/PronounComponent.tsx b/src/plugins/pronoundb/PronounComponent.tsx deleted file mode 100644 index 35cd44b4..00000000 --- a/src/plugins/pronoundb/PronounComponent.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { fetchPronouns } from "./utils"; -import { classes, lazyWebpack, useAwaiter } from "../../utils/misc"; -import { PronounMapping } from "./types"; -import { filters } from "../../webpack"; -import { Message } from "discord-types/general"; - -const styles: Record = lazyWebpack(filters.byProps(["timestampInline"])); - -export default function PronounComponent({ message }: { message: Message; }) { - // Don't bother fetching bot or system users - if (message.author.bot && message.author.system) return null; - - const [result, , isPending] = useAwaiter( - () => fetchPronouns(message.author.id), - null, - e => console.error("Fetching pronouns failed: ", e) - ); - - // If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns - if (!isPending && result && result !== "unspecified" && PronounMapping[result]) return ( - • {PronounMapping[result]} - ); - // Otherwise, return null so nothing else is rendered - else return null; -} diff --git a/src/plugins/pronoundb/components/PronounsAboutComponent.tsx b/src/plugins/pronoundb/components/PronounsAboutComponent.tsx new file mode 100644 index 00000000..09035097 --- /dev/null +++ b/src/plugins/pronoundb/components/PronounsAboutComponent.tsx @@ -0,0 +1,18 @@ +import { Forms, React } from "../../../webpack/common"; + +export default function PronounsAboutComponent() { + return ( + + More Information + + 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/components/PronounsChatComponent.tsx b/src/plugins/pronoundb/components/PronounsChatComponent.tsx new file mode 100644 index 00000000..3e4e77ff --- /dev/null +++ b/src/plugins/pronoundb/components/PronounsChatComponent.tsx @@ -0,0 +1,33 @@ +import { Message } from "discord-types/general"; +import { fetchPronouns, formatPronouns } from "../utils"; +import { classes, lazyWebpack, useAwaiter } from "../../../utils/misc"; +import { PronounMapping } from "../types"; +import { filters } from "../../../webpack"; +import { UserStore } from "../../../webpack/common"; +import { Settings } from "../../../Vencord"; + +const styles: Record = lazyWebpack(filters.byProps(["timestampInline"])); + +export default function PronounsChatComponent({ message }: { message: Message; }) { + // Don't bother fetching bot or system users + if (message.author.bot || message.author.system) return null; + // Respect showSelf options + if (!Settings.plugins.PronounDB.showSelf && message.author.id === UserStore.getCurrentUser().id) return null; + + const [result, , isPending] = useAwaiter( + () => fetchPronouns(message.author.id), + null, + e => console.error("Fetching pronouns failed: ", e) + ); + + // If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns + if (!isPending && result && result !== "unspecified" && PronounMapping[result]) { + return ( + • {formatPronouns(result)} + ); + } + // Otherwise, return null so nothing else is rendered + else return null; +} diff --git a/src/plugins/pronoundb/components/PronounsProfileWrapper.tsx b/src/plugins/pronoundb/components/PronounsProfileWrapper.tsx new file mode 100644 index 00000000..a8f2ff4d --- /dev/null +++ b/src/plugins/pronoundb/components/PronounsProfileWrapper.tsx @@ -0,0 +1,28 @@ +import { UserStore } from "../../../webpack/common"; +import { Settings } from "../../../Vencord"; +import { PronounMapping, UserProfileProps } from "../types"; +import { useAwaiter, classes } from "../../../utils"; +import { fetchPronouns, formatPronouns } from "../utils"; + +export default function PronounsProfileWrapper(props: UserProfileProps, pronounsComponent: JSX.Element) { + // Don't bother fetching bot or system users + if (props.user.bot || props.user.system) return null; + // Respect showSelf options + if (!Settings.plugins.PronounDB.showSelf && props.user.id === UserStore.getCurrentUser().id) return null; + + const [result, , isPending] = useAwaiter( + () => fetchPronouns(props.user.id), + null, + e => console.error("Fetching pronouns failed: ", e) + ); + + // If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns + if (!isPending && result && result !== "unspecified" && PronounMapping[result]) { + // First child is the header, second is a div with the actual text + const [, pronounsBodyComponent] = pronounsComponent.props.children as [JSX.Element, JSX.Element]; + pronounsBodyComponent.props.children = formatPronouns(result); + return pronounsComponent; + } + // Otherwise, return null so nothing else is rendered + else return null; +} diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index bc31b481..e71d3dd3 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -1,6 +1,12 @@ -import definePlugin from "../../utils/types"; -import PronounComponent from "./PronounComponent"; -import { fetchPronouns } from "./utils"; +import definePlugin, { OptionType } from "../../utils/types"; +import PronounsAboutComponent from "./components/PronounsAboutComponent"; +import PronounsChatComponent from "./components/PronounsChatComponent"; +import PronounsProfileWrapper from "./components/PronounsProfileWrapper"; + +export enum PronounsFormat { + Lowercase = "LOWERCASE", + Capitalized = "CAPITALIZED" +} export default definePlugin({ name: "PronounDB", @@ -10,14 +16,47 @@ export default definePlugin({ }], description: "Adds pronouns to user messages using pronoundb", patches: [ + // Patch the chat timestamp element { find: "showCommunicationDisabledStyles", replacement: { match: /(?<=return\s+\w{1,3}\.createElement\(.+!\w{1,3}&&)(\w{1,3}.createElement\(.+?\{.+?\}\))/, - replace: "[$1, Vencord.Plugins.plugins.PronounDB.PronounComponent(e)]" + replace: "[$1, Vencord.Plugins.plugins.PronounDB.PronounsChatComponent(e)]" + } + }, + // Hijack the discord pronouns section (hidden without experiment) and add a wrapper around the text section + { + find: ".headerTagUsernameNoNickname", + replacement: { + match: /""!==(.{1,2})&&(r\.createElement\(r\.Fragment.+?\.Messages\.USER_POPOUT_PRONOUNS.+?pronounsText.+?\},\1\)\))/, + replace: (_, __, fragment) => `Vencord.Plugins.plugins.PronounDB.PronounsProfileWrapper(e, ${fragment})` } } ], - // Re-export the component on the plugin object so it is easily accessible in patches - PronounComponent + options: { + pronounsFormat: { + type: OptionType.SELECT, + description: "The format for pronouns to appear in chat", + options: [ + { + label: "Lowercase", + value: PronounsFormat.Lowercase, + default: true + }, + { + label: "Capitalized", + value: PronounsFormat.Capitalized + } + ] + }, + showSelf: { + type: OptionType.BOOLEAN, + description: "Enable or disable showing pronouns for the current user", + default: true + } + }, + settingsAboutComponent: PronounsAboutComponent, + // Re-export the components on the plugin object so it is easily accessible in patches + PronounsChatComponent, + PronounsProfileWrapper }); diff --git a/src/plugins/pronoundb/types.ts b/src/plugins/pronoundb/types.ts index 98a0bfca..cfd3a6a8 100644 --- a/src/plugins/pronoundb/types.ts +++ b/src/plugins/pronoundb/types.ts @@ -1,3 +1,14 @@ +import { User } from "discord-types/general"; + +export interface UserProfileProps { + customStatus: JSX.Element, + displayProfile: { + // In the future (if discord ever uses their pronouns system) this taking priority can be a plugin setting + pronouns: string; + }; + user: User; +} + export interface PronounsResponse { [id: string]: PronounCode; } @@ -5,7 +16,6 @@ export interface PronounsResponse { export type PronounCode = keyof typeof PronounMapping; export const PronounMapping = { - unspecified: "Unspecified", hh: "He/Him", hi: "He/It", hs: "He/She", @@ -25,5 +35,6 @@ export const PronounMapping = { any: "Any pronouns", other: "Other pronouns", ask: "Ask me my pronouns", - avoid: "Avoid pronouns, use my name" + avoid: "Avoid pronouns, use my name", + unspecified: "Unspecified" } as const; diff --git a/src/plugins/pronoundb/utils.ts b/src/plugins/pronoundb/utils.ts index 24a55daf..4547f942 100644 --- a/src/plugins/pronoundb/utils.ts +++ b/src/plugins/pronoundb/utils.ts @@ -1,5 +1,8 @@ +import gitHash from "git-hash"; +import { PronounsFormat } from "."; import { debounce } from "../../utils"; -import { PronounCode, PronounsResponse } from "./types"; +import { Settings } from "../../Vencord"; +import { PronounCode, PronounMapping, PronounsResponse } from "./types"; // A map of cached pronouns so the same request isn't sent twice const cache: Record = {}; @@ -41,7 +44,8 @@ async function bulkFetchPronouns(ids: string[]): Promise { const req = await fetch("https://pronoundb.org/api/v1/lookup-bulk?" + params.toString(), { method: "GET", headers: { - "Accept": "application/json" + "Accept": "application/json", + "X-PronounDB-Source": `Vencord/${gitHash} (github.com/Vendicated/Vencord)` } }); return await req.json() @@ -57,3 +61,16 @@ async function bulkFetchPronouns(ids: string[]): Promise { return dummyPronouns; } } + +export function formatPronouns(pronouns: PronounCode): string { + const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; }; + // For capitalized pronouns, just return the mapping (it is by default capitalized) + if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns]; + // If it is set to lowercase and a special code (any, ask, avoid), then just return the capitalized text + else if ( + pronounsFormat === PronounsFormat.Lowercase + && ["any", "ask", "avoid", "other"].includes(pronouns) + ) return PronounMapping[pronouns]; + // Otherwise (lowercase and not a special code), then convert the mapping to lowercase + else return PronounMapping[pronouns].toLowerCase(); +}