diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml index ba2b15b0e..e8ca246de 100644 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ b/.github/ISSUE_TEMPLATE/blank.yml @@ -2,9 +2,29 @@ name: Blank Issue description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. body: + - type: markdown + attributes: + value: | + # READ THIS BEFORE OPENING AN ISSUE + + This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS. + + DO NOT USE THIS FORM, unless + - you are a vencord contributor + - you were given explicit permission to use this form by a moderator in our support server + - you are filing a security related report + - type: textarea id: content attributes: label: Content validations: required: true + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + options: + - label: I have read the requirements for opening an issue above + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e7afec3c6..74b2a418b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,6 +4,18 @@ labels: [bug] title: "[Bug] " body: + - type: markdown + attributes: + value: | + # READ THIS BEFORE OPENING AN ISSUE + + This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS. + + DO NOT USE THIS FORM, unless + - you are a vencord contributor + - you were given explicit permission to use this form by a moderator in our support server + - you are filing a security related report + - type: input id: discord attributes: @@ -64,3 +76,5 @@ body: options: - label: I am using Discord Stable or tried on Stable and this bug happens there as well required: true + - label: I have read the requirements for opening an issue above + required: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9d56e9a9f..83236c11e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -36,26 +36,10 @@ jobs: - name: Publish extension run: | - # Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later - EXIT_CODE=0 - - # Chrome cd dist/chromium-unpacked - pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$? - - # Firefox - cd ../firefox-unpacked - npm i -g web-ext@7.4.0 web-ext-submit@7.4.0 - web-ext-submit || EXIT_CODE=$? - - exit $EXIT_CODE + pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish env: - # Chrome EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} - - # Firefox - WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }} - WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }} diff --git a/README.md b/README.md index e848fd25b..cd54fcdc8 100644 --- a/README.md +++ b/README.md @@ -22,29 +22,7 @@ The cutest Discord client mod ## Installing / Uninstalling -Click the below button to install Vencord to the Discord Desktop app - -[![Download and run the Installer](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#vencord-installer) - -## Installing on Browser - -[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb) - -Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those - -<details> -<summary>Alternative Downloads</summary> - -## Vencord Desktop - -> **Warning** -> This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up - -As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app - -[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop) - -</details> +Visit https://vencord.dev/download ## Join our Support/Community Server diff --git a/browser/background.js b/browser/background.js new file mode 100644 index 000000000..1f2d5ec17 --- /dev/null +++ b/browser/background.js @@ -0,0 +1,32 @@ +/** + * @template T + * @param {T[]} arr + * @param {(v: T) => boolean} predicate + */ +function removeFirst(arr, predicate) { + const idx = arr.findIndex(predicate); + if (idx !== -1) arr.splice(idx, 1); +} + +chrome.webRequest.onHeadersReceived.addListener( + ({ responseHeaders, type, url }) => { + if (!responseHeaders) return; + + if (type === "main_frame") { + // In main frame requests, the CSP needs to be removed to enable fetching of custom css + // as desired by the user + removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy"); + } else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) { + // Most users will load css from GitHub, but GitHub doesn't set the correct content type, + // so we fix it here + removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type"); + responseHeaders.push({ + name: "Content-Type", + value: "text/css" + }); + } + return { responseHeaders }; + }, + { urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] }, + ["blocking", "responseHeaders"] +); diff --git a/browser/manifestv2.json b/browser/manifestv2.json index a6feada7e..3cac9450a 100644 --- a/browser/manifestv2.json +++ b/browser/manifestv2.json @@ -26,7 +26,11 @@ } ], - "web_accessible_resources": ["dist/*", "third-party/*"], + "background": { + "scripts": ["background.js"] + }, + + "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"], "browser_specific_settings": { "gecko": { diff --git a/package.json b/package.json index c0b3998f1..d0ce62998 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.5.2", + "version": "1.5.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -99,7 +99,7 @@ "build": { "overwriteDest": true }, - "sourceDir": "./dist/extension-v2-unpacked" + "sourceDir": "./dist/firefox-unpacked" }, "engines": { "node": ">=18", diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index e4eeb53ea..353f4e060 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -145,11 +145,11 @@ async function loadDir(dir, basePath = "") { /** * @type {(target: string, files: string[]) => Promise<void>} */ -async function buildExtension(target, files, noMonaco = false) { +async function buildExtension(target, files) { const entries = { "dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.css": await readFile("dist/extension.css"), - ...(noMonaco ? {} : await loadDir("dist/monaco")), + ...await loadDir("dist/monaco"), ...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file => [`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)] ))), @@ -195,8 +195,11 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content await Promise.all([ appendCssRuntime, buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), - buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true), + buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), ]); -Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip"); -console.info("Packed Chromium Extension written to dist/extension.zip"); +Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip"); +console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); + +Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); +console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); diff --git a/src/api/MemberListDecorators.ts b/src/api/MemberListDecorators.ts index fade2a7ca..e148bb0a4 100644 --- a/src/api/MemberListDecorators.ts +++ b/src/api/MemberListDecorators.ts @@ -20,7 +20,6 @@ import { Channel, User } from "discord-types/general/index.js"; interface DecoratorProps { activities: any[]; - canUseAvatarDecorations: boolean; channel: Channel; /** * Only for DM members @@ -52,9 +51,9 @@ export function removeDecorator(identifier: string) { decorators.delete(identifier); } -export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] { +export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { const isInGuild = !!(props.guildId); - return [...decorators.values()].map(decoratorObj => { + return Array.from(decorators.values(), decoratorObj => { const { decorator, onlyIn } = decoratorObj; // this can most likely be done cleaner if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) { diff --git a/src/api/Settings.ts b/src/api/Settings.ts index a803b8d46..8e2a1c79d 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -253,7 +253,8 @@ type ResolvePropDeep<T, P> = P extends "" ? T : export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void, exact?: boolean): void; export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void, exact?: boolean): void; export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void, exact = true) { - ((onUpdate as SubscriptionCallback)._paths ??= []).push(path); + if (path) + ((onUpdate as SubscriptionCallback)._paths ??= []).push(path); (onUpdate as SubscriptionCallback)._exact = exact; subscriptions.add(onUpdate); } diff --git a/src/components/ThemeSettings/ThemesTab.tsx b/src/components/ThemeSettings/ThemesTab.tsx index 1c99df803..297fbfe34 100644 --- a/src/components/ThemeSettings/ThemesTab.tsx +++ b/src/components/ThemeSettings/ThemesTab.tsx @@ -20,13 +20,11 @@ import "./themesStyles.css"; import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; -import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { CogWheel, DeleteIcon } from "@components/Icons"; import { Link } from "@components/Link"; import { AddonCard } from "@components/VencordSettings/AddonCard"; import { SettingsTab, wrapTab } from "@components/VencordSettings/shared"; -import { IsFirefox } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; @@ -353,14 +351,12 @@ function ThemesTab() { > Load missing Themes </Button> - {!IsFirefox && ( - <Button - onClick={() => VencordNative.quickCss.openEditor()} - size={Button.Sizes.SMALL} - > - Edit QuickCSS - </Button> - )} + <Button + onClick={() => VencordNative.quickCss.openEditor()} + size={Button.Sizes.SMALL} + > + Edit QuickCSS + </Button> </> </Card> @@ -435,15 +431,6 @@ function ThemesTab() { return ( <SettingsTab title="Themes"> - {IsFirefox && ( - <ErrorCard> - <Forms.FormTitle tag="h5">Warning</Forms.FormTitle> - <Forms.FormText> - You are using Firefox. Expect the vast majority of themes to not work. - If this is a problem, use a chromium browser or Discord Desktop / Vesktop. - </Forms.FormText> - </ErrorCard> - )} <TabBar type="top" look="brand" diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 520e4daad..a8e9ea5b0 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -21,7 +21,6 @@ import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import DonateButton from "@components/DonateButton"; import { ErrorCard } from "@components/ErrorCard"; -import { IsFirefox } from "@utils/constants"; import { Margins } from "@utils/margins"; import { identity } from "@utils/misc"; import { relaunch, showItemInFolder } from "@utils/native"; @@ -110,14 +109,12 @@ function VencordSettings() { Restart Client </Button> )} - {!IsFirefox && ( - <Button - onClick={() => VencordNative.quickCss.openEditor()} - size={Button.Sizes.SMALL} - disabled={settingsDir === "Loading..."}> - Open QuickCSS File - </Button> - )} + <Button + onClick={() => VencordNative.quickCss.openEditor()} + size={Button.Sizes.SMALL} + disabled={settingsDir === "Loading..."}> + Open QuickCSS File + </Button> {!IS_WEB && ( <Button onClick={() => showItemInFolder(settingsDir)} diff --git a/src/plugins/_api/commands.ts b/src/plugins/_api/commands.ts index 2197b3075..b7d6ef9fe 100644 --- a/src/plugins/_api/commands.ts +++ b/src/plugins/_api/commands.ts @@ -26,7 +26,7 @@ export default definePlugin({ patches: [ // obtain BUILT_IN_COMMANDS instance { - find: '"giphy","tenor"', + find: ',"tenor"', replacement: [ { // Matches BUILT_IN_COMMANDS. This is not exported so this is @@ -34,7 +34,7 @@ export default definePlugin({ // patch simpler // textCommands = builtInCommands.filter(...) - match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/, + match: /(?<=\w=)(\w)(\.filter\(.{0,60}tenor)/, replace: "Vencord.Api.Commands._init($1)$2", } ], diff --git a/src/plugins/_api/memberListDecorators.ts b/src/plugins/_api/memberListDecorators.ts index 6b8cffabc..a6d4125d3 100644 --- a/src/plugins/_api/memberListDecorators.ts +++ b/src/plugins/_api/memberListDecorators.ts @@ -22,21 +22,28 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "MemberListDecoratorsAPI", description: "API to add decorators to member list (both in servers and DMs)", - authors: [Devs.TheSun], + authors: [Devs.TheSun, Devs.Ven], patches: [ { find: "lostPermissionTooltipText,", replacement: { - match: /Fragment,{children:\[(.{30,80})\]/, - replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)" + match: /decorators:.{0,100}?children:\[(?<=(\i)\.lostPermissionTooltipText.+?)/, + replace: "$&...Vencord.Api.MemberListDecorators.__getDecorators($1)," } }, { find: "PrivateChannel.renderAvatar", - replacement: { - match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/, - replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)" - } + replacement: [ + // props are shadowed by nested props so we have to do this + { + match: /\i=(\i)\.applicationStream,/, + replace: "$&vencordProps=$1," + }, + { + match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/, + replace: "decorators:[...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)), $1?$2:null]" + } + ] } ], }); diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 670e8fcf3..b5a1a7091 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -39,8 +39,9 @@ export default definePlugin({ addContextMenuPatch("user-settings-cog", children => () => { const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any; section?.forEach(c => { - if (c?.props?.id?.startsWith("Vencord")) { - c.props.action = () => SettingsRouter.open(c.props.id); + const id = c?.props?.id; + if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) { + c.props.action = () => SettingsRouter.open(id); } }); }); diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 2e86869d3..674be8e53 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -17,7 +17,7 @@ */ import { DataStore } from "@api/index"; -import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants"; +import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants"; import { isPluginDev } from "@utils/misc"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; @@ -30,7 +30,6 @@ import plugins from "~plugins"; import settings from "./settings"; const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; -const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss"; const AllowedChannelIds = [ SUPPORT_CHANNEL_ID, @@ -116,22 +115,6 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", " onConfirm: rememberDismiss }); } - - if (IsFirefox) { - const rememberDismiss = () => DataStore.set(FIREFOX_DISMISS_KEY, true); - - Alerts.show({ - title: "Hold on!", - body: <div> - <Forms.FormText>You are using Firefox.</Forms.FormText> - <Forms.FormText>Due to Firefox's stupid extension guidelines, most themes and many plugins will not function correctly.</Forms.FormText> - <Forms.FormText>Do not report bugs. Do not ask for help with broken plugins.</Forms.FormText> - <Forms.FormText>Instead, use a chromium browser, Discord Desktop, or Vesktop.</Forms.FormText> - </div>, - onCancel: rememberDismiss, - onConfirm: rememberDismiss - }); - } } } }); diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index f0d484141..1db0c4578 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -58,6 +58,26 @@ export default definePlugin({ </> ), + async handleEvent(e: MessageEvent<any>) { + const data = JSON.parse(e.data); + + const { activity } = data; + const assets = activity?.assets; + + if (assets?.large_image) assets.large_image = await lookupAsset(activity.application_id, assets.large_image); + if (assets?.small_image) assets.small_image = await lookupAsset(activity.application_id, assets.small_image); + + if (activity) { + const appId = activity.application_id; + apps[appId] ||= await lookupApp(appId); + + const app = apps[appId]; + activity.name ||= app.name; + } + + FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data }); + }, + async start() { // ArmCord comes with its own arRPC implementation, so this plugin just confuses users if ("armcord" in window) return; @@ -65,22 +85,7 @@ export default definePlugin({ if (ws) ws.close(); ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket - ws.onmessage = async e => { // on message, set status to data - const data = JSON.parse(e.data); - - if (data.activity?.assets?.large_image) data.activity.assets.large_image = await lookupAsset(data.activity.application_id, data.activity.assets.large_image); - if (data.activity?.assets?.small_image) data.activity.assets.small_image = await lookupAsset(data.activity.application_id, data.activity.assets.small_image); - - if (data.activity) { - const appId = data.activity.application_id; - apps[appId] ||= await lookupApp(appId); - - const app = apps[appId]; - data.activity.name ||= app.name; - } - - FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data }); - }; + ws.onmessage = this.handleEvent; const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s if (!connectionSuccessful) { diff --git a/src/plugins/betterRoleDot/index.ts b/src/plugins/betterRoleDot/index.ts index e8f69b897..ed121b9ab 100644 --- a/src/plugins/betterRoleDot/index.ts +++ b/src/plugins/betterRoleDot/index.ts @@ -48,6 +48,7 @@ export default definePlugin({ { find: ".ADD_ROLE_A11Y_LABEL", predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, + noWarn: true, replacement: { match: /"dot"===\i/, replace: "true" @@ -56,6 +57,7 @@ export default definePlugin({ { find: ".roleVerifiedIcon", predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, + noWarn: true, replacement: { match: /"dot"===\i/, replace: "true" diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index d38687f58..cf5f4e6fd 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -33,12 +33,6 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: false, restartNeeded: true - }, - forceStagingBanner: { - description: "Whether to force Staging banner under user area.", - type: OptionType.BOOLEAN, - default: false, - restartNeeded: true } }); @@ -83,12 +77,13 @@ export default definePlugin({ } ] }, + // Fix search history being disabled / broken with isStaff { - find: ".Messages.DEV_NOTICE_STAGING", - predicate: () => settings.store.forceStagingBanner, + find: 'get("disable_new_search")', + predicate: () => settings.store.enableIsStaff, replacement: { - match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/, - replace: "true" + match: /(?<=showNewSearch"\);return)\s?!/, + replace: "!1&&!" } }, { diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index 07191e14b..a11a43de0 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -222,8 +222,7 @@ export default definePlugin({ predicate: () => settings.store.enableStreamQualityBypass, replacement: [ "canUseHighVideoUploadQuality", - // TODO: Remove the last two when they get removed from stable - "(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)", + "canStreamQuality", ].map(func => { return { match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"), diff --git a/src/plugins/forceOwnerCrown/index.ts b/src/plugins/forceOwnerCrown/index.ts index 3122410f9..5fa63f00b 100644 --- a/src/plugins/forceOwnerCrown/index.ts +++ b/src/plugins/forceOwnerCrown/index.ts @@ -27,12 +27,12 @@ export default definePlugin({ patches: [ { // This is the logic where it decides whether to render the owner crown or not - find: ".renderOwner=", + find: ".MULTIPLE_AVATAR", replacement: { - match: /isOwner;return null!=(\w+)?&&/g, - replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&" + match: /(\i)=(\i)\.isOwner,/, + replace: "$1=$self.isGuildOwner($2)," } - }, + } ], isGuildOwner(props) { // Check if channel is a Group DM, if so return false diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 6d58eb48f..e2888dd1e 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -1,27 +1,17 @@ /* - * 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 <https://www.gnu.org/licenses/>. -*/ + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ import * as DataStore from "@api/DataStore"; +import { definePluginSettings } from "@api/Settings"; +import { getSettingStoreLazy } from "@api/SettingsStore"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findStoreLazy } from "@webpack"; import { Tooltip } from "webpack/common"; const enum ActivitiesTypes { @@ -31,203 +21,154 @@ const enum ActivitiesTypes { interface IgnoredActivity { id: string; + name: string; type: ActivitiesTypes; } -const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn"); -const TryItOutClasses = findByPropsLazy("tryItOutBadge", "tryItOutBadgeIcon"); -const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight"); const RunningGameStore = findStoreLazy("RunningGameStore"); +const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame"); -function ToggleIconOff() { - return ( - <svg - className={RegisteredGamesClasses.overlayToggleIconOff} - height="24" - width="24" - viewBox="0 2.2 32 26" - aria-hidden={true} - role="img" - > - <g - fill="none" - fillRule="evenodd" - > - <path - className={RegisteredGamesClasses.fill} - fill="currentColor" - d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z" - /> - <rect - className={RegisteredGamesClasses.fill} - x="3" - y="26" - width="26" - height="2" - transform="rotate(-45 2 20)" - /> - </g> - </svg> - ); -} - -function ToggleIconOn({ forceWhite }: { forceWhite?: boolean; }) { - return ( - <svg - className={RegisteredGamesClasses.overlayToggleIconOn} - height="24" - width="24" - viewBox="0 2.2 32 26" - > - <path - className={forceWhite ? "" : RegisteredGamesClasses.fill} - fill={forceWhite ? "var(--white-500)" : ""} - d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z" - /> - </svg> - ); -} - -function ToggleActivityComponent({ activity, forceWhite, forceLeftMargin }: { activity: IgnoredActivity; forceWhite?: boolean; forceLeftMargin?: boolean; }) { +function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) { const forceUpdate = useForceUpdater(); return ( - <Tooltip text="Toggle activity"> - {({ onMouseLeave, onMouseEnter }) => ( - <div - onMouseLeave={onMouseLeave} - onMouseEnter={onMouseEnter} - className={RegisteredGamesClasses.overlayToggleIcon} - role="button" - aria-label="Toggle activity" - tabIndex={0} - style={forceLeftMargin ? { marginLeft: "2px" } : undefined} + <Tooltip text={tooltipText}> + {tooltipProps => ( + <button + {...tooltipProps} onClick={e => handleActivityToggle(e, activity, forceUpdate)} + style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }} > - { - ignoredActivitiesCache.has(activity.id) - ? <ToggleIconOff /> - : <ToggleIconOn forceWhite={forceWhite} /> - } - </div> + <svg + width="24" + height="24" + viewBox="0 -960 960 960" + > + <path fill={fill} d={path} /> + </svg> + </button> )} </Tooltip> ); } -function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) { - return ( - <div - className={`${TryItOutClasses.tryItOutBadge} ${BaseShapeRoundClasses.baseShapeRound}`} - style={{ padding: "0px 2px", height: 28 }} - > - <ToggleActivityComponent activity={activity} forceWhite={true} /> - </div> - ); +const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill); +const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill); + +function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) { + if (getIgnoredActivities().some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)"); + return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)"); } -function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) { +function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity, forceUpdateButton: () => void) { e.stopPropagation(); - if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id); - else ignoredActivitiesCache.set(activity.id, activity); - forceUpdateComponent(); - saveCacheToDatastore(); + + const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id); + if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity); + else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex); + + // Trigger activities recalculation + ShowCurrentGame?.updateSetting(old => old); + forceUpdateButton(); } -async function saveCacheToDatastore() { - await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache); -} +const settings = definePluginSettings({}).withPrivateSettings<{ + ignoredActivities: IgnoredActivity[]; +}>(); -let ignoredActivitiesCache = new Map<IgnoredActivity["id"], IgnoredActivity>(); +function getIgnoredActivities() { + return settings.store.ignoredActivities ??= []; +} export default definePlugin({ name: "IgnoreActivities", authors: [Devs.Nuckyz], - description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.", + description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.", + + dependencies: ["SettingsStoreAPI"], + settings, + patches: [ { - find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", - replacement: { - match: /!(\i)(\)return null;var \i=(\i)\.overlay.+?children:)(\[.{0,70}overlayStatusText.+?\])(?=}\)}\(\))/, - replace: (_, platformCheck, restWithoutPlatformCheck, props, children) => "false" - + `${restWithoutPlatformCheck}` - + `(${platformCheck}?${children}:[])` - + `.concat(Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(${props}))` - } - }, - { - find: ".overlayBadge", + find: '.displayName="LocalActivityStore"', replacement: [ { - match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i)\.name.+?null/, - replace: (m, props) => `[${m},$self.renderToggleActivityButton(${props})]` - }, - { - match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i\.application)\.name.+?null/, - replace: (m, props) => `${m},$self.renderToggleActivityButton(${props})` + match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/, + replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` } ] }, { - find: '.displayName="LocalActivityStore"', + find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", replacement: { - match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/, - replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` + match: /\(\)\.removeGame.+?null(?<=(\i)\?\i=\i\.\i\.Messages\.SETTINGS_GAMES_NOW_PLAYING_STATE.+?=(\i)\.overlay.+?)/, + replace: (m, nowPlaying, props) => `${m},$self.renderToggleGameActivityButton(${props},${nowPlaying})` } + }, + { + find: ".Messages.EMBEDDED_ACTIVITIES_HAVE_PLAYED_ONE_KNOWN", + replacement: [ + { + match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/, + replace: (_, props) => `$self.renderToggleActivityButton(${props}),` + }, + { + match: /(?<=\(\)\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/, + replace: (_, props) => `$self.renderToggleActivityButton(${props}),` + } + ] } ], async start() { - const ignoredActivitiesData = await DataStore.get<string[] | Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities") ?? new Map<IgnoredActivity["id"], IgnoredActivity>(); - /** Migrate old data */ - if (Array.isArray(ignoredActivitiesData)) { - for (const id of ignoredActivitiesData) { - ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game }); - } + const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities"); - await saveCacheToDatastore(); - } else ignoredActivitiesCache = ignoredActivitiesData; + if (oldIgnoredActivitiesData != null) { + settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values()) + .map(activity => ({ ...activity, name: "Unknown Name" })); - if (ignoredActivitiesCache.size !== 0) { - const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen(); + DataStore.del("IgnoreActivities_ignoredActivities"); + } - for (const ignoredActivity of ignoredActivitiesCache.values()) { + if (getIgnoredActivities().length !== 0) { + const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[]; + + for (const [index, ignoredActivity] of getIgnoredActivities().entries()) { if (ignoredActivity.type !== ActivitiesTypes.Game) continue; if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) { - /** Custom added game which no longer exists */ - ignoredActivitiesCache.delete(ignoredActivity.id); + getIgnoredActivities().splice(index, 1); } } - - await saveCacheToDatastore(); } }, - renderToggleGameActivityButton(props: { id?: string; exePath: string; }) { - return ( - <ErrorBoundary noop> - <ToggleActivityComponent activity={{ id: props.id ?? props.exePath, type: ActivitiesTypes.Game }} forceLeftMargin={true} /> - </ErrorBoundary> - ); - }, - - renderToggleActivityButton(props: { id: string; }) { - return ( - <ErrorBoundary noop> - <ToggleActivityComponentWithBackground activity={{ id: props.id, type: ActivitiesTypes.Embedded }} /> - </ErrorBoundary> - ); - }, - isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) { - if (props.type === 0) { - if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id); + if (props.type === 0 || props.type === 3) { + if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id); else { const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; - if (exePath) return !ignoredActivitiesCache.has(exePath); + if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath); } } return true; + }, + + renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) { + return ( + <ErrorBoundary noop> + <div style={{ marginLeft: 12, zIndex: 0 }}> + {ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)} + </div> + </ErrorBoundary> + ); + }, + + renderToggleActivityButton(props: { id: string; name: string; }) { + return ( + <ErrorBoundary noop> + {ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })} + </ErrorBoundary> + ); } }); diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index cca0db023..1f0d7e127 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -165,7 +165,7 @@ export default definePlugin({ { find: '"renderLinkComponent","maxWidth"', replacement: { - match: /(return\(.{1,100}\(\)\.wrapper.{1,100})(src)/, + match: /(return\(.{1,100}\(\)\.wrapper.{1,200})(src)/, replace: `$1id: '${ELEMENT_ID}',$2` } }, @@ -174,8 +174,8 @@ export default definePlugin({ find: "handleImageLoad=", replacement: [ { - match: /(render=function\(\){.{1,500}limitResponsiveWidth.{1,600})onMouseEnter:/, - replace: "$1...$self.makeProps(this),onMouseEnter:" + match: /showThumbhashPlaceholder:/, + replace: "...$self.makeProps(this),$&" }, { @@ -189,7 +189,6 @@ export default definePlugin({ } ] }, - { find: ".carouselModal,", replacement: { diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx index 595a8edde..c8820f8be 100644 --- a/src/plugins/moreUserTags/index.tsx +++ b/src/plugins/moreUserTags/index.tsx @@ -53,8 +53,6 @@ interface TagSettings { [k: string]: TagSetting; } -const CLYDE_ID = "1081004946872352958"; - // PermissionStore.computePermissions is not the same function and doesn't work here const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as { computePermissions({ ...args }): bigint; @@ -215,7 +213,7 @@ export default definePlugin({ }, // add HTML data attributes (for easier theming) { - match: /children:\[(?=\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/, + match: /children:\[(?=\i\?null:\i,\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/, replace: "'data-tag':$1.toLowerCase(),children:[" } ], @@ -230,10 +228,10 @@ export default definePlugin({ }, // in the member list { - find: ".renderBot=function(){", + find: ".Messages.GUILD_OWNER,", replacement: { - match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/, - replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type" + match: /(?<type>\i)=\(null==.{0,50}\.BOT,null!=(?<user>\i)&&\i\.bot/, + replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }), typeof $<type> === 'number'" } }, // pass channel id down props to be used in profiles @@ -253,7 +251,7 @@ export default definePlugin({ }, // in profiles { - find: ",botType:", + find: "showStreamerModeTooltip:", replacement: { match: /,botType:(\i\((\i)\)),/g, replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'})," @@ -341,15 +339,17 @@ export default definePlugin({ message, user, channelId, origType, location, channel }: { message?: Message, - user: User, + user: User & { isClyde(): boolean; }, channel?: Channel & { isForumPost(): boolean; }, channelId?: string; origType?: number; location: "chat" | "not-chat"; }): number | null { + if (!user) + return null; if (location === "chat" && user.id === "1") return Tag.Types.OFFICIAL; - if (user.id === CLYDE_ID) + if (user.isClyde()) return Tag.Types.AI; let type = typeof origType === "number" ? origType : null; diff --git a/src/plugins/noMosaic/index.ts b/src/plugins/noMosaic/index.ts new file mode 100644 index 000000000..60576ba93 --- /dev/null +++ b/src/plugins/noMosaic/index.ts @@ -0,0 +1,41 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { disableStyle, enableStyle } from "@api/Styles"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +import style from "./styles.css?managed"; + +export default definePlugin({ + name: "NoMosaic", + authors: [Devs.AutumnVN], + description: "Removes Discord new image mosaic", + tags: ["image", "mosaic", "media"], + patches: [{ + find: "Media Mosaic", + replacement: [ + { + match: /mediaLayoutType:\i\.\i\.MOSAIC/, + replace: 'mediaLayoutType:"RESPONSIVE"', + }, + { + match: /\i===\i\.\i\.MOSAIC/, + replace: "true", + }, + { + match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/, + replace: '"INVALID"', + }, + ], + }], + start() { + enableStyle(style); + }, + stop() { + disableStyle(style); + } +}); diff --git a/src/plugins/noMosaic/styles.css b/src/plugins/noMosaic/styles.css new file mode 100644 index 000000000..546168584 --- /dev/null +++ b/src/plugins/noMosaic/styles.css @@ -0,0 +1,3 @@ +[class^="nonMediaAttachmentsContainer-"] [class*="messageAttachment-"] { + position: relative; +} diff --git a/src/plugins/onePingPerDM/README.md b/src/plugins/onePingPerDM/README.md new file mode 100644 index 000000000..43f89b72c --- /dev/null +++ b/src/plugins/onePingPerDM/README.md @@ -0,0 +1,7 @@ +# OnePingPerDM +If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit + +## Purpose +- Prevents ping audio spam in DMs +- Be able to distinguish more than one ping as multiple users +- Be less annoyed while gaming diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts new file mode 100644 index 000000000..47502ebe8 --- /dev/null +++ b/src/plugins/onePingPerDM/index.ts @@ -0,0 +1,39 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { ChannelStore, ReadStateStore } from "@webpack/common"; +import { Message } from "discord-types/general"; + +const enum ChannelType { + DM = 1, + GROUP_DM = 3 +} + +export default definePlugin({ + name: "OnePingPerDM", + description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit", + authors: [Devs.ProffDea], + patches: [{ + find: ".getDesktopType()===", + replacement: [{ + match: /if\((\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\){/, + replace: "if($1){if(!$self.isPrivateChannelRead(arguments[0]?.message))return;" + }, + { + match: /sound:(\i\?\i:void 0,volume:\i,onClick:)/, + replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1" + }] + }], + isPrivateChannelRead(message: Message) { + const channelType = ChannelStore.getChannel(message.channel_id)?.type; + if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) { + return false; + } + return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id; + }, +}); diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts index bb822f5a0..06e87195e 100644 --- a/src/plugins/partyMode/index.ts +++ b/src/plugins/partyMode/index.ts @@ -19,10 +19,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findStoreLazy } from "@webpack"; -import { GenericStore } from "@webpack/common"; - -const PoggerModeSettingsStore: GenericStore = findStoreLazy("PoggermodeSettingsStore"); +import { FluxDispatcher } from "@webpack/common"; const enum Intensity { Normal, @@ -61,9 +58,12 @@ export default definePlugin({ }); function setPoggerState(state: boolean) { - Object.assign(PoggerModeSettingsStore.__getLocalVars().state, { - enabled: state, - settingsVisible: state + FluxDispatcher.dispatch({ + type: "POGGERMODE_SETTINGS_UPDATE", + settings: { + enabled: state, + settingsVisible: state + } }); } @@ -101,5 +101,8 @@ function setSettings(intensity: Intensity) { } } - Object.assign(PoggerModeSettingsStore.__getLocalVars().state, state); + FluxDispatcher.dispatch({ + type: "POGGERMODE_SETTINGS_UPDATE", + settings: state + }); } diff --git a/src/plugins/permissionFreeWill/README.md b/src/plugins/permissionFreeWill/README.md new file mode 100644 index 000000000..ca30575ff --- /dev/null +++ b/src/plugins/permissionFreeWill/README.md @@ -0,0 +1,9 @@ +# PermissionFreeWill + +Removes the client-side restrictions that prevent editing channel permissions, such as permission lockouts ("Pretty sure +you don't want to do this") and onboarding requirements ("Making this change will make your server incompatible [...]") + +## Warning + +This plugin will let you create permissions in servers that **WILL** lock you out of channels until an administrator +can resolve it for you. Please be careful with the overwrites you are making and check carefully. diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts new file mode 100644 index 000000000..f3fdf159a --- /dev/null +++ b/src/plugins/permissionFreeWill/index.ts @@ -0,0 +1,56 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const settings = definePluginSettings({ + lockout: { + type: OptionType.BOOLEAN, + default: true, + description: 'Bypass the permission lockout prevention ("Pretty sure you don\'t want to do this")', + restartNeeded: true + }, + onboarding: { + type: OptionType.BOOLEAN, + default: true, + description: 'Bypass the onboarding requirements ("Making this change will make your server incompatible [...]")', + restartNeeded: true + } +}); + +export default definePlugin({ + name: "PermissionFreeWill", + description: "Disables the client-side restrictions for channel permission management.", + authors: [Devs.lewisakura], + + patches: [ + // Permission lockout, just set the check to true + { + find: "Messages.SELF_DENY_PERMISSION_BODY", + replacement: [ + { + match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/, + replace: "$&true||" + } + ], + predicate: () => settings.store.lockout + }, + // Onboarding, same thing but we need to prevent the check + { + find: "Messages.ONBOARDING_CHANNEL_THRESHOLD_WARNING", + replacement: [ + { + match: /case 1:if\((?=!\i\.sent.{20,30}Messages\.CANNOT_CHANGE_CHANNEL_PERMS)/, + replace: "$&false&&" + } + ], + predicate: () => settings.store.onboarding + } + ], + settings +}); diff --git a/src/plugins/pictureInPicture/index.tsx b/src/plugins/pictureInPicture/index.tsx index d10d42fff..bb6aee18f 100644 --- a/src/plugins/pictureInPicture/index.tsx +++ b/src/plugins/pictureInPicture/index.tsx @@ -4,6 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import "./styles.css"; + import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; @@ -41,6 +43,7 @@ export default definePlugin({ {tooltipProps => ( <div {...tooltipProps} + className="vc-pip-button" role="button" style={{ cursor: "pointer", @@ -71,7 +74,7 @@ export default definePlugin({ > <svg width="24px" height="24px" viewBox="0 0 24 24"> <path - fill="var(--interactive-normal)" + fill="currentColor" d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z" /> </svg> diff --git a/src/plugins/pictureInPicture/styles.css b/src/plugins/pictureInPicture/styles.css new file mode 100644 index 000000000..d0a81ca57 --- /dev/null +++ b/src/plugins/pictureInPicture/styles.css @@ -0,0 +1,8 @@ +.vc-pip-button { + color: var(--interactive-normal); +} + +.vc-pip-button:hover { + background-color: var(--background-modifier-hover); + color: var(--interactive-hover); +} diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index 52fefdc3a..c9dc2725e 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -68,8 +68,7 @@ export default definePlugin({ find: ".USER_PROFILE_ACTIVITY", replacement: [ { - /* FIXME: old name is getGlobalName, new name is getName. Remove optional Global once stable has also migrated */ - match: /\.get(?:Global)?Name\(\i\);(?<=displayProfile.{0,200})/, + match: /\.getName\(\i\);(?<=displayProfile.{0,200})/, replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;" }, PRONOUN_TOOLTIP_PATCH diff --git a/src/plugins/reviewDB/components/MessageButton.tsx b/src/plugins/reviewDB/components/MessageButton.tsx deleted file mode 100644 index 965fd1c1a..000000000 --- a/src/plugins/reviewDB/components/MessageButton.tsx +++ /dev/null @@ -1,61 +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 <https://www.gnu.org/licenses/>. -*/ - -import { DeleteIcon } from "@components/Icons"; -import { classes } from "@utils/misc"; -import { findByPropsLazy } from "@webpack"; -import { Tooltip } from "@webpack/common"; - -const iconClasses = findByPropsLazy("button", "wrapper", "disabled", "separator"); - -export function DeleteButton({ onClick }: { onClick(): void; }) { - return ( - <Tooltip text="Delete Review"> - {props => ( - <div - {...props} - className={classes(iconClasses.button, iconClasses.dangerous)} - onClick={onClick} - > - <DeleteIcon width="20" height="20" /> - </div> - )} - </Tooltip> - ); -} - -export function ReportButton({ onClick }: { onClick(): void; }) { - return ( - <Tooltip text="Report Review"> - {props => ( - <div - {...props} - className={iconClasses.button} - onClick={onClick} - > - <svg width="20" height="20" viewBox="0 0 24 24"> - <path - fill="currentColor" - d="M20,6.002H14V3.002C14,2.45 13.553,2.002 13,2.002H4C3.447,2.002 3,2.45 3,3.002V22.002H5V14.002H10.586L8.293,16.295C8.007,16.581 7.922,17.011 8.076,17.385C8.23,17.759 8.596,18.002 9,18.002H20C20.553,18.002 21,17.554 21,17.002V7.002C21,6.45 20.553,6.002 20,6.002Z" - /> - </svg> - </div> - )} - </Tooltip> - ); -} diff --git a/src/plugins/reviewDB/components/ReviewBadge.tsx b/src/plugins/reviewDB/components/ReviewBadge.tsx deleted file mode 100644 index 3c02c3542..000000000 --- a/src/plugins/reviewDB/components/ReviewBadge.tsx +++ /dev/null @@ -1,46 +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 <https://www.gnu.org/licenses/>. -*/ - -import { MaskedLinkStore, Tooltip } from "@webpack/common"; - -import { Badge } from "../entities"; -import { cl } from "../utils"; - -export default function ReviewBadge(badge: Badge) { - return ( - <Tooltip - text={badge.name}> - {({ onMouseEnter, onMouseLeave }) => ( - <img - className={cl("badge")} - width="22px" - height="22px" - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - src={badge.icon} - alt={badge.description} - onClick={() => - MaskedLinkStore.openUntrustedLink({ - href: badge.redirectURL, - }) - } - /> - )} - </Tooltip> - ); -} diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx deleted file mode 100644 index 1278a5084..000000000 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ /dev/null @@ -1,147 +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 <https://www.gnu.org/licenses/>. -*/ - -import { openUserProfile } from "@utils/discord"; -import { classes } from "@utils/misc"; -import { LazyComponent } from "@utils/react"; -import { filters, findBulk } from "@webpack"; -import { Alerts, moment, Parser, Timestamp, UserStore } from "@webpack/common"; - -import { Review, ReviewType } from "../entities"; -import { deleteReview, reportReview } from "../reviewDbApi"; -import { settings } from "../settings"; -import { canDeleteReview, cl, showToast } from "../utils"; -import { DeleteButton, ReportButton } from "./MessageButton"; -import ReviewBadge from "./ReviewBadge"; - -export default LazyComponent(() => { - // this is terrible, blame ven - const p = filters.byProps; - const [ - { cozyMessage, buttons, message, buttonsInner, groupStart }, - { container, isHeader }, - { avatar, clickable, username, wrapper, cozy }, - buttonClasses, - botTag - ] = findBulk( - p("cozyMessage"), - p("container", "isHeader"), - p("avatar", "zalgo"), - p("button", "wrapper", "selected"), - p("botTag") - ); - - const dateFormat = new Intl.DateTimeFormat(); - - return function ReviewComponent({ review, refetch }: { review: Review; refetch(): void; }) { - function openModal() { - openUserProfile(review.sender.discordID); - } - - function delReview() { - Alerts.show({ - title: "Are you sure?", - body: "Do you really want to delete this review?", - confirmText: "Delete", - cancelText: "Nevermind", - onConfirm: () => { - deleteReview(review.id).then(res => { - if (res.success) { - refetch(); - } - showToast(res.message); - }); - } - }); - } - - function reportRev() { - Alerts.show({ - title: "Are you sure?", - body: "Do you really you want to report this review?", - confirmText: "Report", - cancelText: "Nevermind", - // confirmColor: "red", this just adds a class name and breaks the submit button guh - onConfirm: () => reportReview(review.id) - }); - } - - return ( - <div className={classes(cozyMessage, wrapper, message, groupStart, cozy, cl("review"))} style={ - { - marginLeft: "0px", - paddingLeft: "52px", // wth is this - paddingRight: "16px" - } - }> - - <img - className={classes(avatar, clickable)} - onClick={openModal} - src={review.sender.profilePhoto || "/assets/1f0bfc0865d324c2587920a7d80c609b.png?size=128"} - style={{ left: "0px" }} - /> - <div style={{ display: "inline-flex", justifyContent: "center", alignItems: "center" }}> - <span - className={classes(clickable, username)} - style={{ color: "var(--channels-default)", fontSize: "14px" }} - onClick={() => openModal()} - > - {review.sender.username} - </span> - - {review.type === ReviewType.System && ( - <span - className={classes(botTag.botTagVerified, botTag.botTagRegular, botTag.botTag, botTag.px, botTag.rem)} - style={{ marginLeft: "4px" }}> - <span className={botTag.botText}> - System - </span> - </span> - )} - </div> - {review.sender.badges.map(badge => <ReviewBadge {...badge} />)} - - { - !settings.store.hideTimestamps && review.type !== ReviewType.System && ( - <Timestamp timestamp={moment(review.timestamp * 1000)} > - {dateFormat.format(review.timestamp * 1000)} - </Timestamp>) - } - - <div className={cl("review-comment")}> - {Parser.parseGuildEventDescription(review.comment)} - </div> - - {review.id !== 0 && ( - <div className={classes(container, isHeader, buttons)} style={{ - padding: "0px", - }}> - <div className={classes(buttonClasses.wrapper, buttonsInner)} > - <ReportButton onClick={reportRev} /> - - {canDeleteReview(review, UserStore.getCurrentUser().id) && ( - <DeleteButton onClick={delReview} /> - )} - </div> - </div> - )} - </div> - ); - }; -}); diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx deleted file mode 100644 index 6e85dc295..000000000 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 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 <https://www.gnu.org/licenses/>. -*/ - -import ErrorBoundary from "@components/ErrorBoundary"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { useForceUpdater } from "@utils/react"; -import { Paginator, Text, useRef, useState } from "@webpack/common"; - -import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; -import { settings } from "../settings"; -import { cl } from "../utils"; -import ReviewComponent from "./ReviewComponent"; -import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; - -function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: string; name: string; }) { - const [data, setData] = useState<Response>(); - const [signal, refetch] = useForceUpdater(true); - const [page, setPage] = useState(1); - - const ref = useRef<HTMLDivElement>(null); - - const reviewCount = data?.reviewCount; - const ownReview = data?.reviews.find(r => r.sender.discordID === settings.store.user?.discordID); - - return ( - <ErrorBoundary> - <ModalRoot {...modalProps} size={ModalSize.MEDIUM}> - <ModalHeader> - <Text variant="heading-lg/semibold" className={cl("modal-header")}> - {name}'s Reviews - {!!reviewCount && <span> ({reviewCount} Reviews)</span>} - </Text> - <ModalCloseButton onClick={modalProps.onClose} /> - </ModalHeader> - - <ModalContent scrollerRef={ref}> - <div className={cl("modal-reviews")}> - <ReviewsView - discordId={discordId} - name={name} - page={page} - refetchSignal={signal} - onFetchReviews={setData} - scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })} - hideOwnReview - /> - </div> - </ModalContent> - - <ModalFooter className={cl("modal-footer")}> - <div> - {ownReview && ( - <ReviewComponent - refetch={refetch} - review={ownReview} - /> - )} - <ReviewsInputComponent - isAuthor={ownReview != null} - discordId={discordId} - name={name} - refetch={refetch} - /> - - {!!reviewCount && ( - <Paginator - currentPage={page} - maxVisiblePages={5} - pageSize={REVIEWS_PER_PAGE} - totalCount={reviewCount} - onPageChange={setPage} - /> - )} - </div> - </ModalFooter> - </ModalRoot> - </ErrorBoundary> - ); -} - -export function openReviewsModal(discordId: string, name: string) { - openModal(props => ( - <Modal - modalProps={props} - discordId={discordId} - name={name} - /> - )); -} diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx deleted file mode 100644 index e5bc426d7..000000000 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ /dev/null @@ -1,197 +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 <https://www.gnu.org/licenses/>. -*/ - -import { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react"; -import { find, findByPropsLazy } from "@webpack"; -import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; - -import { Review } from "../entities"; -import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; -import { settings } from "../settings"; -import { authorize, cl, showToast } from "../utils"; -import ReviewComponent from "./ReviewComponent"; - - -const Editor = findByPropsLazy("start", "end", "addMark"); -const Transform = findByPropsLazy("unwrapNodes"); -const InputTypes = findByPropsLazy("VOICE_CHANNEL_STATUS", "SIDEBAR"); - -const InputComponent = LazyComponent(() => find(m => m?.type?.render?.toString().includes("CHANNEL_TEXT_AREA).AnalyticsLocationProvider"))); - -interface UserProps { - discordId: string; - name: string; -} - -interface Props extends UserProps { - onFetchReviews(data: Response): void; - refetchSignal?: unknown; - showInput?: boolean; - page?: number; - scrollToTop?(): void; - hideOwnReview?: boolean; -} - -export default function ReviewsView({ - discordId, - name, - onFetchReviews, - refetchSignal, - scrollToTop, - page = 1, - showInput = false, - hideOwnReview = false, -}: Props) { - const [signal, refetch] = useForceUpdater(true); - - const [reviewData] = useAwaiter(() => getReviews(discordId, (page - 1) * REVIEWS_PER_PAGE), { - fallbackValue: null, - deps: [refetchSignal, signal, page], - onSuccess: data => { - if (settings.store.hideBlockedUsers) - data!.reviews = data!.reviews?.filter(r => !RelationshipStore.isBlocked(r.sender.discordID)); - - scrollToTop?.(); - onFetchReviews(data!); - } - }); - - if (!reviewData) return null; - - return ( - <> - <ReviewList - refetch={refetch} - reviews={reviewData!.reviews} - hideOwnReview={hideOwnReview} - /> - - {showInput && ( - <ReviewsInputComponent - name={name} - discordId={discordId} - refetch={refetch} - isAuthor={reviewData!.reviews?.some(r => r.sender.discordID === UserStore.getCurrentUser().id)} - /> - )} - </> - ); -} - -function ReviewList({ refetch, reviews, hideOwnReview }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; }) { - const myId = UserStore.getCurrentUser().id; - - return ( - <div className={cl("view")}> - {reviews?.map(review => - (review.sender.discordID !== myId || !hideOwnReview) && - <ReviewComponent - key={review.id} - review={review} - refetch={refetch} - /> - )} - - {reviews?.length === 0 && ( - <Forms.FormText className={cl("placeholder")}> - Looks like nobody reviewed this user yet. You could be the first! - </Forms.FormText> - )} - </div> - ); -} - - -export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) { - const { token } = settings.store; - const editorRef = useRef<any>(null); - const inputType = InputTypes.FORM; - inputType.disableAutoFocus = true; - - const channel = { - flags_: 256, - guild_id_: null, - id: "0", - getGuildId: () => null, - isPrivate: () => true, - isActiveThread: () => false, - isArchivedLockedThread: () => false, - isDM: () => true, - roles: { "0": { permissions: 0n } }, - getRecipientId: () => "0", - hasFlag: () => false, - }; - - return ( - <> - <div onClick={() => { - if (!token) { - showToast("Opening authorization window..."); - authorize(); - } - }}> - <InputComponent - className={cl("input")} - channel={channel} - placeholder={ - !token - ? "You need to authorize to review users!" - : isAuthor - ? `Update review for @${name}` - : `Review @${name}` - } - type={inputType} - disableThemedBackground={true} - setEditorRef={ref => editorRef.current = ref} - textValue="" - onSubmit={ - async res => { - const response = await addReview({ - userid: discordId, - comment: res.value, - }); - - if (response?.success) { - refetch(); - - const slateEditor = editorRef.current.ref.current.getSlateEditor(); - - // clear editor - Transform.delete(slateEditor, { - at: { - anchor: Editor.start(slateEditor, []), - focus: Editor.end(slateEditor, []), - } - }); - } else if (response?.message) { - showToast(response.message); - } - - // even tho we need to return this, it doesnt do anything - return { - shouldClear: false, - shouldRefocus: true, - }; - } - } - /> - </div> - - </> - ); -} diff --git a/src/plugins/reviewDB/entities.ts b/src/plugins/reviewDB/entities.ts deleted file mode 100644 index fefb1d3f4..000000000 --- a/src/plugins/reviewDB/entities.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 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 <https://www.gnu.org/licenses/>. -*/ - -export const enum UserType { - Banned = -1, - Normal = 0, - Admin = 1 -} - -export const enum ReviewType { - User = 0, - Server = 1, - Support = 2, - System = 3 -} - -export const enum NotificationType { - Info = 0, - Ban = 1, - Unban = 2, - Warning = 3 -} - -export interface Badge { - name: string; - description: string; - icon: string; - redirectURL: string; - type: number; -} - -export interface BanInfo { - id: string; - discordID: string; - reviewID: number; - reviewContent: string; - banEndDate: number; -} - -export interface Notification { - id: number; - title: string; - content: string; - type: NotificationType; -} - -export interface ReviewDBUser { - ID: number; - discordID: string; - username: string; - profilePhoto: string; - clientMod: string; - warningCount: number; - badges: any[]; - banInfo: BanInfo | null; - notification: Notification | null; - lastReviewID: number; - type: UserType; -} - -export interface ReviewAuthor { - id: number, - discordID: string, - username: string, - profilePhoto: string, - badges: Badge[]; -} - -export interface Review { - comment: string, - id: number, - star: number, - sender: ReviewAuthor, - timestamp: number; - type?: ReviewType; -} diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx deleted file mode 100644 index a51971392..000000000 --- a/src/plugins/reviewDB/index.tsx +++ /dev/null @@ -1,140 +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 <https://www.gnu.org/licenses/>. -*/ - -import "./style.css"; - -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; -import ErrorBoundary from "@components/ErrorBoundary"; -import ExpandableHeader from "@components/ExpandableHeader"; -import { OpenExternalIcon } from "@components/Icons"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; -import { Alerts, Menu, Parser, useState } from "@webpack/common"; -import { Guild, User } from "discord-types/general"; - -import { openReviewsModal } from "./components/ReviewModal"; -import ReviewsView from "./components/ReviewsView"; -import { NotificationType } from "./entities"; -import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; -import { settings } from "./settings"; -import { showToast } from "./utils"; - -const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => { - children.push( - <Menu.MenuItem - label="View Reviews" - id="vc-rdb-server-reviews" - icon={OpenExternalIcon} - action={() => openReviewsModal(props.guild.id, props.guild.name)} - /> - ); -}; - -export default definePlugin({ - name: "ReviewDB", - description: "Review other users (Adds a new settings to profiles)", - authors: [Devs.mantikafasi, Devs.Ven], - - settings, - - patches: [ - { - find: "disableBorderColor:!0", - replacement: { - match: /\(.{0,10}\{user:(.),setNote:.,canDM:.,.+?\}\)/, - replace: "$&,$self.getReviewsComponent($1)" - } - } - ], - - async start() { - const s = settings.store; - const { token, lastReviewId, notifyReviews } = s; - - if (!notifyReviews || !token) return; - - setTimeout(async () => { - const user = await getCurrentUserInfo(token); - if (lastReviewId && lastReviewId < user.lastReviewID) { - s.lastReviewId = user.lastReviewID; - if (user.lastReviewID !== 0) - showToast("You have new reviews on your profile!"); - } - - addContextMenuPatch("guild-header-popout", guildPopoutPatch); - - if (user.notification) { - const props = user.notification.type === NotificationType.Ban ? { - cancelText: "Appeal", - confirmText: "Ok", - onCancel: () => - VencordNative.native.openExternal( - "https://reviewdb.mantikafasi.dev/api/redirect?" - + new URLSearchParams({ - token: settings.store.token!, - page: "dashboard/appeal" - }) - ) - } : {}; - - Alerts.show({ - title: user.notification.title, - body: ( - Parser.parse( - user.notification.content, - false - ) - ), - ...props - }); - - readNotification(user.notification.id); - } - s.user = user; - }, 4000); - }, - - stop() { - removeContextMenuPatch("guild-header-popout", guildPopoutPatch); - }, - - getReviewsComponent: ErrorBoundary.wrap((user: User) => { - const [reviewCount, setReviewCount] = useState<number>(); - - return ( - <ExpandableHeader - headerText="User Reviews" - onMoreClick={() => openReviewsModal(user.id, user.username)} - moreTooltipText={ - reviewCount && reviewCount > 50 - ? `View all ${reviewCount} reviews` - : "Open Review Modal" - } - onDropDownClick={state => settings.store.reviewsDropdownState = !state} - defaultState={settings.store.reviewsDropdownState} - > - <ReviewsView - discordId={user.id} - name={user.username} - onFetchReviews={r => setReviewCount(r.reviewCount)} - showInput - /> - </ExpandableHeader> - ); - }, { message: "Failed to render Reviews" }) -}); diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts deleted file mode 100644 index 5370a9b34..000000000 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ /dev/null @@ -1,151 +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 <https://www.gnu.org/licenses/>. -*/ - -import { Review, ReviewDBUser } from "./entities"; -import { settings } from "./settings"; -import { authorize, showToast } from "./utils"; - -const API_URL = "https://manti.vendicated.dev"; - -export const REVIEWS_PER_PAGE = 50; - -export interface Response { - success: boolean, - message: string; - reviews: Review[]; - updated: boolean; - hasNextPage: boolean; - reviewCount: number; -} - -const WarningFlag = 0b00000010; - -export async function getReviews(id: string, offset = 0): Promise<Response> { - let flags = 0; - if (!settings.store.showWarning) flags |= WarningFlag; - - const params = new URLSearchParams({ - flags: String(flags), - offset: String(offset) - }); - const req = await fetch(`${API_URL}/api/reviewdb/users/${id}/reviews?${params}`); - - const res = (req.status === 200) - ? await req.json() as Response - : { - success: false, - message: "An Error occured while fetching reviews. Please try again later.", - reviews: [], - updated: false, - hasNextPage: false, - reviewCount: 0 - }; - - if (!res.success) { - showToast(res.message); - return { - ...res, - reviews: [ - { - id: 0, - comment: "An Error occured while fetching reviews. Please try again later.", - star: 0, - timestamp: 0, - sender: { - id: 0, - username: "Error", - profilePhoto: "https://cdn.discordapp.com/attachments/1045394533384462377/1084900598035513447/646808599204593683.png?size=128", - discordID: "0", - badges: [] - } - } - ] - }; - } - - return res; -} - -export async function addReview(review: any): Promise<Response | null> { - review.token = settings.store.token; - - if (!review.token) { - showToast("Please authorize to add a review."); - authorize(); - return null; - } - - return fetch(API_URL + `/api/reviewdb/users/${review.userid}/reviews`, { - method: "PUT", - body: JSON.stringify(review), - headers: { - "Content-Type": "application/json", - } - }) - .then(r => r.json()) - .then(res => { - showToast(res.message); - return res ?? null; - }); -} - -export function deleteReview(id: number): Promise<Response> { - return fetch(API_URL + `/api/reviewdb/users/${id}/reviews`, { - method: "DELETE", - headers: new Headers({ - "Content-Type": "application/json", - Accept: "application/json", - }), - body: JSON.stringify({ - token: settings.store.token, - reviewid: id - }) - }).then(r => r.json()); -} - -export async function reportReview(id: number) { - const res = await fetch(API_URL + "/api/reviewdb/reports", { - method: "PUT", - headers: new Headers({ - "Content-Type": "application/json", - Accept: "application/json", - }), - body: JSON.stringify({ - reviewid: id, - token: settings.store.token - }) - }).then(r => r.json()) as Response; - - showToast(res.message); -} - -export function getCurrentUserInfo(token: string): Promise<ReviewDBUser> { - return fetch(API_URL + "/api/reviewdb/users", { - body: JSON.stringify({ token }), - method: "POST", - }).then(r => r.json()); -} - -export function readNotification(id: number) { - return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, { - method: "PATCH", - headers: { - "Authorization": settings.store.token || "", - }, - }); -} diff --git a/src/plugins/reviewDB/settings.tsx b/src/plugins/reviewDB/settings.tsx deleted file mode 100644 index e318bc7bd..000000000 --- a/src/plugins/reviewDB/settings.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 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 <https://www.gnu.org/licenses/>. -*/ - -import { definePluginSettings } from "@api/Settings"; -import { OptionType } from "@utils/types"; -import { Button } from "@webpack/common"; - -import { ReviewDBUser } from "./entities"; -import { authorize } from "./utils"; - -export const settings = definePluginSettings({ - authorize: { - type: OptionType.COMPONENT, - description: "Authorize with ReviewDB", - component: () => ( - <Button onClick={authorize}> - Authorize with ReviewDB - </Button> - ) - }, - notifyReviews: { - type: OptionType.BOOLEAN, - description: "Notify about new reviews on startup", - default: true, - }, - showWarning: { - type: OptionType.BOOLEAN, - description: "Display warning to be respectful at the top of the reviews list", - default: true, - }, - hideTimestamps: { - type: OptionType.BOOLEAN, - description: "Hide timestamps on reviews", - default: false, - }, - hideBlockedUsers: { - type: OptionType.BOOLEAN, - description: "Hide reviews from blocked users", - default: true, - }, - website: { - type: OptionType.COMPONENT, - description: "ReviewDB website", - component: () => ( - <Button onClick={() => { - let url = "https://reviewdb.mantikafasi.dev/"; - if (settings.store.token) - url += "/api/redirect?token=" + encodeURIComponent(settings.store.token); - - VencordNative.native.openExternal(url); - }}> - ReviewDB website - </Button> - ) - }, - supportServer: { - type: OptionType.COMPONENT, - description: "ReviewDB Support Server", - component: () => ( - <Button onClick={() => { - VencordNative.native.openExternal("https://discord.gg/eWPBSbvznt"); - }}> - ReviewDB Support Server - </Button> - ) - } -}).withPrivateSettings<{ - token?: string; - user?: ReviewDBUser; - lastReviewId?: number; - reviewsDropdownState?: boolean; -}>(); diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css deleted file mode 100644 index f4d890fd6..000000000 --- a/src/plugins/reviewDB/style.css +++ /dev/null @@ -1,76 +0,0 @@ -[class|="section"]:not([class|="lastSection"]) + .vc-rdb-view { - margin-top: 12px; -} - -.vc-rdb-badge { - vertical-align: middle; - margin-left: 4px; -} - -.vc-rdb-input { - margin-top: 6px; - margin-bottom: 12px; - resize: none; - overflow: hidden; - background: transparent; - border: 1px solid var(--profile-message-input-border-color); -} - -.vc-rdb-modal-footer > div { - width: 100%; - margin: 6px 16px; -} - -/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */ -.vc-rdb-input > div > div { - padding-left: 0 !important; -} - -.vc-rdb-placeholder { - margin-bottom: 4px; - font-weight: bold; - font-style: italic; - color: var(--text-muted); -} - -.vc-rdb-input * { - font-size: 14px; -} - -.vc-rdb-modal-footer { - padding: 0; -} - -.vc-rdb-modal-footer .vc-rdb-input { - margin-bottom: 0; - background: var(--input-background); -} - -.vc-rdb-modal-footer [class|="pageControlContainer"] { - margin-top: 0; -} - -.vc-rdb-modal-header { - flex-grow: 1; -} - -.vc-rdb-modal-reviews { - margin-top: 16px; -} - -.vc-rdb-review { - margin-top: 8px; - margin-bottom: 8px; -} - -.vc-rdb-review-comment img { - vertical-align: text-top; -} - -.vc-rdb-review-comment { - overflow-y: hidden; - margin-top: 1px; - margin-bottom: 8px; - color: var(--text-normal); - font-size: 15px; -} diff --git a/src/plugins/reviewDB/utils.tsx b/src/plugins/reviewDB/utils.tsx deleted file mode 100644 index a9c8ca57b..000000000 --- a/src/plugins/reviewDB/utils.tsx +++ /dev/null @@ -1,81 +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 <https://www.gnu.org/licenses/>. -*/ - -import { classNameFactory } from "@api/Styles"; -import { Logger } from "@utils/Logger"; -import { openModal } from "@utils/modal"; -import { findByProps } from "@webpack"; -import { React, Toasts } from "@webpack/common"; - -import { Review, UserType } from "./entities"; -import { settings } from "./settings"; - -export const cl = classNameFactory("vc-rdb-"); - -export function authorize(callback?: any) { - const { OAuth2AuthorizeModal } = findByProps("OAuth2AuthorizeModal"); - - openModal((props: any) => - <OAuth2AuthorizeModal - {...props} - scopes={["identify"]} - responseType="code" - redirectUri="https://manti.vendicated.dev/api/reviewdb/auth" - permissions={0n} - clientId="915703782174752809" - cancelCompletesFlow={false} - callback={async (response: any) => { - try { - const url = new URL(response.location); - url.searchParams.append("clientMod", "vencord"); - const res = await fetch(url, { - headers: new Headers({ Accept: "application/json" }) - }); - const { token, success } = await res.json(); - if (success) { - settings.store.token = token; - showToast("Successfully logged in!"); - callback?.(); - } else if (res.status === 1) { - showToast("An Error occurred while logging in."); - } - } catch (e) { - new Logger("ReviewDB").error("Failed to authorize", e); - } - }} - /> - ); -} - -export function showToast(text: string) { - Toasts.show({ - type: Toasts.Type.MESSAGE, - message: text, - id: Toasts.genId(), - options: { - position: Toasts.Position.BOTTOM - }, - }); -} - -export function canDeleteReview(review: Review, userId: string) { - return ( - review.sender.discordID === userId - || settings.store.user?.type === UserType.Admin - ); -} diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 8b256f40c..e4fa4fe10 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -44,7 +44,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "RoleColorEverywhere", - authors: [Devs.KingFish, Devs.lewisakura], + authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN], description: "Adds the top role color anywhere possible", patches: [ // Chat Mentions @@ -78,6 +78,10 @@ export default definePlugin({ match: /(memo\(\(function\((\i)\).{300,500}CHANNEL_MEMBERS_A11Y_LABEL.{100,200}roleIcon.{5,20}null,).," \u2014 ",.\]/, replace: "$1$self.roleGroupColor($2)]" }, + { + match: /children:\[.," \u2014 ",.\]/, + replace: "children:[$self.roleGroupColor(arguments[0])]" + }, ], predicate: () => settings.store.memberList, }, @@ -105,7 +109,7 @@ export default definePlugin({ return colorString && parseInt(colorString.slice(1), 16); }, - roleGroupColor({ id, count, title, guildId }: { id: string; count: number; title: string; guildId: string; }) { + roleGroupColor({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) { const guild = GuildStore.getGuild(guildId); const role = guild?.roles[id]; @@ -113,7 +117,7 @@ export default definePlugin({ color: role?.colorString, fontWeight: "unset", letterSpacing: ".05em" - }}>{title} — {count}</span>; + }}>{title ?? label} — {count}</span>; }, getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) { diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx index a69136ba3..a69dbaccc 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -124,7 +124,7 @@ export default definePlugin({ find: ".activeCommandOption", replacement: { match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}", + replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}", } }, ], @@ -139,7 +139,9 @@ export default definePlugin({ removePreSendListener(this.listener); }, - chatBarIcon() { + chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) { + if (chatBoxProps.type.analyticsName !== "normal") return null; + return ( <Tooltip text="Insert Timestamp"> {({ onMouseEnter, onMouseLeave }) => ( diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 79b387771..2be9b57d9 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -170,7 +170,7 @@ function ServerInfoTab({ guild }: GuildProps) { const Fields = { "Server Owner": owner ? Owner(guild.id, owner) : "Loading...", "Created At": renderTimestamp(SnowflakeUtils.extractTimestamp(guild.id)), - "Joined At": renderTimestamp(guild.joinedAt.getTime()), + "Joined At": guild.joinedAt ? renderTimestamp(guild.joinedAt.getTime()) : "-", // Not available in lurked guild "Vanity Link": guild.vanityURLCode ? (<a>{`discord.gg/${guild.vanityURLCode}`}</a>) : "-", // Making the anchor href valid would cause Discord to reload "Preferred Locale": guild.preferredLocale || "-", "Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?", diff --git a/src/plugins/serverProfile/index.tsx b/src/plugins/serverProfile/index.tsx index c27f8cd53..68f6193cc 100644 --- a/src/plugins/serverProfile/index.tsx +++ b/src/plugins/serverProfile/index.tsx @@ -18,7 +18,7 @@ const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; group?.push( <Menu.MenuItem id="vc-server-profile" - label="Server Profile" + label="Server Info" action={() => openGuildProfileModal(guild)} /> ); diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index 4447e080d..39dabfc36 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -70,10 +70,11 @@ function clean(str: string) { .trim(); } -function formatText(str: string, user: string, channel: string) { +function formatText(str: string, user: string, channel: string, displayName: string) { return str .replaceAll("{{USER}}", clean(user) || (user ? "Someone" : "")) - .replaceAll("{{CHANNEL}}", clean(channel) || "channel"); + .replaceAll("{{CHANNEL}}", clean(channel) || "channel") + .replaceAll("{{DISPLAY_NAME}}", clean(displayName) || (displayName ? "Someone" : "")); } /* @@ -143,8 +144,9 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId, function playSample(tempSettings: any, type: string) { const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings); + const currentUser = UserStore.getCurrentUser(); - speak(formatText(settings[type + "Message"], UserStore.getCurrentUser().username, "general"), settings); + speak(formatText(settings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username), settings); } export default definePlugin({ @@ -172,9 +174,10 @@ export default definePlugin({ const template = Settings.plugins.VcNarrator[type + "Message"]; const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username; + const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user); const channel = ChannelStore.getChannel(id).name; - speak(formatText(template, user, channel)); + speak(formatText(template, user, channel, displayName)); // updateStatuses(type, state, isMe); } @@ -186,7 +189,7 @@ export default definePlugin({ if (!s) return; const event = s.mute || s.selfMute ? "unmute" : "mute"; - speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name)); + speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "")); }, AUDIO_TOGGLE_SELF_DEAF() { @@ -195,7 +198,7 @@ export default definePlugin({ if (!s) return; const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen"; - speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name)); + speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "")); } }, @@ -312,8 +315,8 @@ export default definePlugin({ You can customise the spoken messages below. You can disable specific messages by setting them to nothing </Forms.FormText> <Forms.FormText> - The special placeholders <code>{"{{USER}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "} - will be replaced with the user's name (nothing if it's yourself) and the channel's name respectively + The special placeholders <code>{"{{USER}}"}</code>, <code>{"{{DISPLAY_NAME}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "} + will be replaced with the user's name (nothing if it's yourself), the user's display name and the channel's name respectively </Forms.FormText> {hasEnglishVoices && ( <> diff --git a/src/plugins/whoReacted/README.md b/src/plugins/whoReacted/README.md new file mode 100644 index 000000000..ee4227ab8 --- /dev/null +++ b/src/plugins/whoReacted/README.md @@ -0,0 +1,5 @@ +# WhoReacted + +Next to each reaction, display each user's avatar. Each avatar can be clicked and will open the profile. + +![](https://github.com/Vendicated/Vencord/assets/57493648/97fec9e8-396f-4f5e-916e-1ec21445113d) diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 0bdb5c27e..a4e74f941 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -24,14 +24,14 @@ import { LazyComponent, useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; import { findByCode, findByPropsLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; -import { ReactionEmoji, User } from "discord-types/general"; +import { CustomEmoji } from "@webpack/types"; +import { Message, ReactionEmoji, User } from "discord-types/general"; const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); -const ReactionStore = findByPropsLazy("getReactions"); - const queue = new Queue(); +let reactions: Record<string, ReactionCacheEntry>; function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { const key = emoji.name + (emoji.id ? `:${emoji.id}` : ""); @@ -57,11 +57,9 @@ function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) { const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`; - const cache = ReactionStore.__getLocalVars().reactions[key] ??= { fetched: false, users: {} }; + const cache = reactions[key] ??= { fetched: false, users: {} }; if (!cache.fetched) { - queue.unshift(() => - fetchReactions(msg, e, type) - ); + queue.unshift(() => fetchReactions(msg, e, type)); cache.fetched = true; } @@ -92,7 +90,7 @@ function handleClickAvatar(event: React.MouseEvent<HTMLElement, MouseEvent>) { export default definePlugin({ name: "WhoReacted", - description: "Renders the Avatars of reactors", + description: "Renders the avatars of users who reacted to a message", authors: [Devs.Ven, Devs.KannaDev], patches: [{ @@ -101,6 +99,12 @@ export default definePlugin({ match: /(?<=(\i)=(\i)\.hideCount,)(.+?reactionCount.+?\}\))/, replace: (_, hideCount, props, rest) => `whoReactedProps=${props},${rest},${hideCount}?null:$self.renderUsers(whoReactedProps)` } + }, { + find: '.displayName="MessageReactionsStore";', + replacement: { + match: /(?<=CONNECTION_OPEN:function\(\){)(\i)={}/, + replace: "$&;$self.reactions=$1" + } }], renderUsers(props: RootObject) { @@ -150,106 +154,25 @@ export default definePlugin({ </div> </div> ); + }, + + set reactions(value: any) { + reactions = value; } }); - -export interface GuildMemberAvatar { } - -export interface Author { - id: string; - username: string; - discriminator: string; - avatar: string; - avatarDecoration?: any; - email: string; - verified: boolean; - bot: boolean; - system: boolean; - mfaEnabled: boolean; - mobile: boolean; - desktop: boolean; - premiumType: number; - flags: number; - publicFlags: number; - purchasedFlags: number; - premiumUsageFlags: number; - phone: string; - nsfwAllowed: boolean; - guildMemberAvatars: GuildMemberAvatar; +interface ReactionCacheEntry { + fetched: boolean; + users: Record<string, User>; } -export interface Emoji { - id: string; - name: string; -} - -export interface Reaction { - emoji: Emoji; - count: number; - burst_user_ids: any[]; - burst_count: number; - burst_colors: any[]; - burst_me: boolean; - me: boolean; -} - -export interface Message { - id: string; - type: number; - channel_id: string; - author: Author; - content: string; - deleted: boolean; - editHistory: any[]; - attachments: any[]; - embeds: any[]; - mentions: any[]; - mentionRoles: any[]; - mentionChannels: any[]; - mentioned: boolean; - pinned: boolean; - mentionEveryone: boolean; - tts: boolean; - codedLinks: any[]; - giftCodes: any[]; - timestamp: string; - editedTimestamp?: any; - state: string; - nonce?: any; - blocked: boolean; - call?: any; - bot: boolean; - webhookId?: any; - reactions: Reaction[]; - applicationId?: any; - application?: any; - activity?: any; - messageReference?: any; - flags: number; - isSearchHit: boolean; - stickers: any[]; - stickerItems: any[]; - components: any[]; - loggingName?: any; - interaction?: any; - interactionData?: any; - interactionError?: any; -} - -export interface Emoji { - id: string; - name: string; - animated: boolean; -} - -export interface RootObject { +interface RootObject { message: Message; readOnly: boolean; isLurking: boolean; isPendingMember: boolean; useChatFontScaling: boolean; - emoji: Emoji; + emoji: CustomEmoji; count: number; burst_user_ids: any[]; burst_count: number; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7264c40d9..6395ddfb1 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -374,7 +374,11 @@ export const Devs = /* #__PURE__*/ Object.freeze({ archeruwu: { name: "archer_uwu", id: 160068695383736320n - } + }, + ProffDea: { + name: "ProffDea", + id: 609329952180928513n + }, } satisfies Record<string, Dev>); // iife so #__PURE__ works correctly @@ -385,5 +389,3 @@ export const DevsById = /* #__PURE__*/ (() => .map(([_, v]) => [v.id, v] as const) )) )() as Record<string, Dev>; - -export const IsFirefox = IS_EXTENSION && navigator.userAgent.toLowerCase().includes("firefox"); diff --git a/src/webpack/common/classes.ts b/src/webpack/common/classes.ts index 5c1a67630..8bc76409d 100644 --- a/src/webpack/common/classes.ts +++ b/src/webpack/common/classes.ts @@ -20,5 +20,5 @@ import { findByPropsLazy } from "@webpack"; import * as t from "./types/classes"; -export const ModalImageClasses: t.ImageModalClasses = findByPropsLazy("image", "modal"); +export const ModalImageClasses: t.ImageModalClasses = findByPropsLazy("image", "modal", "responsiveWidthMobile"); export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent");