diff --git a/browser/userscript.meta.js b/browser/userscript.meta.js index 5b2a39be6..1d986aaee 100644 --- a/browser/userscript.meta.js +++ b/browser/userscript.meta.js @@ -5,6 +5,7 @@ // @author Vendicated (https://github.com/Vendicated) // @namespace https://github.com/Vendicated/Vencord // @supportURL https://github.com/Vendicated/Vencord +// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png // @license GPL-3.0 // @match *://*.discord.com/* // @grant GM_xmlhttpRequest diff --git a/package.json b/package.json index a41fe162a..9d5ad3e58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.10.4", + "version": "1.10.5", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/main/patcher.ts b/src/main/patcher.ts index e5b87290d..e858f3fcd 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -17,7 +17,7 @@ */ import { onceDefined } from "@shared/onceDefined"; -import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron"; +import electron, { app, BrowserWindowConstructorOptions, Menu, nativeTheme } from "electron"; import { dirname, join } from "path"; import { initIpc } from "./ipcMain"; @@ -100,6 +100,19 @@ if (!IS_VANILLA) { super(options); initIpc(this); + + // Workaround for https://github.com/electron/electron/issues/43367. Vesktop also has its own workaround + // @TODO: Remove this when the issue is fixed + if (IS_DISCORD_DESKTOP) { + this.webContents.on("devtools-opened", () => { + if (!nativeTheme.shouldUseDarkColors) return; + + nativeTheme.themeSource = "light"; + setTimeout(() => { + nativeTheme.themeSource = "dark"; + }, 100); + }); + } } else super(options); } } diff --git a/src/plugins/_api/dynamicImageModalApi.ts b/src/plugins/_api/dynamicImageModalApi.ts new file mode 100644 index 000000000..2ce51400d --- /dev/null +++ b/src/plugins/_api/dynamicImageModalApi.ts @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + + +export default definePlugin({ + name: "DynamicImageModalAPI", + authors: [Devs.sadan, Devs.Nuckyz], + description: "Allows you to omit either width or height when opening an image modal", + patches: [ + { + find: "SCALE_DOWN:", + replacement: { + match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/, + replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))` + } + } + ] +}); diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index be220db1a..329c389c4 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -197,7 +197,7 @@ export default definePlugin({ }, get electronVersion() { - return VencordNative.native.getVersions().electron || window.armcord?.electron || null; + return VencordNative.native.getVersions().electron || window.legcord?.electron || null; }, get chromiumVersion() { diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 432896fc7..cb8d1d056 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -77,7 +77,7 @@ async function generateDebugInfoMessage() { const client = (() => { if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`; if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`; - if ("armcord" in window) return `ArmCord v${window.armcord.version}`; + if ("legcord" in window) return `Legcord v${window.legcord.version}`; // @ts-expect-error const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web"; @@ -149,8 +149,8 @@ export default definePlugin({ patches: [{ find: ".BEGINNING_DM.format", replacement: { - match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/, - replace: "$& $self.ContributorDmWarningCard({ userId: $1 })," + match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,300}(\i)\.isMultiUserDM)/, + replace: "$& $self.renderContributorDmWarningCard({ channel: $1 })," } }], @@ -235,7 +235,8 @@ export default definePlugin({ } }, - ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => { + renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => { + const userId = channel.getRecipientId(); if (!isPluginDev(userId)) return null; if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null; diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index df307e756..61e048dce 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -73,8 +73,8 @@ export default definePlugin({ }, async start() { - // ArmCord comes with its own arRPC implementation, so this plugin just confuses users - if ("armcord" in window) return; + // Legcord comes with its own arRPC implementation, so this plugin just confuses users + if ("legcord" in window) return; if (ws) ws.close(); ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index bf4cf0f37..1029c07e2 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -99,7 +99,11 @@ export default definePlugin({ id="vc-view-role-icon" label="View Role Icon" action={() => { - openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`); + openImageModal({ + url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`, + height: 128, + width: 128 + }); }} icon={ImageIcon} /> diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx index 8cca912bc..92b6f57fd 100644 --- a/src/plugins/biggerStreamPreview/index.tsx +++ b/src/plugins/biggerStreamPreview/index.tsx @@ -57,7 +57,11 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId); if (!previewUrl) return; - openImageModal(previewUrl); + openImageModal({ + url: previewUrl, + height: 720, + width: 1280 + }); }; export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => { diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index fb2c67c33..ca348c614 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -350,7 +350,7 @@ export default definePlugin({ { // Filter attachments to remove fake nitro stickers or emojis predicate: () => settings.store.transformStickers, - match: /renderAttachments\(\i\){let{attachments:(\i).+?;/, + match: /renderAttachments\(\i\){.+?{attachments:(\i).+?;/, replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});` } ] diff --git a/src/plugins/fixImagesQuality/README.md b/src/plugins/fixImagesQuality/README.md new file mode 100644 index 000000000..8e3cf6548 --- /dev/null +++ b/src/plugins/fixImagesQuality/README.md @@ -0,0 +1,3 @@ +# Fix Images Quality + +Prevents images from being loaded as webp, which can cause quality loss diff --git a/src/plugins/fixImagesQuality/index.ts b/src/plugins/fixImagesQuality/index.ts new file mode 100644 index 000000000..8f84573c0 --- /dev/null +++ b/src/plugins/fixImagesQuality/index.ts @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "FixImagesQuality", + description: "Prevents images from being loaded as webp, which can cause quality loss", + authors: [Devs.Nuckyz], + patches: [ + { + find: "getFormatQuality(){", + replacement: { + match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/, + replace: "" + } + } + ] +}); diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index da01f1a79..adb0ab220 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -156,14 +156,10 @@ export default definePlugin({ patches: [ { - find: "Messages.OPEN_IN_BROWSER", + find: ".contain,SCALE_DOWN:", replacement: { - // there are 2 image thingies. one for carosuel and one for the single image. - // so thats why i added global flag. - // also idk if this patch is good, should it be more specific? - // https://regex101.com/r/xfvNvV/1 - match: /return.{1,200}\.wrapper.{1,200}src:\i,/g, - replace: `$&id: '${ELEMENT_ID}',` + match: /\.slide,\i\),/g, + replace: `$&id:"${ELEMENT_ID}",` } }, @@ -185,13 +181,6 @@ export default definePlugin({ replace: "$&$self.unMountMagnifier();" } ] - }, - { - find: ".carouselModal", - replacement: { - match: /(?<=\.carouselModal.{0,100}onClick:)\i,/, - replace: "()=>{}," - } } ], diff --git a/src/plugins/imageZoom/styles.css b/src/plugins/imageZoom/styles.css index c3776d90e..63a51e297 100644 --- a/src/plugins/imageZoom/styles.css +++ b/src/plugins/imageZoom/styles.css @@ -23,12 +23,3 @@ /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */ } - -/* make the carousel take up less space so we can click the backdrop and exit out of it */ -[class*="modalCarouselWrapper_"] { - top: 0 !important; -} - -[class*="carouselModal_"] { - height: 0 !important; -} diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 14899c367..7437cace7 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -74,7 +74,7 @@ export default definePlugin({ if (msg.deleted === true) return; if (isMe) { - if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return; + if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return; MessageActions.startEditMessage(channel.id, msg.id, msg.content); event.preventDefault(); diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index ec52b4061..a4f690af1 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -28,7 +28,7 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); -const ExpandableList = findComponentByCodeLazy(".mutualFriendItem]"); +const ExpandableList = findComponentByCodeLazy('"PRESS_SECTION"'); const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon"); function getGroupDMName(channel: Channel) { @@ -142,16 +142,15 @@ export default definePlugin({ const mutualGDms = getMutualGroupDms(user.id); if (mutualGDms.length === 0) return null; - const header = getMutualGDMCountText(user); return ( <> {Divider} { })} + listClassName={listStyle} + header={"Mutual Groups"} + isLoading={false} + items={renderClickableGDMs(mutualGDms, () => { })} /> ); diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 576980cba..1e4f30412 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -91,15 +91,6 @@ export default definePlugin({ replace: "async function $1 if(await $self.handleLink(...arguments)) return;" } }, - // Make Spotify profile activity links open in app on web - { - find: "WEB_OPEN(", - predicate: () => !IS_DISCORD_DESKTOP && pluginSettings.store.spotify, - replacement: { - match: /\i\.\i\.isProtocolRegistered\(\)(.{0,100})window.open/g, - replace: "true$1VencordNative.native.openExternal" - } - }, { find: "no artist ids in metadata", predicate: () => !IS_DISCORD_DESKTOP && pluginSettings.store.spotify, 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/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index fb8df2ce1..a0d138cda 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -80,7 +80,10 @@ function GuildInfoModal({ guild }: GuildProps) { className={cl("banner")} src={bannerUrl} alt="" - onClick={() => openImageModal(bannerUrl)} + onClick={() => openImageModal({ + url: bannerUrl, + width: 1024 + })} /> )} @@ -89,7 +92,11 @@ function GuildInfoModal({ guild }: GuildProps) { ? openImageModal(iconUrl)} + onClick={() => openImageModal({ + url: iconUrl, + height: 512, + width: 512, + })} /> :
{guild.acronym}
} @@ -151,7 +158,15 @@ function Owner(guildId: string, owner: User) { return (
- openImageModal(ownerAvatarUrl)} /> + openImageModal({ + url: ownerAvatarUrl, + height: 512, + width: 512 + })} + /> {Parser.parse(`<@${owner.id}>`)}
); diff --git a/src/plugins/serverInfo/index.tsx b/src/plugins/serverInfo/index.tsx index be3172f01..a6dd6edf9 100644 --- a/src/plugins/serverInfo/index.tsx +++ b/src/plugins/serverInfo/index.tsx @@ -30,7 +30,9 @@ export default definePlugin({ name: "ServerInfo", description: "Allows you to view info about a server", authors: [Devs.Ven, Devs.Nuckyz], + dependencies: ["DynamicImageModalAPI"], tags: ["guild", "info", "ServerProfile"], + contextMenus: { "guild-context": Patch, "guild-header-popout": Patch diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 7f008c8cb..31f90242f 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -61,6 +61,10 @@ export const settings = definePluginSettings({ } }); +function isUncategorized(objChannel: { channel: Channel; comparator: number; }) { + return objChannel.channel.id === "null" && objChannel.channel.name === "Uncategorized" && objChannel.comparator === -1; +} + export default definePlugin({ name: "ShowHiddenChannels", description: "Show channels that you do not have access to view.", @@ -99,7 +103,7 @@ export default definePlugin({ replacement: [ { // Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel - match: /(?<=getCurrentClientVoiceChannelId\((\i)\.guild_id\);return)/, + match: /(?<=getBlockedUsersForVoiceChannel\((\i)\.id\);return)/, replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&` }, { @@ -503,7 +507,7 @@ export default definePlugin({ res[key] ??= []; for (const objChannel of maybeObjChannels) { - if (objChannel.channel.id === null || !this.isHiddenChannel(objChannel.channel)) res[key].push(objChannel); + if (isUncategorized(objChannel) || objChannel.channel.id === null || !this.isHiddenChannel(objChannel.channel)) res[key].push(objChannel); } } diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index aef0c7362..41e09c160 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -229,7 +229,7 @@ function AlbumContextMenu({ track }: { track: Track; }) { id="view-cover" label="View Album Cover" // trolley - action={() => openImageModal(track.album.image.url)} + action={() => openImageModal(track.album.image)} icon={ImageIcon} /> . */ +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; +} diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index c154de636..629708140 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -67,7 +67,10 @@ const settings = definePluginSettings({ } }); -function openImage(url: string) { +const openAvatar = (url: string) => openImage(url, 512, 512); +const openBanner = (url: string) => openImage(url, 1024); + +function openImage(url: string, width: number, height?: number) { const format = url.startsWith("/") ? "png" : settings.store.format; const u = new URL(url, window.location.href); @@ -76,11 +79,13 @@ function openImage(url: string) { url = u.toString(); u.searchParams.set("size", "4096"); - const originalUrl = u.toString(); + const original = u.toString(); - openImageModal(url, { - original: originalUrl, - height: 256 + openImageModal({ + url, + original, + width, + height }); } @@ -93,14 +98,14 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U openImage(IconUtils.getUserAvatarURL(user, true))} + action={() => openAvatar(IconUtils.getUserAvatarURL(user, true))} icon={ImageIcon} /> {memberAvatar && ( openImage(IconUtils.getGuildMemberAvatarURLSimple({ + action={() => openAvatar(IconUtils.getGuildMemberAvatarURLSimple({ userId: user.id, avatar: memberAvatar, guildId: guildId!, @@ -126,7 +131,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon id="view-icon" label="View Icon" action={() => - openImage(IconUtils.getGuildIconURL({ + openAvatar(IconUtils.getGuildIconURL({ id, icon, canAnimate: true @@ -140,7 +145,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon id="view-banner" label="View Banner" action={() => - openImage(IconUtils.getGuildBannerURL(guild, true)!) + openBanner(IconUtils.getGuildBannerURL(guild, true)!) } icon={ImageIcon} /> @@ -158,7 +163,7 @@ const GroupDMContext: NavContextMenuPatchCallback = (children, { channel }: Grou id="view-group-channel-icon" label="View Icon" action={() => - openImage(IconUtils.getChannelIconURL(channel)!) + openAvatar(IconUtils.getChannelIconURL(channel)!) } icon={ImageIcon} /> @@ -171,10 +176,12 @@ export default definePlugin({ authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx], description: "Makes avatars and banners in user profiles clickable, adds View Icon/Banner entries in the user, server and group channel context menu.", tags: ["ImageUtilities"], + dependencies: ["DynamicImageModalAPI"], settings, - openImage, + openAvatar, + openBanner, contextMenus: { "user-context": UserContext, @@ -188,7 +195,7 @@ export default definePlugin({ find: ".overlay:void 0,status:", replacement: { match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, - replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)}," + replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)}," }, all: true }, @@ -197,7 +204,7 @@ export default definePlugin({ find: 'backgroundColor:"COMPLETE"', replacement: { match: /(\.banner,.+?),style:{(?=.+?backgroundImage:null!=(\i)\?"url\("\.concat\(\2,)/, - replace: (_, rest, bannerSrc) => `${rest},onClick:()=>${bannerSrc}!=null&&$self.openImage(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,` + replace: (_, rest, bannerSrc) => `${rest},onClick:()=>${bannerSrc}!=null&&$self.openBanner(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,` } }, // Group DMs top small & large icon @@ -205,7 +212,7 @@ export default definePlugin({ find: /\.recipients\.length>=2(?! `${m},onClick:()=>$self.openImage(${iconUrl})` + replace: (m, iconUrl) => `${m},onClick:()=>$self.openAvatar(${iconUrl})` } }, // User DMs top small icon @@ -213,7 +220,7 @@ export default definePlugin({ find: ".cursorPointer:null,children", replacement: { match: /.Avatar,.+?src:(.+?\))(?=[,}])/, - replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})` + replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})` } }, // User Dms top large icon @@ -221,7 +228,7 @@ export default definePlugin({ find: 'experimentLocation:"empty_messages"', replacement: { match: /.Avatar,.+?src:(.+?\))(?=[,}])/, - replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})` + replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})` } } ] diff --git a/src/plugins/webKeybinds.web/index.ts b/src/plugins/webKeybinds.web/index.ts index 1c43dc0cf..a7435c954 100644 --- a/src/plugins/webKeybinds.web/index.ts +++ b/src/plugins/webKeybinds.web/index.ts @@ -25,7 +25,7 @@ const KeyBinds = findByPropsLazy("JUMP_TO_GUILD", "SERVER_NEXT"); export default definePlugin({ name: "WebKeybinds", - description: "Re-adds keybinds missing in the web version of Discord: ctrl+t, ctrl+shift+t, ctrl+tab, ctrl+shift+tab, ctrl+1-9, ctrl+,. Only works fully on Vesktop/ArmCord, not inside your browser", + description: "Re-adds keybinds missing in the web version of Discord: ctrl+t, ctrl+shift+t, ctrl+tab, ctrl+shift+tab, ctrl+1-9, ctrl+,. Only works fully on Vesktop/Legcord, not inside your browser", authors: [Devs.Ven], enabledByDefault: true, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 6653e6307..70eca56fd 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -575,6 +575,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "RamziAH", id: 1279957227612147747n, }, + SomeAspy: { + name: "SomeAspy", + id: 516750892372852754n, + }, } satisfies Record); // iife so #__PURE__ works correctly diff --git a/src/utils/discord.css b/src/utils/discord.css new file mode 100644 index 000000000..a7f95fbe6 --- /dev/null +++ b/src/utils/discord.css @@ -0,0 +1,24 @@ +.vc-position-inherit { + position: inherit; +} + +/** + * copy pasted from discord css. not really webpack-findable since it's the only class in the module +**/ + +.vc-image-modal { + background: transparent!important; + box-shadow: none!important; + display: flex; + justify-content: center; + align-items: center; + border-radius: 0 +} + +@media(width <= 485px) { + .vc-image-modal { + display:relative; + overflow: visible; + overflow: initial + } +} diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 4c7cc38a0..08de22ccd 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -16,11 +16,13 @@ * along with this program. If not, see . */ +import "./discord.css"; + import { MessageObject } from "@api/MessageEvents"; -import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; +import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Channel, Guild, Message, User } from "discord-types/general"; -import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal"; +import { ImageModal, ImageModalItem, openModal } from "./modal"; /** * Open the invite modal @@ -108,25 +110,24 @@ export function sendMessage( return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra); } -export function openImageModal(url: string, props?: Partial>): string { +/** + * You must specify either height or width + */ +export function openImageModal(props: Omit): string { return openModal(modalProps => ( - - } - // Don't render forward message button - renderForwardComponent={() => null} - shouldHideMediaOptions={false} - shouldAnimate - {...props} - /> - + className="vc-image-modal" + fit="vc-position-inherit" + items={[{ + type: "IMAGE", + original: props.url, + ...props, + }]} + onClose={modalProps.onClose} + shouldHideMediaOptions={false} + shouldAnimate + /> )); } diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 79f777088..7459379ea 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -101,25 +101,24 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { }>; }; -export type ImageModal = ComponentType<{ - className?: string; - src: string; - placeholder: string; - original: string; +export interface ImageModalItem { + type: "IMAGE" | "VIDEO"; + url: string; width?: number; height?: number; - animated?: boolean; - responsive?: boolean; - renderLinkComponent(props: any): ReactNode; - renderForwardComponent(props: any): ReactNode; - maxWidth?: number; - maxHeight?: number; - shouldAnimate?: boolean; + original?: string; +} + +export type ImageModal = ComponentType<{ + className?: string; + fit?: string; onClose?(): void; shouldHideMediaOptions?: boolean; + shouldAnimate?: boolean; + items: ImageModalItem[]; }>; -export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE", "responsive") as ImageModal; +export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE") as ImageModal; export const ModalRoot = LazyComponent(() => Modals.ModalRoot); export const ModalHeader = LazyComponent(() => Modals.ModalHeader);