From 77084203c6ff51d1a484bb22ba57cf7017d647ad Mon Sep 17 00:00:00 2001 From: dorkbutt Date: Fri, 6 Jun 2025 12:20:47 -0400 Subject: [PATCH 001/141] fork init --- src/plugins/other-usrbg/index.tsx | 120 +++++++++++++++++++++++++++++ src/plugins/other-usrbg/users.json | 6 ++ 2 files changed, 126 insertions(+) create mode 100644 src/plugins/other-usrbg/index.tsx create mode 100644 src/plugins/other-usrbg/users.json diff --git a/src/plugins/other-usrbg/index.tsx b/src/plugins/other-usrbg/index.tsx new file mode 100644 index 00000000..d77e8780 --- /dev/null +++ b/src/plugins/other-usrbg/index.tsx @@ -0,0 +1,120 @@ +/* + * 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 . +*/ + +import { definePluginSettings } from "@api/Settings"; +import { Link } from "@components/Link"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const API_URL = "https://usrbg.dorkbutt.lol/users/users.json"; + +interface UsrbgApiReturn { + endpoint: string; + bucket: string; + prefix: string; + users: Record; +} + +const settings = definePluginSettings({ + nitroFirst: { + description: "Banner to use if both Nitro and USRBG banners are present", + type: OptionType.SELECT, + options: [ + { label: "Nitro banner", value: true, default: true }, + { label: "USRBG banner", value: false }, + ] + }, + voiceBackground: { + description: "Use USRBG banners as voice chat backgrounds", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "FORKED - USRBG", + description: "Displays user banners from dorkbutt's USRBG server, allowing anyone to get a banner without Nitro", + authors: [Devs.dorkbutt, Devs.AutumnVN, Devs.katlyn, Devs.pylix, Devs.TheKodeToad], + settings, + patches: [ + { + find: '.banner)==null?"COMPLETE"', + replacement: { + match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, + replace: "$self.patchBannerUrl(arguments[0])||$&" + + } + }, + { + find: "\"data-selenium-video-tile\":", + predicate: () => settings.store.voiceBackground, + replacement: [ + { + match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, + replace: "$1.style=$self.getVoiceBackgroundStyles($1);" + } + ] + } + ], + + data: null as UsrbgApiReturn | null, + + settingsAboutComponent: () => { + return ( + "Message @dorkbutt about getting your personal banner added!" + ); + }, + + getVoiceBackgroundStyles({ className, participantUserId }: any) { + if (className.includes("tile_")) { + if (this.userHasBackground(participantUserId)) { + return { + backgroundImage: `url(${this.getImageUrl(participantUserId)})`, + backgroundSize: "cover", + backgroundPosition: "center", + backgroundRepeat: "no-repeat" + }; + } + } + }, + + patchBannerUrl({ displayProfile }: any) { + if (displayProfile?.banner && settings.store.nitroFirst) return; + if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId); + }, + + userHasBackground(userId: string) { + return !!this.data?.users[userId]; + }, + + getImageUrl(userId: string): string | null { + if (!this.userHasBackground(userId)) return null; + + // We can assert that data exists because userHasBackground returned true + const { endpoint, bucket, prefix, users: { [userId]: etag } } = this.data!; + return `${endpoint}/${bucket}/${prefix}${userId}?${etag}`; + }, + + async start() { + const res = await fetch(API_URL); + if (res.ok) { + this.data = await res.json(); + } + } +}); diff --git a/src/plugins/other-usrbg/users.json b/src/plugins/other-usrbg/users.json new file mode 100644 index 00000000..9ac83479 --- /dev/null +++ b/src/plugins/other-usrbg/users.json @@ -0,0 +1,6 @@ +{"endpoint":"https://usrbg.dorkbutt.lol","bucket":"usrbg","prefix":"v2/", + "users":{ + "862105885660676146":"db1d", + "578012873813524530":"315c" + } +} From acccc963ae18024412fa7b8f0f1210c78be92d54 Mon Sep 17 00:00:00 2001 From: dorkbutt Date: Fri, 6 Jun 2025 13:00:30 -0400 Subject: [PATCH 002/141] please work --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 61575d4b..3a2b1492 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# A fork. A fork. We're a fork. + + # Vencord [![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/cord&color=2185D0&logo=data:image/png;base64,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)](https://codeberg.org/Vee/cord) From 741af9eb43f0daa2e37aeea91dbf75ab8e0ec366 Mon Sep 17 00:00:00 2001 From: dorkbutt Date: Fri, 6 Jun 2025 13:07:45 -0400 Subject: [PATCH 003/141] changed branches. --- .github/ISSUE_TEMPLATE/blank.yml | 25 ------ .github/ISSUE_TEMPLATE/bug_report.yml | 66 -------------- .github/ISSUE_TEMPLATE/config.yml | 8 -- .github/ISSUE_TEMPLATE/developer-banner.png | Bin 31992 -> 0 bytes .github/workflows/build.yml | 80 ----------------- .github/workflows/codeberg-mirror.yml | 22 ----- .github/workflows/publish.yml | 45 ---------- .github/workflows/reportBrokenPlugins.yml | 95 -------------------- .github/workflows/test.yml | 32 ------- 9 files changed, 373 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/blank.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/developer-banner.png delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/codeberg-mirror.yml delete mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/reportBrokenPlugins.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml deleted file mode 100644 index 89588f3d..00000000 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ /dev/null @@ -1,25 +0,0 @@ -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: | - ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) - - GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - - - 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 deleted file mode 100644 index c08f4635..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Bug/Crash Report -description: Create a bug or crash report for Vencord. 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. -labels: [bug] -title: "[Bug] " - -body: - - type: markdown - attributes: - value: | - ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) - - GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - - - type: textarea - id: bug-description - attributes: - label: What happens when the bug or crash occurs? - description: Where does this bug or crash occur, when does it occur, etc. - placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ... - validations: - required: true - - - type: textarea - id: expected-behaviour - attributes: - label: What is the expected behaviour? - description: Simply detail what the expected behaviour is. - placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ... - validations: - required: true - - - type: textarea - id: steps-to-take - attributes: - label: How do you recreate this bug or crash? - description: Give us a list of steps in order to recreate the bug or crash. - placeholder: | - 1. Do ... - 2. Then ... - 3. Do this ..., ... and then ... - 4. Observe "the bug" or "the crash" - validations: - required: true - - - type: textarea - id: crash-log - attributes: - label: Errors - description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". - value: | - ``` - Replace this text with your crash-log. - ``` - validations: - required: false - - - type: checkboxes - id: agreement-check - attributes: - label: Request Agreement - description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable - options: - - label: I am using Discord Stable or tried on Stable and this bug happens there as well - required: true - - label: I am a Vencord Developer - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index bc5d9766..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Vencord Support Server - url: https://discord.gg/D9uwnFnqmd - about: If you need help regarding Vencord, please join our support server! - - name: Vencord Installer - url: https://github.com/Vencord/Installer - about: You can find the Vencord Installer here diff --git a/.github/ISSUE_TEMPLATE/developer-banner.png b/.github/ISSUE_TEMPLATE/developer-banner.png deleted file mode 100644 index 5fa12fc370750c460f8d0959d5f73828c63b6e3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31992 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 WKXLqM0$yeQOioH!vP#@2=>GuWUFmuN diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index b1cbc302..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Build DevBuild -on: - push: - branches: - - main - paths: - - .github/workflows/build.yml - - src/** - - browser/** - - scripts/build/** - - package.json - - pnpm-lock.yaml -env: - FORCE_COLOR: true - -jobs: - Build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - - - name: Use Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build web - run: pnpm buildWebStandalone - - - name: Build - run: pnpm build --standalone - - - name: Generate plugin list - run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json - - - name: Clean up obsolete files - run: | - rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map - - - name: Get some values needed for the release - id: release_values - run: | - echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - - name: Upload DevBuild as release - if: github.repository == 'Vendicated/Vencord' - run: | - gh release upload devbuild --clobber dist/* - gh release edit devbuild --title "DevBuild $RELEASE_TAG" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ env.release_tag }} - - - name: Upload DevBuild to builds repo - if: github.repository == 'Vendicated/Vencord' - run: | - git config --global user.name "$USERNAME" - git config --global user.email actions@github.com - - git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload - cd upload - - GLOBIGNORE=.git:.gitignore:README.md:LICENSE - rm -rf * - cp -r ../dist/* . - - git add -A - git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" - git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git - env: - API_TOKEN: ${{ secrets.BUILDS_TOKEN }} - GH_REPO: Vencord/builds - USERNAME: GitHub-Actions diff --git a/.github/workflows/codeberg-mirror.yml b/.github/workflows/codeberg-mirror.yml deleted file mode 100644 index 5acae6a4..00000000 --- a/.github/workflows/codeberg-mirror.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Sync to Codeberg -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true -on: - push: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" - -jobs: - codeberg: - if: github.repository == 'Vendicated/Vencord' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1 - with: - target_repo_url: "git@codeberg.org:Vee/cord.git" - ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 190e3069..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Release Browser Extension -on: - push: - tags: - - v* - -jobs: - Publish: - if: github.repository == 'Vendicated/Vencord' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: check that tag matches package.json version - run: | - pkg_version="v$(jq -r .version < package.json)" - if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then - echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 - exit 1 - fi - - - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - - - name: Use Node.js 19 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build web - run: pnpm buildWebStandalone - - - name: Publish extension - run: | - cd dist/chromium-unpacked - pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish - env: - EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml deleted file mode 100644 index f1e53e4d..00000000 --- a/.github/workflows/reportBrokenPlugins.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Test Patches -on: - workflow_dispatch: - inputs: - discord_branch: - type: choice - description: "Discord Branch to test patches on" - options: - - both - - stable - - canary - default: both - webhook_url: - type: string - description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot." - required: false - # schedule: - # # Every day at midnight - # - cron: 0 0 * * * - -jobs: - TestPlugins: - if: github.repository == 'Vendicated/Vencord' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - if: ${{ github.event_name == 'schedule' }} - with: - ref: dev - - - uses: actions/checkout@v4 - if: ${{ github.event_name == 'workflow_dispatch' }} - - - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - - - name: Use Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: | - pnpm install --frozen-lockfile - - - name: Install Google Chrome - id: setup-chrome - uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2 - with: - chrome-version: stable - - - name: Build Vencord Reporter Version - run: pnpm buildReporter - - - name: Run Reporter - timeout-minutes: 10 - run: | - export PATH="$PWD/node_modules/.bin:$PATH" - export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} - - esbuild scripts/generateReport.ts > dist/report.mjs - - stable_output_file=$(mktemp) - canary_output_file=$(mktemp) - - pids="" - - branch="${{ inputs.discord_branch }}" - if [[ "${{ github.event_name }}" = "schedule" ]]; then - branch="both" - fi - - if [[ "$branch" = "both" || "$branch" = "stable" ]]; then - node dist/report.mjs > "$stable_output_file" & - pids+=" $!" - fi - - if [[ "$branch" = "both" || "$branch" = "canary" ]]; then - USE_CANARY=true node dist/report.mjs > "$canary_output_file" & - pids+=" $!" - fi - - exit_code=0 - for pid in $pids; do - if ! wait "$pid"; then - exit_code=1 - fi - done - - cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY - exit $exit_code - env: - WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }} - WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 7a2b320b..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: test -on: - push: - pull_request: - branches: - - main - - dev -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json - - - name: Use Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Lint & Test if desktop version compiles - run: pnpm test - - - name: Test if web version compiles - run: pnpm buildWeb - - - name: Test if plugin structure is valid - run: pnpm generatePluginJson From 2a87063176b881ceed67779aed15b4e54d56cce9 Mon Sep 17 00:00:00 2001 From: dorkbutt <git@nomadants.net> Date: Fri, 6 Jun 2025 13:08:27 -0400 Subject: [PATCH 004/141] and added a loose piece. --- .jforgejo/ISSUE_TEMPLATE/blank.yml | 25 +++++ .jforgejo/ISSUE_TEMPLATE/bug_report.yml | 66 ++++++++++++ .jforgejo/ISSUE_TEMPLATE/config.yml | 8 ++ .jforgejo/ISSUE_TEMPLATE/developer-banner.png | Bin 0 -> 31992 bytes .jforgejo/workflows/build.yml | 80 +++++++++++++++ .jforgejo/workflows/codeberg-mirror.yml | 22 ++++ .jforgejo/workflows/publish.yml | 45 +++++++++ .jforgejo/workflows/reportBrokenPlugins.yml | 95 ++++++++++++++++++ .jforgejo/workflows/test.yml | 32 ++++++ 9 files changed, 373 insertions(+) create mode 100644 .jforgejo/ISSUE_TEMPLATE/blank.yml create mode 100644 .jforgejo/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .jforgejo/ISSUE_TEMPLATE/config.yml create mode 100644 .jforgejo/ISSUE_TEMPLATE/developer-banner.png create mode 100644 .jforgejo/workflows/build.yml create mode 100644 .jforgejo/workflows/codeberg-mirror.yml create mode 100644 .jforgejo/workflows/publish.yml create mode 100644 .jforgejo/workflows/reportBrokenPlugins.yml create mode 100644 .jforgejo/workflows/test.yml diff --git a/.jforgejo/ISSUE_TEMPLATE/blank.yml b/.jforgejo/ISSUE_TEMPLATE/blank.yml new file mode 100644 index 00000000..89588f3d --- /dev/null +++ b/.jforgejo/ISSUE_TEMPLATE/blank.yml @@ -0,0 +1,25 @@ +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: | + ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) + + GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. + + - 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/.jforgejo/ISSUE_TEMPLATE/bug_report.yml b/.jforgejo/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..c08f4635 --- /dev/null +++ b/.jforgejo/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: Bug/Crash Report +description: Create a bug or crash report for Vencord. 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. +labels: [bug] +title: "[Bug] <title>" + +body: + - type: markdown + attributes: + value: | + ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) + + GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. + + - type: textarea + id: bug-description + attributes: + label: What happens when the bug or crash occurs? + description: Where does this bug or crash occur, when does it occur, etc. + placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ... + validations: + required: true + + - type: textarea + id: expected-behaviour + attributes: + label: What is the expected behaviour? + description: Simply detail what the expected behaviour is. + placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ... + validations: + required: true + + - type: textarea + id: steps-to-take + attributes: + label: How do you recreate this bug or crash? + description: Give us a list of steps in order to recreate the bug or crash. + placeholder: | + 1. Do ... + 2. Then ... + 3. Do this ..., ... and then ... + 4. Observe "the bug" or "the crash" + validations: + required: true + + - type: textarea + id: crash-log + attributes: + label: Errors + description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". + value: | + ``` + Replace this text with your crash-log. + ``` + validations: + required: false + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable + options: + - label: I am using Discord Stable or tried on Stable and this bug happens there as well + required: true + - label: I am a Vencord Developer + required: true diff --git a/.jforgejo/ISSUE_TEMPLATE/config.yml b/.jforgejo/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..bc5d9766 --- /dev/null +++ b/.jforgejo/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Vencord Support Server + url: https://discord.gg/D9uwnFnqmd + about: If you need help regarding Vencord, please join our support server! + - name: Vencord Installer + url: https://github.com/Vencord/Installer + about: You can find the Vencord Installer here diff --git a/.jforgejo/ISSUE_TEMPLATE/developer-banner.png b/.jforgejo/ISSUE_TEMPLATE/developer-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..5fa12fc370750c460f8d0959d5f73828c63b6e3d GIT binary patch literal 31992 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 WKXLqM0$yeQOioH!vP#@2=>GuWUFmuN literal 0 HcmV?d00001 diff --git a/.jforgejo/workflows/build.yml b/.jforgejo/workflows/build.yml new file mode 100644 index 00000000..b1cbc302 --- /dev/null +++ b/.jforgejo/workflows/build.yml @@ -0,0 +1,80 @@ +name: Build DevBuild +on: + push: + branches: + - main + paths: + - .github/workflows/build.yml + - src/** + - browser/** + - scripts/build/** + - package.json + - pnpm-lock.yaml +env: + FORCE_COLOR: true + +jobs: + Build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build web + run: pnpm buildWebStandalone + + - name: Build + run: pnpm build --standalone + + - name: Generate plugin list + run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json + + - name: Clean up obsolete files + run: | + rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map + + - name: Get some values needed for the release + id: release_values + run: | + echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + + - name: Upload DevBuild as release + if: github.repository == 'Vendicated/Vencord' + run: | + gh release upload devbuild --clobber dist/* + gh release edit devbuild --title "DevBuild $RELEASE_TAG" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ env.release_tag }} + + - name: Upload DevBuild to builds repo + if: github.repository == 'Vendicated/Vencord' + run: | + git config --global user.name "$USERNAME" + git config --global user.email actions@github.com + + git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload + cd upload + + GLOBIGNORE=.git:.gitignore:README.md:LICENSE + rm -rf * + cp -r ../dist/* . + + git add -A + git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" + git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git + env: + API_TOKEN: ${{ secrets.BUILDS_TOKEN }} + GH_REPO: Vencord/builds + USERNAME: GitHub-Actions diff --git a/.jforgejo/workflows/codeberg-mirror.yml b/.jforgejo/workflows/codeberg-mirror.yml new file mode 100644 index 00000000..5acae6a4 --- /dev/null +++ b/.jforgejo/workflows/codeberg-mirror.yml @@ -0,0 +1,22 @@ +name: Sync to Codeberg +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true +on: + push: + workflow_dispatch: + schedule: + - cron: "0 */6 * * *" + +jobs: + codeberg: + if: github.repository == 'Vendicated/Vencord' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1 + with: + target_repo_url: "git@codeberg.org:Vee/cord.git" + ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }} diff --git a/.jforgejo/workflows/publish.yml b/.jforgejo/workflows/publish.yml new file mode 100644 index 00000000..190e3069 --- /dev/null +++ b/.jforgejo/workflows/publish.yml @@ -0,0 +1,45 @@ +name: Release Browser Extension +on: + push: + tags: + - v* + +jobs: + Publish: + if: github.repository == 'Vendicated/Vencord' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: check that tag matches package.json version + run: | + pkg_version="v$(jq -r .version < package.json)" + if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then + echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 + exit 1 + fi + + - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json + + - name: Use Node.js 19 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build web + run: pnpm buildWebStandalone + + - name: Publish extension + run: | + cd dist/chromium-unpacked + pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish + env: + EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} diff --git a/.jforgejo/workflows/reportBrokenPlugins.yml b/.jforgejo/workflows/reportBrokenPlugins.yml new file mode 100644 index 00000000..f1e53e4d --- /dev/null +++ b/.jforgejo/workflows/reportBrokenPlugins.yml @@ -0,0 +1,95 @@ +name: Test Patches +on: + workflow_dispatch: + inputs: + discord_branch: + type: choice + description: "Discord Branch to test patches on" + options: + - both + - stable + - canary + default: both + webhook_url: + type: string + description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot." + required: false + # schedule: + # # Every day at midnight + # - cron: 0 0 * * * + +jobs: + TestPlugins: + if: github.repository == 'Vendicated/Vencord' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + if: ${{ github.event_name == 'schedule' }} + with: + ref: dev + + - uses: actions/checkout@v4 + if: ${{ github.event_name == 'workflow_dispatch' }} + + - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: | + pnpm install --frozen-lockfile + + - name: Install Google Chrome + id: setup-chrome + uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2 + with: + chrome-version: stable + + - name: Build Vencord Reporter Version + run: pnpm buildReporter + + - name: Run Reporter + timeout-minutes: 10 + run: | + export PATH="$PWD/node_modules/.bin:$PATH" + export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} + + esbuild scripts/generateReport.ts > dist/report.mjs + + stable_output_file=$(mktemp) + canary_output_file=$(mktemp) + + pids="" + + branch="${{ inputs.discord_branch }}" + if [[ "${{ github.event_name }}" = "schedule" ]]; then + branch="both" + fi + + if [[ "$branch" = "both" || "$branch" = "stable" ]]; then + node dist/report.mjs > "$stable_output_file" & + pids+=" $!" + fi + + if [[ "$branch" = "both" || "$branch" = "canary" ]]; then + USE_CANARY=true node dist/report.mjs > "$canary_output_file" & + pids+=" $!" + fi + + exit_code=0 + for pid in $pids; do + if ! wait "$pid"; then + exit_code=1 + fi + done + + cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY + exit $exit_code + env: + WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }} + WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} diff --git a/.jforgejo/workflows/test.yml b/.jforgejo/workflows/test.yml new file mode 100644 index 00000000..7a2b320b --- /dev/null +++ b/.jforgejo/workflows/test.yml @@ -0,0 +1,32 @@ +name: test +on: + push: + pull_request: + branches: + - main + - dev +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint & Test if desktop version compiles + run: pnpm test + + - name: Test if web version compiles + run: pnpm buildWeb + + - name: Test if plugin structure is valid + run: pnpm generatePluginJson From 9051269657880b0e6615815d5f6bea5d74250629 Mon Sep 17 00:00:00 2001 From: dorkbutt <git@nomadants.net> Date: Fri, 6 Jun 2025 15:00:54 -0400 Subject: [PATCH 005/141] resorting to original installation method --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3a2b1492..be6f3936 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # A fork. A fork. We're a fork. +Installing is the same as the Vencord devs laid out: [(Install)](https://docs.vencord.dev/installing/) +* Beyond that, be sure to clone *this* repository instead of their upstream one. + * `git clone https://git.dorkbutt.lol/dorkbutt/vencord` +* After completing the steps, go to Settings > Vencord > Plugins and search for +"FORKED - usrbg" and enable it. Be sure to tweak its settings! + # Vencord From c653e361378888b8d460805ab9d0aface5e7b161 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:14:59 -0400 Subject: [PATCH 006/141] fix Expression Cloner, ServerInfo & SeeSummaries plugins (#3536) --- src/plugins/expressionCloner/index.tsx | 14 ++++++++++---- src/plugins/pauseInvitesForever/index.tsx | 6 +++--- src/plugins/seeSummaries/index.tsx | 8 +++++--- src/plugins/serverInfo/GuildInfoModal.tsx | 4 ++-- src/utils/discord.tsx | 13 +++++++++++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index c1244fae..6f84e7f7 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -20,12 +20,13 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { migratePluginSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Devs } from "@utils/constants"; +import { getGuildAcronym } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; -import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Guild } from "discord-types/general"; import { Promisable } from "type-fest"; @@ -250,13 +251,18 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) { width: "100%", height: "100%", }} - src={g.getIconURL(512, true)} + src={IconUtils.getGuildIconURL({ + id: g.id, + icon: g.icon, + canAnimate: true, + size: 512 + })} alt={g.name} /> ) : ( <Forms.FormText style={{ - fontSize: getFontSize(g.acronym), + fontSize: getFontSize(getGuildAcronym(g)), width: "100%", overflow: "hidden", whiteSpace: "nowrap", @@ -264,7 +270,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) { cursor: isCloning ? "not-allowed" : "pointer", }} > - {g.acronym} + {getGuildAcronym(g)} </Forms.FormText> )} </div> diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 432d1c1c..577d8bd7 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -18,7 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; +import { getIntlMessage, hasGuildFeature } from "@utils/discord"; import definePlugin from "@utils/types"; import { Constants, GuildStore, PermissionStore, RestAPI } from "@webpack/common"; @@ -27,8 +27,8 @@ function showDisableInvites(guildId: string) { if (!guild) return false; return ( - // @ts-ignore - !guild.hasFeature("INVITES_DISABLED") && + // @ts-expect-error + !hasGuildFeature(guild, "INVITES_DISABLED") && PermissionStore.getGuildPermissionProps(guild).canManageRoles ); } diff --git a/src/plugins/seeSummaries/index.tsx b/src/plugins/seeSummaries/index.tsx index de50e0a9..343348f1 100644 --- a/src/plugins/seeSummaries/index.tsx +++ b/src/plugins/seeSummaries/index.tsx @@ -7,6 +7,7 @@ import { DataStore } from "@api/index"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; +import { hasGuildFeature } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { ChannelStore, GuildStore } from "@webpack/common"; @@ -57,7 +58,7 @@ export default definePlugin({ { find: "SUMMARIZEABLE.has", replacement: { - match: /\i\.hasFeature\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g, + match: /\i\.features\.has\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g, replace: "true" } }, @@ -109,7 +110,8 @@ export default definePlugin({ const channel = ChannelStore.getChannel(channelId); // SUMMARIES_ENABLED feature is not in discord-types const guild = GuildStore.getGuild(channel.guild_id); - // @ts-ignore - return guild.hasFeature("SUMMARIES_ENABLED_GA"); + + // @ts-expect-error + return hasGuildFeature(guild, "SUMMARIES_ENABLED_GA"); } }); diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index c62352f2..359590cf 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -7,7 +7,7 @@ import "./styles.css"; import { classNameFactory } from "@api/Styles"; -import { openImageModal, openUserProfile } from "@utils/discord"; +import { getGuildAcronym, openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; @@ -103,7 +103,7 @@ function GuildInfoModal({ guild }: GuildProps) { width: 512, })} /> - : <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{guild.acronym}</div> + : <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{getGuildAcronym(guild)}</div> } <div className={cl("name-and-description")}> diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index e04ad201..fce909c3 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -19,6 +19,7 @@ import { MessageObject } from "@api/MessageEvents"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Channel, Guild, Message, User } from "discord-types/general"; +import GuildFeatures from "discord-types/other/Constants"; import { Except } from "type-fest"; import { runtimeHashMessageKey } from "./intlHash"; @@ -218,3 +219,15 @@ export function getEmojiURL(id: string, animated: boolean, size: number) { const url = IconUtils.getEmojiURL({ id, animated, size }); return animated ? url.replace(".webp", ".gif") : url; } + +// Discord has a similar function in their code +export function getGuildAcronym(guild: Guild): string { + return guild.name + .replaceAll("'s ", " ") + .replace(/\w+/g, m => m[0]) + .replace(/\s/g, ""); +} + +export function hasGuildFeature(guild: Guild, feature: keyof GuildFeatures["GuildFeatures"]): boolean { + return guild.features?.has(feature) ?? false; +} From 310d8e6140278c9a918031c183b9779b30430b63 Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:04:12 +0200 Subject: [PATCH 007/141] Fix ImplicitRelationships & RelationshipNotifier (#3539) --- src/plugins/implicitRelationships/index.ts | 3 +-- src/plugins/relationshipNotifier/utils.ts | 4 ++-- src/webpack/common/types/stores.d.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index 4a8cf5fb..d57370a0 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -125,12 +125,11 @@ export default definePlugin({ // Implicit relationships are defined as users that you: // 1. Have an affinity for // 2. Do not have a relationship with - await this.refreshUserAffinities(); const userAffinities: Record<string, any>[] = UserAffinitiesStore.getUserAffinities(); const relationships = RelationshipStore.getMutableRelationships(); const nonFriendAffinities = userAffinities.filter(a => !RelationshipStore.getRelationshipType(a.otherUserId)); nonFriendAffinities.forEach(a => { - relationships[a.otherUserId] = 5; + relationships.set(a.otherUserId, 5); }); RelationshipStore.emitChange(); diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts index 75cf2d3a..09ef5cdd 100644 --- a/src/plugins/relationshipNotifier/utils.ts +++ b/src/plugins/relationshipNotifier/utils.ts @@ -173,8 +173,8 @@ export async function syncFriends() { friends.requests = []; const relationShips = RelationshipStore.getMutableRelationships(); - for (const id in relationShips) { - switch (relationShips[id]) { + for (const [id, type] of relationShips) { + switch (type) { case RelationshipType.FRIEND: friends.friends.push(id); break; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 9a2dd132..f0235986 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -255,5 +255,5 @@ export class RelationshipStore extends FluxStore { getSince(userId: string): string; /** @returns Format: [userId: Enum value from constants.RelationshipTypes] */ - getMutableRelationships(): Record<number, number>; + getMutableRelationships(): Map<string, number>; } From 643122e323fd9b36b456b42ef13e159f5b10015e Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:09:10 -0700 Subject: [PATCH 008/141] Experiments: fix edge case in experiment rollout detection (#3497) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/experiments/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 967197ad..eee732d0 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -68,8 +68,8 @@ export default definePlugin({ { find: 'type:"user",revision', replacement: { - match: /!(\i)&&"CONNECTION_OPEN".+?;/g, - replace: "$1=!0;" + match: /!(\i)(?=&&"CONNECTION_OPEN")/, + replace: "!($1=true)" } }, { From f4888f29f192a9924e19f2c24fafdbe2e381267e Mon Sep 17 00:00:00 2001 From: dorkbutt <git@nomadants.net> Date: Fri, 4 Jul 2025 13:12:52 -0400 Subject: [PATCH 009/141] equal parts lazy and educated CSP fix. --- src/main/csp/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts index c4192f4b..72c29ca0 100644 --- a/src/main/csp/index.ts +++ b/src/main/csp/index.ts @@ -63,6 +63,9 @@ export const CspPolicies: PolicyMap = { "dearrow-thumb.ajay.app": ImageSrc, // Dearrow Thumbnail CDN "usrbg.is-hardly.online": ImageSrc, // USRBG API "icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search) + + // forked addition + "usrbg.dorkbutt.lol": ImageAndCssSrc, }; const findHeader = (headers: PolicyMap, headerName: Lowercase<string>) => { From a202904f7dc76c7aafeca766c8ca5431d2b434d0 Mon Sep 17 00:00:00 2001 From: dorkbutt <git@nomadants.net> Date: Fri, 4 Jul 2025 13:15:16 -0400 Subject: [PATCH 010/141] add all subdomains for my personal domain. --- src/main/csp/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts index 72c29ca0..9075b9ad 100644 --- a/src/main/csp/index.ts +++ b/src/main/csp/index.ts @@ -65,7 +65,7 @@ export const CspPolicies: PolicyMap = { "icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search) // forked addition - "usrbg.dorkbutt.lol": ImageAndCssSrc, + "*.dorkbutt.lol": ImageAndCssSrc; }; const findHeader = (headers: PolicyMap, headerName: Lowercase<string>) => { From 82f668ab92afd21302833b278f215a7abebfcbb8 Mon Sep 17 00:00:00 2001 From: dorkbutt <git@nomadants.net> Date: Fri, 4 Jul 2025 13:16:12 -0400 Subject: [PATCH 011/141] replaced incorrect semicolon with comma --- src/main/csp/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts index 9075b9ad..a46da7ee 100644 --- a/src/main/csp/index.ts +++ b/src/main/csp/index.ts @@ -65,7 +65,7 @@ export const CspPolicies: PolicyMap = { "icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search) // forked addition - "*.dorkbutt.lol": ImageAndCssSrc; + "*.dorkbutt.lol": ImageAndCssSrc, }; const findHeader = (headers: PolicyMap, headerName: Lowercase<string>) => { From 44f7ccafcbce7b3aca2c7953418ba04488ce2502 Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Tue, 8 Jul 2025 01:04:14 +0200 Subject: [PATCH 012/141] MutualGroupDMs: fix member count label (#3541) --- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 796a91db..b90eb7ed 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -24,7 +24,7 @@ import { isNonNullish } from "@utils/guards"; import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; +import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, Text, useMemo, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; import { JSX } from "react"; @@ -73,7 +73,7 @@ function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { </Avatar> <div className={MutualsListClasses.details}> <div className={MutualsListClasses.name}>{getGroupDMName(c)}</div> - <div className={MutualsListClasses.nick}>{c.recipients.length + 1} Members</div> + <Text variant="text-xs/medium">{c.recipients.length + 1} Members</Text> </div> </Clickable> )); From c55833d95f4a15c5a8ebdc45a5087234d40d90fe Mon Sep 17 00:00:00 2001 From: nyx <60797172+verticalsync@users.noreply.github.com> Date: Tue, 8 Jul 2025 02:05:56 +0300 Subject: [PATCH 013/141] ShowMeYourName: fix gradient role compatibility (#3495) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/showMeYourName/styles.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/showMeYourName/styles.css b/src/plugins/showMeYourName/styles.css index 7a1455d9..e93fc451 100644 --- a/src/plugins/showMeYourName/styles.css +++ b/src/plugins/showMeYourName/styles.css @@ -1,5 +1,7 @@ .vc-smyn-suffix { color: var(--text-muted); + -webkit-text-fill-color: initial; + isolation: isolate; } .vc-smyn-suffix::before { From dc9064326b63bba837b7260d2b14b9c2926f5645 Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Thu, 10 Jul 2025 00:38:39 +0200 Subject: [PATCH 014/141] refactor discord types into separate npm package (#3520) Co-authored-by: V <vendicated@riseup.net> Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com> Co-authored-by: ezzud <contact@ezzud.fr> --- package.json | 2 +- packages/discord-types/LICENSE | 165 +++++++++++ packages/discord-types/README.md | 42 +++ packages/discord-types/enums/commands.ts | 27 ++ packages/discord-types/enums/index.ts | 1 + packages/discord-types/package.json | 19 ++ packages/discord-types/src/classes.d.ts | 21 ++ .../discord-types/src/common/Channel.d.ts | 83 ++++++ packages/discord-types/src/common/Guild.d.ts | 64 +++++ .../discord-types/src/common/GuildMember.d.ts | 26 ++ packages/discord-types/src/common/Record.d.ts | 12 + packages/discord-types/src/common/Role.d.ts | 33 +++ packages/discord-types/src/common/User.d.ts | 66 +++++ packages/discord-types/src/common/index.d.ts | 7 + .../src/common/messages/Commands.d.ts | 61 +++++ .../src/common/messages/Embed.d.ts | 70 +++++ .../src/common/messages/Emoji.d.ts | 42 +++ .../src/common/messages/Message.d.ts | 191 +++++++++++++ .../src/common/messages/index.d.ts | 4 + .../discord-types/src}/components.d.ts | 22 +- packages/discord-types/src/flux.d.ts | 30 ++ packages/discord-types/src/fluxEvents.d.ts | 22 ++ packages/discord-types/src/index.d.ts | 9 + .../discord-types/src}/menu.d.ts | 20 +- .../src/stores/ChannelStore.d.ts | 24 ++ .../discord-types/src/stores/DraftStore.d.ts | 43 +++ .../discord-types/src/stores/EmojiStore.d.ts | 57 ++++ .../discord-types/src/stores/FluxStore.d.ts | 44 +++ .../src/stores/GuildMemberStore.d.ts | 27 ++ .../src/stores/GuildRoleStore.d.ts | 7 + .../discord-types/src/stores/GuildStore.d.ts | 8 + .../src/stores/MessageStore.d.ts | 13 + .../src/stores/RelationshipStore.d.ts | 21 ++ .../src/stores/SelectedChannelStore.d.ts | 14 + .../src/stores/SelectedGuildStore.d.ts | 14 + .../discord-types/src/stores/ThemeStore.d.ts | 18 ++ .../discord-types/src/stores/UserStore.d.ts | 10 + .../discord-types/src/stores/WindowStore.d.ts | 7 + packages/discord-types/src/stores/index.d.ts | 32 +++ .../discord-types/src}/utils.d.ts | 36 ++- .../discord-types/webpack/index.d.ts | 27 +- packages/vencord-types/package.json | 1 - pnpm-lock.yaml | 43 ++- scripts/build/build.mjs | 2 +- scripts/build/common.mjs | 2 +- src/api/ChatButtons.tsx | 2 +- src/api/Commands/commandHelpers.ts | 10 +- src/api/Commands/index.ts | 25 +- src/api/Commands/types.ts | 108 +------- src/api/DataStore/index.ts | 4 +- src/api/MemberListDecorators.tsx | 2 +- src/api/MessageDecorations.tsx | 2 +- src/api/MessageEvents.ts | 3 +- src/api/MessagePopover.tsx | 2 +- src/api/MessageUpdater.ts | 3 +- src/api/Settings.ts | 2 +- src/components/DonateButton.tsx | 2 +- .../PluginSettings/ContributorModal.tsx | 2 +- src/components/PluginSettings/PluginModal.tsx | 2 +- src/debug/loadLazyChunks.ts | 3 +- src/debug/runReporter.ts | 3 +- src/main/patcher.ts | 2 +- src/plugins/_api/badges/index.tsx | 2 +- src/plugins/_core/noTrack.ts | 2 +- src/plugins/_core/settings.tsx | 2 +- src/plugins/_core/supportHelper.tsx | 3 +- .../accountPanelServerProfile/index.tsx | 2 +- src/plugins/biggerStreamPreview/index.tsx | 2 +- .../webpack/types/stores.ts | 2 +- src/plugins/copyUserURLs/index.tsx | 2 +- .../decor/lib/stores/UsersDecorationsStore.ts | 2 +- .../decor/ui/modals/ChangeDecorationModal.tsx | 2 +- src/plugins/expressionCloner/index.tsx | 2 +- src/plugins/fakeNitro/index.tsx | 4 +- src/plugins/fakeProfileThemes/index.tsx | 8 +- src/plugins/favEmojiFirst/index.ts | 2 +- src/plugins/forceOwnerCrown/index.ts | 2 +- src/plugins/fullSearchContext/index.tsx | 2 +- src/plugins/greetStickerPicker/index.tsx | 2 +- src/plugins/hideAttachments/index.tsx | 4 +- src/plugins/index.ts | 2 +- src/plugins/invisibleChat.desktop/index.tsx | 2 +- src/plugins/memberCount/index.tsx | 2 +- src/plugins/mentionAvatars/index.tsx | 2 +- src/plugins/messageLatency/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 6 +- src/plugins/messageLogger/index.tsx | 2 +- src/plugins/messageTags/index.ts | 4 +- src/plugins/mutualGroupDMs/index.tsx | 2 +- src/plugins/newGuildSettings/index.tsx | 2 +- src/plugins/noBlockedMessages/index.ts | 2 +- src/plugins/noReplyMention/index.tsx | 2 +- src/plugins/onePingPerDM/index.ts | 2 +- src/plugins/pauseInvitesForever/index.tsx | 1 - .../components/RolesAndUsersPermissions.tsx | 3 +- .../components/UserPermissions.tsx | 2 +- src/plugins/permissionsViewer/index.tsx | 4 +- src/plugins/permissionsViewer/utils.ts | 2 +- src/plugins/petpet/index.ts | 7 +- src/plugins/pinDms/index.tsx | 2 +- src/plugins/platformIndicators/index.tsx | 2 +- src/plugins/previewMessage/index.tsx | 2 +- src/plugins/quickReply/index.ts | 2 +- .../readAllNotificationsButton/index.tsx | 2 +- src/plugins/relationshipNotifier/types.ts | 2 +- src/plugins/relationshipNotifier/utils.ts | 2 +- src/plugins/replyTimestamp/index.tsx | 2 +- src/plugins/reviewDB/index.tsx | 2 +- src/plugins/seeSummaries/index.tsx | 1 - src/plugins/serverInfo/GuildInfoModal.tsx | 2 +- src/plugins/serverInfo/index.tsx | 2 +- src/plugins/showConnections/index.tsx | 2 +- .../components/HiddenChannelLockScreen.tsx | 2 +- src/plugins/showHiddenChannels/index.tsx | 2 +- src/plugins/showMeYourName/index.tsx | 4 +- src/plugins/showTimeoutDuration/index.tsx | 2 +- src/plugins/sortFriendRequests/index.tsx | 2 +- src/plugins/spotifyShareCommands/index.ts | 3 +- src/plugins/themeAttributes/index.ts | 2 +- .../translate/TranslationAccessory.tsx | 2 +- src/plugins/typingIndicator/index.tsx | 2 +- src/plugins/typingTweaks/index.tsx | 2 +- src/plugins/unsuppressEmbeds/index.tsx | 2 +- .../PronounsChatComponent.tsx | 2 +- src/plugins/userVoiceShow/components.tsx | 2 +- src/plugins/validReply/index.ts | 3 +- src/plugins/vcNarrator/index.tsx | 11 +- src/plugins/viewIcons/index.tsx | 2 +- src/plugins/viewRaw/index.tsx | 2 +- src/plugins/whoReacted/index.tsx | 3 +- src/plugins/xsOverlay/index.tsx | 2 +- src/utils/apng-canvas.js | 19 +- src/utils/discord.tsx | 5 +- src/utils/types.ts | 3 +- src/webpack/common/classes.ts | 3 +- src/webpack/common/components.ts | 2 +- src/webpack/common/index.ts | 3 - src/webpack/common/menu.ts | 3 +- src/webpack/common/stores.ts | 24 +- src/webpack/common/types/classes.d.ts | 39 --- src/webpack/common/types/fluxEvents.d.ts | 40 --- src/webpack/common/types/index.d.ts | 24 -- src/webpack/common/types/stores.d.ts | 259 ------------------ src/webpack/common/utils.ts | 6 +- src/webpack/index.ts | 2 +- src/webpack/patchWebpack.ts | 3 +- src/webpack/types.ts | 28 ++ src/webpack/webpack.ts | 5 +- tsconfig.json | 3 +- 149 files changed, 1561 insertions(+), 751 deletions(-) create mode 100644 packages/discord-types/LICENSE create mode 100644 packages/discord-types/README.md create mode 100644 packages/discord-types/enums/commands.ts create mode 100644 packages/discord-types/enums/index.ts create mode 100644 packages/discord-types/package.json create mode 100644 packages/discord-types/src/classes.d.ts create mode 100644 packages/discord-types/src/common/Channel.d.ts create mode 100644 packages/discord-types/src/common/Guild.d.ts create mode 100644 packages/discord-types/src/common/GuildMember.d.ts create mode 100644 packages/discord-types/src/common/Record.d.ts create mode 100644 packages/discord-types/src/common/Role.d.ts create mode 100644 packages/discord-types/src/common/User.d.ts create mode 100644 packages/discord-types/src/common/index.d.ts create mode 100644 packages/discord-types/src/common/messages/Commands.d.ts create mode 100644 packages/discord-types/src/common/messages/Embed.d.ts create mode 100644 packages/discord-types/src/common/messages/Emoji.d.ts create mode 100644 packages/discord-types/src/common/messages/Message.d.ts create mode 100644 packages/discord-types/src/common/messages/index.d.ts rename {src/webpack/common/types => packages/discord-types/src}/components.d.ts (89%) create mode 100644 packages/discord-types/src/flux.d.ts create mode 100644 packages/discord-types/src/fluxEvents.d.ts create mode 100644 packages/discord-types/src/index.d.ts rename {src/webpack/common/types => packages/discord-types/src}/menu.d.ts (71%) create mode 100644 packages/discord-types/src/stores/ChannelStore.d.ts create mode 100644 packages/discord-types/src/stores/DraftStore.d.ts create mode 100644 packages/discord-types/src/stores/EmojiStore.d.ts create mode 100644 packages/discord-types/src/stores/FluxStore.d.ts create mode 100644 packages/discord-types/src/stores/GuildMemberStore.d.ts create mode 100644 packages/discord-types/src/stores/GuildRoleStore.d.ts create mode 100644 packages/discord-types/src/stores/GuildStore.d.ts create mode 100644 packages/discord-types/src/stores/MessageStore.d.ts create mode 100644 packages/discord-types/src/stores/RelationshipStore.d.ts create mode 100644 packages/discord-types/src/stores/SelectedChannelStore.d.ts create mode 100644 packages/discord-types/src/stores/SelectedGuildStore.d.ts create mode 100644 packages/discord-types/src/stores/ThemeStore.d.ts create mode 100644 packages/discord-types/src/stores/UserStore.d.ts create mode 100644 packages/discord-types/src/stores/WindowStore.d.ts create mode 100644 packages/discord-types/src/stores/index.d.ts rename {src/webpack/common/types => packages/discord-types/src}/utils.d.ts (92%) rename src/webpack/wreq.d.ts => packages/discord-types/webpack/index.d.ts (90%) delete mode 100644 src/webpack/common/types/classes.d.ts delete mode 100644 src/webpack/common/types/fluxEvents.d.ts delete mode 100644 src/webpack/common/types/index.d.ts delete mode 100644 src/webpack/common/types/stores.d.ts create mode 100644 src/webpack/types.ts diff --git a/package.json b/package.json index 8a6173ef..81b9a1c5 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@types/yazl": "^2.4.5", + "@vencord/discord-types": "link:packages/discord-types", "diff": "^7.0.0", - "discord-types": "^1.3.26", "esbuild": "^0.25.1", "eslint": "9.20.1", "eslint-import-resolver-alias": "^1.1.2", diff --git a/packages/discord-types/LICENSE b/packages/discord-types/LICENSE new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/packages/discord-types/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/packages/discord-types/README.md b/packages/discord-types/README.md new file mode 100644 index 00000000..82a98dd8 --- /dev/null +++ b/packages/discord-types/README.md @@ -0,0 +1,42 @@ +# Discord Types + +This package provides TypeScript types for the Webpack modules of Discord's web app. + +While it was primarily created for Vencord, other client mods could also benefit from this, so it is published as a standalone package! + +## Installation + +```bash +npm install -D @vencord/discord-types +yarn add -D @vencord/discord-types +pnpm add -D @vencord/discord-types +``` + +## Example Usage + +```ts +import type { UserStore } from "@vencord/discord-types"; + +const userStore: UserStore = findStore("UserStore"); // findStore is up to you to implement, this library only provides types and no runtime code +``` + +## Enums + +This library also exports some const enums that you can use from Typescript code: +```ts +import { ApplicationCommandType } from "@vencord/discord-types/enums"; + +console.log(ApplicationCommandType.CHAT_INPUT); // 1 +``` + +### License + +This package is licensed under the [LGPL-3.0](./LICENSE) (or later) license. + +A very short summary of the license is that you can use this package as a library in both open source and closed source projects, +similar to an MIT-licensed project. +However, if you modify the code of this package, you must release source code of your modified version under the same license. + +### Credit + +This package was inspired by Swishilicous' [discord-types](https://www.npmjs.com/package/discord-types) package. diff --git a/packages/discord-types/enums/commands.ts b/packages/discord-types/enums/commands.ts new file mode 100644 index 00000000..0556e72d --- /dev/null +++ b/packages/discord-types/enums/commands.ts @@ -0,0 +1,27 @@ +export const enum ApplicationCommandOptionType { + SUB_COMMAND = 1, + SUB_COMMAND_GROUP = 2, + STRING = 3, + INTEGER = 4, + BOOLEAN = 5, + USER = 6, + CHANNEL = 7, + ROLE = 8, + MENTIONABLE = 9, + NUMBER = 10, + ATTACHMENT = 11, +} + +export const enum ApplicationCommandInputType { + BUILT_IN = 0, + BUILT_IN_TEXT = 1, + BUILT_IN_INTEGRATION = 2, + BOT = 3, + PLACEHOLDER = 4, +} + +export const enum ApplicationCommandType { + CHAT_INPUT = 1, + USER = 2, + MESSAGE = 3, +} diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts new file mode 100644 index 00000000..62074d46 --- /dev/null +++ b/packages/discord-types/enums/index.ts @@ -0,0 +1 @@ +export * from "./commands"; diff --git a/packages/discord-types/package.json b/packages/discord-types/package.json new file mode 100644 index 00000000..7b8726fd --- /dev/null +++ b/packages/discord-types/package.json @@ -0,0 +1,19 @@ +{ + "name": "@vencord/discord-types", + "author": "Vencord Contributors", + "description": "Typescript definitions for the webpack modules of the Discord Web app", + "version": "1.0.0", + "license": "LGPL-3.0-or-later", + "types": "src/index.d.ts", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/Vendicated/Vencord.git", + "directory": "packages/discord-types" + }, + "dependencies": { + "@types/react": "^19.0.10", + "moment": "^2.22.2", + "type-fest": "^4.41.0" + } +} diff --git a/packages/discord-types/src/classes.d.ts b/packages/discord-types/src/classes.d.ts new file mode 100644 index 00000000..752ba3f0 --- /dev/null +++ b/packages/discord-types/src/classes.d.ts @@ -0,0 +1,21 @@ +export interface ImageModalClasses { + image: string, + modal: string, +} + +export interface ButtonWrapperClasses { + hoverScale: string; + buttonWrapper: string; + button: string; + iconMask: string; + buttonContent: string; + icon: string; + pulseIcon: string; + pulseButton: string; + notificationDot: string; + sparkleContainer: string; + sparkleStar: string; + sparklePlus: string; + sparkle: string; + active: string; +} diff --git a/packages/discord-types/src/common/Channel.d.ts b/packages/discord-types/src/common/Channel.d.ts new file mode 100644 index 00000000..7ad5ce53 --- /dev/null +++ b/packages/discord-types/src/common/Channel.d.ts @@ -0,0 +1,83 @@ +import { DiscordRecord } from "./Record"; + +export class Channel extends DiscordRecord { + constructor(channel: object); + application_id: number | undefined; + bitrate: number; + defaultAutoArchiveDuration: number | undefined; + flags: number; + guild_id: string; + icon: string; + id: string; + lastMessageId: string; + lastPinTimestamp: string | undefined; + member: unknown; + memberCount: number | undefined; + memberIdsPreview: string[] | undefined; + memberListId: unknown; + messageCount: number | undefined; + name: string; + nicks: Record<string, unknown>; + nsfw: boolean; + originChannelId: unknown; + ownerId: string; + parent_id: string; + permissionOverwrites: { + [role: string]: { + id: string; + type: number; + deny: bigint; + allow: bigint; + }; + }; + position: number; + rateLimitPerUser: number; + rawRecipients: { + id: string; + avatar: string; + username: string; + public_flags: number; + discriminator: string; + }[]; + recipients: string[]; + rtcRegion: string; + threadMetadata: { + locked: boolean; + archived: boolean; + invitable: boolean; + createTimestamp: string | undefined; + autoArchiveDuration: number; + archiveTimestamp: string | undefined; + }; + topic: string; + type: number; + userLimit: number; + videoQualityMode: undefined; + + get accessPermissions(): bigint; + get lastActiveTimestamp(): number; + + computeLurkerPermissionsAllowList(): unknown; + getApplicationId(): unknown; + getGuildId(): string; + getRecipientId(): unknown; + hasFlag(flag: number): boolean; + isActiveThread(): boolean; + isArchivedThread(): boolean; + isCategory(): boolean; + isDM(): boolean; + isDirectory(): boolean; + isForumChannel(): boolean; + isGroupDM(): boolean; + isGuildStageVoice(): boolean; + isGuildVoice(): boolean; + isListenModeCapable(): boolean; + isManaged(): boolean; + isMultiUserDM(): boolean; + isNSFW(): boolean; + isOwner(): boolean; + isPrivate(): boolean; + isSystemDM(): boolean; + isThread(): boolean; + isVocal(): boolean; +} diff --git a/packages/discord-types/src/common/Guild.d.ts b/packages/discord-types/src/common/Guild.d.ts new file mode 100644 index 00000000..5b5c3c74 --- /dev/null +++ b/packages/discord-types/src/common/Guild.d.ts @@ -0,0 +1,64 @@ +import { Role } from './Role'; +import { DiscordRecord } from './Record'; + +// copy(Object.keys(findByProps("CREATOR_MONETIZABLE")).map(JSON.stringify).join("|")) +export type GuildFeatures = + "INVITE_SPLASH" | "VIP_REGIONS" | "VANITY_URL" | "MORE_EMOJI" | "MORE_STICKERS" | "MORE_SOUNDBOARD" | "VERIFIED" | "COMMERCE" | "DISCOVERABLE" | "COMMUNITY" | "FEATURABLE" | "NEWS" | "HUB" | "PARTNERED" | "ANIMATED_ICON" | "BANNER" | "ENABLED_DISCOVERABLE_BEFORE" | "WELCOME_SCREEN_ENABLED" | "MEMBER_VERIFICATION_GATE_ENABLED" | "PREVIEW_ENABLED" | "ROLE_SUBSCRIPTIONS_ENABLED" | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" | "CREATOR_MONETIZABLE" | "CREATOR_MONETIZABLE_PROVISIONAL" | "CREATOR_MONETIZABLE_WHITEGLOVE" | "CREATOR_MONETIZABLE_DISABLED" | "CREATOR_MONETIZABLE_RESTRICTED" | "CREATOR_STORE_PAGE" | "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" | "PRODUCTS_AVAILABLE_FOR_PURCHASE" | "GUILD_WEB_PAGE_VANITY_URL" | "THREADS_ENABLED" | "THREADS_ENABLED_TESTING" | "NEW_THREAD_PERMISSIONS" | "ROLE_ICONS" | "TEXT_IN_STAGE_ENABLED" | "TEXT_IN_VOICE_ENABLED" | "HAS_DIRECTORY_ENTRY" | "ANIMATED_BANNER" | "LINKED_TO_HUB" | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" | "GUILD_HOME_DEPRECATION_OVERRIDE" | "GUILD_HOME_TEST" | "GUILD_HOME_OVERRIDE" | "GUILD_ONBOARDING" | "GUILD_ONBOARDING_EVER_ENABLED" | "GUILD_ONBOARDING_HAS_PROMPTS" | "GUILD_SERVER_GUIDE" | "INTERNAL_EMPLOYEE_ONLY" | "AUTO_MODERATION" | "INVITES_DISABLED" | "BURST_REACTIONS" | "SOUNDBOARD" | "SHARD" | "ACTIVITY_FEED_ENABLED_BY_USER" | "ACTIVITY_FEED_DISABLED_BY_USER" | "SUMMARIES_ENABLED_GA" | "LEADERBOARD_ENABLED" | "SUMMARIES_ENABLED_BY_USER" | "SUMMARIES_OPT_OUT_EXPERIENCE" | "CHANNEL_ICON_EMOJIS_GENERATED" | "NON_COMMUNITY_RAID_ALERTS" | "RAID_ALERTS_DISABLED" | "AUTOMOD_TRIGGER_USER_PROFILE" | "ENABLED_MODERATION_EXPERIENCE_FOR_NON_COMMUNITY" | "GUILD_PRODUCTS_ALLOW_ARCHIVED_FILE" | "CLAN" | "MEMBER_VERIFICATION_MANUAL_APPROVAL" | "FORWARDING_DISABLED" | "MEMBER_VERIFICATION_ROLLOUT_TEST" | "AUDIO_BITRATE_128_KBPS" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS" | "VIDEO_BITRATE_ENHANCED" | "MAX_FILE_SIZE_50_MB" | "MAX_FILE_SIZE_100_MB" | "GUILD_TAGS" | "ENHANCED_ROLE_COLORS" | "PREMIUM_TIER_3_OVERRIDE" | "REPORT_TO_MOD_PILOT" | "TIERLESS_BOOSTING_SYSTEM_MESSAGE"; +export type GuildPremiumFeatures = + "ANIMATED_ICON" | "STAGE_CHANNEL_VIEWERS_150" | "ROLE_ICONS" | "GUILD_TAGS" | "BANNER" | "MAX_FILE_SIZE_50_MB" | "VIDEO_QUALITY_720_60FPS" | "STAGE_CHANNEL_VIEWERS_50" | "VIDEO_QUALITY_1080_60FPS" | "MAX_FILE_SIZE_100_MB" | "VANITY_URL" | "VIDEO_BITRATE_ENHANCED" | "STAGE_CHANNEL_VIEWERS_300" | "AUDIO_BITRATE_128_KBPS" | "ANIMATED_BANNER" | "TIERLESS_BOOSTING" | "ENHANCED_ROLE_COLORS" | "INVITE_SPLASH" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS"; + +export class Guild extends DiscordRecord { + constructor(guild: object); + afkChannelId: string | undefined; + afkTimeout: number; + applicationCommandCounts: { + 0: number; + 1: number; + 2: number; + }; + application_id: unknown; + banner: string | undefined; + defaultMessageNotifications: number; + description: string | undefined; + discoverySplash: string | undefined; + explicitContentFilter: number; + features: Set<GuildFeatures>; + homeHeader: string | undefined; + hubType: unknown; + icon: string | undefined; + id: string; + joinedAt: Date; + latestOnboardingQuestionId: string | undefined; + maxMembers: number; + maxStageVideoChannelUsers: number; + maxVideoChannelUsers: number; + mfaLevel: number; + moderatorReporting: unknown; + name: string; + nsfwLevel: number; + ownerConfiguredContentLevel: number; + ownerId: string; + preferredLocale: string; + premiumFeatures: { + additionalEmojiSlots: number; + additionalSoundSlots: number; + additionalStickerSlots: number; + features: Array<GuildPremiumFeatures>; + }; + premiumProgressBarEnabled: boolean; + premiumSubscriberCount: number; + premiumTier: number; + profile: { + badge: string | undefined; + tag: string | undefined; + } | undefined; + publicUpdatesChannelId: string | undefined; + roles: Record<string, Role>; + rulesChannelId: string | undefined; + safetyAlertsChannelId: string | undefined; + splash: string | undefined; + systemChannelFlags: number; + systemChannelId: string | undefined; + vanityURLCode: string | undefined; + verificationLevel: number; +} diff --git a/packages/discord-types/src/common/GuildMember.d.ts b/packages/discord-types/src/common/GuildMember.d.ts new file mode 100644 index 00000000..5cd47682 --- /dev/null +++ b/packages/discord-types/src/common/GuildMember.d.ts @@ -0,0 +1,26 @@ +export interface GuildMember { + avatar: string | undefined; + avatarDecoration: string | undefined; + banner: string | undefined; + bio: string; + colorRoleId: string | undefined; + colorString: string; + colorStrings: { + primaryColor: string | undefined; + secondaryColor: string | undefined; + tertiaryColor: string | undefined; + }; + communicationDisabledUntil: string | undefined; + flags: number; + fullProfileLoadedTimestamp: number; + guildId: string; + highestRoleId: string; + hoistRoleId: string; + iconRoleId: string; + isPending: boolean | undefined; + joinedAt: string | undefined; + nick: string | undefined; + premiumSince: string | undefined; + roles: string[]; + userId: string; +} diff --git a/packages/discord-types/src/common/Record.d.ts b/packages/discord-types/src/common/Record.d.ts new file mode 100644 index 00000000..8f033058 --- /dev/null +++ b/packages/discord-types/src/common/Record.d.ts @@ -0,0 +1,12 @@ +type Updater = (value: any) => any; + +/** + * Common Record class extended by various Discord data structures, like User, Channel, Guild, etc. + */ +export class DiscordRecord { + toJS(): Record<string, any>; + + set(key: string, value: any): this; + merge(data: Record<string, any>): this; + update(key: string, defaultValueOrUpdater: Updater | any, updater?: Updater): this; +} diff --git a/packages/discord-types/src/common/Role.d.ts b/packages/discord-types/src/common/Role.d.ts new file mode 100644 index 00000000..7b038984 --- /dev/null +++ b/packages/discord-types/src/common/Role.d.ts @@ -0,0 +1,33 @@ +export interface Role { + color: number; + colorString: string | undefined; + colorStrings: { + primaryColor: string | undefined; + secondaryColor: string | undefined; + tertiaryColor: string | undefined; + }; + colors: { + primary_color: number | undefined; + secondary_color: number | undefined; + tertiary_color: number | undefined; + }; + flags: number; + hoist: boolean; + icon: string | undefined; + id: string; + managed: boolean; + mentionable: boolean; + name: string; + originalPosition: number; + permissions: bigint; + position: number; + /** + * probably incomplete + */ + tags: { + bot_id: string; + integration_id: string; + premium_subscriber: unknown; + } | undefined; + unicodeEmoji: string | undefined; +} diff --git a/packages/discord-types/src/common/User.d.ts b/packages/discord-types/src/common/User.d.ts new file mode 100644 index 00000000..a5503bcb --- /dev/null +++ b/packages/discord-types/src/common/User.d.ts @@ -0,0 +1,66 @@ +// TODO: a lot of optional params can also be null, not just undef + +import { DiscordRecord } from "./Record"; + +export class User extends DiscordRecord { + constructor(user: object); + accentColor: number; + avatar: string; + banner: string; + bio: string; + bot: boolean; + desktop: boolean; + discriminator: string; + email: string | undefined; + flags: number; + globalName: string | undefined; + guildMemberAvatars: Record<string, string>; + id: string; + mfaEnabled: boolean; + mobile: boolean; + nsfwAllowed: boolean | undefined; + phone: string | undefined; + premiumType: number | undefined; + premiumUsageFlags: number; + publicFlags: number; + purchasedFlags: number; + system: boolean; + username: string; + verified: boolean; + themeColors?: [number, number]; + + get createdAt(): Date; + get hasPremiumPerks(): boolean; + get tag(): string; + get usernameNormalized(): string; + + addGuildAvatarHash(guildId: string, avatarHash: string): User; + getAvatarSource(guildId: string, canAnimate?: boolean): { uri: string; }; + getAvatarURL(guildId?: string | null, t?: unknown, canAnimate?: boolean): string; + hasAvatarForGuild(guildId: string): boolean; + hasDisabledPremium(): boolean; + hasFlag(flag: number): boolean; + hasFreePremium(): boolean; + hasHadSKU(e: unknown): boolean; + hasPremiumUsageFlag(flag: number): boolean; + hasPurchasedFlag(flag: number): boolean; + hasUrgentMessages(): boolean; + isClaimed(): boolean; + isLocalBot(): boolean; + isNonUserBot(): boolean; + isPhoneVerified(): boolean; + isStaff(): boolean; + isSystemUser(): boolean; + isVerifiedBot(): boolean; + removeGuildAvatarHash(guildId: string): User; + toString(): string; +} + +export interface UserJSON { + avatar: string; + avatarDecoration: unknown | undefined; + discriminator: string; + id: string; + publicFlags: number; + username: string; +} diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts new file mode 100644 index 00000000..f85a7c72 --- /dev/null +++ b/packages/discord-types/src/common/index.d.ts @@ -0,0 +1,7 @@ +export * from "./Channel"; +export * from "./Guild"; +export * from "./GuildMember"; +export * from "./messages"; +export * from "./Role"; +export * from "./User"; +export * from "./Record"; diff --git a/packages/discord-types/src/common/messages/Commands.d.ts b/packages/discord-types/src/common/messages/Commands.d.ts new file mode 100644 index 00000000..8dc9f9a7 --- /dev/null +++ b/packages/discord-types/src/common/messages/Commands.d.ts @@ -0,0 +1,61 @@ +import { Channel } from "../Channel"; +import { Guild } from "../Guild"; +import { Promisable } from "type-fest"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "../../../enums"; + +export interface CommandContext { + channel: Channel; + guild?: Guild; +} + +export interface CommandOption { + name: string; + displayName?: string; + type: ApplicationCommandOptionType; + description: string; + displayDescription?: string; + required?: boolean; + options?: CommandOption[]; + choices?: Array<ChoicesOption>; +} + +export interface ChoicesOption { + label: string; + value: string; + name: string; + displayName?: string; +} + +export interface CommandReturnValue { + content: string; + // TODO: implement + // cancel?: boolean; +} + +export interface CommandArgument { + type: ApplicationCommandOptionType; + name: string; + value: string; + focused: undefined; + options: CommandArgument[]; +} + +export interface Command { + id?: string; + applicationId?: string; + type?: ApplicationCommandType; + inputType?: ApplicationCommandInputType; + plugin?: string; + + name: string; + untranslatedName?: string; + displayName?: string; + description: string; + untranslatedDescription?: string; + displayDescription?: string; + + options?: CommandOption[]; + predicate?(ctx: CommandContext): boolean; + + execute(args: CommandArgument[], ctx: CommandContext): Promisable<void | CommandReturnValue>; +} diff --git a/packages/discord-types/src/common/messages/Embed.d.ts b/packages/discord-types/src/common/messages/Embed.d.ts new file mode 100644 index 00000000..4edd9ced --- /dev/null +++ b/packages/discord-types/src/common/messages/Embed.d.ts @@ -0,0 +1,70 @@ +export interface Embed { + author?: { + name: string; + url: string; + iconURL: string | undefined; + iconProxyURL: string | undefined; + }; + color: string; + fields: []; + id: string; + image?: { + height: number; + width: number; + url: string; + proxyURL: string; + }; + provider?: { + name: string; + url: string | undefined; + }; + rawDescription: string; + rawTitle: string; + referenceId: unknown; + timestamp: string; + thumbnail?: { + height: number; + proxyURL: string | undefined; + url: string; + width: number; + }; + type: string; + url: string | undefined; + video?: { + height: number; + width: number; + url: string; + proxyURL: string | undefined; + }; +} + +export interface EmbedJSON { + author?: { + name: string; + url: string; + icon_url: string; + proxy_icon_url: string; + }; + title: string; + color: string; + description: string; + type: string; + url: string | undefined; + provider?: { + name: string; + url: string; + }; + timestamp: string; + thumbnail?: { + height: number; + width: number; + url: string; + proxy_url: string | undefined; + }; + video?: { + height: number; + width: number; + url: string; + proxy_url: string | undefined; + }; +} diff --git a/packages/discord-types/src/common/messages/Emoji.d.ts b/packages/discord-types/src/common/messages/Emoji.d.ts new file mode 100644 index 00000000..793e90f8 --- /dev/null +++ b/packages/discord-types/src/common/messages/Emoji.d.ts @@ -0,0 +1,42 @@ +export type Emoji = CustomEmoji | UnicodeEmoji; + +export interface CustomEmoji { + type: 1; + allNamesString: string; + animated: boolean; + available: boolean; + guildId: string; + id: string; + managed: boolean; + name: string; + originalName?: string; + require_colons: boolean; + roles: string[]; +} + +export interface UnicodeEmoji { + type: 0; + diversityChildren: Record<any, any>; + emojiObject: { + names: string[]; + surrogates: string; + unicodeVersion: number; + }; + index: number; + surrogates: string; + uniqueName: string; + useSpriteSheet: boolean; + get allNamesString(): string; + get animated(): boolean; + get defaultDiversityChild(): any; + get hasDiversity(): boolean | undefined; + get hasDiversityParent(): boolean | undefined; + get hasMultiDiversity(): boolean | undefined; + get hasMultiDiversityParent(): boolean | undefined; + get managed(): boolean; + get name(): string; + get names(): string[]; + get optionallyDiverseSequence(): string | undefined; + get unicodeVersion(): number; + get url(): string; +} diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts new file mode 100644 index 00000000..38010caf --- /dev/null +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -0,0 +1,191 @@ +import { CommandOption } from './Commands'; +import { User, UserJSON } from '../User'; +import { Embed, EmbedJSON } from './Embed'; +import { DiscordRecord } from "../Record"; + +/** + * TODO: looks like discord has moved over to Date instead of Moment; + */ +export class Message extends DiscordRecord { + constructor(message: object); + activity: unknown; + application: unknown; + applicationId: string | unknown; + attachments: MessageAttachment[]; + author: User; + blocked: boolean; + bot: boolean; + call: { + duration: moment.Duration; + endedTimestamp: moment.Moment; + participants: string[]; + }; + channel_id: string; + /** + * NOTE: not fully typed + */ + codedLinks: { + code?: string; + type: string; + }[]; + colorString: unknown; + components: unknown[]; + content: string; + customRenderedContent: unknown; + editedTimestamp: Date; + embeds: Embed[]; + flags: number; + giftCodes: string[]; + id: string; + interaction: { + id: string; + name: string; + type: number; + user: User; + }[] | undefined; + interactionData: { + application_command: { + application_id: string; + default_member_permissions: unknown; + default_permission: boolean; + description: string; + dm_permission: unknown; + id: string; + name: string; + options: CommandOption[]; + permissions: unknown[]; + type: number; + version: string; + }; + attachments: MessageAttachment[]; + guild_id: string | undefined; + id: string; + name: string; + options: { + focused: unknown; + name: string; + type: number; + value: string; + }[]; + type: number; + version: string; + }[]; + interactionError: unknown[]; + isSearchHit: boolean; + loggingName: unknown; + mentionChannels: string[]; + mentionEveryone: boolean; + mentionRoles: string[]; + mentioned: boolean; + mentions: string[]; + messageReference: { + guild_id?: string; + channel_id: string; + message_id: string; + } | undefined; + nick: unknown; // probably a string + nonce: string | undefined; + pinned: boolean; + reactions: MessageReaction[]; + state: string; + stickerItems: { + format_type: number; + id: string; + name: string; + }[]; + stickers: unknown[]; + timestamp: moment.Moment; + tts: boolean; + type: number; + webhookId: string | undefined; + + /** + * Doesn't actually update the original message; it just returns a new message instance with the added reaction. + */ + addReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; + /** + * Searches each reaction and if the provided string has an index above -1 it'll return the reaction object. + */ + getReaction(name: string): MessageReaction; + /** + * Doesn't actually update the original message; it just returns the message instance without the reaction searched with the provided emoji object. + */ + removeReactionsForEmoji(emoji: ReactionEmoji): Message; + /** + * Doesn't actually update the original message; it just returns the message instance without the reaction. + */ + removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; + + getChannelId(): string; + hasFlag(flag: number): boolean; + isCommandType(): boolean; + isEdited(): boolean; + isSystemDM(): boolean; +} + +/** A smaller Message object found in FluxDispatcher and elsewhere. */ +export interface MessageJSON { + attachments: MessageAttachment[]; + author: UserJSON; + channel_id: string; + components: unknown[]; + content: string; + edited_timestamp: string; + embeds: EmbedJSON[]; + flags: number; + guild_id: string | undefined; + id: string; + loggingName: unknown; + member: { + avatar: string | undefined; + communication_disabled_until: string | undefined; + deaf: boolean; + hoisted_role: string | undefined; + is_pending: boolean; + joined_at: string; + mute: boolean; + nick: string | boolean; + pending: boolean; + premium_since: string | undefined; + roles: string[]; + } | undefined; + mention_everyone: boolean; + mention_roles: string[]; + mentions: UserJSON[]; + message_reference: { + guild_id?: string; + channel_id: string; + message_id: string; + } | undefined; + nonce: string | undefined; + pinned: boolean; + referenced_message: MessageJSON | undefined; + state: string; + timestamp: string; + tts: boolean; + type: number; +} + +export interface MessageAttachment { + filename: string; + id: string; + proxy_url: string; + size: number; + spoiler: boolean; + url: string; + content_type?: string; + width?: number; + height?: number; +} + +export interface ReactionEmoji { + id: string | undefined; + name: string; + animated: boolean; +} + +export interface MessageReaction { + count: number; + emoji: ReactionEmoji; + me: boolean; +} diff --git a/packages/discord-types/src/common/messages/index.d.ts b/packages/discord-types/src/common/messages/index.d.ts new file mode 100644 index 00000000..245e971e --- /dev/null +++ b/packages/discord-types/src/common/messages/index.d.ts @@ -0,0 +1,4 @@ +export * from "./Commands"; +export * from "./Message"; +export * from "./Embed"; +export * from "./Emoji"; diff --git a/src/webpack/common/types/components.d.ts b/packages/discord-types/src/components.d.ts similarity index 89% rename from src/webpack/common/types/components.d.ts rename to packages/discord-types/src/components.d.ts index 783663cb..8b2fd1cf 100644 --- a/src/webpack/common/types/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -1,25 +1,7 @@ -/* - * 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 type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react"; - -export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; +// copy(find(m => Array.isArray(m) && m.includes("heading-sm/normal")).map(JSON.stringify).join("|")) +export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-sm/extrabold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-md/extrabold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-lg/extrabold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/semibold" | "heading-xl/bold" | "heading-xl/extrabold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/semibold" | "heading-xxl/bold" | "heading-xxl/extrabold" | "eyebrow" | "heading-deprecated-12/normal" | "heading-deprecated-12/medium" | "heading-deprecated-12/semibold" | "heading-deprecated-12/bold" | "heading-deprecated-12/extrabold" | "redesign/heading-18/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "redesign/message-preview/normal" | "redesign/message-preview/medium" | "redesign/message-preview/semibold" | "redesign/message-preview/bold" | "redesign/channel-title/normal" | "redesign/channel-title/medium" | "redesign/channel-title/semibold" | "redesign/channel-title/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; diff --git a/packages/discord-types/src/flux.d.ts b/packages/discord-types/src/flux.d.ts new file mode 100644 index 00000000..bb5600dd --- /dev/null +++ b/packages/discord-types/src/flux.d.ts @@ -0,0 +1,30 @@ +import { FluxStore } from "./stores/FluxStore"; + +export class FluxEmitter { + constructor(); + + changeSentinel: number; + changedStores: Set<FluxStore>; + isBatchEmitting: boolean; + isDispatching: boolean; + isPaused: boolean; + pauseTimer: NodeJS.Timeout | null; + reactChangedStores: Set<FluxStore>; + + batched(batch: (...args: any[]) => void): void; + destroy(): void; + emit(): void; + emitNonReactOnce(): void; + emitReactOnce(): void; + getChangeSentinel(): number; + getIsPaused(): boolean; + injectBatchEmitChanges(batch: (...args: any[]) => void): void; + markChanged(store: FluxStore): void; + pause(): void; + resume(): void; +} + +export interface Flux { + Store: typeof FluxStore; + Emitter: FluxEmitter; +} diff --git a/packages/discord-types/src/fluxEvents.d.ts b/packages/discord-types/src/fluxEvents.d.ts new file mode 100644 index 00000000..c210f4b9 --- /dev/null +++ b/packages/discord-types/src/fluxEvents.d.ts @@ -0,0 +1,22 @@ +/* +function makeFluxEventList() { + // prefill MESSAGE_CREATE so that typescript infers this is a String Set + // without explicitly typing so that this function is also valid javascript + const events = new Set(["MESSAGE_CREATE"]); + + const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; + for (const nodeId in nodes) { + for (const event in nodes[nodeId].actionHandler) { + events.add(event); + } + } + for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { + events.add(event); + } + + return Array.from(events, e => JSON.stringify(e)).sort().join("|"); +} +copy(makeFluxEventList()) +*/ + +export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_CONTRAST" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CROSSFADES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACKNOWLEDGE_CHANNEL_SAFETY_WARNING_TOOLTIP" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_AV_ERRORS_CHANGED" | "ACTIVE_BOGO_PROMOTION_FETCH" | "ACTIVE_BOGO_PROMOTION_FETCH_FAIL" | "ACTIVE_BOGO_PROMOTION_FETCH_SUCCESS" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_INVITE_MODAL_QUERY" | "ACTIVITY_INVITE_MODAL_SEND" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_LAYOUT_MODE_UPDATE" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_POPOUT_WINDOW_OPEN" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "ADYEN_CASH_APP_PAY_SUBMIT_SUCCESS" | "ADYEN_CREATE_CASH_APP_PAY_COMPONENT_SUCCESS" | "ADYEN_CREATE_CLIENT_SUCCESS" | "ADYEN_TEARDOWN_CLIENT" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "APEX_EXPERIMENT_CLEAR_SERVER_ASSIGNMENTS" | "APEX_EXPERIMENT_OVERRIDE_CLEAR" | "APEX_EXPERIMENT_OVERRIDE_CREATE" | "APEX_EXPERIMENT_OVERRIDE_DELETE" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_ASSETS_FETCH" | "APPLICATION_ASSETS_FETCH_SUCCESS" | "APPLICATION_ASSETS_UPDATE" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_EXECUTE_BAD_VERSION" | "APPLICATION_COMMAND_INDEX_FETCH_FAILURE" | "APPLICATION_COMMAND_INDEX_FETCH_REQUEST" | "APPLICATION_COMMAND_INDEX_FETCH_SUCCESS" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_DIRECTORY_FETCH_APPLICATION" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_FAILURE" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_CATEGORIES_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SEARCH" | "APPLICATION_DIRECTORY_FETCH_SEARCH_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SEARCH_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_SUCCESS" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCHING" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_FAILED" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_SUCCESS" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLICATION_UPDATE" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_RESET" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_DM_OPEN" | "APP_ICON_EDITOR_RESET" | "APP_ICON_TRACK_IMPRESSION" | "APP_ICON_UPDATED" | "APP_LAUNCHER_ADD_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_DISMISS" | "APP_LAUNCHER_REMOVE_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_SET_ACTIVE_COMMAND" | "APP_LAUNCHER_SHOW" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_FAILURE" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_SUCCESS" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ACTIVE_INPUT_PROFILE" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_BYPASS_SYSTEM_INPUT_PROCESSING" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_KRISP_MODEL_OVERRIDE" | "AUDIO_SET_KRISP_SUPPRESSION_LEVEL" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SELF_MUTE" | "AUDIO_SET_SIDECHAIN_COMPRESSION" | "AUDIO_SET_SIDECHAIN_COMPRESSION_STRENGTH" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_LOCAL_SOUNDBOARD_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_TARGET" | "AUDIT_LOG_FILTER_BY_USER" | "AUTHENTICATOR_CREATE" | "AUTHENTICATOR_DELETE" | "AUTHENTICATOR_UPDATE" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "AUTO_UPDATER_QUIT_AND_INSTALL" | "BACKGROUND_SYNC" | "BACKGROUND_SYNC_CHANNEL_MESSAGES" | "BASIC_GUILD_FETCH" | "BASIC_GUILD_FETCH_FAILURE" | "BASIC_GUILD_FETCH_SUCCESS" | "BILLING_CREATE_REFERRAL_SUCCESS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_IP_LOCATION_FAILURE" | "BILLING_IP_LOCATION_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_NITRO_AFFINITY_FETCHED" | "BILLING_NITRO_AFFINITY_FETCH_SUCCEEDED" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PREVIOUS_PREMIUM_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_REFERRALS_REMAINING_FETCH_FAIL" | "BILLING_REFERRALS_REMAINING_FETCH_START" | "BILLING_REFERRALS_REMAINING_FETCH_SUCCESS" | "BILLING_REFERRAL_RESOLVE_FAIL" | "BILLING_REFERRAL_RESOLVE_SUCCESS" | "BILLING_REFERRAL_TRIAL_OFFER_UPDATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_IP_LOCATION" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_FAILURE" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_START" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_OFFER_FETCH_FAIL" | "BILLING_USER_OFFER_FETCH_START" | "BILLING_USER_OFFER_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_FAIL" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_FAIL" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_PICKER_ANIMATION_ADD" | "BURST_REACTION_PICKER_ANIMATION_CLEAR" | "CACHED_EMOJIS_LOADED" | "CACHED_STICKERS_LOADED" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CACHE_LOADED_LAZY_NO_CACHE" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CONNECT_MULTIPLE" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_FETCH_FAILED" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_MARK_SEEN" | "CHANGE_LOG_RESOLVED" | "CHANGE_LOG_SET_CONFIG" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MEMBER_COUNT_UPDATE" | "CHANNEL_MUTE_EXPIRED" | "CHANNEL_PERMISSIONS_DELETE_OVERWRITE_SUCCESS" | "CHANNEL_PERMISSIONS_PUT_OVERWRITE_SUCCESS" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_JUMP_TO_VOICE_CHANNEL_MESSAGE" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTCIPANTS_LIST_OPEN" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SAFETY_WARNING_FEEDBACK" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_STATUSES" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_TOGGLE_SUMMARIES_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECKOUT_RECOVERY_STATUS_FETCH" | "CHECKOUT_RECOVERY_STATUS_FETCH_FAILURE" | "CHECKOUT_RECOVERY_STATUS_FETCH_SUCCESS" | "CHECK_LAUNCHABLE_GAME" | "CLEAR_CACHES" | "CLEAR_CHANNEL_SAFETY_WARNINGS" | "CLEAR_CONSUMED_ENTITLEMENT" | "CLEAR_CONVERSATION_SUMMARIES" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_LAST_SESSION_VOICE_CHANNEL_ID" | "CLEAR_MENTIONS" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLEAR_THEME_OVERRIDE" | "CLEAR_VIDEO_STREAM_READY_TIMEOUT" | "CLICKER_GAME_ADD_POINTS" | "CLICKER_GAME_PURCHASE_ITEM" | "CLICKER_GAME_PURCHASE_ITEM_UPGRADE" | "CLICKER_GAME_REDEEM_PRIZE_FAIL" | "CLICKER_GAME_REDEEM_PRIZE_START" | "CLICKER_GAME_REDEEM_PRIZE_SUCCESS" | "CLICKER_GAME_RESET" | "CLICKER_GAME_SET_MUTED" | "CLICKER_GAME_SET_VOLUME" | "CLICKER_GAME_UNLOCK_ACHIEVEMENT" | "CLICKER_GAME_UPDATE_ITEM_METADATA" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIPS_ALLOW_VOICE_RECORDING_UPDATE" | "CLIPS_CLASSIFY_HARDWARE" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_CLEAR_NEW_CLIP_IDS" | "CLIPS_DELETE_CLIP" | "CLIPS_DISMISS_EDUCATION" | "CLIPS_INIT" | "CLIPS_INIT_FAILURE" | "CLIPS_LOAD_DIRECTORY_SUCCESS" | "CLIPS_RESTART" | "CLIPS_SAVE_ANIMATION_END" | "CLIPS_SAVE_CLIP" | "CLIPS_SAVE_CLIP_ERROR" | "CLIPS_SAVE_CLIP_PLACEHOLDER" | "CLIPS_SAVE_CLIP_PLACEHOLDER_ERROR" | "CLIPS_SAVE_CLIP_START" | "CLIPS_SETTINGS_UPDATE" | "CLIPS_SHOW_CALL_WARNING" | "CLIPS_UPDATE_METADATA" | "CLOSE_AGE_VERIFICATION_MODAL" | "CLOSE_SUSPENDED_USER" | "COLLECTIBLES_CATEGORIES_FETCH" | "COLLECTIBLES_CATEGORIES_FETCH_FAILURE" | "COLLECTIBLES_CATEGORIES_FETCH_SUCCESS" | "COLLECTIBLES_CLAIM" | "COLLECTIBLES_CLAIM_FAILURE" | "COLLECTIBLES_CLAIM_SUCCESS" | "COLLECTIBLES_MARKETING_FETCH" | "COLLECTIBLES_MARKETING_FETCH_SUCCESS" | "COLLECTIBLES_PRODUCT_DETAILS_OPEN" | "COLLECTIBLES_PRODUCT_FETCH" | "COLLECTIBLES_PRODUCT_FETCH_FAILURE" | "COLLECTIBLES_PRODUCT_FETCH_SUCCESS" | "COLLECTIBLES_PURCHASES_FETCH" | "COLLECTIBLES_PURCHASES_FETCH_FAILURE" | "COLLECTIBLES_PURCHASES_FETCH_SUCCESS" | "COLLECTIBLES_SET_SHOP_HOME_CONFIG_OVERRIDE" | "COLLECTIBLES_SHOP_CLOSE" | "COLLECTIBLES_SHOP_HOME_FETCH" | "COLLECTIBLES_SHOP_HOME_FETCH_FAILURE" | "COLLECTIBLES_SHOP_HOME_FETCH_SUCCESS" | "COLLECTIBLES_SHOP_OPEN" | "COLLECTIBLES_SKIP_NUM_CATEGORIES" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "COMPLETE_NEW_MEMBER_ACTION" | "CONNECTED_DEVICE_DONT_SWITCH" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SWITCH" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_STATE_UPDATE" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONSUMABLES_CLEAR_ERROR" | "CONSUMABLES_ENTITLEMENT_FETCH_COMPLETED" | "CONSUMABLES_ENTITLEMENT_FETCH_FAILED" | "CONSUMABLES_ENTITLEMENT_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_FAILED" | "CONSUMABLES_PRICE_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_SUCCEEDED" | "CONTENT_INVENTORY_CLEAR_DELETE_HISTORY_ERROR" | "CONTENT_INVENTORY_CLEAR_FEED" | "CONTENT_INVENTORY_DEBUG_CLEAR_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_LOG_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_TOGGLE_FAST_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DEBUG_TOGGLE_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_FAILURE" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_START" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_SUCCESS" | "CONTENT_INVENTORY_FETCH_OUTBOX_FAILURE" | "CONTENT_INVENTORY_FETCH_OUTBOX_START" | "CONTENT_INVENTORY_FETCH_OUTBOX_SUCCESS" | "CONTENT_INVENTORY_FORCE_SHOW_GAME_SHARING" | "CONTENT_INVENTORY_INBOX_STALE" | "CONTENT_INVENTORY_MANUAL_REFRESH" | "CONTENT_INVENTORY_SET_FEED" | "CONTENT_INVENTORY_SET_FEED_STATE" | "CONTENT_INVENTORY_SET_FILTERS" | "CONTENT_INVENTORY_TOGGLE_FEED_HIDDEN" | "CONTENT_INVENTORY_TRACK_ITEM_IMPRESSIONS" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CONVERSATION_SUMMARY_UPDATE" | "CREATE_PENDING_REPLY" | "CREATE_PENDING_SCHEDULED_MESSAGE" | "CREATE_REFERRALS_SUCCESS" | "CREATE_SHALLOW_PENDING_REPLY" | "CREATOR_MONETIZATION_NAG_ACTIVATE_ELIGIBLITY_FETCH_SUCCESS" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_FAILURE" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_SUCCESS" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "CUSTOM_ACTIVITY_LINK_FETCH_SUCCESS" | "DCF_DAILY_CAP_OVERRIDE" | "DCF_EVENT_LOGGED" | "DCF_HANDLE_DC_DISMISSED" | "DCF_HANDLE_DC_SHOWN" | "DCF_NEW_USER_MIN_AGE_REQUIRED_OVERRIDE" | "DCF_OVERRIDE_LAST_DC_DISMISSED" | "DCF_RESET" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DELETE_PENDING_SCHEDULED_MESSAGE" | "DELETE_SUMMARY" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_FAILURE" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_SUCCESS" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_UPDATE_FILTER" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_DESIGN_TOGGLE_SET" | "DEV_TOOLS_DESIGN_TOGGLE_WEB_SET" | "DEV_TOOLS_DEV_SETTING_SET" | "DEV_TOOLS_FRIENDS_LIST_GIFT_INTENTS_SHOWN_RESET" | "DEV_TOOLS_FRIENDS_TAB_BADGE_COOLDOWN_RESET" | "DEV_TOOLS_GIFT_MESSAGE_COOLDOWN_RESET" | "DEV_TOOLS_SETTINGS_UPDATE" | "DEV_TOOLS_SET_FRIEND_ANNIVERSARY_COUNT" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISMISS_CHANNEL_SAFETY_WARNINGS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_MEDIA_POST_SHARE_PROMPT" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_FOCUSED_LAYOUT" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMBEDDED_ACTIVITY_SET_PANEL_MODE" | "EMBEDDED_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_UPDATE_POPOUT_WINDOW_LAYOUT" | "EMBEDDED_ACTIVITY_UPDATE_V2" | "EMOJI_AUTOSUGGESTION_UPDATE" | "EMOJI_DELETE" | "EMOJI_FETCH_FAILURE" | "EMOJI_FETCH_SUCCESS" | "EMOJI_INTERACTION_INITIATED" | "EMOJI_TRACK_USAGE" | "EMOJI_UPLOAD_START" | "EMOJI_UPLOAD_STOP" | "ENABLE_AUTOMATIC_ACK" | "ENTITLEMENTS_FETCH_FOR_USER_FAIL" | "ENTITLEMENTS_FETCH_FOR_USER_START" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "FAMILY_CENTER_FETCH_START" | "FAMILY_CENTER_HANDLE_TAB_SELECT" | "FAMILY_CENTER_INITIAL_LOAD" | "FAMILY_CENTER_LINKED_USERS_FETCH_SUCCESS" | "FAMILY_CENTER_LINK_CODE_FETCH_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_REMOVE_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_UPDATE_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_FETCH_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_MORE_FETCH_SUCCESS" | "FEEDBACK_OVERRIDE_CLEAR" | "FEEDBACK_OVERRIDE_SET" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_CHAT_WALLPAPERS_FAILURE" | "FETCH_CHAT_WALLPAPERS_START" | "FETCH_CHAT_WALLPAPERS_SUCCESS" | "FETCH_GUILD_EVENT" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_FAILURE" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_SUCCESS" | "FETCH_SCHEDULED_MESSAGES" | "FETCH_SCHEDULED_MESSAGES_FAILURE" | "FETCH_SCHEDULED_MESSAGES_SUCCESS" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_LIST_GIFT_INTENTS_SHOWN" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIENDS_TAB_BADGE_DISMISS" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_CONSOLE_SELECT_DEVICE" | "GAME_DETECTION_DEBUGGING_START" | "GAME_DETECTION_DEBUGGING_STOP" | "GAME_DETECTION_DEBUGGING_TICK" | "GAME_DETECTION_WATCH_CANDIDATE_GAMES_START" | "GAME_ICON_UPDATE" | "GAME_INVITE_CLEAR_UNSEEN" | "GAME_INVITE_CREATE" | "GAME_INVITE_DELETE" | "GAME_INVITE_DELETE_MANY" | "GAME_INVITE_UPDATE_STATUS" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GAME_PROFILE_OPEN" | "GAME_RELATIONSHIP_ADD" | "GAME_RELATIONSHIP_REMOVE" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIFT_INTENT_FLOW_PURCHASED_GIFT" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_CLEAR" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_LAYOUT_RESET" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_SUCCESS" | "GUILD_ACK" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_SUCCESS" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLIED_BOOSTS_UPDATE" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FOLDER_COLLAPSE" | "GUILD_FOLDER_CREATE_LOCAL" | "GUILD_FOLDER_DELETE_LOCAL" | "GUILD_FOLDER_EDIT_LOCAL" | "GUILD_GEO_RESTRICTED" | "GUILD_HOME_SETTINGS_FETCH_FAIL" | "GUILD_HOME_SETTINGS_FETCH_START" | "GUILD_HOME_SETTINGS_FETCH_SUCCESS" | "GUILD_HOME_SETTINGS_TOGGLE_ENABLED" | "GUILD_HOME_SETTINGS_UPDATE_FAIL" | "GUILD_HOME_SETTINGS_UPDATE_START" | "GUILD_HOME_SETTINGS_UPDATE_SUCCESS" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR_DECORATION" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PROFILE_EFFECT_ID" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_TAB" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_BY_ID_FETCH_SUCCESS" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_LOCAL_RING_START" | "GUILD_MEMBERS_CHUNK_BATCH" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_REMOVE_LOCAL" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE_BY_ID" | "GUILD_MUTE_EXPIRED" | "GUILD_NEW_MEMBER_ACTIONS_DELETE_SUCCESS" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_FAIL" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_START" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_SUCCESS" | "GUILD_NEW_MEMBER_ACTION_UPDATE_SUCCESS" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POWERUPS_ACK_NOTIFICATION" | "GUILD_POWERUPS_RESET_NOTIFICATIONS" | "GUILD_POWERUP_CATALOG_FETCH_SUCCESS" | "GUILD_POWERUP_ENTITLEMENTS_CREATE" | "GUILD_POWERUP_ENTITLEMENTS_DELETE" | "GUILD_PRODUCTS_FETCH" | "GUILD_PRODUCTS_FETCH_FAILURE" | "GUILD_PRODUCTS_FETCH_SUCCESS" | "GUILD_PRODUCT_CREATE" | "GUILD_PRODUCT_DELETE" | "GUILD_PRODUCT_FETCH" | "GUILD_PRODUCT_FETCH_FAILURE" | "GUILD_PRODUCT_FETCH_SUCCESS" | "GUILD_PRODUCT_UPDATE" | "GUILD_PROFILE_FETCH" | "GUILD_PROFILE_FETCH_FAILURE" | "GUILD_PROFILE_FETCH_SUCCESS" | "GUILD_PROFILE_UPDATE" | "GUILD_PROFILE_UPDATE_FAILURE" | "GUILD_PROFILE_UPDATE_SUCCESS" | "GUILD_PROFILE_UPDATE_VISIBILITY" | "GUILD_PROFILE_UPDATE_VISIBILITY_FAILURE" | "GUILD_PROFILE_UPDATE_VISIBILITY_SUCCESS" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RESOURCE_CHANNEL_UPDATE_SUCCESS" | "GUILD_RING_START" | "GUILD_RING_STOP" | "GUILD_ROLE_CONNECTIONS_CONFIGURATIONS_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTIONS_MODAL_SHOW" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_TEMPLATES" | "GUILD_ROLE_SUBSCRIPTIONS_STASH_TEMPLATE_CHANNELS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTIONS_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_COUNTS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SEARCH_RECENT_MEMBERS" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_RESET" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_FAILED" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SUBMIT" | "GUILD_SETTINGS_DEFAULT_CHANNELS_TOGGLE" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_JOIN_RULES_APPLY_SET_PENDING_FORM_FIELDS" | "GUILD_SETTINGS_JOIN_RULES_INVITE_SET_PENDING_RULES" | "GUILD_SETTINGS_JOIN_RULES_SET_CONTENT_LEVEL" | "GUILD_SETTINGS_JOIN_RULES_SET_SELECTED_TYPE" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_BANS_BATCH" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_ADD_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_ADD_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DELETE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_DELETE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DISMISS_RESOURCE_CHANNEL_SUGGESTION" | "GUILD_SETTINGS_ONBOARDING_EDUCATION_UPSELL_DISMISSED" | "GUILD_SETTINGS_ONBOARDING_HOME_SETTINGS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_EDIT" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_ERRORS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_FAILED" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SUBMIT" | "GUILD_SETTINGS_ONBOARDING_REORDER_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_REORDER_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_SET_MODE" | "GUILD_SETTINGS_ONBOARDING_STEP" | "GUILD_SETTINGS_ONBOARDING_UPDATE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_UPDATE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_UPDATE_WELCOME_MESSAGE" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_PROFILE_UPDATE" | "GUILD_SETTINGS_ROLES_CLEAR_PERMISSIONS" | "GUILD_SETTINGS_ROLES_INIT" | "GUILD_SETTINGS_ROLES_ROLE_STYLE_UPDATE" | "GUILD_SETTINGS_ROLES_SAVE_FAIL" | "GUILD_SETTINGS_ROLES_SAVE_SUCCESS" | "GUILD_SETTINGS_ROLES_SORT_UPDATE" | "GUILD_SETTINGS_ROLES_SUBMITTING" | "GUILD_SETTINGS_ROLES_UPDATE_COLOR" | "GUILD_SETTINGS_ROLES_UPDATE_COLORS" | "GUILD_SETTINGS_ROLES_UPDATE_DESCRIPTION" | "GUILD_SETTINGS_ROLES_UPDATE_NAME" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSIONS" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSION_SET" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_CONNECTION_CONFIGURATIONS" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_ICON" | "GUILD_SETTINGS_ROLES_UPDATE_SETTINGS" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAFETY_PAGE" | "GUILD_SETTINGS_SAFETY_SET_SUBSECTION" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SETTINGS_VANITY_URL_ERROR" | "GUILD_SETTINGS_VANITY_URL_RESET" | "GUILD_SETTINGS_VANITY_URL_SET" | "GUILD_SETTINGS_WIDGET_UPDATE" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_SOUNDS_UPDATE" | "GUILD_SOUNDBOARD_SOUND_CREATE" | "GUILD_SOUNDBOARD_SOUND_DELETE" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_LOCALLY" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_SOUND_UPDATE" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_ADD_MEMBER_UPDATES" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_SUBSCRIPTIONS_REMOVE_MEMBER_UPDATES" | "GUILD_TAG_CHANGED_COACHMARK_SEEN" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_TOP_READ_CHANNELS_FETCH_SUCCESS" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UNLOCKED_POWERUPS_FETCH_SUCCESS" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HABITUAL_DND_CLEAR" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIGH_FIVE_COMPLETE" | "HIGH_FIVE_COMPLETE_CLEAR" | "HIGH_FIVE_QUEUE" | "HIGH_FIVE_REMOVE" | "HIGH_FIVE_SET_ENABLED" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INITIALIZE_MEMBER_SAFETY_STORE" | "INITIATE_AGE_VERIFICATION" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CLEAR" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_CREATE" | "INTEGRATION_DELETE" | "INTEGRATION_PERMISSION_SETTINGS_APPLICATION_PERMISSIONS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_CLEAR" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_SUCCESS" | "INTEGRATION_PERMISSION_SETTINGS_COMMAND_UPDATE" | "INTEGRATION_PERMISSION_SETTINGS_EDIT" | "INTEGRATION_PERMISSION_SETTINGS_INIT" | "INTEGRATION_PERMISSION_SETTINGS_RESET" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_COMMAND" | "INTEGRATION_SETTINGS_START_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_COMMAND" | "INTEGRATION_SETTINGS_STOP_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_INTEGRATION" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_IFRAME_MODAL_CLOSE" | "INTERACTION_IFRAME_MODAL_CREATE" | "INTERACTION_IFRAME_MODAL_KEY_CREATE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "INVITE_SUGGESTIONS_SEARCH" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAB_FEATURE_TOGGLE" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_SET_WIDGET_META" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_CHANNELS" | "LOAD_DATA_HARVEST_TYPE_FAILURE" | "LOAD_DATA_HARVEST_TYPE_START" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_ICYMI_HYDRATED" | "LOAD_INVITE_SUGGESTIONS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES_V2" | "LOAD_USER_AFFINITIES_V2_FAILURE" | "LOAD_USER_AFFINITIES_V2_SUCCESS" | "LOCAL_ACTIVITY_UPDATE" | "LOCAL_MESSAGES_LOADED" | "LOCAL_MESSAGE_CREATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_STEP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGIN_SUSPENDED_USER" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_CONNECTION_STATS" | "MEDIA_ENGINE_CONNECTION_STATS_HISTORY_RESET" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_ENABLE_HARDWARE_MUTE_NOTICE" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_GO_LIVE_SOURCE" | "MEDIA_ENGINE_SET_HARDWARE_ENCODING" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_USE_SYSTEM_SCREENSHARE_PICKER" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEDIA_ENGINE_VOICE_ACTIVITY_DETECTION_ERROR" | "MEDIA_PLAYBACK_POSITION_UPDATE" | "MEDIA_PLAYBACK_RATE_UPDATE" | "MEDIA_POST_EMBED_FETCH" | "MEDIA_POST_EMBED_FETCH_FAILURE" | "MEDIA_POST_EMBED_FETCH_SUCCESS" | "MEDIA_SESSION_JOINED" | "MEMBER_SAFETY_GUILD_MEMBER_SEARCH_SUCCESS" | "MEMBER_SAFETY_GUILD_MEMBER_UPDATE_BATCH" | "MEMBER_SAFETY_NEW_MEMBER_TIMESTAMP_REFRESH" | "MEMBER_SAFETY_PAGINATION_TOKEN_UPDATE" | "MEMBER_SAFETY_PAGINATION_UPDATE" | "MEMBER_SAFETY_SEARCH_STATE_UPDATE" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MESSAGE_ACK" | "MESSAGE_ACKED" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_EXPLICIT_CONTENT_FP_CREATE" | "MESSAGE_EXPLICIT_CONTENT_FP_SUBMIT" | "MESSAGE_EXPLICIT_CONTENT_SCAN_TIMEOUT" | "MESSAGE_GIFT_INTENT_SHOWN" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_NOTIFICATION_SHOWN" | "MESSAGE_PREVIEWS_LOADED" | "MESSAGE_PREVIEWS_LOCALLY_LOADED" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_MANY" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REMINDER_DUE" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REQUEST_ACK" | "MESSAGE_REQUEST_CLEAR_ACK" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_SUCCESS" | "MFA_SEEN_BACKUP_CODE_PROMPT" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MOBILE_NATIVE_UPDATE_CHECK_FINISHED" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MOD_VIEW_SEARCH_FINISH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NATIVE_APP_MODAL_OPENED" | "NATIVE_APP_MODAL_OPENING" | "NATIVE_APP_MODAL_OPEN_FAILED" | "NATIVE_SCREEN_SHARE_PICKER_CANCEL" | "NATIVE_SCREEN_SHARE_PICKER_ERROR" | "NATIVE_SCREEN_SHARE_PICKER_PRESENT" | "NATIVE_SCREEN_SHARE_PICKER_RELEASE" | "NATIVE_SCREEN_SHARE_PICKER_UPDATE" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_CLEAR_GUILD_MENTIONS" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_REFRESH" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CENTER_TAB_FOCUSED" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOTIFICATION_SETTINGS_UPDATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_FORCE_RENDER_MODE" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_MESSAGE_EVENT_ACTION" | "OVERLAY_NOTIFICATION_EVENT" | "OVERLAY_READY" | "OVERLAY_RELOAD" | "OVERLAY_RENDER_DEBUG_CLEAR_TRACKED_PIDS" | "OVERLAY_RENDER_DEBUG_MODE" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_ASSOCIATED_GAME" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_CLICK_ZONES" | "OVERLAY_SET_DISABLE_CLICKABLE_REGIONS" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_GAME_INVITE_NOTIFICATION" | "OVERLAY_SET_GPU_BOOST_REQUESTED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_INVITE_MESSAGE" | "OVERLAY_SET_LIMITED_INTERACTION_OVERRIDE" | "OVERLAY_SET_NOTIFICATION_DISABLED_SETTING" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_SHOW_KEYBIND_INDICATORS" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SOUNDBOARD_SOUNDS_FETCH_REQUEST" | "OVERLAY_START_SESSION" | "OVERLAY_SUCCESSFULLY_SHOWN" | "OVERLAY_UPDATE_OVERLAY_METHOD" | "OVERLAY_UPDATE_OVERLAY_STATE" | "OVERLAY_WIDGET_CHANGED" | "PASSIVE_UPDATE_V2" | "PASSWORDLESS_FAILURE" | "PASSWORDLESS_START" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_RESIZE" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_ADD_STYLESHEET" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "POTIONS_SET_CONFETTI_MODE" | "POTIONS_TRIGGER_MESSAGE_CONFETTI" | "PREMIUM_MARKETING_DATA_READY" | "PREMIUM_MARKETING_PREVIEW" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_SUBSCRIPTIONS_ADD" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_EFFECTS_SET_TRY_IT_OUT" | "PROXY_BLOCKED_REQUEST" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_FAILURE" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_SUCCESS" | "PURCHASED_ITEMS_FESTIVITY_IS_FETCHING_WOW_MOMENT_MEDIA" | "PURCHASED_ITEMS_FESTIVITY_SET_CAN_PLAY_WOW_MOMENT" | "PUSH_NOTIFICATION_CLICK" | "QUESTS_CLAIM_REWARD_BEGIN" | "QUESTS_CLAIM_REWARD_FAILURE" | "QUESTS_CLAIM_REWARD_SUCCESS" | "QUESTS_DELIVERY_OVERRIDE" | "QUESTS_DISMISS_CONTENT_BEGIN" | "QUESTS_DISMISS_CONTENT_FAILURE" | "QUESTS_DISMISS_CONTENT_SUCCESS" | "QUESTS_DISMISS_PROGRESS_TRACKING_FAILURE_NOTICE" | "QUESTS_ENROLL_BEGIN" | "QUESTS_ENROLL_FAILURE" | "QUESTS_ENROLL_SUCCESS" | "QUESTS_FETCH_CLAIMED_QUESTS_BEGIN" | "QUESTS_FETCH_CLAIMED_QUESTS_FAILURE" | "QUESTS_FETCH_CLAIMED_QUESTS_SUCCESS" | "QUESTS_FETCH_CURRENT_QUESTS_BEGIN" | "QUESTS_FETCH_CURRENT_QUESTS_FAILURE" | "QUESTS_FETCH_CURRENT_QUESTS_SUCCESS" | "QUESTS_FETCH_QUEST_TO_DELIVER_BEGIN" | "QUESTS_FETCH_QUEST_TO_DELIVER_FAILURE" | "QUESTS_FETCH_QUEST_TO_DELIVER_SUCCESS" | "QUESTS_FETCH_REWARD_CODE_BEGIN" | "QUESTS_FETCH_REWARD_CODE_FAILURE" | "QUESTS_FETCH_REWARD_CODE_SUCCESS" | "QUESTS_PREVIEW_UPDATE_SUCCESS" | "QUESTS_SELECT_TASK_PLATFORM" | "QUESTS_SEND_HEARTBEAT_FAILURE" | "QUESTS_SEND_HEARTBEAT_SUCCESS" | "QUESTS_UPDATE_OPTIMISTIC_PROGRESS" | "QUESTS_USER_COMPLETION_UPDATE" | "QUESTS_USER_STATUS_UPDATE" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECEIVE_CHANNEL_AFFINITIES" | "RECEIVE_CHANNEL_SUMMARIES" | "RECEIVE_CHANNEL_SUMMARIES_BULK" | "RECEIVE_CHANNEL_SUMMARY" | "RECENT_MENTION_DELETE" | "RECOMPUTE_READ_STATES" | "REFERRALS_FETCH_ELIGIBLE_USER_FAIL" | "REFERRALS_FETCH_ELIGIBLE_USER_START" | "REFERRALS_FETCH_ELIGIBLE_USER_SUCCESS" | "REGISTER" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_IGNORE_USER_SUCCESS" | "RELATIONSHIP_PENDING_INCOMING_REMOVED" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REPORT_AV_ERROR" | "REPORT_TO_MOD_REPORT_MESSAGE_SUCCESS" | "REQUEST_CHANNEL_AFFINITIES" | "REQUEST_CHANNEL_SUMMARIES" | "REQUEST_CHANNEL_SUMMARIES_BULK" | "REQUEST_CHANNEL_SUMMARY" | "REQUEST_FORUM_UNREADS" | "REQUEST_SOUNDBOARD_SOUNDS" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESET_PREVIEW_CLIENT_THEME" | "RESET_SOCKET" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_CLIENT_CONNECT" | "RTC_CONNECTION_CLIENT_DISCONNECT" | "RTC_CONNECTION_FLAGS" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_PLATFORM" | "RTC_CONNECTION_REMOTE_VIDEO_SINK_WANTS" | "RTC_CONNECTION_ROSTER_MAP_UPDATE" | "RTC_CONNECTION_SECURE_FRAMES_UPDATE" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_USERS_MERGED" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_DEBUG_SET_SIMULCAST_OVERRIDE" | "RTC_LATENCY_TEST_COMPLETE" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_DETECTION" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAFETY_HUB_APPEAL_CLOSE" | "SAFETY_HUB_APPEAL_OPEN" | "SAFETY_HUB_APPEAL_SIGNAL_CUSTOM_INPUT_CHANGE" | "SAFETY_HUB_APPEAL_SIGNAL_SELECT" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_CLOSE" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_OPEN" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_START_POLL" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_SUBMIT_SUCCESS" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_FETCH_CLASSIFICATION_FAILURE" | "SAFETY_HUB_FETCH_CLASSIFICATION_START" | "SAFETY_HUB_FETCH_CLASSIFICATION_SUCCESS" | "SAFETY_HUB_FETCH_FAILURE" | "SAFETY_HUB_FETCH_START" | "SAFETY_HUB_FETCH_SUCCESS" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_REQUEST_REVIEW_FAILURE" | "SAFETY_HUB_REQUEST_REVIEW_START" | "SAFETY_HUB_REQUEST_REVIEW_SUCCESS" | "SAVED_MESSAGES_UPDATE" | "SAVED_MESSAGE_CREATE" | "SAVED_MESSAGE_DELETE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SCHEDULED_MESSAGES_CREATE_SUCCESS" | "SCHEDULED_MESSAGES_DELETE_FAILURE" | "SCHEDULED_MESSAGES_DELETE_START" | "SCHEDULED_MESSAGES_DELETE_SUCCESS" | "SEARCH_ADD_HISTORY" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MESSAGES_CLEAR_ALL" | "SEARCH_MESSAGES_FAILURE" | "SEARCH_MESSAGES_INDEXING" | "SEARCH_MESSAGES_START" | "SEARCH_MESSAGES_SUCCESS" | "SEARCH_RECENT_MESSAGES_CLEAR" | "SEARCH_REMOVE_HISTORY" | "SEARCH_RESULTS_QUERY_UPDATE" | "SEARCH_SCREEN_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_SET_SHOW_NO_RESULTS_ALT" | "SEARCH_START" | "SECURE_FRAMES_SETTINGS_UPDATE" | "SECURE_FRAMES_TRANSIENT_KEY_CREATE" | "SECURE_FRAMES_TRANSIENT_KEY_DELETE" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_ADD" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_CLEAR" | "SECURE_FRAMES_USER_VERIFIED_KEYS_DELETE" | "SECURE_FRAMES_VERIFIED_KEY_CREATE" | "SECURE_FRAMES_VERIFIED_KEY_DELETE" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELECT_HOME_RESOURCE_CHANNEL" | "SELECT_NEW_MEMBER_ACTION_CHANNEL" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_CREATED_AT_OVERRIDE" | "SET_GUILD_FOLDER_EXPANDED" | "SET_GUILD_LEADERBOARD" | "SET_HIGHLIGHTED_SUMMARY" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_PREMIUM_TYPE_OVERRIDE" | "SET_PREVIOUS_GO_LIVE_SETTINGS" | "SET_RECENTLY_ACTIVE_COLLAPSED" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_RPC_NOTIFICATION_SETTINGS" | "SET_SELECTED_SUMMARY" | "SET_SOUNDPACK" | "SET_STREAM_APP_INTENT" | "SET_SUMMARY_FEEDBACK" | "SET_THEME_OVERRIDE" | "SET_TTS_SPEECH_RATE" | "SET_USER_LEADERBOARD_LAST_UPDATE_REQUESTED" | "SET_VAD_PERMISSION" | "SHARED_CANVAS_CLEAR_DRAWABLES" | "SHARED_CANVAS_DRAW_LINE_POINT" | "SHARED_CANVAS_SET_DRAW_MODE" | "SHARED_CANVAS_UPDATE_EMOJI_HOSE" | "SHARED_CANVAS_UPDATE_LINE_POINTS" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CLOSE_GUILD" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SIDEBAR_VIEW_GUILD" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_AWAIT_CONFIRMATION" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH" | "SKU_PURCHASE_PREVIEW_FETCH_FAILURE" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS_SUCCESS" | "SOUNDBOARD_MUTE_JOIN_SOUND" | "SOUNDBOARD_SET_OVERLAY_ENABLED" | "SOUNDBOARD_SOUNDS_RECEIVED" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPELLCHECK_UNLEARN_WORD" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_NEW_TRACK" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "STAGE_MUSIC_MUTE" | "STAGE_MUSIC_PLAY" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_LISTINGS_FETCH_FAIL" | "STORE_LISTINGS_FETCH_START" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SURVEY_SEEN" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_EXPANDED_STATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOGGLE_OVERLAY_CANVAS" | "TOGGLE_TOPICS_BAR" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TRY_ACK" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPCOMING_GUILD_EVENT_NOTICE_HIDE" | "UPCOMING_GUILD_EVENT_NOTICE_SEEN" | "UPDATE_AVAILABLE" | "UPDATE_BACKGROUND_GRADIENT_PRESET" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CHANNEL_LIST_SUBTITLES" | "UPDATE_CHAT_WALLPAPER_FLAG_COMPLETE" | "UPDATE_CHAT_WALLPAPER_FLAG_START" | "UPDATE_CHAT_WALLPAPER_OVERRIDES" | "UPDATE_CLIENT_PREMIUM_TYPE" | "UPDATE_CONSENTS" | "UPDATE_DATA_HARVEST_TYPE" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_MANUALLY" | "UPDATE_MOBILE_PENDING_THEME_INDEX" | "UPDATE_NOT_AVAILABLE" | "UPDATE_STRANGER_STATUS" | "UPDATE_THEME_PREFERENCES" | "UPDATE_TOKEN" | "UPDATE_VISIBLE_MESSAGES" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_FILE_UPDATE" | "UPLOAD_ITEM_CANCEL_REQUEST" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLICATION_REMOVE" | "USER_APPLICATION_UPDATE" | "USER_APPLIED_BOOSTS_FETCH_START" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_REQUEST" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_CALLBACK" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_INTEGRATION_JOINING_ERROR" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_COACHMARK_CLEAR" | "USER_GUILD_JOIN_REQUEST_COACHMARK_SHOW" | "USER_GUILD_JOIN_REQUEST_COOLDOWN_FETCH" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_BROWSER_CHECKOUT_DONE" | "USER_PAYMENT_BROWSER_CHECKOUT_STARTED" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_EFFECTS_FETCH" | "USER_PROFILE_EFFECTS_FETCH_FAILURE" | "USER_PROFILE_EFFECTS_FETCH_SUCCESS" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_PIN_BADGES_ON_CLIENT" | "USER_PROFILE_SIDEBAR_TOGGLE_SECTION" | "USER_PROFILE_UPDATE_FAILURE" | "USER_PROFILE_UPDATE_START" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_RESET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME" | "USER_SETTINGS_ACCOUNT_SET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_NAMEPLATE" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_SINGLE_TRY_IT_OUT_COLLECTIBLES_ITEM" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PRESET" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_RESET_PENDING_PRIMARY_GUILD_CHANGES" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SETTINGS_SET_PENDING_PRIMARY_GUILD_ID" | "USER_SOUNDBOARD_SET_VOLUME" | "USER_UPDATE" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIDEO_SIZE_UPDATE" | "VIDEO_STREAM_READY_TIMEOUT" | "VIEW_HISTORY_MARK_VIEW" | "VIRTUAL_CURRENCY_BALANCE_FETCH" | "VIRTUAL_CURRENCY_BALANCE_FETCH_FAIL" | "VIRTUAL_CURRENCY_BALANCE_FETCH_SUCCESS" | "VIRTUAL_CURRENCY_BALANCE_UPDATE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_CLOSE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_RESET" | "VIRTUAL_CURRENCY_REDEEM_FAIL" | "VIRTUAL_CURRENCY_REDEEM_START" | "VIRTUAL_CURRENCY_REDEEM_SUCCESS" | "VIRTUAL_CURRENCY_SET_BALANCE_PILL_OVERLAY" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_CHANNEL_STATUS_UPDATE" | "VOICE_FILTER_APPLIED" | "VOICE_FILTER_APPLY_FAILED" | "VOICE_FILTER_CATALOG_FETCH_FAILED" | "VOICE_FILTER_CATALOG_FETCH_SUCCESS" | "VOICE_FILTER_DEV_TOOLS_SET_UPDATE_TIME" | "VOICE_FILTER_DOWNLOAD_FAILED" | "VOICE_FILTER_DOWNLOAD_PROGRESS" | "VOICE_FILTER_DOWNLOAD_STARTED" | "VOICE_FILTER_FILE_READY" | "VOICE_FILTER_LAGGING" | "VOICE_FILTER_LOOPBACK_TOGGLE" | "VOICE_FILTER_NATIVE_MODULE_STATE_CHANGE" | "VOICE_FILTER_REQUEST_SWITCH" | "VOICE_FILTER_UPDATE_LIMITED_TIME_VOICES" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SETTINGS_CLEAR" | "WELCOME_SCREEN_SETTINGS_RESET" | "WELCOME_SCREEN_SETTINGS_UPDATE" | "WELCOME_SCREEN_SUBMIT" | "WELCOME_SCREEN_SUBMIT_FAILURE" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD" | "WINDOW_VISIBILITY_CHANGE" | "WRITE_CACHES"; diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts new file mode 100644 index 00000000..6d9356b4 --- /dev/null +++ b/packages/discord-types/src/index.d.ts @@ -0,0 +1,9 @@ +export * from "./common"; +export * from "./classes"; +export * from "./components"; +export * from "./flux"; +export * from "./fluxEvents"; +export * from "./menu"; +export * from "./stores"; +export * from "./utils"; +export * as Webpack from "../webpack"; diff --git a/src/webpack/common/types/menu.d.ts b/packages/discord-types/src/menu.d.ts similarity index 71% rename from src/webpack/common/types/menu.d.ts rename to packages/discord-types/src/menu.d.ts index 5ae9062c..0866e1c3 100644 --- a/src/webpack/common/types/menu.d.ts +++ b/packages/discord-types/src/menu.d.ts @@ -1,21 +1,3 @@ -/* - * 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 type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>; @@ -73,7 +55,7 @@ export interface Menu { renderValue?(value: number): string, }>; MenuSearchControl: RC<{ - query: string + query: string; onChange(query: string): void; placeholder?: string; }>; diff --git a/packages/discord-types/src/stores/ChannelStore.d.ts b/packages/discord-types/src/stores/ChannelStore.d.ts new file mode 100644 index 00000000..1507ba5e --- /dev/null +++ b/packages/discord-types/src/stores/ChannelStore.d.ts @@ -0,0 +1,24 @@ +import { Channel, FluxStore } from ".."; + +export class ChannelStore extends FluxStore { + getChannel(channelId: string): Channel; + getBasicChannel(channelId: string): Channel | undefined; + hasChannel(channelId: string): boolean; + + getChannelIds(guildId?: string | null): string[]; + getMutableBasicGuildChannelsForGuild(guildId: string): Record<string, Channel>; + getMutableGuildChannelsForGuild(guildId: string): Record<string, Channel>; + getAllThreadsForGuild(guildId: string): Channel[]; + getAllThreadsForParent(channelId: string): Channel[]; + + getDMFromUserId(userId: string): string; + getDMChannelFromUserId(userId: string): Channel | undefined; + getDMUserIds(): string[]; + getMutableDMsByUserIds(): Record<string, string>; + getMutablePrivateChannels(): Record<string, Channel>; + getSortedPrivateChannels(): Channel[]; + + getGuildChannelsVersion(guildId: string): number; + getPrivateChannelsVersion(): number; + getInitialOverlayState(): Record<string, Channel>; +} diff --git a/packages/discord-types/src/stores/DraftStore.d.ts b/packages/discord-types/src/stores/DraftStore.d.ts new file mode 100644 index 00000000..98b34cdf --- /dev/null +++ b/packages/discord-types/src/stores/DraftStore.d.ts @@ -0,0 +1,43 @@ +import { FluxStore } from ".."; + +export enum DraftType { + ChannelMessage = 0, + ThreadSettings = 1, + FirstThreadMessage = 2, + ApplicationLauncherCommand = 3, + Poll = 4, + SlashCommand = 5, + ForwardContextMessage = 6 +} + +export interface Draft { + timestamp: number; + draft: string; +} + +export interface ThreadSettingsDraft { + timestamp: number; + parentMessageId?: string; + name?: string; + isPrivate?: boolean; + parentChannelId?: string; + location?: string; +} + +export type ChannelDrafts = { + [DraftType.ThreadSettings]: ThreadSettingsDraft; +} & { + [key in Exclude<DraftType, DraftType.ThreadSettings>]: Draft; +}; + +export type UserDrafts = Partial<Record<string, ChannelDrafts>>; +export type DraftState = Partial<Record<string, UserDrafts>>; + +export class DraftStore extends FluxStore { + getState(): DraftState; + getRecentlyEditedDrafts(type: DraftType): Array<Draft & { channelId: string; }>; + getDraft(channelId: string, type: DraftType): string; + + getThreadSettings(channelId: string): ThreadSettingsDraft | null | undefined; + getThreadDraftWithParentMessageId(parentMessageId: string): ThreadSettingsDraft | null | undefined; +} diff --git a/packages/discord-types/src/stores/EmojiStore.d.ts b/packages/discord-types/src/stores/EmojiStore.d.ts new file mode 100644 index 00000000..93161b2f --- /dev/null +++ b/packages/discord-types/src/stores/EmojiStore.d.ts @@ -0,0 +1,57 @@ +import { Channel, CustomEmoji, Emoji, FluxStore } from ".."; + +export class EmojiStore extends FluxStore { + getCustomEmojiById(id?: string | null): CustomEmoji | undefined; + getUsableCustomEmojiById(id?: string | null): CustomEmoji | undefined; + getGuilds(): Record<string, { + id: string; + get emojis(): CustomEmoji[]; + get rawEmojis(): CustomEmoji[]; + get usableEmojis(): CustomEmoji[]; + get emoticons(): any[]; + getEmoji(id: string): CustomEmoji | undefined; + isUsable(emoji: CustomEmoji): boolean; + }>; + getGuildEmoji(guildId?: string | null): CustomEmoji[]; + getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[]; + getTopEmoji(guildId?: string | null): CustomEmoji[]; + getTopEmojisMetadata(guildId?: string | null): { + emojiIds: string[]; + topEmojisTTL: number; + }; + hasPendingUsage(): boolean; + hasUsableEmojiInAnyGuild(): boolean; + searchWithoutFetchingLatest(data: any): any; + getSearchResultsOrder(...args: any[]): any; + getState(): { + pendingUsages: { key: string, timestamp: number; }[]; + }; + searchWithoutFetchingLatest(data: { + channel: Channel; + query: string; + count?: number; + intention: number; + includeExternalGuilds?: boolean; + matchComparator?(name: string): boolean; + }): Record<"locked" | "unlocked", Emoji[]>; + + getDisambiguatedEmojiContext(): { + backfillTopEmojis: Record<any, any>; + customEmojis: Record<string, CustomEmoji>; + emojisById: Record<string, CustomEmoji>; + emojisByName: Record<string, CustomEmoji>; + emoticonRegex: RegExp | null; + emoticonsByName: Record<string, any>; + escapedEmoticonNames: string; + favoriteNamesAndIds?: any; + favorites?: any; + frequentlyUsed?: any; + groupedCustomEmojis: Record<string, CustomEmoji[]>; + guildId?: string; + isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean; + newlyAddedEmoji: Record<string, CustomEmoji[]>; + topEmojis?: any; + unicodeAliases: Record<string, string>; + get favoriteEmojisWithoutFetchingLatest(): Emoji[]; + }; +} diff --git a/packages/discord-types/src/stores/FluxStore.d.ts b/packages/discord-types/src/stores/FluxStore.d.ts new file mode 100644 index 00000000..da55ac0b --- /dev/null +++ b/packages/discord-types/src/stores/FluxStore.d.ts @@ -0,0 +1,44 @@ +import { FluxDispatcher, FluxEvents } from ".."; + +type Callback = () => void; + +/* + For some reason, this causes type errors when you try to destructure it: + ```ts + interface FluxEvent { + type: FluxEvents; + [key: string]: any; + } + ``` + */ +export type FluxEvent = any; + +export type ActionHandler = (event: FluxEvent) => void; +export type ActionHandlers = Partial<Record<FluxEvents, ActionHandler>>; + +export class FluxStore { + constructor(dispatcher: FluxDispatcher, actionHandlers?: ActionHandlers); + + getName(): string; + + addChangeListener(callback: Callback): void; + /** Listener will be removed once the callback returns false. */ + addConditionalChangeListener(callback: () => boolean, preemptive?: boolean): void; + addReactChangeListener(callback: Callback): void; + removeChangeListener(callback: Callback): void; + removeReactChangeListener(callback: Callback): void; + + doEmitChanges(event: FluxEvent): void; + emitChange(): void; + + getDispatchToken(): string; + initialize(): void; + initializeIfNeeded(): void; + /** this is a setter */ + mustEmitChanges(actionHandler: ActionHandler | undefined): void; + registerActionHandlers(actionHandlers: ActionHandlers): void; + syncWith(stores: FluxStore[], callback: Callback, timeout?: number): void; + waitFor(...stores: FluxStore[]): void; + + static getAll(): FluxStore[]; +} diff --git a/packages/discord-types/src/stores/GuildMemberStore.d.ts b/packages/discord-types/src/stores/GuildMemberStore.d.ts new file mode 100644 index 00000000..9dec139a --- /dev/null +++ b/packages/discord-types/src/stores/GuildMemberStore.d.ts @@ -0,0 +1,27 @@ +import { FluxStore, GuildMember } from ".."; + +export class GuildMemberStore extends FluxStore { + /** @returns Format: [guildId-userId: Timestamp (string)] */ + getCommunicationDisabledUserMap(): Record<string, string>; + getCommunicationDisabledVersion(): number; + + getMutableAllGuildsAndMembers(): Record<string, Record<string, GuildMember>>; + + getMember(guildId: string, userId: string): GuildMember | null; + getTrueMember(guildId: string, userId: string): GuildMember | null; + getMemberIds(guildId: string): string[]; + getMembers(guildId: string): GuildMember[]; + + getCachedSelfMember(guildId: string): GuildMember | null; + getSelfMember(guildId: string): GuildMember | null; + getSelfMemberJoinedAt(guildId: string): Date | null; + + getNick(guildId: string, userId: string): string | null; + getNicknameGuildsMapping(userId: string): Record<string, string[]>; + getNicknames(userId: string): string[]; + + isMember(guildId: string, userId: string): boolean; + isMember(guildId: string, userId: string): boolean; + isGuestOrLurker(guildId: string, userId: string): boolean; + isCurrentUserGuest(guildId: string): boolean; +} diff --git a/packages/discord-types/src/stores/GuildRoleStore.d.ts b/packages/discord-types/src/stores/GuildRoleStore.d.ts new file mode 100644 index 00000000..bf0d4042 --- /dev/null +++ b/packages/discord-types/src/stores/GuildRoleStore.d.ts @@ -0,0 +1,7 @@ +import { FluxStore, Role } from ".."; + +export class GuildRoleStore extends FluxStore { + getRole(guildId: string, roleId: string): Role; + getRoles(guildId: string): Record<string, Role>; + getAllGuildRoles(): Record<string, Record<string, Role>>; +} diff --git a/packages/discord-types/src/stores/GuildStore.d.ts b/packages/discord-types/src/stores/GuildStore.d.ts new file mode 100644 index 00000000..d1a3b9b3 --- /dev/null +++ b/packages/discord-types/src/stores/GuildStore.d.ts @@ -0,0 +1,8 @@ +import { Guild, FluxStore } from ".."; + +export class GuildStore extends FluxStore { + getGuild(guildId: string): Guild; + getGuildCount(): number; + getGuilds(): Record<string, Guild>; + getGuildIds(): string[]; +} diff --git a/packages/discord-types/src/stores/MessageStore.d.ts b/packages/discord-types/src/stores/MessageStore.d.ts new file mode 100644 index 00000000..d4823fc8 --- /dev/null +++ b/packages/discord-types/src/stores/MessageStore.d.ts @@ -0,0 +1,13 @@ +import { MessageJSON, FluxStore, Message } from ".."; + +export class MessageStore extends FluxStore { + getMessage(channelId: string, messageId: string): Message; + /** @returns This return object is fucking huge; I'll type it later. */ + getMessages(channelId: string): unknown; + getRawMessages(channelId: string): Record<string | number, MessageJSON>; + hasCurrentUserSentMessage(channelId: string): boolean; + hasPresent(channelId: string): boolean; + isLoadingMessages(channelId: string): boolean; + jumpedMessageId(channelId: string): string | undefined; + whenReady(channelId: string, callback: () => void): void; +} diff --git a/packages/discord-types/src/stores/RelationshipStore.d.ts b/packages/discord-types/src/stores/RelationshipStore.d.ts new file mode 100644 index 00000000..5d1a08af --- /dev/null +++ b/packages/discord-types/src/stores/RelationshipStore.d.ts @@ -0,0 +1,21 @@ +import { FluxStore } from ".."; + +export class RelationshipStore extends FluxStore { + getFriendIDs(): string[]; + getIgnoredIDs(): string[]; + getBlockedIDs(): string[]; + + getPendingCount(): number; + getRelationshipCount(): number; + + /** Related to friend nicknames. */ + getNickname(userId: string): string; + /** @returns Enum value from constants.RelationshipTypes */ + getRelationshipType(userId: string): number; + isFriend(userId: string): boolean; + isBlocked(userId: string): boolean; + isIgnored(userId: string): boolean; + getSince(userId: string): string; + + getMutableRelationships(): Map<string, number>; +} diff --git a/packages/discord-types/src/stores/SelectedChannelStore.d.ts b/packages/discord-types/src/stores/SelectedChannelStore.d.ts new file mode 100644 index 00000000..13ac98ac --- /dev/null +++ b/packages/discord-types/src/stores/SelectedChannelStore.d.ts @@ -0,0 +1,14 @@ +import { FluxStore } from ".."; + +export class SelectedChannelStore extends FluxStore { + getChannelId(guildId?: string | null): string; + getVoiceChannelId(): string | undefined; + getCurrentlySelectedChannelId(guildId?: string): string | undefined; + getMostRecentSelectedTextChannelId(guildId: string): string | undefined; + getLastSelectedChannelId(guildId?: string): string; + // yes this returns a string + getLastSelectedChannels(guildId?: string): string; + + /** If you follow an announcement channel, this will return whichever channel you chose as destination */ + getLastChannelFollowingDestination(): { guildId?: string; channelId?: string; } | undefined; +} diff --git a/packages/discord-types/src/stores/SelectedGuildStore.d.ts b/packages/discord-types/src/stores/SelectedGuildStore.d.ts new file mode 100644 index 00000000..0ee9c207 --- /dev/null +++ b/packages/discord-types/src/stores/SelectedGuildStore.d.ts @@ -0,0 +1,14 @@ +import { FluxStore } from ".."; + +export interface SelectedGuildState { + selectedGuildTimestampMillis: Record<string | number, number>; + selectedGuildId: string | null; + lastSelectedGuildId: string | null; +} + +export class SelectedGuildStore extends FluxStore { + getGuildId(): string | null; + getLastSelectedGuildId(): string | null; + getLastSelectedTimestamp(guildId: string): number | null; + getState(): SelectedGuildState | undefined; +} diff --git a/packages/discord-types/src/stores/ThemeStore.d.ts b/packages/discord-types/src/stores/ThemeStore.d.ts new file mode 100644 index 00000000..2900f7f6 --- /dev/null +++ b/packages/discord-types/src/stores/ThemeStore.d.ts @@ -0,0 +1,18 @@ +import { FluxStore } from ".."; + +export type ThemePreference = "dark" | "light" | "unknown"; +export type SystemTheme = "dark" | "light"; +export type Theme = "light" | "dark" | "darker" | "midnight"; + +export interface ThemeState { + theme: Theme; + status: 0 | 1; + preferences: Record<ThemePreference, Theme>; +} +export class ThemeStore extends FluxStore { + get theme(): Theme; + get darkSidebar(): boolean; + get systemTheme(): SystemTheme; + themePreferenceForSystemTheme(preference: ThemePreference): Theme; + getState(): ThemeState; +} diff --git a/packages/discord-types/src/stores/UserStore.d.ts b/packages/discord-types/src/stores/UserStore.d.ts new file mode 100644 index 00000000..323a1df0 --- /dev/null +++ b/packages/discord-types/src/stores/UserStore.d.ts @@ -0,0 +1,10 @@ +import { FluxStore, User } from ".."; + +export class UserStore extends FluxStore { + filter(filter: (user: User) => boolean, sort?: boolean): Record<string, User>; + findByTag(username: string, discriminator: string): User; + forEach(action: (user: User) => void): void; + getCurrentUser(): User; + getUser(userId: string): User; + getUsers(): Record<string, User>; +} diff --git a/packages/discord-types/src/stores/WindowStore.d.ts b/packages/discord-types/src/stores/WindowStore.d.ts new file mode 100644 index 00000000..53a40e96 --- /dev/null +++ b/packages/discord-types/src/stores/WindowStore.d.ts @@ -0,0 +1,7 @@ +import { FluxStore } from ".."; + +export class WindowStore extends FluxStore { + isElementFullScreen(): boolean; + isFocused(): boolean; + windowSize(): Record<"width" | "height", number>; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts new file mode 100644 index 00000000..b6254844 --- /dev/null +++ b/packages/discord-types/src/stores/index.d.ts @@ -0,0 +1,32 @@ +// please keep in alphabetical order +export * from "./ChannelStore"; +export * from "./DraftStore"; +export * from "./EmojiStore"; +export * from "./FluxStore"; +export * from "./GuildMemberStore"; +export * from "./GuildRoleStore"; +export * from "./GuildStore"; +export * from "./MessageStore"; +export * from "./RelationshipStore"; +export * from "./SelectedChannelStore"; +export * from "./SelectedGuildStore"; +export * from "./ThemeStore"; +export * from "./UserStore"; +export * from "./WindowStore"; + +/** + * React hook that returns stateful data for one or more stores + * You might need a custom comparator (4th argument) if your store data is an object + * @param stores The stores to listen to + * @param mapper A function that returns the data you need + * @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook + * @param isEqual A custom comparator for the data returned by mapper + * + * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); + */ +export type useStateFromStores = <T>( + stores: any[], + mapper: () => T, + dependencies?: any, + isEqual?: (old: T, newer: T) => boolean +) => T; diff --git a/src/webpack/common/types/utils.d.ts b/packages/discord-types/src/utils.d.ts similarity index 92% rename from src/webpack/common/types/utils.d.ts rename to packages/discord-types/src/utils.d.ts index cfea5d76..77b6f88b 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -1,22 +1,4 @@ -/* - * 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 { Channel, Guild, GuildMember, Message, User } from "discord-types/general"; +import { Channel, Guild, GuildMember, Message, User } from "."; import type { ReactNode } from "react"; import { LiteralUnion } from "type-fest"; @@ -335,3 +317,19 @@ export interface DateUtils { dateFormat(date: Date, format: string): string; diffAsUnits(start: Date, end: Date, stopAtOneSecond?: boolean): Record<"days" | "hours" | "minutes" | "seconds", number>; } + +export interface CommandOptions { + type: number; + name: string; + description: string; + required?: boolean; + choices?: { + name: string; + values: string | number; + }[]; + options?: CommandOptions[]; + channel_types?: number[]; + min_value?: number; + max_value?: number; + autocomplete?: boolean; +} diff --git a/src/webpack/wreq.d.ts b/packages/discord-types/webpack/index.d.ts similarity index 90% rename from src/webpack/wreq.d.ts rename to packages/discord-types/webpack/index.d.ts index 2b356f9d..4a0011e8 100644 --- a/src/webpack/wreq.d.ts +++ b/packages/discord-types/webpack/index.d.ts @@ -1,11 +1,9 @@ /* - * Vencord, a Discord client mod + * @vencord/discord-types * Copyright (c) 2024 Vendicated, Nuckyz and contributors - * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-License-Identifier: LGPL-3.0-or-later */ -import { SYM_ORIGINAL_FACTORY, SYM_PATCHED_BY, SYM_PATCHED_SOURCE } from "./patchWebpack"; - export type ModuleExports = any; export type Module = { @@ -215,24 +213,3 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & { /** rspack unique id */ ruid: string; }; - -// Utility section for Vencord - -export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & { - /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ - m: Record<PropertyKey, AnyModuleFactory>; -}; - -/** exports can be anything, however initially it is always an empty object */ -export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & { - [SYM_PATCHED_SOURCE]?: string; - [SYM_PATCHED_BY]?: Set<string>; -}; - -export type PatchedModuleFactory = AnyModuleFactory & { - [SYM_ORIGINAL_FACTORY]: AnyModuleFactory; - [SYM_PATCHED_SOURCE]?: string; - [SYM_PATCHED_BY]?: Set<string>; -}; - -export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory; diff --git a/packages/vencord-types/package.json b/packages/vencord-types/package.json index b3bbe315..64586919 100644 --- a/packages/vencord-types/package.json +++ b/packages/vencord-types/package.json @@ -21,7 +21,6 @@ "@types/node": "^22.13.4", "@types/react": "18.3.1", "@types/react-dom": "18.3.1", - "discord-types": "^1.3.26", "standalone-electron-types": "^34.2.0", "type-fest": "^4.35.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e493972..cde57a1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,12 +65,12 @@ importers: '@types/yazl': specifier: ^2.4.5 version: 2.4.6 + '@vencord/discord-types': + specifier: link:packages/discord-types + version: link:packages/discord-types diff: specifier: ^7.0.0 version: 7.0.0 - discord-types: - specifier: ^1.3.26 - version: 1.3.26 esbuild: specifier: ^0.25.1 version: 0.25.1 @@ -141,6 +141,18 @@ importers: specifier: ^0.3.5 version: 0.3.5 + packages/discord-types: + dependencies: + '@types/react': + specifier: ^19.0.10 + version: 19.0.12 + moment: + specifier: ^2.22.2 + version: 2.30.1 + type-fest: + specifier: ^4.41.0 + version: 4.41.0 + packages/vencord-types: dependencies: '@types/lodash': @@ -155,9 +167,6 @@ importers: '@types/react-dom': specifier: 18.3.1 version: 18.3.1 - discord-types: - specifier: ^1.3.26 - version: 1.3.26 standalone-electron-types: specifier: ^34.2.0 version: 34.2.0 @@ -522,9 +531,6 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react@17.0.2': - resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==} - '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} @@ -956,9 +962,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - discord-types@1.3.26: - resolution: {integrity: sha512-ToG51AOCH+JTQf7b+8vuYQe5Iqwz7nZ7StpECAZ/VZcI1ZhQk13pvt9KkRTfRv1xNvwJ2qib4e3+RifQlo8VPQ==} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -2328,6 +2331,10 @@ packages: resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==} engines: {node: '>=16'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2764,11 +2771,6 @@ snapshots: dependencies: '@types/react': 19.0.12 - '@types/react@17.0.2': - dependencies: - '@types/prop-types': 15.7.14 - csstype: 3.1.3 - '@types/react@18.3.1': dependencies: '@types/prop-types': 15.7.14 @@ -3255,11 +3257,6 @@ snapshots: dependencies: path-type: 4.0.0 - discord-types@1.3.26: - dependencies: - '@types/react': 17.0.2 - moment: 2.30.1 - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4923,6 +4920,8 @@ snapshots: type-fest@4.38.0: {} + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 0ee24a31..db0a1ce0 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -51,7 +51,7 @@ const nodeCommonOpts = { format: "cjs", platform: "node", target: ["esnext"], - // @ts-ignore this is never undefined + // @ts-expect-error this is never undefined external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external] }; diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 5c1732aa..516f6a1b 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -363,6 +363,6 @@ export const commonRendererPlugins = [ banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), - // @ts-ignore this is never undefined + // @ts-expect-error this is never undefined ...commonOpts.plugins ]; diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 6f4285ff..1cacd06b 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -8,9 +8,9 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; +import { Channel } from "@vencord/discord-types"; import { waitFor } from "@webpack"; import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index ac1dafc9..8ec23135 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -17,13 +17,11 @@ */ import { mergeDefaults } from "@utils/mergeDefaults"; +import { CommandArgument, Message } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { MessageActions, SnowflakeUtils } from "@webpack/common"; -import { Message } from "discord-types/general"; import type { PartialDeep } from "type-fest"; -import { Argument } from "./types"; - const createBotMessage = findByCodeLazy('username:"Clyde"'); export function generateId() { @@ -51,8 +49,8 @@ export function sendBotMessage(channelId: string, message: PartialDeep<Message>) * @param fallbackValue Fallback value in case this option wasn't passed * @returns Value */ -export function findOption<T>(args: Argument[], name: string): T & {} | undefined; -export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {}; -export function findOption(args: Argument[], name: string, fallbackValue?: any) { +export function findOption<T>(args: CommandArgument[], name: string): T & {} | undefined; +export function findOption<T>(args: CommandArgument[], name: string, fallbackValue: T): T & {}; +export function findOption(args: CommandArgument[], name: string, fallbackValue?: any) { return (args.find(a => a.name === name)?.value ?? fallbackValue) as any; } diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index af6a6fdf..231b3daf 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -18,38 +18,39 @@ import { Logger } from "@utils/Logger"; import { makeCodeblock } from "@utils/text"; +import { CommandArgument, CommandContext, CommandOption } from "@vencord/discord-types"; import { sendBotMessage } from "./commandHelpers"; -import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, VencordCommand } from "./types"; export * from "./commandHelpers"; export * from "./types"; -export let BUILT_IN: Command[]; -export const commands = {} as Record<string, Command>; +export let BUILT_IN: VencordCommand[]; +export const commands = {} as Record<string, VencordCommand>; // hack for plugins being evaluated before we can grab these from webpack -const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option; -const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option; +const OptPlaceholder = Symbol("OptionalMessageOption") as any as CommandOption; +const ReqPlaceholder = Symbol("RequiredMessageOption") as any as CommandOption; /** * Optional message option named "message" you can use in commands. * Used in "tableflip" or "shrug" * @see {@link RequiredMessageOption} */ -export let OptionalMessageOption: Option = OptPlaceholder; +export let OptionalMessageOption: CommandOption = OptPlaceholder; /** * Required message option named "message" you can use in commands. * Used in "me" * @see {@link OptionalMessageOption} */ -export let RequiredMessageOption: Option = ReqPlaceholder; +export let RequiredMessageOption: CommandOption = ReqPlaceholder; // Discord's command list has random gaps for some reason, which can cause issues while rendering the commands // Add this offset to every added command to keep them unique let commandIdOffset: number; -export const _init = function (cmds: Command[]) { +export const _init = function (cmds: VencordCommand[]) { try { BUILT_IN = cmds; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; @@ -61,7 +62,7 @@ export const _init = function (cmds: Command[]) { return cmds; } as never; -export const _handleCommand = function (cmd: Command, args: Argument[], ctx: CommandContext) { +export const _handleCommand = function (cmd: VencordCommand, args: CommandArgument[], ctx: CommandContext) { if (!cmd.isVencordCommand) return cmd.execute(args, ctx); @@ -92,7 +93,7 @@ export const _handleCommand = function (cmd: Command, args: Argument[], ctx: Com * Prepare a Command Option for Discord by filling missing fields * @param opt */ -export function prepareOption<O extends Option | Command>(opt: O): O { +export function prepareOption<O extends CommandOption | VencordCommand>(opt: O): O { opt.displayName ||= opt.name; opt.displayDescription ||= opt.description; opt.options?.forEach((opt, i, opts) => { @@ -109,7 +110,7 @@ export function prepareOption<O extends Option | Command>(opt: O): O { // Yes, Discord registers individual commands for each subcommand // TODO: This probably doesn't support nested subcommands. If that is ever needed, // investigate -function registerSubCommands(cmd: Command, plugin: string) { +function registerSubCommands(cmd: VencordCommand, plugin: string) { cmd.options?.forEach(o => { if (o.type !== ApplicationCommandOptionType.SUB_COMMAND) throw new Error("When specifying sub-command options, all options must be sub-commands."); @@ -132,7 +133,7 @@ function registerSubCommands(cmd: Command, plugin: string) { }); } -export function registerCommand<C extends Command>(command: C, plugin: string) { +export function registerCommand<C extends VencordCommand>(command: C, plugin: string) { if (!BUILT_IN) { console.warn( "[CommandsAPI]", diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts index 70b73775..c5ecb35e 100644 --- a/src/api/Commands/types.ts +++ b/src/api/Commands/types.ts @@ -1,106 +1,12 @@ /* - * 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) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ -import { Channel, Guild } from "discord-types/general"; -import { Promisable } from "type-fest"; +import { Command } from "@vencord/discord-types"; +export { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@vencord/discord-types/enums"; -export interface CommandContext { - channel: Channel; - guild?: Guild; -} - -export const enum ApplicationCommandOptionType { - SUB_COMMAND = 1, - SUB_COMMAND_GROUP = 2, - STRING = 3, - INTEGER = 4, - BOOLEAN = 5, - USER = 6, - CHANNEL = 7, - ROLE = 8, - MENTIONABLE = 9, - NUMBER = 10, - ATTACHMENT = 11, -} - -export const enum ApplicationCommandInputType { - BUILT_IN = 0, - BUILT_IN_TEXT = 1, - BUILT_IN_INTEGRATION = 2, - BOT = 3, - PLACEHOLDER = 4, -} - -export interface Option { - name: string; - displayName?: string; - type: ApplicationCommandOptionType; - description: string; - displayDescription?: string; - required?: boolean; - options?: Option[]; - choices?: Array<ChoicesOption>; -} - -export interface ChoicesOption { - label: string; - value: string; - name: string; - displayName?: string; -} - -export const enum ApplicationCommandType { - CHAT_INPUT = 1, - USER = 2, - MESSAGE = 3, -} - -export interface CommandReturnValue { - content: string; - /** TODO: implement */ - cancel?: boolean; -} - -export interface Argument { - type: ApplicationCommandOptionType; - name: string; - value: string; - focused: undefined; - options: Argument[]; -} - -export interface Command { - id?: string; - applicationId?: string; - type?: ApplicationCommandType; - inputType?: ApplicationCommandInputType; - plugin?: string; +export interface VencordCommand extends Command { isVencordCommand?: boolean; - - name: string; - untranslatedName?: string; - displayName?: string; - description: string; - untranslatedDescription?: string; - displayDescription?: string; - - options?: Option[]; - predicate?(ctx: CommandContext): boolean; - - execute(args: Argument[], ctx: CommandContext): Promisable<void | CommandReturnValue>; } diff --git a/src/api/DataStore/index.ts b/src/api/DataStore/index.ts index 47ae39db..80ebb065 100644 --- a/src/api/DataStore/index.ts +++ b/src/api/DataStore/index.ts @@ -22,9 +22,9 @@ export function promisifyRequest<T = undefined>( request: IDBRequest<T> | IDBTransaction, ): Promise<T> { return new Promise<T>((resolve, reject) => { - // @ts-ignore - file size hacks + // @ts-expect-error - file size hacks request.oncomplete = request.onsuccess = () => resolve(request.result); - // @ts-ignore - file size hacks + // @ts-expect-error - file size hacks request.onabort = request.onerror = () => reject(request.error); }); } diff --git a/src/api/MemberListDecorators.tsx b/src/api/MemberListDecorators.tsx index ada60776..2367e4cf 100644 --- a/src/api/MemberListDecorators.tsx +++ b/src/api/MemberListDecorators.tsx @@ -17,7 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { Channel, User } from "discord-types/general/index.js"; +import { Channel, User } from "@vencord/discord-types"; import { JSX } from "react"; interface DecoratorProps { diff --git a/src/api/MessageDecorations.tsx b/src/api/MessageDecorations.tsx index 1b94c18d..8cf492c7 100644 --- a/src/api/MessageDecorations.tsx +++ b/src/api/MessageDecorations.tsx @@ -17,7 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { Channel, Message } from "discord-types/general/index.js"; +import { Channel, Message } from "@vencord/discord-types"; import { JSX } from "react"; export interface MessageDecorationProps { diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 1b55ff34..8b1d9e78 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -17,9 +17,8 @@ */ import { Logger } from "@utils/Logger"; +import type { Channel, CustomEmoji, Message } from "@vencord/discord-types"; import { MessageStore } from "@webpack/common"; -import { CustomEmoji } from "@webpack/types"; -import type { Channel, Message } from "discord-types/general"; import type { Promisable } from "type-fest"; const MessageEventsLogger = new Logger("MessageEvents", "#e5c890"); diff --git a/src/api/MessagePopover.tsx b/src/api/MessagePopover.tsx index 71787954..c7b2a090 100644 --- a/src/api/MessagePopover.tsx +++ b/src/api/MessagePopover.tsx @@ -18,7 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; -import { Channel, Message } from "discord-types/general"; +import { Channel, Message } from "@vencord/discord-types"; import type { ComponentType, MouseEventHandler } from "react"; const logger = new Logger("MessagePopover"); diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts index 284a2088..7c4b3d5c 100644 --- a/src/api/MessageUpdater.ts +++ b/src/api/MessageUpdater.ts @@ -4,9 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { FluxStore, Message } from "@vencord/discord-types"; import { MessageCache, MessageStore } from "@webpack/common"; -import { FluxStore } from "@webpack/types"; -import { Message } from "discord-types/general"; /** * Update and re-render a message diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 08d2f8ca..5c8965bf 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -268,7 +268,7 @@ type ResolveUseSettings<T extends object> = { [Key in keyof T]: Key extends string ? T[Key] extends Record<string, unknown> - // @ts-ignore "Type instantiation is excessively deep and possibly infinite" + // @ts-expect-error "Type instantiation is excessively deep and possibly infinite" ? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never : Key : never; diff --git a/src/components/DonateButton.tsx b/src/components/DonateButton.tsx index ee2f3ed3..92af9831 100644 --- a/src/components/DonateButton.tsx +++ b/src/components/DonateButton.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { ButtonProps } from "@vencord/discord-types"; import { Button } from "@webpack/common"; -import { ButtonProps } from "@webpack/types"; import { Heart } from "./Heart"; diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx index fe0e987c..378ee34c 100644 --- a/src/components/PluginSettings/ContributorModal.tsx +++ b/src/components/PluginSettings/ContributorModal.tsx @@ -14,8 +14,8 @@ import { DevsById } from "@utils/constants"; import { fetchUserProfile } from "@utils/discord"; import { classes, pluralise } from "@utils/misc"; import { ModalContent, ModalRoot, openModal } from "@utils/modal"; +import { User } from "@vencord/discord-types"; import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; -import { User } from "discord-types/general"; import Plugins from "~plugins"; diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 17ab2662..29ce90c9 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -29,9 +29,9 @@ import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; -import { User } from "discord-types/general"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index f8c71cae..fcb33d38 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -6,9 +6,10 @@ import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; +import { ModuleFactory } from "@vencord/discord-types/webpack"; import * as Webpack from "@webpack"; import { wreq } from "@webpack"; -import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d"; +import { AnyModuleFactory } from "webpack"; export async function loadLazyChunks() { const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index 4ee2d394..39071a0c 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -28,7 +28,7 @@ async function runReporter() { } }, "Vencord Reporter"); - // @ts-ignore + // @ts-expect-error Vencord.Webpack._initReporter = function () { // initReporter is called in the patched entry point of Discord // setImmediate to only start searching for lazy chunks after Discord initialized the app @@ -83,7 +83,6 @@ async function runReporter() { result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports); if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail"); } else { - // @ts-ignore result = Webpack[method](...args); } diff --git a/src/main/patcher.ts b/src/main/patcher.ts index 60b169af..8868caa7 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -38,7 +38,7 @@ const asarPath = join(dirname(injectorPath), "..", asarName); const discordPkg = require(join(asarPath, "package.json")); require.main!.filename = join(asarPath, discordPkg.main); -// @ts-ignore Untyped method? Dies from cringe +// @ts-expect-error Untyped method? Dies from cringe app.setAppPath(asarPath); if (!IS_VANILLA) { diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index b00df6b0..8011e331 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -30,8 +30,8 @@ import { Margins } from "@utils/margins"; import { shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; +import { User } from "@vencord/discord-types"; import { Forms, Toasts, UserStore } from "@webpack/common"; -import { User } from "discord-types/general"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index 30920a06..ad1f255b 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { WebpackRequire } from "@webpack/wreq.d"; +import { WebpackRequire } from "@vencord/discord-types/webpack"; const settings = definePluginSettings({ disableAnalytics: { diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index a9e34f78..0eb8e13c 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -213,7 +213,7 @@ export default definePlugin({ get chromiumVersion() { try { return VencordNative.native.getVersions().chrome - // @ts-ignore Typescript will add userAgentData IMMEDIATELY + // @ts-expect-error Typescript will add userAgentData IMMEDIATELY || navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version || null; } catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 825bbc20..956343f6 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -32,8 +32,8 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; +import { Channel } from "@vencord/discord-types"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -196,7 +196,6 @@ export default definePlugin({ } } - // @ts-ignore outdated type const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles; if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index f871b8ee..ce9f6b51 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -9,9 +9,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common"; -import { User } from "discord-types/general"; interface UserProfileProps { popoutProps: Record<string, any>; diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx index 92b6f57f..e77e3a02 100644 --- a/src/plugins/biggerStreamPreview/index.tsx +++ b/src/plugins/biggerStreamPreview/index.tsx @@ -21,8 +21,8 @@ import { ScreenshareIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { openImageModal } from "@utils/discord"; import definePlugin from "@utils/types"; +import { Channel, User } from "@vencord/discord-types"; import { Menu } from "@webpack/common"; -import { Channel, User } from "discord-types/general"; import { ApplicationStreamingStore, ApplicationStreamPreviewStore } from "./webpack/stores"; import { ApplicationStream, Stream } from "./webpack/types/stores"; diff --git a/src/plugins/biggerStreamPreview/webpack/types/stores.ts b/src/plugins/biggerStreamPreview/webpack/types/stores.ts index 0265986f..e11ede50 100644 --- a/src/plugins/biggerStreamPreview/webpack/types/stores.ts +++ b/src/plugins/biggerStreamPreview/webpack/types/stores.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { FluxStore } from "@webpack/types"; +import { FluxStore } from "@vencord/discord-types"; export interface ApplicationStreamPreviewStore extends FluxStore { getIsPreviewLoading: (guildId: string | bigint | null, channelId: string | bigint, ownerId: string | bigint) => boolean; diff --git a/src/plugins/copyUserURLs/index.tsx b/src/plugins/copyUserURLs/index.tsx index 9e15cc82..744f7cca 100644 --- a/src/plugins/copyUserURLs/index.tsx +++ b/src/plugins/copyUserURLs/index.tsx @@ -21,8 +21,8 @@ import { LinkIcon } from "@components/Icons"; import { copyToClipboard } from "@utils/clipboard"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import type { Channel, User } from "@vencord/discord-types"; import { Menu } from "@webpack/common"; -import type { Channel, User } from "discord-types/general"; interface UserContextProps { channel: Channel; diff --git a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts index f47ccbc5..588bbdf9 100644 --- a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts +++ b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts @@ -6,8 +6,8 @@ import { debounce } from "@shared/debounce"; import { proxyLazy } from "@utils/lazy"; +import { User } from "@vencord/discord-types"; import { useEffect, useState, zustandCreate } from "@webpack/common"; -import { User } from "discord-types/general"; import { AvatarDecoration } from "../../"; import { getUsersDecorations } from "../api"; diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx index a3edc097..56c68410 100644 --- a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx @@ -11,9 +11,9 @@ import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { Queue } from "@utils/Queue"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common"; -import { User } from "discord-types/general"; import { Decoration, getPresets, Preset } from "../../lib/api"; import { GUILD_ID, INVITE_KEY } from "../../lib/constants"; diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index 6f84e7f7..60a95336 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -25,9 +25,9 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; +import { Guild } from "@vencord/discord-types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; -import { Guild } from "discord-types/general"; import { Promisable } from "type-fest"; const StickersStore = findStoreLazy("StickersStore"); diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index b3909595..380147fe 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -23,10 +23,9 @@ import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { getCurrentGuild, getEmojiURL } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, Patch } from "@utils/types"; +import type { Emoji, Message } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; -import type { Emoji } from "@webpack/types"; -import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import type { ReactElement, ReactNode } from "react"; @@ -813,7 +812,6 @@ export default definePlugin({ let isUsableTwitchSubEmote = false; if (e.managed && e.guildId) { - // @ts-ignore outdated type const myRoles = GuildMemberStore.getSelfMember(e.guildId)?.roles ?? []; isUsableTwitchSubEmote = e.roles.some(r => myRoles.includes(r)); } diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 61ab1731..a84722c1 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -25,16 +25,12 @@ import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; -import { User } from "discord-types/general"; import { ReactElement } from "react"; import virtualMerge from "virtual-merge"; -interface UserProfile extends User { - themeColors?: Array<number>; -} - interface Colors { primary: number; accent: number; @@ -220,7 +216,7 @@ export default definePlugin({ </Forms.FormSection>); }, settings, - colorDecodeHook(user: UserProfile) { + colorDecodeHook(user: User) { if (user) { // don't replace colors if already set with nitro if (settings.store.nitroFirst && user.themeColors) return user; diff --git a/src/plugins/favEmojiFirst/index.ts b/src/plugins/favEmojiFirst/index.ts index ebb89f7e..e9a9bc58 100644 --- a/src/plugins/favEmojiFirst/index.ts +++ b/src/plugins/favEmojiFirst/index.ts @@ -18,8 +18,8 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Emoji } from "@vencord/discord-types"; import { EmojiStore } from "@webpack/common"; -import { Emoji } from "@webpack/types"; interface EmojiAutocompleteState { query?: { diff --git a/src/plugins/forceOwnerCrown/index.ts b/src/plugins/forceOwnerCrown/index.ts index bf115c64..f7e4b470 100644 --- a/src/plugins/forceOwnerCrown/index.ts +++ b/src/plugins/forceOwnerCrown/index.ts @@ -18,8 +18,8 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Channel, User } from "@vencord/discord-types"; import { GuildStore } from "@webpack/common"; -import { Channel, User } from "discord-types/general"; export default definePlugin({ name: "ForceOwnerCrown", diff --git a/src/plugins/fullSearchContext/index.tsx b/src/plugins/fullSearchContext/index.tsx index 7c0dbea6..d5b9c8a1 100644 --- a/src/plugins/fullSearchContext/index.tsx +++ b/src/plugins/fullSearchContext/index.tsx @@ -22,9 +22,9 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { NoopComponent } from "@utils/react"; import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { filters, findByCodeLazy, waitFor } from "@webpack"; import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:"); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 3ab5fca4..b2454f9c 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -19,9 +19,9 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { Channel, Message } from "@vencord/discord-types"; import { findLazy } from "@webpack"; import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common"; -import { Channel, Message } from "discord-types/general"; interface Sticker { id: string; diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx index fe9e6899..0198a3e1 100644 --- a/src/plugins/hideAttachments/index.tsx +++ b/src/plugins/hideAttachments/index.tsx @@ -25,8 +25,8 @@ import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; +import { MessageSnapshot } from "@vencord/discord-types"; import { ChannelStore } from "@webpack/common"; -import { MessageSnapshot } from "@webpack/types"; const KEY = "HideAttachments_HiddenIds"; @@ -56,7 +56,7 @@ export default definePlugin({ }], renderMessagePopoverButton(msg) { - // @ts-ignore - discord-types lags behind discord. + // @ts-expect-error - discord-types lags behind discord. const hasAttachmentsInShapshots = msg.messageSnapshots.some( (snapshot: MessageSnapshot) => snapshot?.message.attachments.length ); diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 4a268868..8288935b 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -30,9 +30,9 @@ import { disableStyle, enableStyle } from "@api/Styles"; import { Logger } from "@utils/Logger"; import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; +import { FluxEvents } from "@vencord/discord-types"; import { FluxDispatcher } from "@webpack/common"; import { patches } from "@webpack/patcher"; -import { FluxEvents } from "@webpack/types"; import Plugins from "~plugins"; diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index ab124192..a19a4615 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -23,8 +23,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getStegCloak } from "@utils/dependencies"; import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common"; -import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; import { buildEncModal } from "./components/EncryptionModal"; diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index ad7491cc..97f6f23c 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -23,8 +23,8 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { FluxStore } from "@vencord/discord-types"; import { findStoreLazy } from "@webpack"; -import { FluxStore } from "@webpack/types"; import { MemberCount } from "./MemberCount"; diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx index 8f18567e..11316ef8 100644 --- a/src/plugins/mentionAvatars/index.tsx +++ b/src/plugins/mentionAvatars/index.tsx @@ -10,8 +10,8 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { GuildRoleStore, SelectedGuildStore, useState } from "@webpack/common"; -import { User } from "discord-types/general"; const settings = definePluginSettings({ showAtSymbol: { diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index 460b95a8..cd331b9d 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -9,9 +9,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { SnowflakeUtils, Tooltip } from "@webpack/common"; -import { Message } from "discord-types/general"; type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted"); type Fill = [FillValue, FillValue, FillValue]; diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 1e4f68e0..663fa349 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -24,6 +24,7 @@ import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin, { OptionType } from "@utils/types"; +import { Channel, Message } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, @@ -39,7 +40,6 @@ import { Text, UserStore } from "@webpack/common"; -import { Channel, Message } from "discord-types/general"; import { JSX } from "react"; const messageCache = new Map<string, { @@ -217,7 +217,7 @@ function withEmbeddedBy(message: Message, embeddedBy: string[]) { return new Proxy(message, { get(_, prop) { if (prop === "vencordEmbeddedBy") return embeddedBy; - // @ts-ignore ts so bad + // @ts-expect-error ts so bad return Reflect.get(...arguments); } }); @@ -225,7 +225,7 @@ function withEmbeddedBy(message: Message, embeddedBy: string[]) { function MessageEmbedAccessory({ message }: { message: Message; }) { - // @ts-ignore + // @ts-expect-error const embeddedBy: string[] = message.vencordEmbeddedBy ?? []; const accessories = [] as (JSX.Element | null)[]; diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 3c7aecae..0be4a0f5 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -28,9 +28,9 @@ import { getIntlMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common"; -import { Message } from "discord-types/general"; import overlayStyle from "./deleteStyleOverlay.css?managed"; import textStyle from "./deleteStyleText.css?managed"; diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts index 49e88c42..90ce0ceb 100644 --- a/src/plugins/messageTags/index.ts +++ b/src/plugins/messageTags/index.ts @@ -92,7 +92,7 @@ export default definePlugin({ // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed const oldTags = await DataStore.get<Tag[]>(DATA_KEY); if (oldTags != null) { - // @ts-ignore + // @ts-expect-error settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag]))); await DataStore.del(DATA_KEY); } @@ -211,7 +211,7 @@ export default definePlugin({ description: Object.values(getTags()) .map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`) .join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`, - // @ts-ignore + // @ts-expect-error color: 0xd77f7f, type: "rich", } diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index b90eb7ed..8d81e43d 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -23,9 +23,9 @@ import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; +import { Channel, User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, Text, useMemo, UserStore } from "@webpack/common"; -import { Channel, User } from "discord-types/general"; import { JSX } from "react"; const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); diff --git a/src/plugins/newGuildSettings/index.tsx b/src/plugins/newGuildSettings/index.tsx index f8a517fa..fba0c1c3 100644 --- a/src/plugins/newGuildSettings/index.tsx +++ b/src/plugins/newGuildSettings/index.tsx @@ -24,9 +24,9 @@ import { definePluginSettings } from "@api/Settings"; import { CogWheel } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { Guild } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { Menu } from "@webpack/common"; -import { Guild } from "discord-types/general"; const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", { diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index 973e3796..1a4d691f 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -21,8 +21,8 @@ import { Devs } from "@utils/constants"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { i18n, RelationshipStore } from "@webpack/common"; -import { Message } from "discord-types/general"; interface MessageDeleteProps { // Internal intl message for BLOCKED_MESSAGE_COUNT diff --git a/src/plugins/noReplyMention/index.tsx b/src/plugins/noReplyMention/index.tsx index 16b3a3e0..b4de573f 100644 --- a/src/plugins/noReplyMention/index.tsx +++ b/src/plugins/noReplyMention/index.tsx @@ -19,7 +19,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import type { Message } from "discord-types/general"; +import type { Message } from "@vencord/discord-types"; const settings = definePluginSettings({ userList: { diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts index e9cde652..0ef81781 100644 --- a/src/plugins/onePingPerDM/index.ts +++ b/src/plugins/onePingPerDM/index.ts @@ -7,8 +7,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { MessageJSON } from "@vencord/discord-types"; import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common"; -import { MessageJSON } from "discord-types/general"; const enum ChannelType { DM = 1, diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 577d8bd7..7ffba7c5 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -27,7 +27,6 @@ function showDisableInvites(guildId: string) { if (!guild) return false; return ( - // @ts-expect-error !hasGuildFeature(guild, "INVITES_DISABLED") && PermissionStore.getGuildPermissionProps(guild).canManageRoles ); diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 46ddb146..28beabe1 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -22,10 +22,9 @@ import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { copyToClipboard } from "@utils/clipboard"; import { getIntlMessage, getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { Guild, Role, UnicodeEmoji, User } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; -import { UnicodeEmoji } from "@webpack/types"; -import type { Guild, Role, User } from "discord-types/general"; import { settings } from ".."; import { cl, getGuildPermissionSpecMap } from "../utils"; diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index 85b1d418..5185abb3 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -19,9 +19,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { getIntlMessage } from "@utils/discord"; import { classes } from "@utils/misc"; +import type { Guild, GuildMember } from "@vencord/discord-types"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common"; -import type { Guild, GuildMember } from "discord-types/general"; import { PermissionsSortOrder, settings } from ".."; import { cl, getGuildPermissionSpecMap, getSortedRoles, sortUserRoles } from "../utils"; diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index cc942f09..ed2471fb 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -25,9 +25,9 @@ import { SafetyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import type { Guild, GuildMember } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, useRef, UserStore } from "@webpack/common"; -import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; import UserPermissions from "./components/UserPermissions"; @@ -71,7 +71,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { const { permissions, header } = match(type) .returnType<{ permissions: RoleOrUserPermission[], header: string; }>() .with(MenuItemParentType.User, () => { - const member = GuildMemberStore.getMember(guildId, id!); + const member = GuildMemberStore.getMember(guildId, id!)!; const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member) .map(role => ({ diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts index 409c0896..022dca6e 100644 --- a/src/plugins/permissionsViewer/utils.ts +++ b/src/plugins/permissionsViewer/utils.ts @@ -17,9 +17,9 @@ */ import { classNameFactory } from "@api/Styles"; +import { Guild, GuildMember, Role } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { GuildRoleStore } from "@webpack/common"; -import { Guild, GuildMember, Role } from "discord-types/general"; import { PermissionsSortOrder, settings } from "."; import { PermissionType } from "./components/RolesAndUsersPermissions"; diff --git a/src/plugins/petpet/index.ts b/src/plugins/petpet/index.ts index 708c6f0c..40fd0af8 100644 --- a/src/plugins/petpet/index.ts +++ b/src/plugins/petpet/index.ts @@ -16,10 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import { makeLazy } from "@utils/lazy"; import definePlugin from "@utils/types"; +import { CommandArgument, CommandContext } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DraftType, UploadHandler, UploadManager, UserUtils } from "@webpack/common"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; @@ -54,7 +55,7 @@ function loadImage(source: File | string) { }); } -async function resolveImage(options: Argument[], ctx: CommandContext, noServerPfp: boolean): Promise<File | string | null> { +async function resolveImage(options: CommandArgument[], ctx: CommandContext, noServerPfp: boolean): Promise<File | string | null> { for (const opt of options) { switch (opt.name) { case "image": @@ -177,6 +178,8 @@ export default definePlugin({ } gif.finish(); + // @ts-ignore This causes a type error on *only some* typescript versions. + // usage adheres to mdn https://developer.mozilla.org/en-US/docs/Web/API/File/File#parameters const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" }); // Immediately after the command finishes, Discord clears all input, including pending attachments. // Thus, setTimeout is needed to make this execute after Discord cleared the input diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index 59fee9c0..53524257 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -11,9 +11,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; +import { Channel } from "@vencord/discord-types"; import { findByPropsLazy, findStoreLazy } from "@webpack"; import { Clickable, ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { contextMenus } from "./components/contextMenu"; import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal"; diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index 7829295a..f804e987 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -25,9 +25,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; -import { User } from "discord-types/general"; export interface Session { sessionId: string; diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index ded408f9..1f64c714 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -20,9 +20,9 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; +import { MessageAttachment } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; -import { MessageAttachment } from "discord-types/general"; const UploadStore = findByPropsLazy("getUploads"); diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index dcd2038c..33e99873 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -19,8 +19,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; import NoReplyMentionPlugin from "plugins/noReplyMention"; diff --git a/src/plugins/readAllNotificationsButton/index.tsx b/src/plugins/readAllNotificationsButton/index.tsx index 419e93af..274e98d3 100644 --- a/src/plugins/readAllNotificationsButton/index.tsx +++ b/src/plugins/readAllNotificationsButton/index.tsx @@ -22,9 +22,9 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Channel } from "@vencord/discord-types"; import { findStoreLazy } from "@webpack"; import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common"; -import { Channel } from "discord-types/general"; interface ThreadJoined { channel: Channel; diff --git a/src/plugins/relationshipNotifier/types.ts b/src/plugins/relationshipNotifier/types.ts index c60d5397..f2e482d6 100644 --- a/src/plugins/relationshipNotifier/types.ts +++ b/src/plugins/relationshipNotifier/types.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Channel } from "discord-types/general"; +import { Channel } from "@vencord/discord-types"; export interface ChannelDelete { type: "CHANNEL_DELETE"; diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts index 09ef5cdd..aaef783b 100644 --- a/src/plugins/relationshipNotifier/utils.ts +++ b/src/plugins/relationshipNotifier/utils.ts @@ -19,9 +19,9 @@ import { DataStore, Notices } from "@api/index"; import { showNotification } from "@api/Notifications"; import { getUniqueUsername, openUserProfile } from "@utils/discord"; +import { FluxStore } from "@vencord/discord-types"; import { findStoreLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common"; -import { FluxStore } from "@webpack/types"; import settings from "./settings"; import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types"; diff --git a/src/plugins/replyTimestamp/index.tsx b/src/plugins/replyTimestamp/index.tsx index 0be5dfec..dcf70c70 100644 --- a/src/plugins/replyTimestamp/index.tsx +++ b/src/plugins/replyTimestamp/index.tsx @@ -9,9 +9,9 @@ import "./style.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import type { Message } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DateUtils, Timestamp } from "@webpack/common"; -import type { Message } from "discord-types/general"; import type { HTMLAttributes } from "react"; const MessageClasses = findByPropsLazy("separator", "latin24CompactTimeStamp"); diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 32546b9b..d8374e6f 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -24,9 +24,9 @@ import { NotesIcon, OpenExternalIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; +import { Guild, User } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { Alerts, Button, Menu, Parser, TooltipContainer } from "@webpack/common"; -import { Guild, User } from "discord-types/general"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; diff --git a/src/plugins/seeSummaries/index.tsx b/src/plugins/seeSummaries/index.tsx index 343348f1..a5af9bc4 100644 --- a/src/plugins/seeSummaries/index.tsx +++ b/src/plugins/seeSummaries/index.tsx @@ -111,7 +111,6 @@ export default definePlugin({ // SUMMARIES_ENABLED feature is not in discord-types const guild = GuildStore.getGuild(channel.guild_id); - // @ts-expect-error return hasGuildFeature(guild, "SUMMARIES_ENABLED_GA"); } }); diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 359590cf..2fa687e8 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -11,9 +11,9 @@ import { getGuildAcronym, openImageModal, openUserProfile } from "@utils/discord import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; +import { Guild, User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildRoleStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; -import { Guild, User } from "discord-types/general"; const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const FriendRow = findComponentByCodeLazy("discriminatorClass:", ".isMobileOnline", "getAvatarURL"); diff --git a/src/plugins/serverInfo/index.tsx b/src/plugins/serverInfo/index.tsx index 2a3f3adf..98b9b2d5 100644 --- a/src/plugins/serverInfo/index.tsx +++ b/src/plugins/serverInfo/index.tsx @@ -7,8 +7,8 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Guild } from "@vencord/discord-types"; import { Menu } from "@webpack/common"; -import { Guild } from "discord-types/general"; import { openGuildInfoModal } from "./GuildInfoModal"; diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index f99c0be9..882c869e 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -25,9 +25,9 @@ import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { Tooltip, UserProfileStore } from "@webpack/common"; -import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index bbe286af..4543301b 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -20,9 +20,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { formatDuration } from "@utils/text"; +import type { Channel } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; -import type { Channel } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; import { sortPermissionOverwrites } from "../../permissionsViewer/utils"; diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 149d422b..c99afc84 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -25,9 +25,9 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; +import type { Channel, Role } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { ChannelStore, PermissionsBits, PermissionStore, Tooltip } from "@webpack/common"; -import type { Channel, Role } from "discord-types/general"; import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index ba92e82a..b9b10714 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -10,7 +10,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Message, User } from "discord-types/general"; +import { Message, User } from "@vencord/discord-types"; interface UsernameProps { author: { nick: string; }; @@ -62,7 +62,7 @@ export default definePlugin({ const user = userOverride ?? message.author; let { username } = user; if (settings.store.displayNames) - username = (user as any).globalName || username; + username = user.globalName || username; const { nick } = author; const prefix = withMentionPrefix ? "@" : ""; diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx index 2c7c8a85..1bd68a0c 100644 --- a/src/plugins/showTimeoutDuration/index.tsx +++ b/src/plugins/showTimeoutDuration/index.tsx @@ -12,9 +12,9 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { findComponentLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, Text, Tooltip } from "@webpack/common"; -import { Message } from "discord-types/general"; import { FunctionComponent, ReactNode } from "react"; const countDownFilter = canonicalizeMatch("#{intl::MAX_AGE_NEVER}"); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index d8b64cf9..42af9a3a 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -23,8 +23,8 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { DateUtils, RelationshipStore, Text, TooltipContainer } from "@webpack/common"; -import { User } from "discord-types/general"; import { PropsWithChildren } from "react"; const formatter = new Intl.DateTimeFormat(undefined, { diff --git a/src/plugins/spotifyShareCommands/index.ts b/src/plugins/spotifyShareCommands/index.ts index ed793963..e11864ae 100644 --- a/src/plugins/spotifyShareCommands/index.ts +++ b/src/plugins/spotifyShareCommands/index.ts @@ -16,10 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ApplicationCommandInputType, Command, findOption, OptionalMessageOption, sendBotMessage } from "@api/Commands"; +import { ApplicationCommandInputType, findOption, OptionalMessageOption, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import definePlugin from "@utils/types"; +import { Command } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, MessageActions } from "@webpack/common"; diff --git a/src/plugins/themeAttributes/index.ts b/src/plugins/themeAttributes/index.ts index 7d904e7e..ca54ea27 100644 --- a/src/plugins/themeAttributes/index.ts +++ b/src/plugins/themeAttributes/index.ts @@ -6,8 +6,8 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; export default definePlugin({ diff --git a/src/plugins/translate/TranslationAccessory.tsx b/src/plugins/translate/TranslationAccessory.tsx index db9461d8..00e35689 100644 --- a/src/plugins/translate/TranslationAccessory.tsx +++ b/src/plugins/translate/TranslationAccessory.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { Message } from "@vencord/discord-types"; import { Parser, useEffect, useState } from "@webpack/common"; -import { Message } from "discord-types/general"; import { TranslateIcon } from "./TranslateIcon"; import { cl, TranslationValue } from "./utils"; diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 160b002d..8f667de9 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -56,7 +56,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s return oldKeys.length === currentKeys.length && currentKeys.every(key => old[key] != null); } ); - const currentChannelId: string = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId()); + const currentChannelId = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId()); if (!settings.store.includeMutedChannels) { const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId); diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index fe7acd48..bcfea898 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -21,8 +21,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; -import { User } from "discord-types/general"; import { PropsWithChildren } from "react"; import managedStyle from "./style.css?managed"; diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index 2df64b72..e5f8557f 100644 --- a/src/plugins/unsuppressEmbeds/index.tsx +++ b/src/plugins/unsuppressEmbeds/index.tsx @@ -20,8 +20,8 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { MessageSnapshot } from "@vencord/discord-types"; import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; -import { MessageSnapshot } from "@webpack/types"; const EMBED_SUPPRESSED = 1 << 2; diff --git a/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx b/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx index c2a54f14..b4c32638 100644 --- a/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx +++ b/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx @@ -20,9 +20,9 @@ import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { getIntlMessage } from "@utils/discord"; import { classes } from "@utils/misc"; +import { Message } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { Tooltip, UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; import { settings } from "./settings"; import { useFormattedPronouns } from "./utils"; diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index 9029cdc5..12d95242 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -7,9 +7,9 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; +import { Channel } from "@vencord/discord-types"; import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores } from "@webpack/common"; -import { Channel } from "discord-types/general"; const cl = classNameFactory("vc-uvs-"); diff --git a/src/plugins/validReply/index.ts b/src/plugins/validReply/index.ts index 989513d0..0f0a198d 100644 --- a/src/plugins/validReply/index.ts +++ b/src/plugins/validReply/index.ts @@ -6,10 +6,9 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Channel, Message, User } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { FluxDispatcher, RestAPI } from "@webpack/common"; -import { Message, User } from "discord-types/general"; -import { Channel } from "discord-types/general/index.js"; const enum ReferencedMessageState { Loaded, diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index 02eb216c..117c1388 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -144,7 +144,14 @@ function playSample(tempSettings: any, type: string) { const currentUser = UserStore.getCurrentUser(); const myGuildId = SelectedGuildStore.getGuildId(); - speak(formatText(s[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), s); + speak(formatText( + s[type + "Message"], + currentUser.username, + "general", + currentUser.globalName ?? currentUser.username, + GuildMemberStore.getNick(myGuildId!, currentUser.id) ?? currentUser.username), + s + ); } export default definePlugin({ @@ -177,7 +184,7 @@ export default definePlugin({ const template = settings.store[type + "Message"]; const user = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username; const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user); - const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user); + const nickname = user && (GuildMemberStore.getNick(myGuildId!, userId) ?? user); const channel = ChannelStore.getChannel(id).name; speak(formatText(template, user, channel, displayName, nickname)); diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 2afdb2d5..3b8485fc 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -22,8 +22,8 @@ import { ImageIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { openImageModal } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import type { Channel, Guild, User } from "@vencord/discord-types"; import { GuildMemberStore, IconUtils, Menu } from "@webpack/common"; -import type { Channel, Guild, User } from "discord-types/general"; interface UserContextProps { diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index 7eb7b78f..e6781784 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -27,8 +27,8 @@ import { Margins } from "@utils/margins"; import { copyWithToast } from "@utils/misc"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { Button, ChannelStore, Forms, GuildRoleStore, Menu, Text } from "@webpack/common"; -import { Message } from "discord-types/general"; const CopyIcon = () => { diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index f14de9f5..a2826a2c 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -22,10 +22,9 @@ import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; +import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common"; -import { CustomEmoji } from "@webpack/types"; -import { Message, ReactionEmoji, User } from "discord-types/general"; const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); diff --git a/src/plugins/xsOverlay/index.tsx b/src/plugins/xsOverlay/index.tsx index 1ffbd3ff..27faac93 100644 --- a/src/plugins/xsOverlay/index.tsx +++ b/src/plugins/xsOverlay/index.tsx @@ -9,9 +9,9 @@ import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import type { Channel, Embed, GuildMember, MessageAttachment, User } from "@vencord/discord-types"; import { findByCodeLazy, findLazy } from "@webpack"; import { Button, ChannelStore, GuildRoleStore, GuildStore, UserStore } from "@webpack/common"; -import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10); diff --git a/src/utils/apng-canvas.js b/src/utils/apng-canvas.js index 5149dcc0..62203a75 100644 --- a/src/utils/apng-canvas.js +++ b/src/utils/apng-canvas.js @@ -1,6 +1,5 @@ /* eslint-disable */ -const self = module.exports; /** * apng-canvas v2.1.2 * @@ -549,11 +548,7 @@ const self = module.exports; }).call( this, Y("VCmEsw"), - "undefined" != typeof self - ? self - : "undefined" != typeof window - ? window - : {} + module.exports ); }, { VCmEsw: 2 }, @@ -880,11 +875,7 @@ const self = module.exports; }); }).call( this, - "undefined" != typeof self - ? self - : "undefined" != typeof window - ? window - : {} + module.exports ); }, { "./loader": 6, "./parser": 7, "./support-test": 8 }, @@ -1153,11 +1144,7 @@ const self = module.exports; }; }).call( this, - "undefined" != typeof self - ? self - : "undefined" != typeof window - ? window - : {} + module.exports ); }, { "es6-promise": 1 }, diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index fce909c3..4a652a29 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,9 +17,8 @@ */ import { MessageObject } from "@api/MessageEvents"; +import { Channel, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; -import { Channel, Guild, Message, User } from "discord-types/general"; -import GuildFeatures from "discord-types/other/Constants"; import { Except } from "type-fest"; import { runtimeHashMessageKey } from "./intlHash"; @@ -228,6 +227,6 @@ export function getGuildAcronym(guild: Guild): string { .replace(/\s/g, ""); } -export function hasGuildFeature(guild: Guild, feature: keyof GuildFeatures["GuildFeatures"]): boolean { +export function hasGuildFeature(guild: Guild, feature: GuildFeatures): boolean { return guild.features?.has(feature) ?? false; } diff --git a/src/utils/types.ts b/src/utils/types.ts index 3ad63ea1..1c53b971 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -18,14 +18,13 @@ import { ProfileBadge } from "@api/Badges"; import { ChatBarButtonFactory } from "@api/ChatButtons"; -import { Command } from "@api/Commands"; import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { MemberListDecoratorFactory } from "@api/MemberListDecorators"; import { MessageAccessoryFactory } from "@api/MessageAccessories"; import { MessageDecorationFactory } from "@api/MessageDecorations"; import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents"; import { MessagePopoverButtonFactory } from "@api/MessagePopover"; -import { FluxEvents } from "@webpack/types"; +import { Command, FluxEvents } from "@vencord/discord-types"; import { ReactNode } from "react"; import { Promisable } from "type-fest"; diff --git a/src/webpack/common/classes.ts b/src/webpack/common/classes.ts index ca3d75f5..85b90537 100644 --- a/src/webpack/common/classes.ts +++ b/src/webpack/common/classes.ts @@ -16,9 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import * as t from "@vencord/discord-types"; import { findByPropsLazy, findLazy } from "@webpack"; -import * as t from "./types/classes"; - export const ModalImageClasses: t.ImageModalClasses = findLazy(m => m.image && m.modal && !m.applicationIcon); export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent"); diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index bcce3cbb..a392a02f 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -17,10 +17,10 @@ */ import { LazyComponent } from "@utils/lazyReact"; +import * as t from "@vencord/discord-types"; import { filters, mapMangledModuleLazy, waitFor } from "@webpack"; import { waitForComponent } from "./internal"; -import * as t from "./types/components"; const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"')); diff --git a/src/webpack/common/index.ts b/src/webpack/common/index.ts index 4193330c..80223b9a 100644 --- a/src/webpack/common/index.ts +++ b/src/webpack/common/index.ts @@ -21,8 +21,5 @@ export * from "./components"; export * from "./menu"; export * from "./react"; export * from "./stores"; -export * as ComponentTypes from "./types/components.d"; -export * as MenuTypes from "./types/menu.d"; -export * as UtilTypes from "./types/utils.d"; export * from "./userSettings"; export * from "./utils"; diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index 2bb05f36..e4c5d0a3 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -16,10 +16,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import type * as t from "@vencord/discord-types"; import { filters, mapMangledModuleLazy, waitFor, wreq } from "@webpack"; -import type * as t from "./types/menu"; - export const Menu = {} as t.Menu; // Relies on .name properties added by the MenuItemDemanglerAPI diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 712f5bdc..be72d47c 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,11 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import * as t from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack"; -import type * as Stores from "discord-types/stores"; import { waitForStore } from "./internal"; -import * as t from "./types/stores"; export const Flux: t.Flux = findByPropsLazy("connectStores"); @@ -28,7 +27,7 @@ export type GenericStore = t.FluxStore & Record<string, any>; export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand"); -export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & GenericStore & { +export let MessageStore: Omit<t.MessageStore, "getMessages"> & GenericStore & { getMessages(chanId: string): any; }; @@ -41,12 +40,12 @@ export let PresenceStore: GenericStore; export let GuildStore: t.GuildStore; export let GuildRoleStore: t.GuildRoleStore; -export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; -export let UserStore: Stores.UserStore & t.FluxStore; +export let GuildMemberStore: t.GuildMemberStore; +export let UserStore: t.UserStore; export let UserProfileStore: GenericStore; -export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore; -export let SelectedGuildStore: t.FluxStore & Record<string, any>; -export let ChannelStore: Stores.ChannelStore & t.FluxStore; +export let SelectedChannelStore: t.SelectedChannelStore; +export let SelectedGuildStore: t.SelectedGuildStore; +export let ChannelStore: t.ChannelStore; export let RelationshipStore: t.RelationshipStore; export let EmojiStore: t.EmojiStore; @@ -55,14 +54,7 @@ export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; /** - * React hook that returns stateful data for one or more stores - * You might need a custom comparator (4th argument) if your store data is an object - * @param stores The stores to listen to - * @param mapper A function that returns the data you need - * @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook - * @param isEqual A custom comparator for the data returned by mapper - * - * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); + * @see jsdoc of {@link t.useStateFromStores} */ export const useStateFromStores: t.useStateFromStores = findByCodeLazy("useStateFromStores"); diff --git a/src/webpack/common/types/classes.d.ts b/src/webpack/common/types/classes.d.ts deleted file mode 100644 index b6066177..00000000 --- a/src/webpack/common/types/classes.d.ts +++ /dev/null @@ -1,39 +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 interface ImageModalClasses { - image: string, - modal: string, -} - -export interface ButtonWrapperClasses { - hoverScale: string; - buttonWrapper: string; - button: string; - iconMask: string; - buttonContent: string; - icon: string; - pulseIcon: string; - pulseButton: string; - notificationDot: string; - sparkleContainer: string; - sparkleStar: string; - sparklePlus: string; - sparkle: string; - active: string; -} diff --git a/src/webpack/common/types/fluxEvents.d.ts b/src/webpack/common/types/fluxEvents.d.ts deleted file mode 100644 index 1bd50f46..00000000 --- a/src/webpack/common/types/fluxEvents.d.ts +++ /dev/null @@ -1,40 +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/>. -*/ - -/* -function makeFluxEventList() { - // prefill MESSAGE_CREATE so that typescript infers this is a String Set - // without explicitly typing so that this function is also valid javascript - const events = new Set(["MESSAGE_CREATE"]); - - const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; - for (const nodeId in nodes) { - for (const event in nodes[nodeId].actionHandler) { - events.add(event); - } - } - for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { - events.add(event); - } - - return Array.from(events, e => JSON.stringify(e)).sort().join("|"); -} -*/ - -// 46kb worth of events ??????? -export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_CONTRAST" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CROSSFADES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACKNOWLEDGE_CHANNEL_SAFETY_WARNING_TOOLTIP" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_BOGO_PROMOTION_FETCH" | "ACTIVE_BOGO_PROMOTION_FETCH_FAIL" | "ACTIVE_BOGO_PROMOTION_FETCH_SUCCESS" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITIES_WHATS_NEW_ACKNOWLEDGE_SECTION" | "ACTIVITIES_WHATS_NEW_MARK_OPENED_SECTION" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_LAYOUT_MODE_UPDATE" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "ADYEN_CASH_APP_PAY_SUBMIT_SUCCESS" | "ADYEN_CREATE_CASH_APP_PAY_COMPONENT_SUCCESS" | "ADYEN_CREATE_CLIENT_SUCCESS" | "ADYEN_TEARDOWN_CLIENT" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATIONS_SHELF_FETCH_FAIL" | "APPLICATIONS_SHELF_FETCH_START" | "APPLICATIONS_SHELF_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_ASSETS_FETCH" | "APPLICATION_ASSETS_FETCH_SUCCESS" | "APPLICATION_ASSETS_UPDATE" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_EXECUTE_BAD_VERSION" | "APPLICATION_COMMAND_INDEX_FETCH_FAILURE" | "APPLICATION_COMMAND_INDEX_FETCH_REQUEST" | "APPLICATION_COMMAND_INDEX_FETCH_SUCCESS" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_DIRECTORY_FETCH_APPLICATION" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_FAILURE" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_CATEGORIES_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SEARCH" | "APPLICATION_DIRECTORY_FETCH_SEARCH_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SEARCH_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_SUCCESS" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_ICON_EDITOR_CLOSE" | "APP_ICON_EDITOR_OPEN" | "APP_ICON_TRACK_IMPRESSION" | "APP_ICON_UPDATED" | "APP_LAUNCHER_DISMISS_APP_DETAIL" | "APP_LAUNCHER_DISMISS_POPUP" | "APP_LAUNCHER_SET_ACTIVE_COMMAND" | "APP_LAUNCHER_SHOW_APP_DETAIL" | "APP_LAUNCHER_SHOW_POPUP" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SELF_MUTE" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_LOCAL_SOUNDBOARD_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_TARGET" | "AUDIT_LOG_FILTER_BY_USER" | "AUTHENTICATOR_CREATE" | "AUTHENTICATOR_DELETE" | "AUTHENTICATOR_UPDATE" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "BACKGROUND_SYNC" | "BACKGROUND_SYNC_CHANNEL_MESSAGES" | "BILLING_ANNUAL_USER_OFFER_FETCH_FAIL" | "BILLING_ANNUAL_USER_OFFER_FETCH_SUCCESS" | "BILLING_CREATE_REFERRAL_PREVIEW_FAIL" | "BILLING_CREATE_REFERRAL_PREVIEW_START" | "BILLING_CREATE_REFERRAL_PREVIEW_SUCCESS" | "BILLING_CREATE_REFERRAL_SUCCESS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_NITRO_AFFINITY_FETCHED" | "BILLING_NITRO_AFFINITY_FETCH_SUCCEEDED" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_PERKS_RELEVANCE_FETCH_FAIL" | "BILLING_PERKS_RELEVANCE_FETCH_START" | "BILLING_PERKS_RELEVANCE_FETCH_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PREVIOUS_PREMIUM_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_REFERRALS_REMAINING_FETCH_FAIL" | "BILLING_REFERRALS_REMAINING_FETCH_START" | "BILLING_REFERRALS_REMAINING_FETCH_SUCCESS" | "BILLING_REFERRAL_RESOLVE_FAIL" | "BILLING_REFERRAL_RESOLVE_SUCCESS" | "BILLING_REFERRAL_TRIAL_OFFER_UPDATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_OFFER_FETCH_FAIL" | "BILLING_USER_OFFER_FETCH_SUCCESS" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH_ERROR" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BLOCKED_DOMAIN_LIST_FETCHED" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_FAIL" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_FAIL" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROADCASTER_BUCKETS_RECEIVED" | "BROADCAST_START" | "BROADCAST_STOP" | "BROADCAST_VIEWERS_UPDATE" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_PICKER_ANIMATION_ADD" | "BURST_REACTION_PICKER_ANIMATION_CLEAR" | "CACHED_EMOJIS_LOADED" | "CACHED_STICKERS_LOADED" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CACHE_LOADED_LAZY_NO_CACHE" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CONNECT_MULTIPLE" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_FETCH_FAILED" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_MARK_SEEN" | "CHANGE_LOG_SET_CONFIG" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MUTE_EXPIRED" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SAFETY_WARNING_FEEDBACK" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OPEN" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_STATUSES" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_TOGGLE_SUMMARIES_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECK_LAUNCHABLE_GAME" | "CLAN_SETUP_ERROR" | "CLAN_SETUP_RESET" | "CLAN_SETUP_SUBMIT" | "CLAN_SETUP_SUCCESS" | "CLAN_SETUP_UPDATE" | "CLEAR_AUTHENTICATION_ERRORS" | "CLEAR_CACHES" | "CLEAR_CHANNEL_SAFETY_WARNINGS" | "CLEAR_CONVERSATION_SUMMARIES" | "CLEAR_HANG_STATUS" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_LAST_SESSION_VOICE_CHANNEL_ID" | "CLEAR_MENTIONS" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIENT_THEMES_EDITOR_OPEN" | "CLIPS_CLASSIFY_HARDWARE" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_CLEAR_NEW_CLIP_IDS" | "CLIPS_DELETE_CLIP" | "CLIPS_DISMISS_EDUCATION" | "CLIPS_INIT" | "CLIPS_INIT_FAILURE" | "CLIPS_LOAD_DIRECTORY_SUCCESS" | "CLIPS_RESTART" | "CLIPS_SAVE_ANIMATION_END" | "CLIPS_SAVE_CLIP" | "CLIPS_SAVE_CLIP_ERROR" | "CLIPS_SAVE_CLIP_PLACEHOLDER" | "CLIPS_SAVE_CLIP_PLACEHOLDER_ERROR" | "CLIPS_SAVE_CLIP_START" | "CLIPS_SETTINGS_UPDATE" | "CLIPS_SHOW_CALL_WARNING" | "CLIPS_UPDATE_METADATA" | "CLOSE_SUSPENDED_USER" | "COLLECTIBLES_CATEGORIES_FETCH" | "COLLECTIBLES_CATEGORIES_FETCH_FAILURE" | "COLLECTIBLES_CATEGORIES_FETCH_SUCCESS" | "COLLECTIBLES_CATEGORY_ITEMS_VIEWED" | "COLLECTIBLES_CLAIM" | "COLLECTIBLES_CLAIM_FAILURE" | "COLLECTIBLES_CLAIM_SUCCESS" | "COLLECTIBLES_PRODUCT_DETAILS_OPEN" | "COLLECTIBLES_PRODUCT_FETCH" | "COLLECTIBLES_PRODUCT_FETCH_FAILURE" | "COLLECTIBLES_PRODUCT_FETCH_SUCCESS" | "COLLECTIBLES_PURCHASES_FETCH" | "COLLECTIBLES_PURCHASES_FETCH_FAILURE" | "COLLECTIBLES_PURCHASES_FETCH_SUCCESS" | "COLLECTIBLES_SHOP_CLOSE" | "COLLECTIBLES_SHOP_OPEN" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "COMPLETE_NEW_MEMBER_ACTION" | "COMPLETE_SIGN_UP" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SET" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_INTERRUPTED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONTENT_INVENTORY_CLEAR_FEED" | "CONTENT_INVENTORY_DEBUG_CLEAR_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_LOG_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_TOGGLE_FAST_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DEBUG_TOGGLE_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_MANUAL_REFRESH" | "CONTENT_INVENTORY_SET_FEED" | "CONTENT_INVENTORY_SET_FILTERS" | "CONTENT_INVENTORY_TOGGLE_FEED_HIDDEN" | "CONTENT_INVENTORY_TOGGLE_REPLY_MODE" | "CONTENT_INVENTORY_TRACK_ITEM_IMPRESSIONS" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CONVERSATION_SUMMARY_UPDATE" | "CREATE_PENDING_REPLY" | "CREATE_REFERRALS_SUCCESS" | "CREATE_SHALLOW_PENDING_REPLY" | "CREATOR_MONETIZATION_NAG_ACTIVATE_ELIGIBLITY_FETCH_SUCCESS" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_FAILURE" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_SUCCESS" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "DCF_DAILY_CAP_OVERRIDE" | "DCF_HANDLE_DC_DISMISSED" | "DCF_HANDLE_DC_SHOWN" | "DCF_RESET" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DELETE_SUMMARY" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_FAILURE" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_SUCCESS" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_UPDATE_FILTER" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_DESIGN_TOGGLE_SET" | "DEV_TOOLS_DESIGN_TOGGLE_WEB_SET" | "DEV_TOOLS_DEV_SETTING_SET" | "DEV_TOOLS_SETTINGS_UPDATE" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISMISS_CHANNEL_SAFETY_WARNINGS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_MEDIA_POST_SHARE_PROMPT" | "DISMISS_SIGN_UP" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "DRAWER_CLOSE" | "DRAWER_OPEN" | "DRAWER_SELECT_TAB" | "DROPS_ELIGIBILITY_FETCH_SUCCESS" | "DROPS_ENROLLED_USER_FETCH_SUCCESS" | "DROPS_FETCH_PROGRESS_FAILURE" | "DROPS_FETCH_PROGRESS_SUCCESS" | "DROPS_HEARTBEAT_FAILURE" | "DROPS_HEARTBEAT_SUCCESS" | "DROPS_PLATFORM_AVAILABILITY_SUCCESS" | "DROPS_UNENROLL_USER" | "DROPS_USER_STATUS_FETCH_FAILURE" | "DROPS_USER_STATUS_FETCH_SUCCESS" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_DISCONNECT" | "EMBEDDED_ACTIVITY_DISMISS_NEW_INDICATOR" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_FOCUSED_LAYOUT" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMBEDDED_ACTIVITY_SET_PANEL_MODE" | "EMBEDDED_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_UPDATE_V2" | "EMOJI_AUTOSUGGESTION_UPDATE" | "EMOJI_CAPTIONS_FETCH" | "EMOJI_CAPTIONS_FETCH_ERROR" | "EMOJI_CAPTIONS_FETCH_SUCCESS" | "EMOJI_INTERACTION_INITIATED" | "EMOJI_TRACK_USAGE" | "ENABLE_AUTOMATIC_ACK" | "ENABLE_GUILD_SIGN_UP" | "ENABLE_USER_SIGN_UP" | "ENTITLEMENTS_FETCH_FOR_USER_FAIL" | "ENTITLEMENTS_FETCH_FOR_USER_START" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "EXPERIMENT_REGISTER_LEGACY" | "FAMILY_CENTER_FETCH_START" | "FAMILY_CENTER_HANDLE_TAB_SELECT" | "FAMILY_CENTER_INITIAL_LOAD" | "FAMILY_CENTER_LINKED_USERS_FETCH_SUCCESS" | "FAMILY_CENTER_LINK_CODE_FETCH_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_REMOVE_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_UPDATE_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_FETCH_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_MORE_FETCH_SUCCESS" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_CLAN_DISCOVERY_SEARCH_RESULT_SUCCESS" | "FETCH_GUILD_EVENT" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_FAILURE" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_SUCCESS" | "FETCH_PRIVATE_CHANNEL_INTEGRATIONS_FAIL" | "FETCH_PRIVATE_CHANNEL_INTEGRATIONS_START" | "FETCH_PRIVATE_CHANNEL_INTEGRATIONS_SUCCESS" | "FETCH_STATIC_CLAN_LIST_SUCCESS" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_CONSOLE_SELECT_DEVICE" | "GAME_DETECTION_WATCH_CANDIDATE_GAMES_START" | "GAME_ICON_UPDATE" | "GAME_INVITE_CLEAR_UNSEEN" | "GAME_INVITE_CREATE" | "GAME_INVITE_DELETE" | "GAME_INVITE_DELETE_MANY" | "GAME_INVITE_UPDATE_STATUS" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GUILD_ACK" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_CLEAR_SEARCH" | "GUILD_DISCOVERY_CLEAR_SEEN_GUILDS" | "GUILD_DISCOVERY_FETCH_FAILURE" | "GUILD_DISCOVERY_FETCH_START" | "GUILD_DISCOVERY_FETCH_SUCCESS" | "GUILD_DISCOVERY_GUILD_SEEN" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_POPULAR_FETCH_FAILURE" | "GUILD_DISCOVERY_POPULAR_FETCH_START" | "GUILD_DISCOVERY_POPULAR_FETCH_SUCCESS" | "GUILD_DISCOVERY_SEARCH_COUNTS_FAIL" | "GUILD_DISCOVERY_SEARCH_FETCH_FAILURE" | "GUILD_DISCOVERY_SEARCH_FETCH_START" | "GUILD_DISCOVERY_SEARCH_FETCH_SUCCESS" | "GUILD_DISCOVERY_SEARCH_INIT" | "GUILD_DISCOVERY_SEARCH_UPDATE_COUNTS" | "GUILD_DISCOVERY_SELECT_CATEGORY" | "GUILD_DISCOVERY_SLUG_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FEED_FEATURED_ITEMS_FETCH_FAILURE" | "GUILD_FEED_FEATURED_ITEMS_FETCH_SUCCESS" | "GUILD_FEED_FEATURE_ITEM" | "GUILD_FEED_FETCH_FAILURE" | "GUILD_FEED_FETCH_FRESH_START" | "GUILD_FEED_FETCH_PAGE_START" | "GUILD_FEED_FETCH_SUCCESS" | "GUILD_FEED_ITEM_HIDE" | "GUILD_FEED_ITEM_READ_ACK" | "GUILD_FEED_ITEM_REMOVE" | "GUILD_FEED_ITEM_UNHIDE" | "GUILD_FEED_UNFEATURE_ITEM" | "GUILD_FOLDER_COLLAPSE" | "GUILD_FOLDER_CREATE_LOCAL" | "GUILD_FOLDER_DELETE_LOCAL" | "GUILD_FOLDER_EDIT_LOCAL" | "GUILD_GEO_RESTRICTED" | "GUILD_HOME_ENSURE_HOME_SESSION" | "GUILD_HOME_SETTINGS_FETCH_FAIL" | "GUILD_HOME_SETTINGS_FETCH_START" | "GUILD_HOME_SETTINGS_FETCH_SUCCESS" | "GUILD_HOME_SETTINGS_TOGGLE_ENABLED" | "GUILD_HOME_SETTINGS_UPDATE_SUCCESS" | "GUILD_HOME_SET_SCROLL_POSITION" | "GUILD_HOME_SET_SOURCE" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_CLOSE" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR_DECORATION" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PROFILE_EFFECT_ID" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_TAB" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_BY_ID_FETCH_SUCCESS" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_MEMBERS_CHUNK_BATCH" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE_BY_ID" | "GUILD_MUTE_EXPIRED" | "GUILD_NEW_MEMBER_ACTIONS_DELETE_SUCCESS" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_FAIL" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_START" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_SUCCESS" | "GUILD_NEW_MEMBER_ACTION_UPDATE_SUCCESS" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POPOUT_FETCH_FAILURE" | "GUILD_POPOUT_FETCH_START" | "GUILD_POPOUT_FETCH_SUCCESS" | "GUILD_PRODUCTS_FETCH" | "GUILD_PRODUCTS_FETCH_FAILURE" | "GUILD_PRODUCTS_FETCH_SUCCESS" | "GUILD_PRODUCT_CREATE" | "GUILD_PRODUCT_DELETE" | "GUILD_PRODUCT_FETCH" | "GUILD_PRODUCT_FETCH_FAILURE" | "GUILD_PRODUCT_FETCH_SUCCESS" | "GUILD_PRODUCT_UPDATE" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RECOMMENDATION_FETCH" | "GUILD_RECOMMENDATION_FETCH_FAILURE" | "GUILD_RECOMMENDATION_FETCH_SUCCESS" | "GUILD_RESOURCE_CHANNEL_UPDATE_SUCCESS" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_TEMPLATES" | "GUILD_ROLE_SUBSCRIPTIONS_STASH_TEMPLATE_CHANNELS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTIONS_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_COUNTS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SEARCH_RECENT_MEMBERS" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_BANS_BATCH" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_SET_MODE" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_SOUNDS_UPDATE" | "GUILD_SOUNDBOARD_SOUND_CREATE" | "GUILD_SOUNDBOARD_SOUND_DELETE" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_LOCALLY" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_SOUND_UPDATE" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_ADD_MEMBER_UPDATES" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_SUBSCRIPTIONS_REMOVE_MEMBER_UPDATES" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HABITUAL_DND_CLEAR" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIGH_FIVE_COMPLETE" | "HIGH_FIVE_COMPLETE_CLEAR" | "HIGH_FIVE_QUEUE" | "HIGH_FIVE_REMOVE" | "HIGH_FIVE_SET_ENABLED" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "I18N_LOAD_ERROR" | "I18N_LOAD_START" | "I18N_LOAD_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INITIALIZE_MEMBER_SAFETY_STORE" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CLEAR" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_CREATE" | "INTEGRATION_DELETE" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_IFRAME_MODAL_CLOSE" | "INTERACTION_IFRAME_MODAL_CREATE" | "INTERACTION_IFRAME_MODAL_KEY_CREATE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIGHTNING_CHECKOUT_CLOSE" | "LIGHTNING_CHECKOUT_OPEN" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_CHANNELS" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES" | "LOAD_USER_AFFINITIES_FAILURE" | "LOAD_USER_AFFINITIES_SUCCESS" | "LOCAL_ACTIVITY_UPDATE" | "LOCAL_MESSAGES_LOADED" | "LOCAL_MESSAGE_CREATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_FAILURE" | "LOGIN_MFA_SMS" | "LOGIN_MFA_SMS_FAILURE" | "LOGIN_MFA_SMS_REQUEST_SUCCESS" | "LOGIN_MFA_STEP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGIN_SUSPENDED_USER" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_GO_LIVE_SOURCE" | "MEDIA_ENGINE_SET_HARDWARE_H264" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEDIA_POST_EMBED_FETCH" | "MEDIA_POST_EMBED_FETCH_FAILURE" | "MEDIA_POST_EMBED_FETCH_SUCCESS" | "MEDIA_SESSION_JOINED" | "MEMBER_SAFETY_GUILD_MEMBER_SEARCH_SUCCESS" | "MEMBER_SAFETY_GUILD_MEMBER_UPDATE_BATCH" | "MEMBER_SAFETY_NEW_MEMBER_TIMESTAMP_REFRESH" | "MEMBER_SAFETY_PAGINATION_TOKEN_UPDATE" | "MEMBER_SAFETY_PAGINATION_UPDATE" | "MEMBER_SAFETY_SEARCH_STATE_UPDATE" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MENTION_MODAL_CLOSE" | "MENTION_MODAL_OPEN" | "MESSAGE_ACK" | "MESSAGE_ACKED" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_EXPLICIT_CONTENT_FP_CREATE" | "MESSAGE_EXPLICIT_CONTENT_FP_SUBMIT" | "MESSAGE_EXPLICIT_CONTENT_SCAN_TIMEOUT" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_NOTIFICATION_SHOWN" | "MESSAGE_PREVIEWS_LOADED" | "MESSAGE_PREVIEWS_LOCALLY_LOADED" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_MANY" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REMINDER_NOTIFIED" | "MESSAGE_REMINDER_TOGGLE" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_EMAIL_TOKEN" | "MFA_ENABLE_SUCCESS" | "MFA_SEEN_BACKUP_CODE_PROMPT" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MFA_WEBAUTHN_CREDENTIALS_LOADING" | "MOBILE_NATIVE_UPDATE_CHECK_FINISHED" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MOD_VIEW_SEARCH_FINISH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_CLEAR_GUILD_MENTIONS" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_REFRESH" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CENTER_TAB_FOCUSED" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOTIFICATION_SETTINGS_UPDATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_MESSAGE_EVENT_ACTION" | "OVERLAY_NOTIFICATION_EVENT" | "OVERLAY_NOTIFY_READY_TO_SHOW" | "OVERLAY_READY" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_ASSOCIATED_GAME" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_CLICK_ZONES" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_SHOW_KEYBIND_INDICATORS" | "OVERLAY_SET_TEXT_CHAT_NOTIFICATION_MODE" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SET_UI_LOCKED" | "OVERLAY_SOUNDBOARD_SOUNDS_FETCH_REQUEST" | "OVERLAY_START_SESSION" | "OVERLAY_SUCCESSFULLY_SHOWN" | "OVERLAY_WIDGET_CHANGED" | "PASSIVE_UPDATE_V1" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "PREMIUM_MARKETING_DATA_READY" | "PREMIUM_MARKETING_PREVIEW" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_PERKS_DEMOS_FETCH_FAILURE" | "PREMIUM_PERKS_DEMOS_FETCH_SUCCESS" | "PREMIUM_PERKS_DEMO_ACTIVATE_FAILURE" | "PREMIUM_PERKS_DEMO_ACTIVATE_SUCCESS" | "PREMIUM_PERKS_DEMO_COMPLETE" | "PREMIUM_PERKS_DEMO_OVERRIDE" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_INTEGRATION_CREATE" | "PRIVATE_CHANNEL_INTEGRATION_DELETE" | "PRIVATE_CHANNEL_INTEGRATION_UPDATE" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_EFFECTS_SET_TRY_IT_OUT" | "PROFILE_PANEL_TOGGLE_SECTION" | "PROXY_BLOCKED_REQUEST" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_SUCCESS" | "PURCHASED_ITEMS_FESTIVITY_IS_FETCHING_WOW_MOMENT_MEDIA" | "PURCHASED_ITEMS_FESTIVITY_SET_CAN_PLAY_WOW_MOMENT" | "PURCHASE_CONFIRMATION_MODAL_CLOSE" | "PURCHASE_CONFIRMATION_MODAL_OPEN" | "PUSH_NOTIFICATION_CLICK" | "QUESTS_CLAIM_REWARD_BEGIN" | "QUESTS_CLAIM_REWARD_CODE_BEGIN" | "QUESTS_CLAIM_REWARD_CODE_FAILURE" | "QUESTS_CLAIM_REWARD_CODE_SUCCESS" | "QUESTS_CLAIM_REWARD_FAILURE" | "QUESTS_CLAIM_REWARD_SUCCESS" | "QUESTS_DELIVERY_OVERRIDE" | "QUESTS_DISMISS_CONTENT_BEGIN" | "QUESTS_DISMISS_CONTENT_FAILURE" | "QUESTS_DISMISS_CONTENT_SUCCESS" | "QUESTS_DISMISS_PROGRESS_TRACKING_FAILURE_NOTICE" | "QUESTS_ENROLL_BEGIN" | "QUESTS_ENROLL_FAILURE" | "QUESTS_ENROLL_SUCCESS" | "QUESTS_FETCH_CURRENT_QUESTS_BEGIN" | "QUESTS_FETCH_CURRENT_QUESTS_FAILURE" | "QUESTS_FETCH_CURRENT_QUESTS_SUCCESS" | "QUESTS_FETCH_REWARD_CODE_BEGIN" | "QUESTS_FETCH_REWARD_CODE_FAILURE" | "QUESTS_FETCH_REWARD_CODE_SUCCESS" | "QUESTS_OPTIMISTIC_PROGRESS_UPDATE" | "QUESTS_PREVIEW_UPDATE_SUCCESS" | "QUESTS_SEND_HEARTBEAT_FAILURE" | "QUESTS_SEND_HEARTBEAT_SUCCESS" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECEIVE_CHANNEL_AFFINITIES" | "RECEIVE_CHANNEL_SUMMARIES" | "RECEIVE_CHANNEL_SUMMARIES_BULK" | "RECEIVE_CHANNEL_SUMMARY" | "RECENT_MENTION_DELETE" | "RECOMPUTE_READ_STATES" | "REFERRALS_FETCH_ELIGIBLE_USER_FAIL" | "REFERRALS_FETCH_ELIGIBLE_USER_START" | "REFERRALS_FETCH_ELIGIBLE_USER_SUCCESS" | "REGISTER" | "REGISTER_FAILURE" | "REGISTER_SAVE_FORM" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_PENDING_INCOMING_REMOVED" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REQUEST_CHANNEL_AFFINITIES" | "REQUEST_CHANNEL_SUMMARIES" | "REQUEST_CHANNEL_SUMMARIES_BULK" | "REQUEST_CHANNEL_SUMMARY" | "REQUEST_FORUM_UNREADS" | "REQUEST_SOUNDBOARD_SOUNDS" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESET_PREVIEW_CLIENT_THEME" | "RESET_SOCKET" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_FLAGS" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_PLATFORM" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_USER_CREATE" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_LATENCY_TEST_COMPLETE" | "RTC_SPEED_TEST_START_TEST" | "RTC_SPEED_TEST_STOP_TEST" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_DETECTION" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAFETY_HUB_APPEAL_CLOSE" | "SAFETY_HUB_APPEAL_OPEN" | "SAFETY_HUB_APPEAL_SIGNAL_CUSTOM_INPUT_CHANGE" | "SAFETY_HUB_APPEAL_SIGNAL_SELECT" | "SAFETY_HUB_FETCH_CLASSIFICATION_FAILURE" | "SAFETY_HUB_FETCH_CLASSIFICATION_START" | "SAFETY_HUB_FETCH_CLASSIFICATION_SUCCESS" | "SAFETY_HUB_FETCH_FAILURE" | "SAFETY_HUB_FETCH_START" | "SAFETY_HUB_FETCH_SUCCESS" | "SAFETY_HUB_REQUEST_REVIEW_FAILURE" | "SAFETY_HUB_REQUEST_REVIEW_START" | "SAFETY_HUB_REQUEST_REVIEW_SUCCESS" | "SAVED_MESSAGES_UPDATE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SEARCH_ADD_HISTORY" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MODAL_CLOSE" | "SEARCH_MODAL_OPEN" | "SEARCH_REMOVE_HISTORY" | "SEARCH_SCREEN_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_START" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELECT_HOME_RESOURCE_CHANNEL" | "SELECT_NEW_MEMBER_ACTION_CHANNEL" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_CREATED_AT_OVERRIDE" | "SET_GUILD_FOLDER_EXPANDED" | "SET_HIGHLIGHTED_SUMMARY" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_LOGIN_CREDENTIALS" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_PREMIUM_TYPE_OVERRIDE" | "SET_RECENTLY_ACTIVE_COLLAPSED" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_SELECTED_SUMMARY" | "SET_SOUNDPACK" | "SET_STREAM_APP_INTENT" | "SET_SUMMARY_FEEDBACK" | "SET_TTS_SPEECH_RATE" | "SET_VAD_PERMISSION" | "SHARED_CANVAS_CLEAR_DRAWABLES" | "SHARED_CANVAS_DRAW_LINE_POINT" | "SHARED_CANVAS_SET_DRAW_MODE" | "SHARED_CANVAS_UPDATE_EMOJI_HOSE" | "SHARED_CANVAS_UPDATE_LINE_POINTS" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CLOSE_GUILD" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SIDEBAR_VIEW_GUILD" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_AWAIT_CONFIRMATION" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH" | "SKU_PURCHASE_PREVIEW_FETCH_FAILURE" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS_SUCCESS" | "SOUNDBOARD_MUTE_JOIN_SOUND" | "SOUNDBOARD_SET_OVERLAY_ENABLED" | "SOUNDBOARD_SOUNDS_RECEIVED" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPEED_TEST_CREATE" | "SPEED_TEST_DELETE" | "SPEED_TEST_SERVER_UPDATE" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPELLCHECK_UNLEARN_WORD" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_NEW_TRACK" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "STAGE_MUSIC_MUTE" | "STAGE_MUSIC_PLAY" | "START_BROADCAST_STREAM" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STATS_UPDATE" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SURVEY_SEEN" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOGGLE_OVERLAY_CANVAS" | "TOGGLE_TOPICS_BAR" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TRY_ACK" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNREAD_SETTING_NOTICE_CHANNEL_VISIT" | "UNREAD_SETTING_NOTICE_RENDERED" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPCOMING_GUILD_EVENT_NOTICE_HIDE" | "UPCOMING_GUILD_EVENT_NOTICE_SEEN" | "UPDATE_AVAILABLE" | "UPDATE_BACKGROUND_GRADIENT_PRESET" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CHANNEL_LIST_SUBTITLES" | "UPDATE_CLIENT_PREMIUM_TYPE" | "UPDATE_CONSENTS" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_HANG_STATUS" | "UPDATE_HANG_STATUS_CUSTOM" | "UPDATE_MANUALLY" | "UPDATE_MOBILE_PENDING_THEME_INDEX" | "UPDATE_NOT_AVAILABLE" | "UPDATE_TOKEN" | "UPDATE_VISIBLE_MESSAGES" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_FILE_UPDATE" | "UPLOAD_ITEM_CANCEL_REQUEST" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACHIEVEMENT_UPDATE" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLICATION_REMOVE" | "USER_APPLICATION_UPDATE" | "USER_APPLIED_BOOSTS_FETCH_START" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_INTEGRATION_JOINING_ERROR" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOADED" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_BROWSER_CHECKOUT_DONE" | "USER_PAYMENT_BROWSER_CHECKOUT_STARTED" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_ACCESSIBILITY_TOOLTIP_VIEWED" | "USER_PROFILE_EFFECTS_FETCH" | "USER_PROFILE_EFFECTS_FETCH_FAILURE" | "USER_PROFILE_EFFECTS_FETCH_SUCCESS" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_UPDATE_FAILURE" | "USER_PROFILE_UPDATE_START" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_RECENT_GAMES_FETCH_ERROR" | "USER_RECENT_GAMES_FETCH_START" | "USER_RECENT_GAMES_FETCH_SUCCESS" | "USER_RECENT_GAMES_UPDATE_LOCAL" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_SINGLE_TRY_IT_OUT_COLLECTIBLES_ITEM" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SOUNDBOARD_SET_VOLUME" | "USER_TENURE_REWARD_STATUS_DELETE" | "USER_TENURE_REWARD_STATUS_RESET" | "USER_TENURE_REWARD_SYNC_START" | "USER_TENURE_REWARD_SYNC_SUCCESS" | "USER_UPDATE" | "VERIFY_FAILURE" | "VERIFY_SUCCESS" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIEW_HISTORY_MARK_VIEW" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_CHANNEL_STATUS_UPDATE" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD" | "WINDOW_VISIBILITY_CHANGE" | "WRITE_CACHES"; diff --git a/src/webpack/common/types/index.d.ts b/src/webpack/common/types/index.d.ts deleted file mode 100644 index 77148dd0..00000000 --- a/src/webpack/common/types/index.d.ts +++ /dev/null @@ -1,24 +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 * from "./classes"; -export * from "./components"; -export * from "./fluxEvents"; -export * from "./menu"; -export * from "./stores"; -export * from "./utils"; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts deleted file mode 100644 index f0235986..00000000 --- a/src/webpack/common/types/stores.d.ts +++ /dev/null @@ -1,259 +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 { Channel, Guild, Role } from "discord-types/general"; - -import { FluxDispatcher, FluxEvents } from "./utils"; - -type GenericFunction = (...args: any[]) => any; - -export class FluxStore { - constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>); - - addChangeListener(callback: () => void): void; - addReactChangeListener(callback: () => void): void; - removeChangeListener(callback: () => void): void; - removeReactChangeListener(callback: () => void): void; - emitChange(): void; - getDispatchToken(): string; - getName(): string; - initialize(): void; - initializeIfNeeded(): void; - registerActionHandlers: GenericFunction; - syncWith: GenericFunction; - waitFor: GenericFunction; - __getLocalVars(): Record<string, any>; - - static getAll(): FluxStore[]; -} - -export class FluxEmitter { - constructor(); - - changeSentinel: number; - changedStores: Set<FluxStore>; - isBatchEmitting: boolean; - isDispatching: boolean; - isPaused: boolean; - pauseTimer: NodeJS.Timeout | null; - reactChangedStores: Set<FluxStore>; - - batched(batch: (...args: any[]) => void): void; - destroy(): void; - emit(): void; - emitNonReactOnce(): void; - emitReactOnce(): void; - getChangeSentinel(): number; - getIsPaused(): boolean; - injectBatchEmitChanges(batch: (...args: any[]) => void): void; - markChanged(store: FluxStore): void; - pause(): void; - resume(): void; -} - -export interface Flux { - Store: typeof FluxStore; - Emitter: FluxEmitter; -} - -export class WindowStore extends FluxStore { - isElementFullScreen(): boolean; - isFocused(): boolean; - windowSize(): Record<"width" | "height", number>; -} - -type Emoji = CustomEmoji | UnicodeEmoji; -export interface CustomEmoji { - allNamesString: string; - animated: boolean; - available: boolean; - guildId: string; - id: string; - managed: boolean; - name: string; - originalName?: string; - require_colons: boolean; - roles: string[]; - type: 1; -} - -export interface UnicodeEmoji { - diversityChildren: Record<any, any>; - emojiObject: { - names: string[]; - surrogates: string; - unicodeVersion: number; - }; - index: number; - surrogates: string; - type: 0; - uniqueName: string; - useSpriteSheet: boolean; - get allNamesString(): string; - get animated(): boolean; - get defaultDiversityChild(): any; - get hasDiversity(): boolean | undefined; - get hasDiversityParent(): boolean | undefined; - get hasMultiDiversity(): boolean | undefined; - get hasMultiDiversityParent(): boolean | undefined; - get managed(): boolean; - get name(): string; - get names(): string[]; - get optionallyDiverseSequence(): string | undefined; - get unicodeVersion(): number; - get url(): string; -} - -export class EmojiStore extends FluxStore { - getCustomEmojiById(id?: string | null): CustomEmoji; - getUsableCustomEmojiById(id?: string | null): CustomEmoji; - getGuilds(): Record<string, { - id: string; - _emojiMap: Record<string, CustomEmoji>; - _emojis: CustomEmoji[]; - get emojis(): CustomEmoji[]; - get rawEmojis(): CustomEmoji[]; - _usableEmojis: CustomEmoji[]; - get usableEmojis(): CustomEmoji[]; - _emoticons: any[]; - get emoticons(): any[]; - }>; - getGuildEmoji(guildId?: string | null): CustomEmoji[]; - getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[]; - getTopEmoji(guildId?: string | null): CustomEmoji[]; - getTopEmojisMetadata(guildId?: string | null): { - emojiIds: string[]; - topEmojisTTL: number; - }; - hasPendingUsage(): boolean; - hasUsableEmojiInAnyGuild(): boolean; - searchWithoutFetchingLatest(data: any): any; - getSearchResultsOrder(...args: any[]): any; - getState(): { - pendingUsages: { key: string, timestamp: number; }[]; - }; - searchWithoutFetchingLatest(data: { - channel: Channel, - query: string; - count?: number; - intention: number; - includeExternalGuilds?: boolean; - matchComparator?(name: string): boolean; - }): Record<"locked" | "unlocked", Emoji[]>; - - getDisambiguatedEmojiContext(): { - backfillTopEmojis: Record<any, any>; - customEmojis: Record<string, CustomEmoji>; - emojisById: Record<string, CustomEmoji>; - emojisByName: Record<string, CustomEmoji>; - emoticonRegex: RegExp | null; - emoticonsByName: Record<string, any>; - escapedEmoticonNames: string; - favoriteNamesAndIds?: any; - favorites?: any; - frequentlyUsed?: any; - groupedCustomEmojis: Record<string, CustomEmoji[]>; - guildId?: string; - isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean; - newlyAddedEmoji: Record<string, CustomEmoji[]>; - topEmojis?: any; - unicodeAliases: Record<string, string>; - get favoriteEmojisWithoutFetchingLatest(): Emoji[]; - }; -} - -export interface DraftObject { - channelId: string; - timestamp: number; - draft: string; -} - -interface DraftState { - [userId: string]: { - [channelId: string]: { - [key in DraftType]?: Omit<DraftObject, "channelId">; - } | undefined; - } | undefined; -} - - -export class DraftStore extends FluxStore { - getDraft(channelId: string, type: DraftType): string; - getRecentlyEditedDrafts(type: DraftType): DraftObject[]; - getState(): DraftState; - getThreadDraftWithParentMessageId?(arg: any): any; - getThreadSettings(channelId: string): any | null; -} - -export enum DraftType { - ChannelMessage, - ThreadSettings, - FirstThreadMessage, - ApplicationLauncherCommand, - Poll, - SlashCommand, -} - -export class GuildStore extends FluxStore { - getGuild(guildId: string): Guild; - getGuildCount(): number; - getGuilds(): Record<string, Guild>; - getGuildIds(): string[]; -} - -export class GuildRoleStore extends FluxStore { - getRole(guildId: string, roleId: string): Role; - getRoles(guildId: string): Record<string, Role>; - getAllGuildRoles(): Record<string, Record<string, Role>>; -} - -export class ThemeStore extends FluxStore { - theme: "light" | "dark" | "darker" | "midnight"; - darkSidebar: boolean; - isSystemThemeAvailable: boolean; - systemPrefersColorScheme: "light" | "dark"; - systemTheme: null; -} - -export type useStateFromStores = <T>( - stores: any[], - mapper: () => T, - dependencies?: any, - isEqual?: (old: T, newer: T) => boolean -) => T; - -export class RelationshipStore extends FluxStore { - getFriendIDs(): string[]; - getIgnoredIDs(): string[]; - getBlockedIDs(): string[]; - - getPendingCount(): number; - getRelationshipCount(): number; - - /** Related to friend nicknames. */ - getNickname(userId: string): string; - /** @returns Enum value from constants.RelationshipTypes */ - getRelationshipType(userId: string): number; - isFriend(userId: string): boolean; - isBlocked(userId: string): boolean; - isIgnored(userId: string): boolean; - getSince(userId: string): string; - - /** @returns Format: [userId: Enum value from constants.RelationshipTypes] */ - getMutableRelationships(): Map<string, number>; -} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 5c54c95a..1d653dea 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,10 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import type * as t from "@vencord/discord-types"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; -import type { Channel } from "discord-types/general"; - -import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; waitFor(["dispatch", "subscribe"], m => { @@ -138,7 +136,7 @@ export const UserUtils = { export const UploadManager = findByPropsLazy("clearAll", "addFile"); export const UploadHandler = { - promptToUpload: findByCodeLazy("=!0,showLargeMessageDialog:") as (files: File[], channel: Channel, draftType: Number) => void + promptToUpload: findByCodeLazy("=!0,showLargeMessageDialog:") as (files: File[], channel: t.Channel, draftType: Number) => void }; export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size must === [", { diff --git a/src/webpack/index.ts b/src/webpack/index.ts index 6f1fd25b..37dda6fe 100644 --- a/src/webpack/index.ts +++ b/src/webpack/index.ts @@ -17,5 +17,5 @@ */ export * as Common from "./common"; +export * from "./types"; export * from "./webpack"; -export * from "./wreq.d"; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index d9b35f01..767fa20d 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -10,10 +10,11 @@ import { Logger } from "@utils/Logger"; import { interpolateIfDefined } from "@utils/misc"; import { canonicalizeReplacement } from "@utils/patches"; import { Patch, PatchReplacement } from "@utils/types"; +import { WebpackRequire } from "@vencord/discord-types/webpack"; import { traceFunctionWithResults } from "../debug/Tracer"; +import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory } from "./types"; import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleFactory, moduleListeners, waitForSubscriptions, wreq } from "./webpack"; -import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory, WebpackRequire } from "./wreq.d"; export const patches = [] as Patch[]; diff --git a/src/webpack/types.ts b/src/webpack/types.ts new file mode 100644 index 00000000..328aa8bc --- /dev/null +++ b/src/webpack/types.ts @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated, Nuckyz and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Module, ModuleExports, WebpackRequire } from "@vencord/discord-types/webpack"; + +import { SYM_ORIGINAL_FACTORY, SYM_PATCHED_BY, SYM_PATCHED_SOURCE } from "./patchWebpack"; + +export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & { + /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ + m: Record<PropertyKey, AnyModuleFactory>; +}; + +/** exports can be anything, however initially it is always an empty object */ +export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & { + [SYM_PATCHED_SOURCE]?: string; + [SYM_PATCHED_BY]?: Set<string>; +}; + +export type PatchedModuleFactory = AnyModuleFactory & { + [SYM_ORIGINAL_FACTORY]: AnyModuleFactory; + [SYM_PATCHED_SOURCE]?: string; + [SYM_PATCHED_BY]?: Set<string>; +}; + +export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 98e7d4d1..247ea3f4 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -20,11 +20,12 @@ import { makeLazy, proxyLazy } from "@utils/lazy"; import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; -import { FluxStore } from "@webpack/types"; +import { FluxStore } from "@vencord/discord-types"; +import { ModuleExports, WebpackRequire } from "@vencord/discord-types/webpack"; import { traceFunction } from "../debug/Tracer"; import { Flux } from "./common"; -import { AnyModuleFactory, AnyWebpackRequire, ModuleExports, WebpackRequire } from "./wreq"; +import { AnyModuleFactory, AnyWebpackRequire } from "./types"; const logger = new Logger("Webpack"); diff --git a/tsconfig.json b/tsconfig.json index d2a42bd5..0123021c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, - "skipLibCheck": true, + "skipLibCheck": false, "allowJs": true, "lib": [ "DOM", @@ -27,7 +27,6 @@ "@components/*": ["./components/*"], "@utils/*": ["./utils/*"], "@shared/*": ["./shared/*"], - "@webpack/types": ["./webpack/common/types"], "@webpack/common": ["./webpack/common"], "@webpack": ["./webpack/webpack"], "@webpack/patcher": ["./webpack/patchWebpack"], From a17803c1c4fd94a79d5e5514eb286c2ec74edff5 Mon Sep 17 00:00:00 2001 From: Cookie <52550063+Covkie@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:42:38 -0400 Subject: [PATCH 015/141] Settings: remove outdated style (#3490) --- src/components/PluginSettings/PluginModal.tsx | 6 +++--- src/components/VencordSettings/PatchHelperTab.tsx | 2 +- src/components/VencordSettings/ThemesTab.tsx | 2 +- src/components/VencordSettings/UpdaterTab.tsx | 2 +- src/components/VencordSettings/settingsStyles.css | 9 --------- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 29ce90c9..ddbcf8b8 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -26,7 +26,7 @@ import { Flex } from "@components/Flex"; import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; -import { classes, isObjectEmpty } from "@utils/misc"; +import { isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; @@ -212,7 +212,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti const pluginMeta = PluginMeta[plugin.name]; return ( - <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable"> + <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}> <ModalHeader separator={false}> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text> @@ -268,7 +268,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti </div> </Forms.FormSection> {!!plugin.settingsAboutComponent && ( - <div className={classes(Margins.bottom8, "vc-text-selectable")}> + <div className={Margins.bottom8}> <Forms.FormSection> <ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component"> <plugin.settingsAboutComponent tempSettings={tempSettings} /> diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index accccd49..83364d19 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -194,7 +194,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) { error={error ?? replacementError} /> {!isFunc && ( - <div className="vc-text-selectable"> + <div> <Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle> {Object.entries({ "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 692e20bf..f557adf1 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -309,7 +309,7 @@ function ThemesTab() { function renderOnlineThemes() { return ( <> - <Card className="vc-settings-card vc-text-selectable"> + <Card className="vc-settings-card"> <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> <Forms.FormText>One link per line</Forms.FormText> <Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText> diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx index 9871bfcc..f5e3d4b2 100644 --- a/src/components/VencordSettings/UpdaterTab.tsx +++ b/src/components/VencordSettings/UpdaterTab.tsx @@ -225,7 +225,7 @@ function Updater() { <Forms.FormTitle tag="h5">Repo</Forms.FormTitle> - <Forms.FormText className="vc-text-selectable"> + <Forms.FormText> {repoPending ? repo : err diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 35c12197..0161ff4e 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -60,15 +60,6 @@ background-color: var(--button-danger-background); } -.vc-text-selectable, -.vc-text-selectable :where([class*="text" i], [class*="title" i]) { - /* make text selectable, silly discord makes the entirety of settings not selectable */ - user-select: text; - - /* discord also sets cursor: default which prevents the cursor from showing as text */ - cursor: initial; -} - .vc-updater-modal { padding: 1.5em !important; } From 8e446e44abcd33dd760772e8dea0e57129d733b0 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 10 Jul 2025 01:37:13 +0200 Subject: [PATCH 016/141] Online Themes: fix & improve ui --- src/components/PluginSettings/index.tsx | 2 +- src/components/PluginSettings/styles.css | 7 -- src/components/VencordSettings/ThemesTab.tsx | 67 +++---------------- .../VencordSettings/settingsStyles.css | 20 ++++-- .../VencordSettings/specialCard.css | 5 -- src/plugins/_core/supportHelper.tsx | 2 +- src/plugins/voiceMessages/index.tsx | 2 +- src/utils/cspViolations.ts | 2 +- 8 files changed, 26 insertions(+), 81 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 27eb10a3..4d4e41e7 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -62,7 +62,7 @@ function showErrorToast(message: string) { function ReloadRequiredCard({ required }: { required: boolean; }) { return ( - <Card className={cl("info-card", { "restart-card": required })}> + <Card className={classes(cl("info-card"), required && "vc-warning-card")}> {required ? ( <> <Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle> diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index ed5e9aa1..c3d97051 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -66,13 +66,6 @@ gap: 0.25em; } -.vc-plugins-restart-card { - padding: 1em; - background: var(--info-warning-background); - border: 1px solid var(--info-warning-foreground); - color: var(--info-warning-foreground); -} - .vc-plugins-restart-button { margin-top: 0.5em; background: var(--info-warning-foreground) !important; diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index f557adf1..6d41ab86 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -29,7 +29,7 @@ import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; -import { useAwaiter, useForceUpdater } from "@utils/react"; +import { useForceUpdater } from "@utils/react"; import { getStylusWebStoreUrl } from "@utils/web"; import { findLazy } from "@webpack"; import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; @@ -52,62 +52,6 @@ const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue & const cl = classNameFactory("vc-settings-theme-"); -function Validator({ link }: { link: string; }) { - const [res, err, pending] = useAwaiter(() => fetch(link).then(res => { - if (res.status > 300) throw `${res.status} ${res.statusText}`; - const contentType = res.headers.get("Content-Type"); - if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain")) - throw "Not a CSS file. Remember to use the raw link!"; - - return "Okay!"; - })); - - const text = pending - ? "Checking..." - : err - ? `Error: ${err instanceof Error ? err.message : String(err)}` - : "Valid!"; - - return <Forms.FormText style={{ - color: pending ? "var(--text-muted)" : err ? "var(--text-danger)" : "var(--status-positive)" - }}>{text}</Forms.FormText>; -} - -function Validators({ themeLinks }: { themeLinks: string[]; }) { - if (!themeLinks.length) return null; - - return ( - <> - <Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle> - <Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText> - <div> - {themeLinks.map(rawLink => { - const { label, link } = (() => { - const match = /^@(light|dark) (.*)/.exec(rawLink); - if (!match) return { label: rawLink, link: rawLink }; - - const [, mode, link] = match; - return { label: `[${mode} mode only] ${link}`, link }; - })(); - - return <Card style={{ - padding: ".5em", - marginBottom: ".5em", - marginTop: ".5em" - }} key={link}> - <Forms.FormTitle tag="h5" style={{ - overflowWrap: "break-word" - }}> - {label} - </Forms.FormTitle> - <Validator link={link} /> - </Card>; - })} - </div> - </> - ); -} - interface ThemeCardProps { theme: UserThemeHeader; enabled: boolean; @@ -309,6 +253,12 @@ function ThemesTab() { function renderOnlineThemes() { return ( <> + <Card className={classes("vc-warning-card", Margins.bottom16)}> + <Forms.FormText> + This section is for advanced users. If you are having difficulties using it, use the + Local Themes tab instead. + </Forms.FormText> + </Card> <Card className="vc-settings-card"> <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> <Forms.FormText>One link per line</Forms.FormText> @@ -321,12 +271,11 @@ function ThemesTab() { value={themeText} onChange={setThemeText} className={"vc-settings-theme-links"} - placeholder="Theme Links" + placeholder="Enter Theme Links..." spellCheck={false} onBlur={onBlur} rows={10} /> - <Validators themeLinks={settings.themeLinks} /> </Forms.FormSection> </> ); diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 0161ff4e..c92d588f 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -1,7 +1,7 @@ .vc-settings-tab-bar { margin-top: 20px; margin-bottom: 10px; - border-bottom: 2px solid var(--background-modifier-accent); + border-bottom: 1px solid var(--border-subtle); } .vc-settings-tab-bar-item { @@ -20,6 +20,13 @@ margin-bottom: 1em; } +.vc-warning-card { + padding: 1em; + background: var(--info-warning-background); + border: 1px solid var(--info-warning-foreground); + color: var(--info-warning-foreground); +} + .vc-backup-restore-card { background-color: var(--info-warning-background); border-color: var(--info-warning-foreground); @@ -30,19 +37,20 @@ /* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */ display: inline-block !important; color: var(--text-default) !important; - padding: 0.5em; - border: 1px solid var(--background-modifier-accent); + padding: 0.5em 1em; + border: 1px solid var(--input-border); max-height: unset; background-color: transparent; box-sizing: border-box; - font-size: 12px; - line-height: 14px; resize: none; width: 100%; + font-size: 1em; + line-height: 2em; + white-space: nowrap; } .vc-settings-theme-links::placeholder { - color: var(--header-secondary); + color: var(--text-muted) !important; } .vc-settings-theme-links:focus { diff --git a/src/components/VencordSettings/specialCard.css b/src/components/VencordSettings/specialCard.css index 07b628f5..fb868283 100644 --- a/src/components/VencordSettings/specialCard.css +++ b/src/components/VencordSettings/specialCard.css @@ -12,11 +12,6 @@ position: relative; } -.vc-settings-card { - padding: 1em; - margin-bottom: 1em; -} - .vc-special-card-special { padding: 1em 1.5em; margin-bottom: 1em; diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 956343f6..82e09b88 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -318,7 +318,7 @@ export default definePlugin({ if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null; return ( - <Card className={`vc-plugins-restart-card ${Margins.top8}`}> + <Card className={`vc-warning-card ${Margins.top8}`}> Please do not private message Vencord plugin developers for support! <br /> Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")} diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 8365bb51..74f39ea3 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -219,7 +219,7 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) { /> {isUnsupportedFormat && ( - <Card className={`vc-plugins-restart-card ${Margins.top16}`}> + <Card className={`vc-warning-card ${Margins.top16}`}> <Forms.FormText>Voice Messages have to be OggOpus to be playable on iOS. This file is <code>{blob.type}</code> so it will not be playable on iOS.</Forms.FormText> <Forms.FormText className={Margins.top8}> diff --git a/src/utils/cspViolations.ts b/src/utils/cspViolations.ts index 9477bcaa..b1acc4c6 100644 --- a/src/utils/cspViolations.ts +++ b/src/utils/cspViolations.ts @@ -8,7 +8,7 @@ import { useLayoutEffect } from "@webpack/common"; import { useForceUpdater } from "./react"; -const cssRelevantDirectives = ["style-src", "img-src", "font-src"] as const; +const cssRelevantDirectives = ["style-src", "style-src-elem", "img-src", "font-src"] as const; export const CspBlockedUrls = new Set<string>(); const CspErrorListeners = new Set<() => void>(); From 19f4d7cdac5d9f65fb1ab132f4ba8c5f40737423 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 10 Jul 2025 01:37:32 +0200 Subject: [PATCH 017/141] fix various plugins still using outdated Discord classes --- src/plugins/clientTheme/clientTheme.css | 2 +- src/plugins/reviewDB/style.css | 2 +- src/plugins/sendTimestamps/styles.css | 4 ++-- src/plugins/serverInfo/styles.css | 2 +- src/plugins/spotifyControls/spotifyStyles.css | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css index 49cc3e15..828703e5 100644 --- a/src/plugins/clientTheme/clientTheme.css +++ b/src/plugins/clientTheme/clientTheme.css @@ -16,7 +16,7 @@ } .vc-clientTheme-container [class^="swatch"] { - border: thin solid var(--background-modifier-accent) !important; + border: thin solid var(--input-border) !important; } .vc-clientTheme-buttons-container { diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css index 50499b29..47ccd091 100644 --- a/src/plugins/reviewDB/style.css +++ b/src/plugins/reviewDB/style.css @@ -123,7 +123,7 @@ display: block; width: 100%; height: 100%; - background-color: var(--background-modifier-accent); + background-color: var(--border-subtle); } .vc-rdb-block-modal-username { diff --git a/src/plugins/sendTimestamps/styles.css b/src/plugins/sendTimestamps/styles.css index d96e886a..7d82b800 100644 --- a/src/plugins/sendTimestamps/styles.css +++ b/src/plugins/sendTimestamps/styles.css @@ -15,11 +15,11 @@ .vc-st-format-select { margin-bottom: 1em; - --background-modifier-accent: transparent; + --border-subtle: transparent; } .vc-st-format-label { - --background-modifier-accent: transparent; + --border-subtle: transparent; } .vc-st-modal-header { diff --git a/src/plugins/serverInfo/styles.css b/src/plugins/serverInfo/styles.css index 0fc44e02..f10fb6ac 100644 --- a/src/plugins/serverInfo/styles.css +++ b/src/plugins/serverInfo/styles.css @@ -38,7 +38,7 @@ } .vc-gp-tab-bar { - border-bottom: 2px solid var(--background-modifier-accent); + border-bottom: 1px solid var(--border-subtle); margin: 20px 12px 0; display: flex; gap: 40px; diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 58d74504..d4afbc83 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -1,6 +1,6 @@ #vc-spotify-player { padding: 0.375rem 0.5rem; - border-bottom: 1px solid var(--background-modifier-accent); + border-bottom: 1px solid var(--border-subtle); --vc-spotify-green: var(--spotify, #1db954); /* so custom themes can easily change it */ --vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%); From 18f083b7e697e95ecd9c820069ef68b6634b7398 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:56:12 -0400 Subject: [PATCH 018/141] fix ImageZoom & FakeProfileThemes (#3546) Co-authored-by: Vendicated <vendicated@riseup.net> --- src/plugins/fakeProfileThemes/index.tsx | 2 +- src/plugins/imageZoom/index.tsx | 11 +++-------- src/plugins/showHiddenChannels/index.tsx | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index a84722c1..1de38ab4 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -122,7 +122,7 @@ export default definePlugin({ { find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}", replacement: { - match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\)}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, + match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\).+?}\)(?=\])(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" } } diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 56e1d4a2..4fbfe3ff 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -171,14 +171,9 @@ export default definePlugin({ replace: `id:"${ELEMENT_ID}",$&` }, { - // This patch needs to be above the next one as it uses the zoomed class as an anchor - match: /\.zoomed]:.+?,(?=children:)/, - replace: "$&onClick:()=>{}," - }, - { - match: /className:\i\(\)\(\i\.wrapper,.+?}\),/, - replace: "" - }, + match: /(?<=null!=(\i)\?.{0,20})\i\.\i,{children:\1/, + replace: "'div',{onClick:e=>e.stopPropagation(),children:$1" + } ] }, // Make media viewer options not hide when zoomed in with the default Discord feature diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index c99afc84..a0ef38a6 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -250,7 +250,7 @@ export default definePlugin({ replace: (m, channel) => `${m}if($self.isHiddenChannel(${channel}))break;` }, { - match: /(?<="renderHeaderBar",\(\)=>{.+?hideSearch:(\i)\.isDirectory\(\))/, + match: /(?<="renderHeaderBar",\i=>{.+?hideSearch:(\i)\.isDirectory\(\))/, replace: (_, channel) => `||$self.isHiddenChannel(${channel})` }, { From 6787e98003e963e9bccfa8f5386fb8c09d230a3a Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:38:27 -0400 Subject: [PATCH 019/141] FakeProfileThemes: fix error when own profile is not loaded (#3514) Co-authored-by: V <vendicated@riseup.net> --- packages/discord-types/enums/commands.ts | 5 + .../discord-types/src/common/Application.d.ts | 23 ++ packages/discord-types/src/common/User.d.ts | 3 +- packages/discord-types/src/common/index.d.ts | 1 + .../src/stores/UserProfileStore.d.ts | 151 +++++++++++++ packages/discord-types/src/stores/index.d.ts | 1 + src/plugins/fakeProfileThemes/index.tsx | 202 +++++++++--------- src/plugins/showConnections/index.tsx | 15 +- src/plugins/validUser/index.tsx | 18 +- src/utils/discord.tsx | 2 +- src/webpack/common/stores.ts | 2 +- 11 files changed, 297 insertions(+), 126 deletions(-) create mode 100644 packages/discord-types/src/common/Application.d.ts create mode 100644 packages/discord-types/src/stores/UserProfileStore.d.ts diff --git a/packages/discord-types/enums/commands.ts b/packages/discord-types/enums/commands.ts index 0556e72d..298f9b7a 100644 --- a/packages/discord-types/enums/commands.ts +++ b/packages/discord-types/enums/commands.ts @@ -25,3 +25,8 @@ export const enum ApplicationCommandType { USER = 2, MESSAGE = 3, } + +export const enum ApplicationIntegrationType { + GUILD_INSTALL = 0, + USER_INSTALL = 1 +} diff --git a/packages/discord-types/src/common/Application.d.ts b/packages/discord-types/src/common/Application.d.ts new file mode 100644 index 00000000..d2ec1e7e --- /dev/null +++ b/packages/discord-types/src/common/Application.d.ts @@ -0,0 +1,23 @@ +import { User } from "./User"; + +export interface Application { + id: string; + name: string; + description?: string | null; + type: number | null; + icon: string | null | undefined; + is_discoverable: boolean; + is_monetized: boolean; + is_verified: boolean; + bot?: User; + deeplink_uri?: string; + flags?: number; + privacy_policy_url?: string; + terms_of_service_url?: string; + install_params?: ApplicationInstallParams; +} + +export interface ApplicationInstallParams { + permissions: string | null; + scopes: string[]; +} diff --git a/packages/discord-types/src/common/User.d.ts b/packages/discord-types/src/common/User.d.ts index a5503bcb..07eb4718 100644 --- a/packages/discord-types/src/common/User.d.ts +++ b/packages/discord-types/src/common/User.d.ts @@ -6,7 +6,7 @@ export class User extends DiscordRecord { constructor(user: object); accentColor: number; avatar: string; - banner: string; + banner: string | null | undefined; bio: string; bot: boolean; desktop: boolean; @@ -27,7 +27,6 @@ export class User extends DiscordRecord { system: boolean; username: string; verified: boolean; - themeColors?: [number, number]; get createdAt(): Date; get hasPremiumPerks(): boolean; diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts index f85a7c72..5e50e96e 100644 --- a/packages/discord-types/src/common/index.d.ts +++ b/packages/discord-types/src/common/index.d.ts @@ -1,3 +1,4 @@ +export * from "./Application"; export * from "./Channel"; export * from "./Guild"; export * from "./GuildMember"; diff --git a/packages/discord-types/src/stores/UserProfileStore.d.ts b/packages/discord-types/src/stores/UserProfileStore.d.ts new file mode 100644 index 00000000..d9efc47b --- /dev/null +++ b/packages/discord-types/src/stores/UserProfileStore.d.ts @@ -0,0 +1,151 @@ +import { FluxStore, Guild, User, Application, ApplicationInstallParams } from ".."; +import { ApplicationIntegrationType } from "../../enums"; + +export interface MutualFriend { + /** + * the userid of the mutual friend + */ + key: string; + /** + * the status of the mutual friend + */ + status: "online" | "offline" | "idle" | "dnd"; + /** + * the user object of the mutual friend + */ + user: User; +} + +export interface MutualGuild { + /** + * the guild object of the mutual guild + */ + guild: Guild; + /** + * the user's nickname in the guild, if any + */ + nick: string | null; + +} + +export interface ProfileBadge { + id: string; + description: string; + icon: string; + link?: string; +} + +export interface ConnectedAccount { + type: "twitch" | "youtube" | "skype" | "steam" | "leagueoflegends" | "battlenet" | "bluesky" | "bungie" | "reddit" | "twitter" | "twitter_legacy" | "spotify" | "facebook" | "xbox" | "samsung" | "contacts" | "instagram" | "mastodon" | "soundcloud" | "github" | "playstation" | "playstation-stg" | "epicgames" | "riotgames" | "roblox" | "paypal" | "ebay" | "tiktok" | "crunchyroll" | "domain" | "amazon-music"; + /** + * underlying id of connected account + * eg. account uuid + */ + id: string; + /** + * display name of connected account + */ + name: string; + verified: boolean; + metadata?: Record<string, string>; +} + +export interface ProfileApplication { + id: string; + customInstallUrl: string | undefined; + installParams: ApplicationInstallParams | undefined; + flags: number; + popularApplicationCommandIds?: string[]; + integrationTypesConfig: Record<ApplicationIntegrationType, Partial<{ + oauth2_install_params: ApplicationInstallParams; + }>>; + primarySkuId: string | undefined; + storefront_available: boolean; +} + +export interface UserProfileBase extends Pick<User, "banner"> { + accentColor: number | null; + /** + * often empty for guild profiles, get the user profile for badges + */ + badges: ProfileBadge[]; + bio: string | undefined; + popoutAnimationParticleType: string | null; + profileEffectExpiresAt: number | Date | undefined; + profileEffectId: undefined | string; + /** + * often an empty string when not set + */ + pronouns: string | "" | undefined; + themeColors: [number, number] | undefined; + userId: string; +} + +export interface ApplicationRoleConnection { + application: Application; + application_metadata: Record<string, any>; + metadata: Record<string, any>; + platform_name: string; + platform_username: string; +} + +export interface UserProfile extends UserProfileBase, Pick<User, "premiumType"> { + /** If this is a bot user profile, this will be its application */ + application: ProfileApplication | null; + applicationRoleConnections: ApplicationRoleConnection[] | undefined; + connectedAccounts: ConnectedAccount[] | undefined; + fetchStartedAt: number; + fetchEndedAt: number; + legacyUsername: string | undefined; + premiumGuildSince: Date | null; + premiumSince: Date | null; +} + +export class UserProfileStore extends FluxStore { + /** + * @param userId the user ID of the profile being fetched. + * @param guildId the guild ID to of the profile being fetched. + * defaults to the internal symbol `NO GUILD ID` if nullish + * + * @returns true if the profile is being fetched, false otherwise. + */ + isFetchingProfile(userId: string, guildId?: string): boolean; + /** + * Check if mutual friends for {@link userId} are currently being fetched. + * + * @param userId the user ID of the mutual friends being fetched. + * + * @returns true if mutual friends are being fetched, false otherwise. + */ + isFetchingFriends(userId: string): boolean; + + get isSubmitting(): boolean; + + getUserProfile(userId: string): UserProfile | undefined; + + getGuildMemberProfile(userId: string, guildId: string | undefined): UserProfileBase | null; + /** + * Get the mutual friends of a user. + * + * @param userId the user ID of the user to get the mutual friends of. + * + * @returns an array of mutual friends, or undefined if the user has no mutual friends + */ + getMutualFriends(userId: string): MutualFriend[] | undefined; + /** + * Get the count of mutual friends for a user. + * + * @param userId the user ID of the user to get the mutual friends count of. + * + * @returns the count of mutual friends, or undefined if the user has no mutual friends + */ + getMutualFriendsCount(userId: string): number | undefined; + /** + * Get the mutual guilds of a user. + * + * @param userId the user ID of the user to get the mutual guilds of. + * + * @returns an array of mutual guilds, or undefined if the user has no mutual guilds + */ + getMutualGuilds(userId: string): MutualGuild[] | undefined; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index b6254844..23045832 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -11,6 +11,7 @@ export * from "./RelationshipStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; export * from "./ThemeStore"; +export * from "./UserProfileStore"; export * from "./UserStore"; export * from "./WindowStore"; diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 1de38ab4..5d16a842 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -22,13 +22,14 @@ import "./index.css"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { fetchUserProfile } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; +import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { User } from "@vencord/discord-types"; +import { User, UserProfile } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; -import { ReactElement } from "react"; import virtualMerge from "virtual-merge"; interface Colors { @@ -81,14 +82,6 @@ const settings = definePluginSettings({ } }); -interface ColorPickerProps { - color: number | null; - label: ReactElement<any>; - showEyeDropper?: boolean; - suggestedColors?: string[]; - onChange(value: number | null): void; -} - // I can't be bothered to figure out the semantics of this component. The // functions surely get some event argument sent to them and they likely aren't // all required. If anyone who wants to use this component stumbles across this @@ -106,6 +99,100 @@ interface ProfileModalProps { const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER"); +function SettingsAboutComponentWrapper() { + const [, , userProfileLoading] = useAwaiter(() => fetchUserProfile(UserStore.getCurrentUser().id)); + + return !userProfileLoading && <SettingsAboutComponent />; +} + +function SettingsAboutComponent() { + const existingColors = decode( + UserProfileStore.getUserProfile(UserStore.getCurrentUser().id)?.bio ?? "" + ) ?? [0, 0]; + const [color1, setColor1] = useState(existingColors[0]); + const [color2, setColor2] = useState(existingColors[1]); + + return ( + <Forms.FormSection> + <Forms.FormTitle tag="h3">Usage</Forms.FormTitle> + <Forms.FormText> + After enabling this plugin, you will see custom colors in + the profiles of other people using compatible plugins.{" "} + <br /> + To set your own colors: + <ul> + <li> + • use the color pickers below to choose your colors + </li> + <li>• click the "Copy 3y3" button</li> + <li>• paste the invisible text anywhere in your bio</li> + </ul><br /> + <Forms.FormDivider + className={classes(Margins.top8, Margins.bottom8)} + /> + <Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle> + <Flex + direction={Flex.Direction.HORIZONTAL} + style={{ gap: "1rem" }} + > + <ColorPicker + color={color1} + label={ + <Text + variant={"text-xs/normal"} + style={{ marginTop: "4px" }} + > + Primary + </Text> + } + onChange={(color: number) => { + setColor1(color); + }} + /> + <ColorPicker + color={color2} + label={ + <Text + variant={"text-xs/normal"} + style={{ marginTop: "4px" }} + > + Accent + </Text> + } + onChange={(color: number) => { + setColor2(color); + }} + /> + <Button + onClick={() => { + const colorString = encode(color1, color2); + copyWithToast(colorString); + }} + color={Button.Colors.PRIMARY} + size={Button.Sizes.XLARGE} + > + Copy 3y3 + </Button> + </Flex> + <Forms.FormDivider + className={classes(Margins.top8, Margins.bottom8)} + /> + <Forms.FormTitle tag="h3">Preview</Forms.FormTitle> + <div className="vc-fpt-preview"> + <ProfileModal + user={UserStore.getCurrentUser()} + pendingThemeColors={[color1, color2]} + onAvatarChange={() => { }} + onBannerChange={() => { }} + canUsePremiumCustomization={true} + hideExampleButton={true} + hideFakeActivity={true} + isTryItOutFlow={true} + /> + </div> + </Forms.FormText> + </Forms.FormSection>); +} export default definePlugin({ name: "FakeProfileThemes", @@ -117,7 +204,7 @@ export default definePlugin({ replacement: { match: /(?<=getUserProfile\(\i\){return )(.+?)(?=})/, replace: "$self.colorDecodeHook($1)" - } + }, }, { find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}", @@ -127,97 +214,12 @@ export default definePlugin({ } } ], - settingsAboutComponent: () => { - const existingColors = decode( - UserProfileStore.getUserProfile(UserStore.getCurrentUser().id).bio - ) ?? [0, 0]; - const [color1, setColor1] = useState(existingColors[0]); - const [color2, setColor2] = useState(existingColors[1]); - return ( - <Forms.FormSection> - <Forms.FormTitle tag="h3">Usage</Forms.FormTitle> - <Forms.FormText> - After enabling this plugin, you will see custom colors in - the profiles of other people using compatible plugins.{" "} - <br /> - To set your own colors: - <ul> - <li> - • use the color pickers below to choose your colors - </li> - <li>• click the "Copy 3y3" button</li> - <li>• paste the invisible text anywhere in your bio</li> - </ul><br /> - <Forms.FormDivider - className={classes(Margins.top8, Margins.bottom8)} - /> - <Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle> - <Flex - direction={Flex.Direction.HORIZONTAL} - style={{ gap: "1rem" }} - > - <ColorPicker - color={color1} - label={ - <Text - variant={"text-xs/normal"} - style={{ marginTop: "4px" }} - > - Primary - </Text> - } - onChange={(color: number) => { - setColor1(color); - }} - /> - <ColorPicker - color={color2} - label={ - <Text - variant={"text-xs/normal"} - style={{ marginTop: "4px" }} - > - Accent - </Text> - } - onChange={(color: number) => { - setColor2(color); - }} - /> - <Button - onClick={() => { - const colorString = encode(color1, color2); - copyWithToast(colorString); - }} - color={Button.Colors.PRIMARY} - size={Button.Sizes.XLARGE} - > - Copy 3y3 - </Button> - </Flex> - <Forms.FormDivider - className={classes(Margins.top8, Margins.bottom8)} - /> - <Forms.FormTitle tag="h3">Preview</Forms.FormTitle> - <div className="vc-fpt-preview"> - <ProfileModal - user={UserStore.getCurrentUser()} - pendingThemeColors={[color1, color2]} - onAvatarChange={() => { }} - onBannerChange={() => { }} - canUsePremiumCustomization={true} - hideExampleButton={true} - hideFakeActivity={true} - isTryItOutFlow={true} - /> - </div> - </Forms.FormText> - </Forms.FormSection>); - }, + settingsAboutComponent: SettingsAboutComponentWrapper, + settings, - colorDecodeHook(user: User) { - if (user) { + colorDecodeHook(user: UserProfile) { + if (user?.bio) { // don't replace colors if already set with nitro if (settings.store.nitroFirst && user.themeColors) return user; const colors = decode(user.bio); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 882c869e..ca90cc48 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -25,7 +25,7 @@ import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { User } from "@vencord/discord-types"; +import { ConnectedAccount, User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { Tooltip, UserProfileStore } from "@webpack/common"; @@ -60,15 +60,8 @@ const settings = definePluginSettings({ } }); -interface Connection { - type: string; - id: string; - name: string; - verified: boolean; -} - interface ConnectionPlatform { - getPlatformUserUrl(connection: Connection): string; + getPlatformUserUrl(connection: ConnectedAccount): string; icon: { lightSVG: string, darkSVG: string; }; } @@ -88,7 +81,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { if (!profile) return null; - const connections: Connection[] = profile.connectedAccounts; + const connections = profile.connectedAccounts; if (!connections?.length) return null; @@ -102,7 +95,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { ); } -function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) { +function CompactConnectionComponent({ connection, theme }: { connection: ConnectedAccount, theme: string; }) { const platform = platforms.get(useLegacyPlatformType(connection.type)); const url = platform.getPlatformUserUrl?.(connection); diff --git a/src/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index 4825cdaa..488b6bd6 100644 --- a/src/plugins/validUser/index.tsx +++ b/src/plugins/validUser/index.tsx @@ -22,6 +22,7 @@ import { isNonNullish } from "@utils/guards"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin from "@utils/types"; +import { ProfileBadge } from "@vencord/discord-types"; import { Constants, FluxDispatcher, RestAPI, UserProfileStore, UserStore, useState } from "@webpack/common"; import { type ComponentType, type ReactNode } from "react"; @@ -47,13 +48,6 @@ const badges: Record<string, ProfileBadge> = { const fetching = new Set<string>(); const queue = new Queue(5); -interface ProfileBadge { - id: string; - description: string; - icon: string; - link?: string; -} - interface MentionProps { data: { userId?: string; @@ -102,10 +96,12 @@ async function getUser(id: string) { // Fill in what we can deduce const profile = UserProfileStore.getUserProfile(id); - profile.accentColor = user.accent_color; - profile.badges = fakeBadges; - profile.banner = user.banner; - profile.premiumType = user.premium_type; + if (profile) { + profile.accentColor = user.accent_color; + profile.badges = fakeBadges; + profile.banner = user.banner; + profile.premiumType = user.premium_type; + } return userObj; } diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 4a652a29..d7a38ebb 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -194,7 +194,7 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt }); FluxDispatcher.dispatch({ type: "USER_UPDATE", user: body.user }); - await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", ...body }); + await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", userProfile: body }); if (options?.guild_id && body.guild_member) FluxDispatcher.dispatch({ type: "GUILD_MEMBER_PROFILE_UPDATE", guildId: options.guild_id, guildMember: body.guild_member }); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index be72d47c..4165af8e 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -42,7 +42,7 @@ export let GuildStore: t.GuildStore; export let GuildRoleStore: t.GuildRoleStore; export let GuildMemberStore: t.GuildMemberStore; export let UserStore: t.UserStore; -export let UserProfileStore: GenericStore; +export let UserProfileStore: t.UserProfileStore; export let SelectedChannelStore: t.SelectedChannelStore; export let SelectedGuildStore: t.SelectedGuildStore; export let ChannelStore: t.ChannelStore; From 5dd6722528c826af45e0b1d59fc515a6c561bd23 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:07:22 -0300 Subject: [PATCH 020/141] Fix MessagePopoverAPI incorrectly hiding the emoji button --- src/plugins/_api/messagePopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index a297fc87..a49658fb 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?children:\[(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},` } From 1a98d54e3ad3ad4c3109c59f148b3b9e2d25ae12 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:07:43 -0300 Subject: [PATCH 021/141] Fix Experiments embed patches --- src/plugins/experiments/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index eee732d0..21c6088b 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -116,11 +116,19 @@ export default definePlugin({ }, // Fix some tricky experiments name causing a client crash { - match: /.getRegisteredExperiments\(\)(?<=(\i)=.+?).+?if\(null==(\i)(?=\)return null;)/, - replace: "$&||!Object.hasOwn($1,$2)" + match: /.getExperimentBucketName.+?if\(null==(\i)\|\|null==\i(?=\)return null;)/, + replace: "$&||({})[$1]!=null" } ] }, + // Fix another function which cases crashes with tricky experiment names and the experiment embed + { + find: "}getServerAssignment(", + replacement: { + match: /}getServerAssignment\((\i),\i,\i\){/, + replace: "$&if($1==null)return;" + } + } ], start: () => !BugReporterExperiment.getCurrentConfig().hasBugReporterAccess && enableStyle(hideBugReport), From ad810df9789bf51bd81226b4bd63efac77e84f39 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:09:21 -0300 Subject: [PATCH 022/141] Attempt to make OverrideForumDefaults not always slow --- src/plugins/overrideForumDefaults/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/overrideForumDefaults/index.tsx b/src/plugins/overrideForumDefaults/index.tsx index be106ac8..a377fff5 100644 --- a/src/plugins/overrideForumDefaults/index.tsx +++ b/src/plugins/overrideForumDefaults/index.tsx @@ -36,11 +36,11 @@ export default definePlugin({ find: "getDefaultLayout(){", replacement: [ { - match: /getDefaultLayout\(\){/, + match: /}getDefaultLayout\(\){/, replace: "$&return $self.getLayout();" }, { - match: /getDefaultSortOrder\(\){/, + match: /}getDefaultSortOrder\(\){/, replace: "$&return $self.getSortOrder();" } ] From f3874d0a26e9994775e9f4fb924d2e5bb87117b3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:02:44 -0300 Subject: [PATCH 023/141] Fix Plugin Settings broken webpack find --- src/components/PluginSettings/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 4d4e41e7..acfb0609 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -45,7 +45,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => const cl = classNameFactory("vc-plugins-"); const logger = new Logger("PluginSettings", "#a6d189"); -const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error"); +const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); @@ -349,7 +349,7 @@ export default function PluginSettings() { select={onStatusChange} isSelected={v => v === searchValue.status} closeOnSelect={true} - className={InputStyles.inputDefault} + className={InputStyles.input} /> </div> </div> From a33e81d1cbd7ab50c0b1e1446c925bf259e671fc Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:05:04 -0300 Subject: [PATCH 024/141] Bump to v1.12.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81b9a1c5..577bf39d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.5", + "version": "1.12.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 3f51ee1b2aef0a9e94a97eed35ff6d0d81aacd48 Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Tue, 15 Jul 2025 15:57:24 +0200 Subject: [PATCH 025/141] refactor Settings UI (#3545) Much improved file structure and cleaner code. Also gets rid of temporary settings & saving and instead applies all changes immediately. Besides that, this change only changes code and doesn't change the ui --- .gitignore | 2 +- packages/discord-types/src/components.d.ts | 2 + src/Vencord.ts | 8 +- src/api/Badges.ts | 5 +- src/api/MessageUpdater.ts | 4 +- src/api/Notifications/notificationLog.tsx | 2 +- src/components/Icons.tsx | 1 - .../PluginSettings/components/index.ts | 43 -- .../VencordSettings/PatchHelperTab.tsx | 393 ----------------- src/components/VencordSettings/PluginsTab.tsx | 23 - src/components/VencordSettings/ThemesTab.tsx | 402 ------------------ src/components/VencordSettings/UpdaterTab.tsx | 268 ------------ src/components/VencordSettings/VencordTab.tsx | 301 ------------- src/components/index.ts | 6 +- .../addonCard.css => settings/AddonCard.css} | 0 .../AddonCard.tsx | 11 +- .../{ => settings}/DonateButton.tsx | 3 +- .../{Badge.tsx => settings/PluginBadge.tsx} | 4 +- .../QuickAction.css} | 0 .../QuickAction.tsx} | 2 +- .../SpecialCard.css} | 0 .../SpecialCard.tsx | 2 +- src/components/{ => settings}/Switch.css | 0 src/components/{ => settings}/Switch.tsx | 0 src/components/settings/index.ts | 13 + .../shared.tsx => settings/tabs/BaseTab.tsx} | 3 - src/components/settings/tabs/index.ts | 18 + .../tabs/patchHelper/FullPatchInput.tsx | 83 ++++ .../tabs/patchHelper/PatchPreview.tsx | 149 +++++++ .../tabs/patchHelper/ReplacementInput.tsx | 83 ++++ .../settings/tabs/patchHelper/index.tsx | 165 +++++++ .../tabs/plugins/ContributorModal.css} | 0 .../tabs/plugins}/ContributorModal.tsx | 4 +- .../tabs/plugins}/LinkIconButton.css | 0 .../tabs/plugins}/LinkIconButton.tsx | 3 +- .../settings/tabs/plugins/PluginCard.tsx | 110 +++++ .../tabs/plugins}/PluginModal.css | 0 .../tabs/plugins}/PluginModal.tsx | 185 ++------ .../plugins/components/BooleanSetting.tsx} | 24 +- .../tabs/plugins/components/Common.tsx | 48 +++ .../plugins/components/ComponentSetting.tsx} | 6 +- .../plugins/components/NumberSetting.tsx} | 35 +- .../plugins/components/SelectSetting.tsx} | 35 +- .../plugins/components/SliderSetting.tsx} | 36 +- .../tabs/plugins/components/TextSetting.tsx} | 34 +- .../settings/tabs/plugins/components/index.ts | 39 ++ .../tabs/plugins}/index.tsx | 231 ++++------ .../tabs/plugins}/styles.css | 2 +- .../tabs/styles.css} | 0 .../tabs/sync}/BackupAndRestoreTab.tsx | 7 +- .../tabs/sync}/CloudTab.tsx | 3 +- .../settings/tabs/themes/CspErrorCard.tsx | 86 ++++ .../settings/tabs/themes/LocalThemesTab.tsx | 170 ++++++++ .../settings/tabs/themes/OnlineThemesTab.tsx | 56 +++ .../settings/tabs/themes/ThemeCard.tsx | 56 +++ src/components/settings/tabs/themes/index.tsx | 85 ++++ .../tabs/themes/styles.css} | 0 .../settings/tabs/updater/Components.tsx | 148 +++++++ .../settings/tabs/updater/index.tsx | 115 +++++ .../settings/tabs/updater/runWithDispatch.tsx | 50 +++ .../settings/tabs/vencord/DonateButton.tsx | 25 ++ .../tabs/vencord/MacVibrancySettings.tsx | 80 ++++ .../tabs/vencord}/NotificationSettings.tsx | 52 ++- .../settings/tabs/vencord/index.tsx | 211 +++++++++ src/plugins/_api/badges/index.tsx | 4 +- src/plugins/_core/settings.tsx | 12 +- src/plugins/_core/supportHelper.tsx | 4 +- src/plugins/appleMusic.desktop/index.tsx | 4 +- src/plugins/betterSettings/PluginsSubmenu.tsx | 2 +- src/plugins/consoleJanitor/index.tsx | 3 +- src/plugins/ctrlEnterSend/index.ts | 4 +- src/plugins/customIdle/index.ts | 3 +- .../decor/ui/modals/ChangeDecorationModal.tsx | 5 +- src/plugins/experiments/index.tsx | 7 +- src/plugins/fixSpotifyEmbeds.desktop/index.ts | 3 +- src/plugins/imageZoom/index.tsx | 3 +- src/plugins/quickReply/index.ts | 7 +- src/plugins/roleColorEverywhere/index.tsx | 3 +- .../components/Highlighter.tsx | 4 +- .../hooks/useShikiSettings.ts | 25 +- src/plugins/shikiCodeblocks.desktop/index.ts | 5 +- src/plugins/showConnections/index.tsx | 4 +- src/plugins/typingIndicator/index.tsx | 3 +- src/plugins/unlockedAvatarZoom/index.ts | 3 +- src/plugins/userVoiceShow/components.tsx | 3 +- src/plugins/vcNarrator/index.tsx | 22 +- src/plugins/volumeBooster/index.ts | 3 +- src/plugins/webKeybinds.web/index.ts | 4 +- src/plugins/whoReacted/index.tsx | 5 +- src/plugins/xsOverlay/index.tsx | 3 +- src/utils/constants.ts | 5 + src/utils/react.tsx | 7 + src/utils/types.ts | 29 +- src/webpack/common/components.ts | 1 + 94 files changed, 2120 insertions(+), 2002 deletions(-) delete mode 100644 src/components/PluginSettings/components/index.ts delete mode 100644 src/components/VencordSettings/PatchHelperTab.tsx delete mode 100644 src/components/VencordSettings/PluginsTab.tsx delete mode 100644 src/components/VencordSettings/ThemesTab.tsx delete mode 100644 src/components/VencordSettings/UpdaterTab.tsx delete mode 100644 src/components/VencordSettings/VencordTab.tsx rename src/components/{VencordSettings/addonCard.css => settings/AddonCard.css} (100%) rename src/components/{VencordSettings => settings}/AddonCard.tsx (93%) rename src/components/{ => settings}/DonateButton.tsx (96%) rename src/components/{Badge.tsx => settings/PluginBadge.tsx} (90%) rename src/components/{VencordSettings/quickActions.css => settings/QuickAction.css} (100%) rename src/components/{VencordSettings/quickActions.tsx => settings/QuickAction.tsx} (97%) rename src/components/{VencordSettings/specialCard.css => settings/SpecialCard.css} (100%) rename src/components/{VencordSettings => settings}/SpecialCard.tsx (99%) rename src/components/{ => settings}/Switch.css (100%) rename src/components/{ => settings}/Switch.tsx (100%) create mode 100644 src/components/settings/index.ts rename src/components/{VencordSettings/shared.tsx => settings/tabs/BaseTab.tsx} (96%) create mode 100644 src/components/settings/tabs/index.ts create mode 100644 src/components/settings/tabs/patchHelper/FullPatchInput.tsx create mode 100644 src/components/settings/tabs/patchHelper/PatchPreview.tsx create mode 100644 src/components/settings/tabs/patchHelper/ReplacementInput.tsx create mode 100644 src/components/settings/tabs/patchHelper/index.tsx rename src/components/{PluginSettings/contributorModal.css => settings/tabs/plugins/ContributorModal.css} (100%) rename src/components/{PluginSettings => settings/tabs/plugins}/ContributorModal.tsx (98%) rename src/components/{PluginSettings => settings/tabs/plugins}/LinkIconButton.css (100%) rename src/components/{PluginSettings => settings/tabs/plugins}/LinkIconButton.tsx (95%) create mode 100644 src/components/settings/tabs/plugins/PluginCard.tsx rename src/components/{PluginSettings => settings/tabs/plugins}/PluginModal.css (100%) rename src/components/{PluginSettings => settings/tabs/plugins}/PluginModal.tsx (55%) rename src/components/{PluginSettings/components/SettingBooleanComponent.tsx => settings/tabs/plugins/components/BooleanSetting.tsx} (72%) create mode 100644 src/components/settings/tabs/plugins/components/Common.tsx rename src/components/{PluginSettings/components/SettingCustomComponent.tsx => settings/tabs/plugins/components/ComponentSetting.tsx} (76%) rename src/components/{PluginSettings/components/SettingNumericComponent.tsx => settings/tabs/plugins/components/NumberSetting.tsx} (59%) rename src/components/{PluginSettings/components/SettingSelectComponent.tsx => settings/tabs/plugins/components/SelectSetting.tsx} (56%) rename src/components/{PluginSettings/components/SettingSliderComponent.tsx => settings/tabs/plugins/components/SliderSetting.tsx} (58%) rename src/components/{PluginSettings/components/SettingTextComponent.tsx => settings/tabs/plugins/components/TextSetting.tsx} (52%) create mode 100644 src/components/settings/tabs/plugins/components/index.ts rename src/components/{PluginSettings => settings/tabs/plugins}/index.tsx (57%) rename src/components/{PluginSettings => settings/tabs/plugins}/styles.css (98%) rename src/components/{VencordSettings/settingsStyles.css => settings/tabs/styles.css} (100%) rename src/components/{VencordSettings => settings/tabs/sync}/BackupAndRestoreTab.tsx (93%) rename src/components/{VencordSettings => settings/tabs/sync}/CloudTab.tsx (99%) create mode 100644 src/components/settings/tabs/themes/CspErrorCard.tsx create mode 100644 src/components/settings/tabs/themes/LocalThemesTab.tsx create mode 100644 src/components/settings/tabs/themes/OnlineThemesTab.tsx create mode 100644 src/components/settings/tabs/themes/ThemeCard.tsx create mode 100644 src/components/settings/tabs/themes/index.tsx rename src/components/{VencordSettings/themesStyles.css => settings/tabs/themes/styles.css} (100%) create mode 100644 src/components/settings/tabs/updater/Components.tsx create mode 100644 src/components/settings/tabs/updater/index.tsx create mode 100644 src/components/settings/tabs/updater/runWithDispatch.tsx create mode 100644 src/components/settings/tabs/vencord/DonateButton.tsx create mode 100644 src/components/settings/tabs/vencord/MacVibrancySettings.tsx rename src/components/{VencordSettings => settings/tabs/vencord}/NotificationSettings.tsx (86%) create mode 100644 src/components/settings/tabs/vencord/index.tsx diff --git a/.gitignore b/.gitignore index 9f877c05..e45af171 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ lerna-debug.log* src/userplugins ExtensionCache/ -settings/ +/settings diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index 8b2fd1cf..eff9d73a 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -244,8 +244,10 @@ export type TextInput = ComponentType<PropsWithChildren<{ Sizes: Record<"DEFAULT" | "MINI", string>; }; +// FIXME: this is wrong, it's not actually just HTMLTextAreaElement export type TextArea = ComponentType<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & { onChange(v: string): void; + inputRef?: Ref<HTMLTextAreaElement>; }>; interface SelectOption { diff --git a/src/Vencord.ts b/src/Vencord.ts index f18c7347..00f29347 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -16,6 +16,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +// DO NOT REMOVE UNLESS YOU WISH TO FACE THE WRATH OF THE CIRCULAR DEPENDENCY DEMON!!!!!!! +import "~plugins"; + export * as Api from "./api"; export * as Components from "./components"; export * as Plugins from "./plugins"; @@ -29,7 +32,8 @@ export { PlainSettings, Settings }; import "./utils/quickCss"; import "./webpack/patchWebpack"; -import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; +import { openUpdaterModal } from "@components/settings/tabs/updater"; +import { IS_WINDOWS } from "@utils/constants"; import { StartAt } from "@utils/types"; import { get as dsGet } from "./api/DataStore"; @@ -161,7 +165,7 @@ init(); document.addEventListener("DOMContentLoaded", () => { startAllPlugins(StartAt.DOMContentLoaded); - if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) { + if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { document.head.append(Object.assign(document.createElement("style"), { id: "vencord-native-titlebar-style", textContent: "[class*=titleBar]{display: none!important}" diff --git a/src/api/Badges.ts b/src/api/Badges.ts index ee2f3a30..f79ca099 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -17,10 +17,9 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; +import BadgeAPIPlugin from "plugins/_api/badges"; import { ComponentType, HTMLProps } from "react"; -import Plugins from "~plugins"; - export const enum BadgePosition { START, END @@ -90,7 +89,7 @@ export function _getBadges(args: BadgeUserArgs) { : badges.push(...b); } } - const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); + const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); if (donorBadges) badges.unshift(...donorBadges); return badges; diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts index 7c4b3d5c..afbb04f0 100644 --- a/src/api/MessageUpdater.ts +++ b/src/api/MessageUpdater.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { FluxStore, Message } from "@vencord/discord-types"; +import { Message } from "@vencord/discord-types"; import { MessageCache, MessageStore } from "@webpack/common"; /** @@ -24,5 +24,5 @@ export function updateMessage(channelId: string, messageId: string, fields?: Par }); MessageCache.commit(newChannelMessageCache); - (MessageStore as unknown as FluxStore).emitChange(); + MessageStore.emitChange(); } diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 5df31d4c..a943db07 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -20,7 +20,7 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; -import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings"; +import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 4c5b0ca0..9862c1b4 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -27,7 +27,6 @@ interface BaseIconProps extends IconProps { } type IconProps = JSX.IntrinsicElements["svg"]; -type ImageProps = JSX.IntrinsicElements["img"]; function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) { return ( diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts deleted file mode 100644 index c38f209b..00000000 --- a/src/components/PluginSettings/components/index.ts +++ /dev/null @@ -1,43 +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 { DefinedSettings, PluginOptionBase } from "@utils/types"; - -interface ISettingElementPropsBase<T> { - option: T; - onChange(newValue: any): void; - pluginSettings: { - [setting: string]: any; - enabled: boolean; - }; - id: string; - onError(hasError: boolean): void; - definedSettings?: DefinedSettings; -} - -export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>; -export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>; - -export * from "../../Badge"; -export * from "./SettingBooleanComponent"; -export * from "./SettingCustomComponent"; -export * from "./SettingNumericComponent"; -export * from "./SettingSelectComponent"; -export * from "./SettingSliderComponent"; -export * from "./SettingTextComponent"; - diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx deleted file mode 100644 index 83364d19..00000000 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ /dev/null @@ -1,393 +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 { CodeBlock } from "@components/CodeBlock"; -import { debounce } from "@shared/debounce"; -import { copyToClipboard } from "@utils/clipboard"; -import { Margins } from "@utils/margins"; -import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; -import { makeCodeblock } from "@utils/text"; -import { Patch, ReplaceFn } from "@utils/types"; -import { search } from "@webpack"; -import { Button, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common"; - -import { SettingsTab, wrapTab } from "./shared"; - -// Do not include diff in non dev builds (side effects import) -if (IS_DEV) { - var differ = require("diff") as typeof import("diff"); -} - -const findCandidates = debounce(function ({ find, setModule, setError }) { - const candidates = search(find); - const keys = Object.keys(candidates); - const len = keys.length; - if (len === 0) - setError("No match. Perhaps that module is lazy loaded?"); - else if (len !== 1) - setError("Multiple matches. Please refine your filter"); - else - setModule([keys[0], candidates[keys[0]]]); -}); - -interface ReplacementComponentProps { - module: [id: number, factory: Function]; - match: string; - replacement: string | ReplaceFn; - setReplacementError(error: any): void; -} - -function ReplacementComponent({ module, match, replacement, setReplacementError }: ReplacementComponentProps) { - const [id, fact] = module; - const [compileResult, setCompileResult] = React.useState<[boolean, string]>(); - - const [patchedCode, matchResult, diff] = React.useMemo(() => { - const src: string = fact.toString().replaceAll("\n", ""); - - try { - new RegExp(match); - } catch (e) { - return ["", [], []]; - } - const canonicalMatch = canonicalizeMatch(new RegExp(match)); - try { - const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]'); - var patched = src.replace(canonicalMatch, canonicalReplace as string); - setReplacementError(void 0); - } catch (e) { - setReplacementError((e as Error).message); - return ["", [], []]; - } - const m = src.match(canonicalMatch); - return [patched, m, makeDiff(src, patched, m)]; - }, [id, match, replacement]); - - function makeDiff(original: string, patched: string, match: RegExpMatchArray | null) { - if (!match || original === patched) return null; - - const changeSize = patched.length - original.length; - - // Use 200 surrounding characters of context - const start = Math.max(0, match.index! - 200); - const end = Math.min(original.length, match.index! + match[0].length + 200); - // (changeSize may be negative) - const endPatched = end + changeSize; - - const context = original.slice(start, end); - const patchedContext = patched.slice(start, endPatched); - - return differ.diffWordsWithSpace(context, patchedContext); - } - - function renderMatch() { - if (!matchResult) - return <Forms.FormText>Regex doesn't match!</Forms.FormText>; - - const fullMatch = matchResult[0] ? makeCodeblock(matchResult[0], "js") : ""; - const groups = matchResult.length > 1 - ? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i + 1}: ${g}`).join("\n"), "yml") - : ""; - - return ( - <> - <div style={{ userSelect: "text" }}>{Parser.parse(fullMatch)}</div> - <div style={{ userSelect: "text" }}>{Parser.parse(groups)}</div> - </> - ); - } - - function renderDiff() { - return diff?.map((p, idx) => { - const color = p.added ? "lime" : p.removed ? "red" : "grey"; - return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>; - }); - } - - return ( - <> - <Forms.FormTitle>Module {id}</Forms.FormTitle> - - {!!matchResult?.[0]?.length && ( - <> - <Forms.FormTitle>Match</Forms.FormTitle> - {renderMatch()} - </>) - } - - {!!diff?.length && ( - <> - <Forms.FormTitle>Diff</Forms.FormTitle> - {renderDiff()} - </> - )} - - {!!diff?.length && ( - <Button className={Margins.top20} onClick={() => { - try { - Function(patchedCode.replace(/^function\(/, "function patchedModule(")); - setCompileResult([true, "Compiled successfully"]); - } catch (err) { - setCompileResult([false, (err as Error).message]); - } - }}>Compile</Button> - )} - - {compileResult && - <Forms.FormText style={{ color: compileResult[0] ? "var(--status-positive)" : "var(--text-danger)" }}> - {compileResult[1]} - </Forms.FormText> - } - </> - ); -} - -function ReplacementInput({ replacement, setReplacement, replacementError }) { - const [isFunc, setIsFunc] = React.useState(false); - const [error, setError] = React.useState<string>(); - - function onChange(v: string) { - setError(void 0); - - if (isFunc) { - try { - const func = (0, eval)(v); - if (typeof func === "function") - setReplacement(() => func); - else - setError("Replacement must be a function"); - } catch (e) { - setReplacement(v); - setError((e as Error).message); - } - } else { - setReplacement(v); - } - } - - React.useEffect( - () => void (isFunc ? onChange(replacement) : setError(void 0)), - [isFunc] - ); - - return ( - <> - {/* FormTitle adds a class if className is not set, so we set it to an empty string to prevent that */} - <Forms.FormTitle className="">replacement</Forms.FormTitle> - <TextInput - value={replacement?.toString()} - onChange={onChange} - error={error ?? replacementError} - /> - {!isFunc && ( - <div> - <Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle> - {Object.entries({ - "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", - "$$": "Insert a $", - "$&": "Insert the entire match", - "$`\u200b": "Insert the substring before the match", - "$'": "Insert the substring after the match", - "$n": "Insert the nth capturing group ($1, $2...)", - "$self": "Insert the plugin instance", - }).map(([placeholder, desc]) => ( - <Forms.FormText key={placeholder}> - {Parser.parse("`" + placeholder + "`")}: {desc} - </Forms.FormText> - ))} - </div> - )} - - <Switch - className={Margins.top8} - value={isFunc} - onChange={setIsFunc} - note="'replacement' will be evaled if this is toggled" - hideBorder={true} - > - Treat as Function - </Switch> - </> - ); -} - -interface FullPatchInputProps { - setFind(v: string): void; - setParsedFind(v: string | RegExp): void; - setMatch(v: string): void; - setReplacement(v: string | ReplaceFn): void; -} - -function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) { - const [fullPatch, setFullPatch] = React.useState<string>(""); - const [fullPatchError, setFullPatchError] = React.useState<string>(""); - - function update() { - if (fullPatch === "") { - setFullPatchError(""); - - setFind(""); - setParsedFind(""); - setMatch(""); - setReplacement(""); - return; - } - - try { - const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch; - - if (!parsed.find) throw new Error("No 'find' field"); - if (!parsed.replacement) throw new Error("No 'replacement' field"); - - if (parsed.replacement instanceof Array) { - if (parsed.replacement.length === 0) throw new Error("Invalid replacement"); - - parsed.replacement = { - match: parsed.replacement[0].match, - replace: parsed.replacement[0].replace - }; - } - - if (!parsed.replacement.match) throw new Error("No 'replacement.match' field"); - if (!parsed.replacement.replace) throw new Error("No 'replacement.replace' field"); - - setFind(parsed.find instanceof RegExp ? parsed.find.toString() : parsed.find); - setParsedFind(parsed.find); - setMatch(parsed.replacement.match instanceof RegExp ? parsed.replacement.match.source : parsed.replacement.match); - setReplacement(parsed.replacement.replace); - setFullPatchError(""); - } catch (e) { - setFullPatchError((e as Error).message); - } - } - - return <> - <Forms.FormText className={Margins.bottom8}>Paste your full JSON patch here to fill out the fields</Forms.FormText> - <TextArea value={fullPatch} onChange={setFullPatch} onBlur={update} /> - {fullPatchError !== "" && <Forms.FormText style={{ color: "var(--text-danger)" }}>{fullPatchError}</Forms.FormText>} - </>; -} - -function PatchHelper() { - const [find, setFind] = React.useState<string>(""); - const [parsedFind, setParsedFind] = React.useState<string | RegExp>(""); - const [match, setMatch] = React.useState<string>(""); - const [replacement, setReplacement] = React.useState<string | ReplaceFn>(""); - - const [replacementError, setReplacementError] = React.useState<string>(); - - const [module, setModule] = React.useState<[number, Function]>(); - const [findError, setFindError] = React.useState<string>(); - const [matchError, setMatchError] = React.useState<string>(); - - const code = React.useMemo(() => { - return ` -{ - find: ${parsedFind instanceof RegExp ? parsedFind.toString() : JSON.stringify(parsedFind)}, - replacement: { - match: /${match.replace(/(?<!\\)\//g, "\\/")}/, - replace: ${typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement)} - } -} - `.trim(); - }, [parsedFind, match, replacement]); - - function onFindChange(v: string) { - setFind(v); - - try { - let parsedFind = v as string | RegExp; - if (/^\/.+?\/$/.test(v)) parsedFind = new RegExp(v.slice(1, -1)); - - setFindError(void 0); - setParsedFind(parsedFind); - - if (v.length) { - findCandidates({ find: parsedFind, setModule, setError: setFindError }); - } - } catch (e: any) { - setFindError((e as Error).message); - } - } - - function onMatchChange(v: string) { - setMatch(v); - - try { - new RegExp(v); - setMatchError(void 0); - } catch (e: any) { - setMatchError((e as Error).message); - } - } - - return ( - <SettingsTab title="Patch Helper"> - <Forms.FormTitle>full patch</Forms.FormTitle> - <FullPatchInput - setFind={onFindChange} - setParsedFind={setParsedFind} - setMatch={onMatchChange} - setReplacement={setReplacement} - /> - - <Forms.FormTitle className={Margins.top8}>find</Forms.FormTitle> - <TextInput - type="text" - value={find} - onChange={onFindChange} - error={findError} - /> - - <Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle> - <TextInput - type="text" - value={match} - onChange={onMatchChange} - error={matchError} - /> - - <div className={Margins.top8} /> - <ReplacementInput - replacement={replacement} - setReplacement={setReplacement} - replacementError={replacementError} - /> - - <Forms.FormDivider /> - {module && ( - <ReplacementComponent - module={module} - match={match} - replacement={replacement} - setReplacementError={setReplacementError} - /> - )} - - {!!(find && match && replacement) && ( - <> - <Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle> - <CodeBlock lang="js" content={code} /> - <Button onClick={() => copyToClipboard(code)}>Copy to Clipboard</Button> - <Button className={Margins.top8} onClick={() => copyToClipboard("```ts\n" + code + "\n```")}>Copy as Codeblock</Button> - </> - )} - </SettingsTab> - ); -} - -export default IS_DEV ? wrapTab(PatchHelper, "PatchHelper") : null; diff --git a/src/components/VencordSettings/PluginsTab.tsx b/src/components/VencordSettings/PluginsTab.tsx deleted file mode 100644 index 6a320959..00000000 --- a/src/components/VencordSettings/PluginsTab.tsx +++ /dev/null @@ -1,23 +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 PluginSettings from "@components/PluginSettings"; - -import { wrapTab } from "./shared"; - -export default wrapTab(PluginSettings, "Plugins"); diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx deleted file mode 100644 index 6d41ab86..00000000 --- a/src/components/VencordSettings/ThemesTab.tsx +++ /dev/null @@ -1,402 +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 { Settings, useSettings } from "@api/Settings"; -import { classNameFactory } from "@api/Styles"; -import { ErrorCard } from "@components/ErrorCard"; -import { Flex } from "@components/Flex"; -import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; -import { Link } from "@components/Link"; -import { openPluginModal } from "@components/PluginSettings/PluginModal"; -import type { UserThemeHeader } from "@main/themes"; -import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations"; -import { openInviteModal } from "@utils/discord"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; -import { relaunch } from "@utils/native"; -import { useForceUpdater } from "@utils/react"; -import { getStylusWebStoreUrl } from "@utils/web"; -import { findLazy } from "@webpack"; -import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; -import type { ComponentType, Ref, SyntheticEvent } from "react"; - -import Plugins from "~plugins"; - -import { AddonCard } from "./AddonCard"; -import { QuickAction, QuickActionCard } from "./quickActions"; -import { SettingsTab, wrapTab } from "./shared"; - -type FileInput = ComponentType<{ - ref: Ref<HTMLInputElement>; - onChange: (e: SyntheticEvent<HTMLInputElement>) => void; - multiple?: boolean; - filters?: { name?: string; extensions: string[]; }[]; -}>; - -const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef); - -const cl = classNameFactory("vc-settings-theme-"); - -interface ThemeCardProps { - theme: UserThemeHeader; - enabled: boolean; - onChange: (enabled: boolean) => void; - onDelete: () => void; -} - -function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) { - return ( - <AddonCard - name={theme.name} - description={theme.description} - author={theme.author} - enabled={enabled} - setEnabled={onChange} - infoButton={ - IS_WEB && ( - <div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}> - <DeleteIcon /> - </div> - ) - } - footer={ - <Flex flexDirection="row" style={{ gap: "0.2em" }}> - {!!theme.website && <Link href={theme.website}>Website</Link>} - {!!(theme.website && theme.invite) && " • "} - {!!theme.invite && ( - <Link - href={`https://discord.gg/${theme.invite}`} - onClick={async e => { - e.preventDefault(); - theme.invite != null && openInviteModal(theme.invite).catch(() => showToast("Invalid or expired invite")); - }} - > - Discord Server - </Link> - )} - </Flex> - } - /> - ); -} - -enum ThemeTab { - LOCAL, - ONLINE -} - -function ThemesTab() { - const settings = useSettings(["themeLinks", "enabledThemes"]); - - const fileInputRef = useRef<HTMLInputElement>(null); - const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); - const [themeText, setThemeText] = useState(settings.themeLinks.join("\n")); - const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null); - - useEffect(() => { - refreshLocalThemes(); - }, []); - - async function refreshLocalThemes() { - const themes = await VencordNative.themes.getThemesList(); - setUserThemes(themes); - } - - // When a local theme is enabled/disabled, update the settings - function onLocalThemeChange(fileName: string, value: boolean) { - if (value) { - if (settings.enabledThemes.includes(fileName)) return; - settings.enabledThemes = [...settings.enabledThemes, fileName]; - } else { - settings.enabledThemes = settings.enabledThemes.filter(f => f !== fileName); - } - } - - async function onFileUpload(e: SyntheticEvent<HTMLInputElement>) { - e.stopPropagation(); - e.preventDefault(); - if (!e.currentTarget?.files?.length) return; - const { files } = e.currentTarget; - - const uploads = Array.from(files, file => { - const { name } = file; - if (!name.endsWith(".css")) return; - - return new Promise<void>((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - VencordNative.themes.uploadTheme(name, reader.result as string) - .then(resolve) - .catch(reject); - }; - reader.readAsText(file); - }); - }); - - await Promise.all(uploads); - refreshLocalThemes(); - } - - function renderLocalThemes() { - return ( - <> - <Card className="vc-settings-card"> - <Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle> - <div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}> - <Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes"> - BetterDiscord Themes - </Link> - <Link href="https://github.com/search?q=discord+theme">GitHub</Link> - </div> - <Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText> - </Card> - - <Card className="vc-settings-card"> - <Forms.FormTitle tag="h5">External Resources</Forms.FormTitle> - <Forms.FormText>For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.</Forms.FormText> - <Forms.FormText>Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</Forms.FormText> - </Card> - - <Forms.FormSection title="Local Themes"> - <QuickActionCard> - <> - {IS_WEB ? - ( - <QuickAction - text={ - <span style={{ position: "relative" }}> - Upload Theme - <FileInput - ref={fileInputRef} - onChange={onFileUpload} - multiple={true} - filters={[{ extensions: ["css"] }]} - /> - </span> - } - Icon={PlusIcon} - /> - ) : ( - <QuickAction - text="Open Themes Folder" - action={() => VencordNative.themes.openFolder()} - Icon={FolderIcon} - /> - )} - <QuickAction - text="Load missing Themes" - action={refreshLocalThemes} - Icon={RestartIcon} - /> - <QuickAction - text="Edit QuickCSS" - action={() => VencordNative.quickCss.openEditor()} - Icon={PaintbrushIcon} - /> - - {Settings.plugins.ClientTheme.enabled && ( - <QuickAction - text="Edit ClientTheme" - action={() => openPluginModal(Plugins.ClientTheme)} - Icon={PencilIcon} - /> - )} - </> - </QuickActionCard> - - <div className={cl("grid")}> - {userThemes?.map(theme => ( - <ThemeCard - key={theme.fileName} - enabled={settings.enabledThemes.includes(theme.fileName)} - onChange={enabled => onLocalThemeChange(theme.fileName, enabled)} - onDelete={async () => { - onLocalThemeChange(theme.fileName, false); - await VencordNative.themes.deleteTheme(theme.fileName); - refreshLocalThemes(); - }} - theme={theme} - /> - ))} - </div> - </Forms.FormSection> - </> - ); - } - - // When the user leaves the online theme textbox, update the settings - function onBlur() { - settings.themeLinks = [...new Set( - themeText - .trim() - .split(/\n+/) - .map(s => s.trim()) - .filter(Boolean) - )]; - } - - function renderOnlineThemes() { - return ( - <> - <Card className={classes("vc-warning-card", Margins.bottom16)}> - <Forms.FormText> - This section is for advanced users. If you are having difficulties using it, use the - Local Themes tab instead. - </Forms.FormText> - </Card> - <Card className="vc-settings-card"> - <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> - <Forms.FormText>One link per line</Forms.FormText> - <Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText> - <Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText> - </Card> - - <Forms.FormSection title="Online Themes" tag="h5"> - <TextArea - value={themeText} - onChange={setThemeText} - className={"vc-settings-theme-links"} - placeholder="Enter Theme Links..." - spellCheck={false} - onBlur={onBlur} - rows={10} - /> - </Forms.FormSection> - </> - ); - } - - return ( - <SettingsTab title="Themes"> - <TabBar - type="top" - look="brand" - className="vc-settings-tab-bar" - selectedItem={currentTab} - onItemSelect={setCurrentTab} - > - <TabBar.Item - className="vc-settings-tab-bar-item" - id={ThemeTab.LOCAL} - > - Local Themes - </TabBar.Item> - <TabBar.Item - className="vc-settings-tab-bar-item" - id={ThemeTab.ONLINE} - > - Online Themes - </TabBar.Item> - </TabBar> - - <CspErrorCard /> - {currentTab === ThemeTab.LOCAL && renderLocalThemes()} - {currentTab === ThemeTab.ONLINE && renderOnlineThemes()} - </SettingsTab> - ); -} - -export function CspErrorCard() { - if (IS_WEB) return null; - - const errors = useCspErrors(); - const forceUpdate = useForceUpdater(); - - if (!errors.length) return null; - - const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/"); - - const allowUrl = async (url: string) => { - const { origin: baseUrl, host } = new URL(url); - - const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes"); - if (result !== "ok") return; - - CspBlockedUrls.forEach(url => { - if (new URL(url).host === host) { - CspBlockedUrls.delete(url); - } - }); - - forceUpdate(); - - Alerts.show({ - title: "Restart Required", - body: "A restart is required to apply this change", - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); - }; - - const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); - - return ( - <ErrorCard className="vc-settings-card"> - <Forms.FormTitle tag="h5">Blocked Resources</Forms.FormTitle> - <Forms.FormText>Some images, styles, or fonts were blocked because they come from disallowed domains.</Forms.FormText> - <Forms.FormText>It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them.</Forms.FormText> - <Forms.FormText> - After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change. - </Forms.FormText> - - <Forms.FormTitle tag="h5" className={classes(Margins.top16, Margins.bottom8)}>Blocked URLs</Forms.FormTitle> - <div className="vc-settings-csp-list"> - {errors.map((url, i) => ( - <div key={url}> - {i !== 0 && <Forms.FormDivider className={Margins.bottom8} />} - <div className="vc-settings-csp-row"> - <Link href={url}>{url}</Link> - <Button color={Button.Colors.PRIMARY} onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}> - Allow - </Button> - </div> - </div> - ))} - </div> - - {hasImgurHtmlDomain && ( - <> - <Forms.FormDivider className={classes(Margins.top8, Margins.bottom16)} /> - <Forms.FormText> - Imgur links should be direct links in the form of <code>https://i.imgur.com/...</code> - </Forms.FormText> - <Forms.FormText>To obtain a direct link, right-click the image and select "Copy image address".</Forms.FormText> - </> - )} - </ErrorCard> - ); -} - -function UserscriptThemesTab() { - return ( - <SettingsTab title="Themes"> - <Card className="vc-settings-card"> - <Forms.FormTitle tag="h5">Themes are not supported on the Userscript!</Forms.FormTitle> - - <Forms.FormText> - You can instead install themes with the <Link href={getStylusWebStoreUrl()}>Stylus extension</Link>! - </Forms.FormText> - </Card> - </SettingsTab> - ); -} - -export default IS_USERSCRIPT - ? wrapTab(UserscriptThemesTab, "Themes") - : wrapTab(ThemesTab, "Themes"); diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx deleted file mode 100644 index f5e3d4b2..00000000 --- a/src/components/VencordSettings/UpdaterTab.tsx +++ /dev/null @@ -1,268 +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 { useSettings } from "@api/Settings"; -import { ErrorCard } from "@components/ErrorCard"; -import { Flex } from "@components/Flex"; -import { Link } from "@components/Link"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { relaunch } from "@utils/native"; -import { useAwaiter } from "@utils/react"; -import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater"; -import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common"; - -import gitHash from "~git-hash"; - -import { handleSettingsTabError, SettingsTab, wrapTab } from "./shared"; - -function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) { - return async () => { - dispatcher(true); - try { - await action(); - } catch (e: any) { - UpdateLogger.error("Failed to update", e); - - let err: string; - if (!e) { - err = "An unknown error occurred (error is undefined).\nPlease try again."; - } else if (e.code && e.cmd) { - const { code, path, cmd, stderr } = e; - - if (code === "ENOENT") - err = `Command \`${path}\` not found.\nPlease install it and try again`; - else { - err = `An error occurred while running \`${cmd}\`:\n`; - err += stderr || `Code \`${code}\`. See the console for more info`; - } - - } else { - err = "An unknown error occurred. See the console for more info."; - } - - Alerts.show({ - title: "Oops!", - body: ( - <ErrorCard> - {err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)} - </ErrorCard> - ) - }); - } - finally { - dispatcher(false); - } - }; -} - -interface CommonProps { - repo: string; - repoPending: boolean; -} - -function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) { - return <Link href={`${repo}/commit/${hash}`} disabled={disabled}> - {hash} - </Link>; -} - -function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { - return ( - <Card style={{ padding: "0 0.5em" }}> - {updates.map(({ hash, author, message }) => ( - <div key={hash} style={{ - marginTop: "0.5em", - marginBottom: "0.5em" - }}> - <code><HashLink {...{ repo, hash }} disabled={repoPending} /></code> - <span style={{ - marginLeft: "0.5em", - color: "var(--text-default)" - }}>{message} - {author}</span> - </div> - ))} - </Card> - ); -} - -function Updatable(props: CommonProps) { - const [updates, setUpdates] = React.useState(changes); - const [isChecking, setIsChecking] = React.useState(false); - const [isUpdating, setIsUpdating] = React.useState(false); - - const isOutdated = (updates?.length ?? 0) > 0; - - return ( - <> - {!updates && updateError ? ( - <> - <Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText> - <ErrorCard style={{ padding: "1em" }}> - <p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p> - </ErrorCard> - </> - ) : ( - <Forms.FormText className={Margins.bottom8}> - {isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"} - </Forms.FormText> - )} - - {isOutdated && <Changes updates={updates} {...props} />} - - <Flex className={classes(Margins.bottom8, Margins.top8)}> - {isOutdated && <Button - size={Button.Sizes.SMALL} - disabled={isUpdating || isChecking} - onClick={withDispatcher(setIsUpdating, async () => { - if (await update()) { - setUpdates([]); - await new Promise<void>(r => { - Alerts.show({ - title: "Update Success!", - body: "Successfully updated. Restart now to apply the changes?", - confirmText: "Restart", - cancelText: "Not now!", - onConfirm() { - relaunch(); - r(); - }, - onCancel: r - }); - }); - } - })} - > - Update Now - </Button>} - <Button - size={Button.Sizes.SMALL} - disabled={isUpdating || isChecking} - onClick={withDispatcher(setIsChecking, async () => { - const outdated = await checkForUpdates(); - if (outdated) { - setUpdates(changes); - } else { - setUpdates([]); - Toasts.show({ - message: "No updates found!", - id: Toasts.genId(), - type: Toasts.Type.MESSAGE, - options: { - position: Toasts.Position.BOTTOM - } - }); - } - })} - > - Check for Updates - </Button> - </Flex> - </> - ); -} - -function Newer(props: CommonProps) { - return ( - <> - <Forms.FormText className={Margins.bottom8}> - Your local copy has more recent commits. Please stash or reset them. - </Forms.FormText> - <Changes {...props} updates={changes} /> - </> - ); -} - -function Updater() { - const settings = useSettings(["autoUpdate", "autoUpdateNotification"]); - - const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." }); - - React.useEffect(() => { - if (err) - UpdateLogger.error("Failed to retrieve repo", err); - }, [err]); - - const commonProps: CommonProps = { - repo, - repoPending - }; - - return ( - <SettingsTab title="Vencord Updater"> - <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> - <Switch - value={settings.autoUpdate} - onChange={(v: boolean) => settings.autoUpdate = v} - note="Automatically update Vencord without confirmation prompt" - > - Automatically update - </Switch> - <Switch - value={settings.autoUpdateNotification} - onChange={(v: boolean) => settings.autoUpdateNotification = v} - note="Shows a notification when Vencord automatically updates" - disabled={!settings.autoUpdate} - > - Get notified when an automatic update completes - </Switch> - - <Forms.FormTitle tag="h5">Repo</Forms.FormTitle> - - <Forms.FormText> - {repoPending - ? repo - : err - ? "Failed to retrieve - check console" - : ( - <Link href={repo}> - {repo.split("/").slice(-2).join("/")} - </Link> - ) - } - {" "}(<HashLink hash={gitHash} repo={repo} disabled={repoPending} />) - </Forms.FormText> - - <Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> - - <Forms.FormTitle tag="h5">Updates</Forms.FormTitle> - - {isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />} - </SettingsTab> - ); -} - -export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater"); - -export const openUpdaterModal = IS_UPDATER_DISABLED ? null : function () { - const UpdaterTab = wrapTab(Updater, "Updater"); - - try { - openModal(wrapTab((modalProps: ModalProps) => ( - <ModalRoot {...modalProps} size={ModalSize.MEDIUM}> - <ModalContent className="vc-updater-modal"> - <ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" /> - <UpdaterTab /> - </ModalContent> - </ModalRoot> - ), "UpdaterModal")); - } catch { - handleSettingsTabError(); - } -}; diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx deleted file mode 100644 index 047ba17d..00000000 --- a/src/components/VencordSettings/VencordTab.tsx +++ /dev/null @@ -1,301 +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 { openNotificationLogModal } from "@api/Notifications/notificationLog"; -import { useSettings } from "@api/Settings"; -import { classNameFactory } from "@api/Styles"; -import DonateButton from "@components/DonateButton"; -import { openContributorModal } from "@components/PluginSettings/ContributorModal"; -import { openPluginModal } from "@components/PluginSettings/PluginModal"; -import { gitRemote } from "@shared/vencordUserAgent"; -import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants"; -import { Margins } from "@utils/margins"; -import { identity, isPluginDev } from "@utils/misc"; -import { relaunch } from "@utils/native"; -import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common"; - -import BadgeAPI from "../../plugins/_api/badges"; -import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from ".."; -import { openNotificationSettingsModal } from "./NotificationSettings"; -import { QuickAction, QuickActionCard } from "./quickActions"; -import { SettingsTab, wrapTab } from "./shared"; -import { SpecialCard } from "./SpecialCard"; - -const cl = classNameFactory("vc-settings-"); - -const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; -const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png"; - -const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png"; -const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png"; - -const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048"; -const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048"; - -type KeysOfType<Object, Type> = { - [K in keyof Object]: Object[K] extends Type ? K : never; -}[keyof Object]; - -function VencordSettings() { - const settings = useSettings(); - - const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []); - - const isWindows = navigator.platform.toLowerCase().startsWith("win"); - const isMac = navigator.platform.toLowerCase().startsWith("mac"); - const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac; - - const user = UserStore.getCurrentUser(); - - const Switches: Array<false | { - key: KeysOfType<typeof settings, boolean>; - title: string; - note: string; - }> = - [ - { - key: "useQuickCss", - title: "Enable Custom CSS", - note: "Loads your Custom CSS" - }, - !IS_WEB && { - key: "enableReactDevtools", - title: "Enable React Developer Tools", - note: "Requires a full restart" - }, - !IS_WEB && (!IS_DISCORD_DESKTOP || !isWindows ? { - key: "frameless", - title: "Disable the window frame", - note: "Requires a full restart" - } : { - key: "winNativeTitleBar", - title: "Use Windows' native title bar instead of Discord's custom one", - note: "Requires a full restart" - }), - !IS_WEB && { - key: "transparent", - title: "Enable window transparency.", - note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart" - }, - !IS_WEB && isWindows && { - key: "winCtrlQ", - title: "Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)", - note: "Requires a full restart" - }, - IS_DISCORD_DESKTOP && { - key: "disableMinSize", - title: "Disable minimum window size", - note: "Requires a full restart" - }, - ]; - - return ( - <SettingsTab title="Vencord Settings"> - {isDonor(user?.id) - ? ( - <SpecialCard - title="Donations" - subtitle="Thank you for donating!" - description="You can manage your perks at any time by messaging @vending.machine." - cardImage={VENNIE_DONATOR_IMAGE} - backgroundImage={DONOR_BACKGROUND_IMAGE} - backgroundColor="#ED87A9" - > - <DonateButtonComponent /> - </SpecialCard> - ) - : ( - <SpecialCard - title="Support the Project" - description="Please consider supporting the development of Vencord by donating!" - cardImage={donateImage} - backgroundImage={DONOR_BACKGROUND_IMAGE} - backgroundColor="#c3a3ce" - > - <DonateButtonComponent /> - </SpecialCard> - ) - } - {isPluginDev(user?.id) && ( - <SpecialCard - title="Contributions" - subtitle="Thank you for contributing!" - description="Since you've contributed to Vencord you now have a cool new badge!" - cardImage={COZY_CONTRIB_IMAGE} - backgroundImage={CONTRIB_BACKGROUND_IMAGE} - backgroundColor="#EDCC87" - buttonTitle="See what you've contributed to" - buttonOnClick={() => openContributorModal(user)} - /> - )} - - <Forms.FormSection title="Quick Actions"> - <QuickActionCard> - <QuickAction - Icon={LogIcon} - text="Notification Log" - action={openNotificationLogModal} - /> - <QuickAction - Icon={PaintbrushIcon} - text="Edit QuickCSS" - action={() => VencordNative.quickCss.openEditor()} - /> - {!IS_WEB && ( - <QuickAction - Icon={RestartIcon} - text="Relaunch Discord" - action={relaunch} - /> - )} - {!IS_WEB && ( - <QuickAction - Icon={FolderIcon} - text="Open Settings Folder" - action={() => VencordNative.settings.openFolder()} - /> - )} - <QuickAction - Icon={GithubIcon} - text="View Source Code" - action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)} - /> - </QuickActionCard> - </Forms.FormSection> - - <Forms.FormDivider /> - - <Forms.FormSection className={Margins.top16} title="Settings" tag="h5"> - <Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}> - Hint: You can change the position of this settings section in the - {" "}<Button - look={Button.Looks.BLANK} - style={{ color: "var(--text-link)", display: "inline-block" }} - onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)} - > - settings of the Settings plugin - </Button>! - </Forms.FormText> - - {Switches.map(s => s && ( - <Switch - key={s.key} - value={settings[s.key]} - onChange={v => settings[s.key] = v} - note={s.note} - > - {s.title} - </Switch> - ))} - </Forms.FormSection> - - - {needsVibrancySettings && <> - <Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle> - <Select - className={Margins.bottom20} - placeholder="Window vibrancy style" - options={[ - // Sorted from most opaque to most transparent - { - label: "No vibrancy", value: undefined - }, - { - label: "Under Page (window tinting)", - value: "under-page" - }, - { - label: "Content", - value: "content" - }, - { - label: "Window", - value: "window" - }, - { - label: "Selection", - value: "selection" - }, - { - label: "Titlebar", - value: "titlebar" - }, - { - label: "Header", - value: "header" - }, - { - label: "Sidebar", - value: "sidebar" - }, - { - label: "Tooltip", - value: "tooltip" - }, - { - label: "Menu", - value: "menu" - }, - { - label: "Popover", - value: "popover" - }, - { - label: "Fullscreen UI (transparent but slightly muted)", - value: "fullscreen-ui" - }, - { - label: "HUD (Most transparent)", - value: "hud" - }, - ]} - select={v => settings.macosVibrancyStyle = v} - isSelected={v => settings.macosVibrancyStyle === v} - serialize={identity} /> - </>} - - <Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5"> - <Flex> - <Button onClick={openNotificationSettingsModal}> - Notification Settings - </Button> - <Button onClick={openNotificationLogModal}> - View Notification Log - </Button> - </Flex> - </Forms.FormSection> - </SettingsTab> - ); -} - -function DonateButtonComponent() { - return ( - <DonateButton - look={Button.Looks.FILLED} - color={Button.Colors.WHITE} - style={{ marginTop: "1em" }} - /> - ); -} - -function isDonor(userId: string): boolean { - const donorBadges = BadgeAPI.getDonorBadges(userId); - return GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges; -} - -export default wrapTab(VencordSettings, "Vencord Settings"); diff --git a/src/components/index.ts b/src/components/index.ts index 2782cb54..9265acfb 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -export * from "./Badge"; export * from "./CheckedTextInput"; export * from "./CodeBlock"; -export * from "./DonateButton"; export { default as ErrorBoundary } from "./ErrorBoundary"; export * from "./ErrorCard"; export * from "./Flex"; +export * from "./Grid"; export * from "./Heart"; export * from "./Icons"; export * from "./Link"; -export * from "./Switch"; +export * from "./settings"; + diff --git a/src/components/VencordSettings/addonCard.css b/src/components/settings/AddonCard.css similarity index 100% rename from src/components/VencordSettings/addonCard.css rename to src/components/settings/AddonCard.css diff --git a/src/components/VencordSettings/AddonCard.tsx b/src/components/settings/AddonCard.tsx similarity index 93% rename from src/components/VencordSettings/AddonCard.tsx rename to src/components/settings/AddonCard.tsx index 1161a641..3701d7cf 100644 --- a/src/components/VencordSettings/AddonCard.tsx +++ b/src/components/settings/AddonCard.tsx @@ -16,11 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import "./addonCard.css"; +import "./AddonCard.css"; import { classNameFactory } from "@api/Styles"; -import { Badge } from "@components/Badge"; -import { Switch } from "@components/Switch"; +import { AddonBadge } from "@components/settings/PluginBadge"; +import { Switch } from "@components/settings/Switch"; import { Text, useRef } from "@webpack/common"; import type { MouseEventHandler, ReactNode } from "react"; @@ -44,6 +44,7 @@ interface Props { export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) { const titleRef = useRef<HTMLDivElement>(null); const titleContainerRef = useRef<HTMLDivElement>(null); + return ( <div className={cl("card", { "card-disabled": disabled })} @@ -67,8 +68,10 @@ export function AddonCard({ disabled, isNew, name, infoButton, footer, author, e > {name} </div> - </div>{isNew && <Badge text="NEW" color="#ED4245" />} + </div> + {isNew && <AddonBadge text="NEW" color="#ED4245" />} </Text> + {!!author && ( <Text variant="text-md/normal" className={cl("author")}> {author} diff --git a/src/components/DonateButton.tsx b/src/components/settings/DonateButton.tsx similarity index 96% rename from src/components/DonateButton.tsx rename to src/components/settings/DonateButton.tsx index 92af9831..bb9b3e4d 100644 --- a/src/components/DonateButton.tsx +++ b/src/components/settings/DonateButton.tsx @@ -16,11 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { Heart } from "@components/Heart"; import { ButtonProps } from "@vencord/discord-types"; import { Button } from "@webpack/common"; -import { Heart } from "./Heart"; - export default function DonateButton({ look = Button.Looks.LINK, color = Button.Colors.TRANSPARENT, diff --git a/src/components/Badge.tsx b/src/components/settings/PluginBadge.tsx similarity index 90% rename from src/components/Badge.tsx rename to src/components/settings/PluginBadge.tsx index 61b36e97..5208fdea 100644 --- a/src/components/Badge.tsx +++ b/src/components/settings/PluginBadge.tsx @@ -16,9 +16,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -export function Badge({ text, color }) { +export function AddonBadge({ text, color }) { return ( - <div className="vc-plugins-badge" style={{ + <div className="vc-addon-badge" style={{ backgroundColor: color, justifySelf: "flex-end", marginLeft: "auto" diff --git a/src/components/VencordSettings/quickActions.css b/src/components/settings/QuickAction.css similarity index 100% rename from src/components/VencordSettings/quickActions.css rename to src/components/settings/QuickAction.css diff --git a/src/components/VencordSettings/quickActions.tsx b/src/components/settings/QuickAction.tsx similarity index 97% rename from src/components/VencordSettings/quickActions.tsx rename to src/components/settings/QuickAction.tsx index 6cc57180..d1a8f52c 100644 --- a/src/components/VencordSettings/quickActions.tsx +++ b/src/components/settings/QuickAction.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "./quickActions.css"; +import "./QuickAction.css"; import { classNameFactory } from "@api/Styles"; import { Card } from "@webpack/common"; diff --git a/src/components/VencordSettings/specialCard.css b/src/components/settings/SpecialCard.css similarity index 100% rename from src/components/VencordSettings/specialCard.css rename to src/components/settings/SpecialCard.css diff --git a/src/components/VencordSettings/SpecialCard.tsx b/src/components/settings/SpecialCard.tsx similarity index 99% rename from src/components/VencordSettings/SpecialCard.tsx rename to src/components/settings/SpecialCard.tsx index 6fd952f4..1c0a12c2 100644 --- a/src/components/VencordSettings/SpecialCard.tsx +++ b/src/components/settings/SpecialCard.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import "./specialCard.css"; +import "./SpecialCard.css"; import { classNameFactory } from "@api/Styles"; import { Card, Clickable, Forms, React } from "@webpack/common"; diff --git a/src/components/Switch.css b/src/components/settings/Switch.css similarity index 100% rename from src/components/Switch.css rename to src/components/settings/Switch.css diff --git a/src/components/Switch.tsx b/src/components/settings/Switch.tsx similarity index 100% rename from src/components/Switch.tsx rename to src/components/settings/Switch.tsx diff --git a/src/components/settings/index.ts b/src/components/settings/index.ts new file mode 100644 index 00000000..c793ef38 --- /dev/null +++ b/src/components/settings/index.ts @@ -0,0 +1,13 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export * from "./AddonCard"; +export * from "./DonateButton"; +export * from "./PluginBadge"; +export * from "./QuickAction"; +export * from "./SpecialCard"; +export * from "./Switch"; +export * from "./tabs"; diff --git a/src/components/VencordSettings/shared.tsx b/src/components/settings/tabs/BaseTab.tsx similarity index 96% rename from src/components/VencordSettings/shared.tsx rename to src/components/settings/tabs/BaseTab.tsx index 1c5f37d8..9ef95463 100644 --- a/src/components/VencordSettings/shared.tsx +++ b/src/components/settings/tabs/BaseTab.tsx @@ -16,9 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import "./settingsStyles.css"; -import "./themesStyles.css"; - import ErrorBoundary from "@components/ErrorBoundary"; import { handleComponentFailed } from "@components/handleComponentFailed"; import { Margins } from "@utils/margins"; diff --git a/src/components/settings/tabs/index.ts b/src/components/settings/tabs/index.ts new file mode 100644 index 00000000..9b4296c3 --- /dev/null +++ b/src/components/settings/tabs/index.ts @@ -0,0 +1,18 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +export * from "./BaseTab"; +export { default as PatchHelperTab } from "./patchHelper"; +export { default as PluginsTab } from "./plugins"; +export { openContributorModal } from "./plugins/ContributorModal"; +export { openPluginModal } from "./plugins/PluginModal"; +export { default as BackupAndRestoreTab } from "./sync/BackupAndRestoreTab"; +export { default as CloudTab } from "./sync/CloudTab"; +export { default as ThemesTab } from "./themes"; +export { openUpdaterModal, default as UpdaterTab } from "./updater"; +export { default as VencordTab } from "./vencord"; diff --git a/src/components/settings/tabs/patchHelper/FullPatchInput.tsx b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx new file mode 100644 index 00000000..f4db51a1 --- /dev/null +++ b/src/components/settings/tabs/patchHelper/FullPatchInput.tsx @@ -0,0 +1,83 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Margins } from "@utils/margins"; +import { Patch, ReplaceFn } from "@utils/types"; +import { Forms, TextArea, useEffect, useRef, useState } from "@webpack/common"; + +export interface FullPatchInputProps { + setFind(v: string): void; + setParsedFind(v: string | RegExp): void; + setMatch(v: string): void; + setReplacement(v: string | ReplaceFn): void; +} + +export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) { + const [patch, setPatch] = useState<string>(""); + const [error, setError] = useState<string>(""); + + const textAreaRef = useRef<HTMLTextAreaElement>(null); + + function update() { + if (patch === "") { + setError(""); + + setFind(""); + setParsedFind(""); + setMatch(""); + setReplacement(""); + return; + } + + try { + let { find, replacement } = (0, eval)(`([${patch}][0])`) as Patch; + + if (!find) throw new Error("No 'find' field"); + if (!replacement) throw new Error("No 'replacement' field"); + + if (replacement instanceof Array) { + if (replacement.length === 0) throw new Error("Invalid replacement"); + + // Only test the first replacement + replacement = replacement[0]; + } + + if (!replacement.match) throw new Error("No 'replacement.match' field"); + if (!replacement.replace) throw new Error("No 'replacement.replace' field"); + + setFind(find instanceof RegExp ? `/${find.source}/` : find); + setParsedFind(find); + setMatch(replacement.match instanceof RegExp ? replacement.match.source : replacement.match); + setReplacement(replacement.replace); + setError(""); + } catch (e) { + setError((e as Error).message); + } + } + + useEffect(() => { + const { current: textArea } = textAreaRef; + if (textArea) { + textArea.style.height = "auto"; + textArea.style.height = `${textArea.scrollHeight}px`; + } + }, [patch]); + + return ( + <> + <Forms.FormText className={Margins.bottom8}> + Paste your full JSON patch here to fill out the fields + </Forms.FormText> + <TextArea + inputRef={textAreaRef} + value={patch} + onChange={setPatch} + onBlur={update} + /> + {error !== "" && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} + </> + ); +} diff --git a/src/components/settings/tabs/patchHelper/PatchPreview.tsx b/src/components/settings/tabs/patchHelper/PatchPreview.tsx new file mode 100644 index 00000000..f00a2ecf --- /dev/null +++ b/src/components/settings/tabs/patchHelper/PatchPreview.tsx @@ -0,0 +1,149 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Margins } from "@utils/margins"; +import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; +import { makeCodeblock } from "@utils/text"; +import { ReplaceFn } from "@utils/types"; +import { Button, Forms, Parser, useMemo, useState } from "@webpack/common"; +import type { Change } from "diff"; + +// Do not include diff in non dev builds (side effects import) +if (IS_DEV) { + var differ = require("diff") as typeof import("diff"); +} + +interface PatchPreviewProps { + module: [id: number, factory: Function]; + match: string; + replacement: string | ReplaceFn; + setReplacementError(error: any): void; +} + +function makeDiff(original: string, patched: string, match: RegExpMatchArray | null) { + if (!match || original === patched) return null; + + const changeSize = patched.length - original.length; + + // Use 200 surrounding characters of context + const start = Math.max(0, match.index! - 200); + const end = Math.min(original.length, match.index! + match[0].length + 200); + // (changeSize may be negative) + const endPatched = end + changeSize; + + const context = original.slice(start, end); + const patchedContext = patched.slice(start, endPatched); + + return differ.diffWordsWithSpace(context, patchedContext); +} + +function Match({ matchResult }: { matchResult: RegExpMatchArray | null; }) { + if (!matchResult) + return null; + + const fullMatch = matchResult[0] + ? makeCodeblock(matchResult[0], "js") + : ""; + const groups = matchResult.length > 1 + ? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i + 1}: ${g}`).join("\n"), "yml") + : ""; + + return ( + <> + <Forms.FormTitle>Match</Forms.FormTitle> + <div style={{ userSelect: "text" }}>{Parser.parse(fullMatch)}</div> + <div style={{ userSelect: "text" }}>{Parser.parse(groups)}</div> + </> + ); +} + +function Diff({ diff }: { diff: Change[] | null; }) { + if (!diff?.length) + return null; + + const diffLines = diff.map((p, idx) => { + const color = p.added + ? "lime" + : p.removed + ? "red" + : "grey"; + + return ( + <div + key={idx} + style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }} + > + {p.value} + </div> + ); + }); + + return ( + <> + <Forms.FormTitle>Diff</Forms.FormTitle> + {diffLines} + </> + ); +} + +export function PatchPreview({ module, match, replacement, setReplacementError }: PatchPreviewProps) { + const [id, fact] = module; + const [compileResult, setCompileResult] = useState<[boolean, string]>(); + + const [patchedCode, matchResult, diff] = useMemo<[string, RegExpMatchArray | null, Change[] | null]>(() => { + const src: string = fact.toString().replaceAll("\n", ""); + + try { + new RegExp(match); + } catch (e) { + return ["", null, null]; + } + + const canonicalMatch = canonicalizeMatch(new RegExp(match)); + try { + const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]'); + var patched = src.replace(canonicalMatch, canonicalReplace as string); + setReplacementError(void 0); + } catch (e) { + setReplacementError((e as Error).message); + return ["", null, null]; + } + + const m = src.match(canonicalMatch); + return [patched, m, makeDiff(src, patched, m)]; + }, [id, match, replacement]); + + return ( + <> + <Forms.FormTitle>Module {id}</Forms.FormTitle> + + <Match matchResult={matchResult} /> + <Diff diff={diff} /> + + {!!diff?.length && ( + <Button + className={Margins.top20} + onClick={() => { + try { + Function(patchedCode.replace(/^(?=function\()/, "0,")); + setCompileResult([true, "Compiled successfully"]); + } catch (err) { + setCompileResult([false, (err as Error).message]); + } + }} + > + Compile + </Button> + )} + + {compileResult && ( + <Forms.FormText style={{ color: compileResult[0] ? "var(--status-positive)" : "var(--text-danger)" }}> + {compileResult[1]} + </Forms.FormText> + )} + </> + ); +} diff --git a/src/components/settings/tabs/patchHelper/ReplacementInput.tsx b/src/components/settings/tabs/patchHelper/ReplacementInput.tsx new file mode 100644 index 00000000..c028c477 --- /dev/null +++ b/src/components/settings/tabs/patchHelper/ReplacementInput.tsx @@ -0,0 +1,83 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Margins } from "@utils/margins"; +import { Forms, Parser, Switch, TextInput, useEffect, useState } from "@webpack/common"; + +const RegexGuide = { + "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", + "$$": "Insert a $", + "$&": "Insert the entire match", + "$`\u200b": "Insert the substring before the match", + "$'": "Insert the substring after the match", + "$n": "Insert the nth capturing group ($1, $2...)", + "$self": "Insert the plugin instance", +} as const; + +export function ReplacementInput({ replacement, setReplacement, replacementError }) { + const [isFunc, setIsFunc] = useState(false); + const [error, setError] = useState<string>(); + + function onChange(v: string) { + setError(void 0); + + if (isFunc) { + try { + const func = (0, eval)(v); + if (typeof func === "function") + setReplacement(() => func); + + else + setError("Replacement must be a function"); + } catch (e) { + setReplacement(v); + setError((e as Error).message); + } + } else { + setReplacement(v); + } + } + + useEffect(() => { + if (isFunc) + onChange(replacement); + else + setError(void 0); + }, [isFunc]); + + return ( + <> + {/* FormTitle adds a class if className is not set, so we set it to an empty string to prevent that */} + <Forms.FormTitle className="">Replacement</Forms.FormTitle> + <TextInput + value={replacement?.toString()} + onChange={onChange} + error={error ?? replacementError} + /> + {!isFunc && ( + <div> + <Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle> + + {Object.entries(RegexGuide).map(([placeholder, desc]) => ( + <Forms.FormText key={placeholder}> + {Parser.parse("`" + placeholder + "`")}: {desc} + </Forms.FormText> + ))} + </div> + )} + + <Switch + className={Margins.top16} + value={isFunc} + onChange={setIsFunc} + note="'replacement' will be evaled if this is toggled" + hideBorder + > + Treat as Function + </Switch> + </> + ); +} diff --git a/src/components/settings/tabs/patchHelper/index.tsx b/src/components/settings/tabs/patchHelper/index.tsx new file mode 100644 index 00000000..596e1f62 --- /dev/null +++ b/src/components/settings/tabs/patchHelper/index.tsx @@ -0,0 +1,165 @@ +/* + * 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 { CodeBlock } from "@components/CodeBlock"; +import { Flex } from "@components/Flex"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; +import { debounce } from "@shared/debounce"; +import { Margins } from "@utils/margins"; +import { copyWithToast } from "@utils/misc"; +import { stripIndent } from "@utils/text"; +import { ReplaceFn } from "@utils/types"; +import { search } from "@webpack"; +import { Button, Forms, React, TextInput, useMemo, useState } from "@webpack/common"; + +import { FullPatchInput } from "./FullPatchInput"; +import { PatchPreview } from "./PatchPreview"; +import { ReplacementInput } from "./ReplacementInput"; + +const findCandidates = debounce(function ({ find, setModule, setError }) { + const candidates = search(find); + const keys = Object.keys(candidates); + const len = keys.length; + + if (len === 0) + setError("No match. Perhaps that module is lazy loaded?"); + else if (len !== 1) + setError("Multiple matches. Please refine your filter"); + else + setModule([keys[0], candidates[keys[0]]]); +}); + +function PatchHelper() { + const [find, setFind] = useState(""); + const [match, setMatch] = useState(""); + const [replacement, setReplacement] = useState<string | ReplaceFn>(""); + + const [parsedFind, setParsedFind] = useState<string | RegExp>(""); + + const [findError, setFindError] = useState<string>(); + const [matchError, setMatchError] = useState<string>(); + const [replacementError, setReplacementError] = useState<string>(); + + const [module, setModule] = useState<[number, Function]>(); + + const code = useMemo(() => { + const find = parsedFind instanceof RegExp ? parsedFind.toString() : JSON.stringify(parsedFind); + const replace = typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement); + + return stripIndent` + { + find: ${find}, + replacement: { + match: /${match.replace(/(?<!\\)\//g, "\\/")}/, + replace: ${replace} + } + } + `; + }, [parsedFind, match, replacement]); + + function onFindChange(v: string) { + setFind(v); + + try { + let parsedFind = v as string | RegExp; + if (/^\/.+?\/$/.test(v)) parsedFind = new RegExp(v.slice(1, -1)); + + setFindError(void 0); + setParsedFind(parsedFind); + + if (v.length) { + findCandidates({ find: parsedFind, setModule, setError: setFindError }); + } + } catch (e: any) { + setFindError((e as Error).message); + } + } + + function onMatchChange(v: string) { + setMatch(v); + + try { + new RegExp(v); + setMatchError(void 0); + } catch (e: any) { + setMatchError((e as Error).message); + } + } + + return ( + <SettingsTab title="Patch Helper"> + <Forms.FormTitle>full patch</Forms.FormTitle> + <FullPatchInput + setFind={onFindChange} + setParsedFind={setParsedFind} + setMatch={onMatchChange} + setReplacement={setReplacement} + /> + + <Forms.FormTitle className={Margins.top8}>find</Forms.FormTitle> + <TextInput + type="text" + value={find} + onChange={onFindChange} + error={findError} + /> + + <Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle> + <TextInput + type="text" + value={match} + onChange={onMatchChange} + error={matchError} + /> + + <div className={Margins.top8} /> + <ReplacementInput + replacement={replacement} + setReplacement={setReplacement} + replacementError={replacementError} + /> + + <Forms.FormDivider /> + {module && ( + <PatchPreview + module={module} + match={match} + replacement={replacement} + setReplacementError={setReplacementError} + /> + )} + + {!!(find && match && replacement) && ( + <> + <Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle> + <CodeBlock lang="js" content={code} /> + <Flex className={Margins.top16}> + <Button onClick={() => copyWithToast(code)}> + Copy to Clipboard + </Button> + <Button onClick={() => copyWithToast("```ts\n" + code + "\n```")}> + Copy as Codeblock + </Button> + </Flex> + </> + )} + </SettingsTab> + ); +} + +export default IS_DEV ? wrapTab(PatchHelper, "PatchHelper") : null; diff --git a/src/components/PluginSettings/contributorModal.css b/src/components/settings/tabs/plugins/ContributorModal.css similarity index 100% rename from src/components/PluginSettings/contributorModal.css rename to src/components/settings/tabs/plugins/ContributorModal.css diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/settings/tabs/plugins/ContributorModal.tsx similarity index 98% rename from src/components/PluginSettings/ContributorModal.tsx rename to src/components/settings/tabs/plugins/ContributorModal.tsx index 378ee34c..4794a084 100644 --- a/src/components/PluginSettings/ContributorModal.tsx +++ b/src/components/settings/tabs/plugins/ContributorModal.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import "./contributorModal.css"; +import "./ContributorModal.css"; import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; @@ -19,8 +19,8 @@ import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromSto import Plugins from "~plugins"; -import { PluginCard } from "."; import { GithubButton, WebsiteButton } from "./LinkIconButton"; +import { PluginCard } from "./PluginCard"; const cl = classNameFactory("vc-author-modal-"); diff --git a/src/components/PluginSettings/LinkIconButton.css b/src/components/settings/tabs/plugins/LinkIconButton.css similarity index 100% rename from src/components/PluginSettings/LinkIconButton.css rename to src/components/settings/tabs/plugins/LinkIconButton.css diff --git a/src/components/PluginSettings/LinkIconButton.tsx b/src/components/settings/tabs/plugins/LinkIconButton.tsx similarity index 95% rename from src/components/PluginSettings/LinkIconButton.tsx rename to src/components/settings/tabs/plugins/LinkIconButton.tsx index 4dae0e1e..9f9ecfeb 100644 --- a/src/components/PluginSettings/LinkIconButton.tsx +++ b/src/components/settings/tabs/plugins/LinkIconButton.tsx @@ -6,11 +6,10 @@ import "./LinkIconButton.css"; +import { GithubIcon, WebsiteIcon } from "@components/Icons"; import { getTheme, Theme } from "@utils/discord"; import { MaskedLink, Tooltip } from "@webpack/common"; -import { GithubIcon, WebsiteIcon } from ".."; - export function GithubLinkIcon() { const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF"; return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />; diff --git a/src/components/settings/tabs/plugins/PluginCard.tsx b/src/components/settings/tabs/plugins/PluginCard.tsx new file mode 100644 index 00000000..b9da5edc --- /dev/null +++ b/src/components/settings/tabs/plugins/PluginCard.tsx @@ -0,0 +1,110 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { showNotice } from "@api/Notices"; +import { CogWheel, InfoIcon } from "@components/Icons"; +import { AddonCard } from "@components/settings/AddonCard"; +import { proxyLazy } from "@utils/lazy"; +import { classes, isObjectEmpty } from "@utils/misc"; +import { Plugin } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { React, showToast, Toasts } from "@webpack/common"; +import { Settings } from "Vencord"; + +import { cl, logger } from "."; +import { openPluginModal } from "./PluginModal"; + +// Avoid circular dependency +const { startDependenciesRecursive, startPlugin, stopPlugin, isPluginEnabled } = proxyLazy(() => require("plugins") as typeof import("plugins")); + +export const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); + +interface PluginCardProps extends React.HTMLProps<HTMLDivElement> { + plugin: Plugin; + disabled: boolean; + onRestartNeeded(name: string, key: string): void; + isNew?: boolean; +} + +export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { + const settings = Settings.plugins[plugin.name]; + + const isEnabled = () => isPluginEnabled(plugin.name); + + function toggleEnabled() { + const wasEnabled = isEnabled(); + + // If we're enabling a plugin, make sure all deps are enabled recursively. + if (!wasEnabled) { + const { restartNeeded, failures } = startDependenciesRecursive(plugin); + + if (failures.length) { + logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`); + showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null); + return; + } + + if (restartNeeded) { + // If any dependencies have patches, don't start the plugin yet. + settings.enabled = true; + onRestartNeeded(plugin.name, "enabled"); + return; + } + } + + // if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes. + if (plugin.patches?.length) { + settings.enabled = !wasEnabled; + onRestartNeeded(plugin.name, "enabled"); + return; + } + + // If the plugin is enabled, but hasn't been started, then we can just toggle it off. + if (wasEnabled && !plugin.started) { + settings.enabled = !wasEnabled; + return; + } + + const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin); + + if (!result) { + settings.enabled = false; + + const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`; + showToast(msg, Toasts.Type.FAILURE, { + position: Toasts.Position.BOTTOM, + }); + + return; + } + + settings.enabled = !wasEnabled; + } + + return ( + <AddonCard + name={plugin.name} + description={plugin.description} + isNew={isNew} + enabled={isEnabled()} + setEnabled={toggleEnabled} + disabled={disabled} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + infoButton={ + <button + role="switch" + onClick={() => openPluginModal(plugin, onRestartNeeded)} + className={classes(ButtonClasses.button, cl("info-button"))} + > + {plugin.options && !isObjectEmpty(plugin.options) + ? <CogWheel className={cl("info-icon")} /> + : <InfoIcon className={cl("info-icon")} /> + } + </button> + } /> + ); +} diff --git a/src/components/PluginSettings/PluginModal.css b/src/components/settings/tabs/plugins/PluginModal.css similarity index 100% rename from src/components/PluginSettings/PluginModal.css rename to src/components/settings/tabs/plugins/PluginModal.css diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx similarity index 55% rename from src/components/PluginSettings/PluginModal.tsx rename to src/components/settings/tabs/plugins/PluginModal.tsx index ddbcf8b8..d34be261 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -23,41 +23,32 @@ import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; +import { debounce } from "@shared/debounce"; import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { isObjectEmpty } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; -import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; +import { findByPropsLazy } from "@webpack"; +import { Clickable, FluxDispatcher, Forms, React, Text, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; -import { - ISettingCustomElementProps, - ISettingElementProps, - SettingBooleanComponent, - SettingCustomComponent, - SettingNumericComponent, - SettingSelectComponent, - SettingSliderComponent, - SettingTextComponent -} from "./components"; +import { OptionComponentMap } from "./components"; import { openContributorModal } from "./ContributorModal"; import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); -const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; interface PluginModalProps extends ModalProps { plugin: Plugin; - onRestartNeeded(): void; + onRestartNeeded(key: string): void; } function makeDummyUser(user: { username: string; id?: string; avatar?: string; }) { @@ -68,39 +59,22 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; } /** To stop discord making unwanted requests... */ bot: true, }); + FluxDispatcher.dispatch({ type: "USER_UPDATE", user: newUser, }); + return newUser; } -const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = { - [OptionType.STRING]: SettingTextComponent, - [OptionType.NUMBER]: SettingNumericComponent, - [OptionType.BIGINT]: SettingNumericComponent, - [OptionType.BOOLEAN]: SettingBooleanComponent, - [OptionType.SELECT]: SettingSelectComponent, - [OptionType.SLIDER]: SettingSliderComponent, - [OptionType.COMPONENT]: SettingCustomComponent, - [OptionType.CUSTOM]: () => null, -}; - export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) { - const [authors, setAuthors] = React.useState<Partial<User>[]>([]); - const pluginSettings = useSettings().plugins[plugin.name]; - - const [tempSettings, setTempSettings] = React.useState<Record<string, any>>({}); - - const [errors, setErrors] = React.useState<Record<string, boolean>>({}); - const [saveError, setSaveError] = React.useState<string | null>(null); - - const canSubmit = () => Object.values(errors).every(e => !e); - const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options)); - React.useEffect(() => { + const [authors, setAuthors] = useState<Partial<User>[]>([]); + + useEffect(() => { (async () => { for (const user of plugin.authors.slice(0, 6)) { const author = user.id @@ -113,63 +87,36 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti })(); }, [plugin.authors]); - async function saveAndClose() { - if (!plugin.options) { - onClose(); - return; - } - - if (plugin.beforeSave) { - const result = await Promise.resolve(plugin.beforeSave(tempSettings)); - if (result !== true) { - setSaveError(result); - return; - } - } - - let restartNeeded = false; - for (const [key, value] of Object.entries(tempSettings)) { - const option = plugin.options[key]; - pluginSettings[key] = value; - - if (option.type === OptionType.CUSTOM) continue; - if (option?.restartNeeded) restartNeeded = true; - } - if (restartNeeded) onRestartNeeded(); - onClose(); - } - function renderSettings() { - if (!hasSettings || !plugin.options) { + if (!hasSettings || !plugin.options) return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>; - } else { - const options = Object.entries(plugin.options).map(([key, setting]) => { - if (setting.type === OptionType.CUSTOM || setting.hidden) return null; - function onChange(newValue: any) { - setTempSettings(s => ({ ...s, [key]: newValue })); - } + const options = Object.entries(plugin.options).map(([key, setting]) => { + if (setting.type === OptionType.CUSTOM || setting.hidden) return null; - function onError(hasError: boolean) { - setErrors(e => ({ ...e, [key]: hasError })); - } + function onChange(newValue: any) { + const option = plugin.options?.[key]; + if (!option || option.type === OptionType.CUSTOM) return; - const Component = Components[setting.type]; - return ( - <Component - id={key} - key={key} - option={setting} - onChange={onChange} - onError={onError} - pluginSettings={pluginSettings} - definedSettings={plugin.settings} - /> - ); - }); + pluginSettings[key] = newValue; - return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>; - } + if (option.restartNeeded) onRestartNeeded(key); + } + + const Component = OptionComponentMap[setting.type]; + return ( + <Component + id={key} + key={key} + option={setting} + onChange={debounce(onChange)} + pluginSettings={pluginSettings} + definedSettings={plugin.settings} + /> + ); + }); + + return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>; } function renderMoreUsers(_label: string, count: number) { @@ -192,35 +139,12 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti ); } - /* - function switchToPopout() { - onClose(); - - const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`; - PopoutActions.open( - PopoutKey, - () => <PluginModal - transitionState={transitionState} - plugin={plugin} - onRestartNeeded={onRestartNeeded} - onClose={() => PopoutActions.close(PopoutKey)} - /> - ); - } - */ - const pluginMeta = PluginMeta[plugin.name]; return ( <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}> <ModalHeader separator={false}> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text> - - {/* - <Button look={Button.Looks.BLANK} onClick={switchToPopout}> - <OpenExternalIcon aria-label="Open in Popout" /> - </Button> - */} <ModalCloseButton onClick={onClose} /> </ModalHeader> <ModalContent> @@ -244,12 +168,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti <div style={{ width: "fit-content", marginBottom: 8 }}> <UserSummaryItem users={authors} - count={plugin.authors.length} guildId={undefined} renderIcon={false} max={6} showDefaultAvatarsForNullUsers - showUserPopout renderMoreUsers={renderMoreUsers} renderUser={(user: User) => ( <Clickable @@ -267,59 +189,32 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti /> </div> </Forms.FormSection> + {!!plugin.settingsAboutComponent && ( <div className={Margins.bottom8}> <Forms.FormSection> <ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component"> - <plugin.settingsAboutComponent tempSettings={tempSettings} /> + <plugin.settingsAboutComponent /> </ErrorBoundary> </Forms.FormSection> </div> )} + <Forms.FormSection className={Margins.bottom16}> <Forms.FormTitle tag="h3">Settings</Forms.FormTitle> {renderSettings()} </Forms.FormSection> </ModalContent> - {hasSettings && <ModalFooter> - <Flex flexDirection="column" style={{ width: "100%" }}> - <Flex style={{ marginLeft: "auto" }}> - <Button - onClick={onClose} - size={Button.Sizes.SMALL} - color={Button.Colors.PRIMARY} - look={Button.Looks.LINK} - > - Cancel - </Button> - <Tooltip text="You must fix all errors before saving" shouldShow={!canSubmit()}> - {({ onMouseEnter, onMouseLeave }) => ( - <Button - size={Button.Sizes.SMALL} - color={Button.Colors.BRAND} - onClick={saveAndClose} - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - disabled={!canSubmit()} - > - Save & Close - </Button> - )} - </Tooltip> - </Flex> - {saveError && <Text variant="text-md/semibold" style={{ color: "var(--text-danger)" }}>Error while saving: {saveError}</Text>} - </Flex> - </ModalFooter>} </ModalRoot> ); } -export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) { +export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string, key: string) => void) { openModal(modalProps => ( <PluginModal {...modalProps} plugin={plugin} - onRestartNeeded={() => onRestartNeeded?.(plugin.name)} + onRestartNeeded={(key: string) => onRestartNeeded?.(plugin.name, key)} /> )); } diff --git a/src/components/PluginSettings/components/SettingBooleanComponent.tsx b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx similarity index 72% rename from src/components/PluginSettings/components/SettingBooleanComponent.tsx rename to src/components/settings/tabs/plugins/components/BooleanSetting.tsx index e5219e45..c022fa2d 100644 --- a/src/components/PluginSettings/components/SettingBooleanComponent.tsx +++ b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx @@ -18,27 +18,23 @@ import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { PluginOptionBoolean } from "@utils/types"; -import { Forms, React, Switch } from "@webpack/common"; +import { Forms, React, Switch, useState } from "@webpack/common"; -import { ISettingElementProps } from "."; +import { resolveError, SettingProps } from "./Common"; -export function SettingBooleanComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionBoolean>) { +export function BooleanSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionBoolean>) { const def = pluginSettings[id] ?? option.default; - const [state, setState] = React.useState(def ?? false); - const [error, setError] = React.useState<string | null>(null); - - React.useEffect(() => { - onError(error !== null); - }, [error]); + const [state, setState] = useState(def ?? false); + const [error, setError] = useState<string | null>(null); function handleChange(newValue: boolean): void { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - if (typeof isValid === "string") setError(isValid); - else if (!isValid) setError("Invalid input provided."); - else { - setError(null); - setState(newValue); + + setState(newValue); + setError(resolveError(isValid)); + + if (isValid === true) { onChange(newValue); } } diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx new file mode 100644 index 00000000..06083767 --- /dev/null +++ b/src/components/settings/tabs/plugins/components/Common.tsx @@ -0,0 +1,48 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Margins } from "@utils/margins"; +import { wordsFromCamel, wordsToTitle } from "@utils/text"; +import { DefinedSettings, PluginOptionBase } from "@utils/types"; +import { Forms } from "@webpack/common"; +import { PropsWithChildren } from "react"; + +interface SettingBaseProps<T> { + option: T; + onChange(newValue: any): void; + pluginSettings: { + [setting: string]: any; + enabled: boolean; + }; + id: string; + definedSettings?: DefinedSettings; +} + +export type SettingProps<T extends PluginOptionBase> = SettingBaseProps<T>; +export type ComponentSettingProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = SettingBaseProps<T>; + +export function resolveError(isValidResult: boolean | string) { + if (typeof isValidResult === "string") return isValidResult; + + return isValidResult ? null : "Invalid input provided"; +} + +interface SettingsSectionProps extends PropsWithChildren { + name: string; + description: string; + error: string | null; +} + +export function SettingsSection({ name, description, error, children }: SettingsSectionProps) { + return ( + <Forms.FormSection> + <Forms.FormTitle>{wordsToTitle(wordsFromCamel(name))}</Forms.FormTitle> + <Forms.FormText className={Margins.bottom20} type="description">{description}</Forms.FormText> + {children} + {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} + </Forms.FormSection> + ); +} diff --git a/src/components/PluginSettings/components/SettingCustomComponent.tsx b/src/components/settings/tabs/plugins/components/ComponentSetting.tsx similarity index 76% rename from src/components/PluginSettings/components/SettingCustomComponent.tsx rename to src/components/settings/tabs/plugins/components/ComponentSetting.tsx index 25e8c9c6..c43b7276 100644 --- a/src/components/PluginSettings/components/SettingCustomComponent.tsx +++ b/src/components/settings/tabs/plugins/components/ComponentSetting.tsx @@ -18,8 +18,8 @@ import { PluginOptionComponent } from "@utils/types"; -import { ISettingCustomElementProps } from "."; +import { ComponentSettingProps } from "./Common"; -export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) { - return option.component({ setValue: onChange, setError: onError, option }); +export function ComponentSetting({ option, onChange }: ComponentSettingProps<PluginOptionComponent>) { + return option.component({ setValue: onChange, option }); } diff --git a/src/components/PluginSettings/components/SettingNumericComponent.tsx b/src/components/settings/tabs/plugins/components/NumberSetting.tsx similarity index 59% rename from src/components/PluginSettings/components/SettingNumericComponent.tsx rename to src/components/settings/tabs/plugins/components/NumberSetting.tsx index b724717d..1b2e01a9 100644 --- a/src/components/PluginSettings/components/SettingNumericComponent.tsx +++ b/src/components/settings/tabs/plugins/components/NumberSetting.tsx @@ -16,48 +16,40 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Margins } from "@utils/margins"; -import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { OptionType, PluginOptionNumber } from "@utils/types"; -import { Forms, React, TextInput } from "@webpack/common"; +import { React, TextInput, useState } from "@webpack/common"; -import { ISettingElementProps } from "."; +import { resolveError, SettingProps, SettingsSection } from "./Common"; const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER); -export function SettingNumericComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionNumber>) { +export function NumberSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionNumber>) { function serialize(value: any) { if (option.type === OptionType.BIGINT) return BigInt(value); return Number(value); } - const [state, setState] = React.useState<any>(`${pluginSettings[id] ?? option.default ?? 0}`); - const [error, setError] = React.useState<string | null>(null); + const [state, setState] = useState<any>(`${pluginSettings[id] ?? option.default ?? 0}`); + const [error, setError] = useState<string | null>(null); - React.useEffect(() => { - onError(error !== null); - }, [error]); - - function handleChange(newValue) { + function handleChange(newValue: any) { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - setError(null); - if (typeof isValid === "string") setError(isValid); - else if (!isValid) setError("Invalid input provided."); + setError(resolveError(isValid)); + + if (isValid === true) { + onChange(serialize(newValue)); + } if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) { setState(`${Number.MAX_SAFE_INTEGER}`); - onChange(serialize(newValue)); } else { setState(newValue); - onChange(serialize(newValue)); } } return ( - <Forms.FormSection> - <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle> - <Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText> + <SettingsSection name={id} description={option.description} error={error}> <TextInput type="number" pattern="-?[0-9]+" @@ -67,7 +59,6 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting disabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} /> - {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} - </Forms.FormSection> + </SettingsSection> ); } diff --git a/src/components/PluginSettings/components/SettingSelectComponent.tsx b/src/components/settings/tabs/plugins/components/SelectSetting.tsx similarity index 56% rename from src/components/PluginSettings/components/SettingSelectComponent.tsx rename to src/components/settings/tabs/plugins/components/SelectSetting.tsx index 5f1cd027..35512a21 100644 --- a/src/components/PluginSettings/components/SettingSelectComponent.tsx +++ b/src/components/settings/tabs/plugins/components/SelectSetting.tsx @@ -16,38 +16,30 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Margins } from "@utils/margins"; -import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { PluginOptionSelect } from "@utils/types"; -import { Forms, React, Select } from "@webpack/common"; +import { React, Select, useState } from "@webpack/common"; -import { ISettingElementProps } from "."; +import { resolveError, SettingProps, SettingsSection } from "./Common"; -export function SettingSelectComponent({ option, pluginSettings, definedSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) { +export function SelectSetting({ option, pluginSettings, definedSettings, onChange, id }: SettingProps<PluginOptionSelect>) { const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value; - const [state, setState] = React.useState<any>(def ?? null); - const [error, setError] = React.useState<string | null>(null); + const [state, setState] = useState<any>(def ?? null); + const [error, setError] = useState<string | null>(null); - React.useEffect(() => { - onError(error !== null); - }, [error]); - - function handleChange(newValue) { + function handleChange(newValue: any) { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - if (typeof isValid === "string") setError(isValid); - else if (!isValid) setError("Invalid input provided."); - else { - setError(null); - setState(newValue); + + setState(newValue); + setError(resolveError(isValid)); + + if (isValid === true) { onChange(newValue); } } return ( - <Forms.FormSection> - <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle> - <Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText> + <SettingsSection name={id} description={option.description} error={error}> <Select isDisabled={option.disabled?.call(definedSettings) ?? false} options={option.options} @@ -59,7 +51,6 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings serialize={v => String(v)} {...option.componentProps} /> - {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} - </Forms.FormSection> + </SettingsSection> ); } diff --git a/src/components/PluginSettings/components/SettingSliderComponent.tsx b/src/components/settings/tabs/plugins/components/SliderSetting.tsx similarity index 58% rename from src/components/PluginSettings/components/SettingSliderComponent.tsx rename to src/components/settings/tabs/plugins/components/SliderSetting.tsx index 738f5f01..936d8363 100644 --- a/src/components/PluginSettings/components/SettingSliderComponent.tsx +++ b/src/components/settings/tabs/plugins/components/SliderSetting.tsx @@ -16,44 +16,28 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Margins } from "@utils/margins"; -import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { PluginOptionSlider } from "@utils/types"; -import { Forms, React, Slider } from "@webpack/common"; +import { React, Slider, useState } from "@webpack/common"; -import { ISettingElementProps } from "."; +import { resolveError, SettingProps, SettingsSection } from "./Common"; -export function makeRange(start: number, end: number, step = 1) { - const ranges: number[] = []; - for (let value = start; value <= end; value += step) { - ranges.push(Math.round(value * 100) / 100); - } - return ranges; -} - -export function SettingSliderComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionSlider>) { +export function SliderSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionSlider>) { const def = pluginSettings[id] ?? option.default; - const [error, setError] = React.useState<string | null>(null); - - React.useEffect(() => { - onError(error !== null); - }, [error]); + const [error, setError] = useState<string | null>(null); function handleChange(newValue: number): void { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - if (typeof isValid === "string") setError(isValid); - else if (!isValid) setError("Invalid input provided."); - else { - setError(null); + + setError(resolveError(isValid)); + + if (isValid === true) { onChange(newValue); } } return ( - <Forms.FormSection> - <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle> - <Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText> + <SettingsSection name={id} description={option.description} error={error}> <Slider disabled={option.disabled?.call(definedSettings) ?? false} markers={option.markers} @@ -65,7 +49,7 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings stickToMarkers={option.stickToMarkers ?? true} {...option.componentProps} /> - </Forms.FormSection> + </SettingsSection> ); } diff --git a/src/components/PluginSettings/components/SettingTextComponent.tsx b/src/components/settings/tabs/plugins/components/TextSetting.tsx similarity index 52% rename from src/components/PluginSettings/components/SettingTextComponent.tsx rename to src/components/settings/tabs/plugins/components/TextSetting.tsx index 36b8b13a..759137f1 100644 --- a/src/components/PluginSettings/components/SettingTextComponent.tsx +++ b/src/components/settings/tabs/plugins/components/TextSetting.tsx @@ -16,35 +16,28 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Margins } from "@utils/margins"; -import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { PluginOptionString } from "@utils/types"; -import { Forms, React, TextInput } from "@webpack/common"; +import { React, TextInput, useState } from "@webpack/common"; -import { ISettingElementProps } from "."; +import { resolveError, SettingProps, SettingsSection } from "./Common"; -export function SettingTextComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) { - const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null); - const [error, setError] = React.useState<string | null>(null); +export function TextSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionString>) { + const [state, setState] = useState(pluginSettings[id] ?? option.default ?? null); + const [error, setError] = useState<string | null>(null); - React.useEffect(() => { - onError(error !== null); - }, [error]); - - function handleChange(newValue) { + function handleChange(newValue: string) { const isValid = option.isValid?.call(definedSettings, newValue) ?? true; - if (typeof isValid === "string") setError(isValid); - else if (!isValid) setError("Invalid input provided."); - else setError(null); setState(newValue); - onChange(newValue); + setError(resolveError(isValid)); + + if (isValid === true) { + onChange(newValue); + } } return ( - <Forms.FormSection> - <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle> - <Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText> + <SettingsSection name={id} description={option.description} error={error}> <TextInput type="text" value={state} @@ -54,7 +47,6 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings, maxLength={null} {...option.componentProps} /> - {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} - </Forms.FormSection> + </SettingsSection> ); } diff --git a/src/components/settings/tabs/plugins/components/index.ts b/src/components/settings/tabs/plugins/components/index.ts new file mode 100644 index 00000000..6068dfef --- /dev/null +++ b/src/components/settings/tabs/plugins/components/index.ts @@ -0,0 +1,39 @@ +/* + * 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 { OptionType } from "@utils/types"; +import { ComponentType } from "react"; + +import { BooleanSetting } from "./BooleanSetting"; +import { ComponentSettingProps, SettingProps } from "./Common"; +import { ComponentSetting } from "./ComponentSetting"; +import { NumberSetting } from "./NumberSetting"; +import { SelectSetting } from "./SelectSetting"; +import { SliderSetting } from "./SliderSetting"; +import { TextSetting } from "./TextSetting"; + +export const OptionComponentMap: Record<OptionType, ComponentType<SettingProps<any> | ComponentSettingProps<any>>> = { + [OptionType.STRING]: TextSetting, + [OptionType.NUMBER]: NumberSetting, + [OptionType.BIGINT]: NumberSetting, + [OptionType.BOOLEAN]: BooleanSetting, + [OptionType.SELECT]: SelectSetting, + [OptionType.SLIDER]: SliderSetting, + [OptionType.COMPONENT]: ComponentSetting, + [OptionType.CUSTOM]: () => null, +}; diff --git a/src/components/PluginSettings/index.tsx b/src/components/settings/tabs/plugins/index.tsx similarity index 57% rename from src/components/PluginSettings/index.tsx rename to src/components/settings/tabs/plugins/index.tsx index acfb0609..6faa6368 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -19,153 +19,53 @@ import "./styles.css"; import * as DataStore from "@api/DataStore"; -import { showNotice } from "@api/Notices"; -import { Settings, useSettings } from "@api/Settings"; +import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; -import { CogWheel, InfoIcon } from "@components/Icons"; -import { openPluginModal } from "@components/PluginSettings/PluginModal"; -import { AddonCard } from "@components/VencordSettings/AddonCard"; -import { SettingsTab } from "@components/VencordSettings/shared"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { ChangeList } from "@utils/ChangeList"; -import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { classes, isObjectEmpty } from "@utils/misc"; -import { useAwaiter } from "@utils/react"; -import { Plugin } from "@utils/types"; +import { classes } from "@utils/misc"; +import { useAwaiter, useCleanupEffect } from "@utils/react"; import { findByPropsLazy } from "@webpack"; -import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common"; +import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Tooltip, useMemo, useState } from "@webpack/common"; import { JSX } from "react"; import Plugins, { ExcludedPlugins } from "~plugins"; -// Avoid circular dependency -const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins")); +import { PluginCard } from "./PluginCard"; -const cl = classNameFactory("vc-plugins-"); -const logger = new Logger("PluginSettings", "#a6d189"); +export const cl = classNameFactory("vc-plugins-"); +export const logger = new Logger("PluginSettings", "#a6d189"); const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error"); -const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); - - -function showErrorToast(message: string) { - Toasts.show({ - message, - type: Toasts.Type.FAILURE, - id: Toasts.genId(), - options: { - position: Toasts.Position.BOTTOM - } - }); -} function ReloadRequiredCard({ required }: { required: boolean; }) { return ( <Card className={classes(cl("info-card"), required && "vc-warning-card")}> - {required ? ( - <> - <Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle> - <Forms.FormText className={cl("dep-text")}> - Restart now to apply new plugins and their settings - </Forms.FormText> - <Button onClick={() => location.reload()} className={cl("restart-button")}> - Restart - </Button> - </> - ) : ( - <> - <Forms.FormTitle tag="h5">Plugin Management</Forms.FormTitle> - <Forms.FormText>Press the cog wheel or info icon to get more info on a plugin</Forms.FormText> - <Forms.FormText>Plugins with a cog wheel have settings you can modify!</Forms.FormText> - </> - )} + {required + ? ( + <> + <Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle> + <Forms.FormText className={cl("dep-text")}> + Restart now to apply new plugins and their settings + </Forms.FormText> + <Button onClick={() => location.reload()} className={cl("restart-button")}> + Restart + </Button> + </> + ) + : ( + <> + <Forms.FormTitle tag="h5">Plugin Management</Forms.FormTitle> + <Forms.FormText>Press the cog wheel or info icon to get more info on a plugin</Forms.FormText> + <Forms.FormText>Plugins with a cog wheel have settings you can modify!</Forms.FormText> + </> + )} </Card> ); } -interface PluginCardProps extends React.HTMLProps<HTMLDivElement> { - plugin: Plugin; - disabled: boolean; - onRestartNeeded(name: string): void; - isNew?: boolean; -} - -export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { - const settings = Settings.plugins[plugin.name]; - - const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name); - - function toggleEnabled() { - const wasEnabled = isEnabled(); - - // If we're enabling a plugin, make sure all deps are enabled recursively. - if (!wasEnabled) { - const { restartNeeded, failures } = startDependenciesRecursive(plugin); - if (failures.length) { - logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`); - showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null); - return; - } else if (restartNeeded) { - // If any dependencies have patches, don't start the plugin yet. - settings.enabled = true; - onRestartNeeded(plugin.name); - return; - } - } - - // if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes. - if (plugin.patches?.length) { - settings.enabled = !wasEnabled; - onRestartNeeded(plugin.name); - return; - } - - // If the plugin is enabled, but hasn't been started, then we can just toggle it off. - if (wasEnabled && !plugin.started) { - settings.enabled = !wasEnabled; - return; - } - - const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin); - - if (!result) { - settings.enabled = false; - - const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`; - logger.error(msg); - showErrorToast(msg); - return; - } - - settings.enabled = !wasEnabled; - } - - return ( - <AddonCard - name={plugin.name} - description={plugin.description} - isNew={isNew} - enabled={isEnabled()} - setEnabled={toggleEnabled} - disabled={disabled} - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - infoButton={ - <button - role="switch" - onClick={() => openPluginModal(plugin, onRestartNeeded)} - className={classes(ButtonClasses.button, cl("info-button"))} - > - {plugin.options && !isObjectEmpty(plugin.options) - ? <CogWheel className={cl("info-icon")} /> - : <InfoIcon className={cl("info-icon")} />} - </button> - } - /> - ); -} - const enum SearchStatus { ALL, ENABLED, @@ -204,31 +104,32 @@ function ExcludedPluginsList({ search }: { search: string; }) { ); } -export default function PluginSettings() { +function PluginSettings() { const settings = useSettings(); - const changes = React.useMemo(() => new ChangeList<string>(), []); + const changes = useMemo(() => new ChangeList<string>(), []); - React.useEffect(() => { - return () => void (changes.hasChanges && Alerts.show({ - title: "Restart required", - body: ( - <> - <p>The following plugins require a restart:</p> - <div>{changes.map((s, i) => ( - <> - {i > 0 && ", "} - {Parser.parse("`" + s + "`")} - </> - ))}</div> - </> - ), - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: () => location.reload() - })); + useCleanupEffect(() => { + if (changes.hasChanges) + Alerts.show({ + title: "Restart required", + body: ( + <> + <p>The following plugins require a restart:</p> + <div>{changes.map((s, i) => ( + <> + {i > 0 && ", "} + {Parser.parse("`" + s.split(".")[0] + "`")} + </> + ))}</div> + </> + ), + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: () => location.reload() + }); }, []); - const depMap = React.useMemo(() => { + const depMap = useMemo(() => { const o = {} as Record<string, string[]>; for (const plugin in Plugins) { const deps = Plugins[plugin].dependencies; @@ -242,10 +143,12 @@ export default function PluginSettings() { return o; }, []); - const sortedPlugins = useMemo(() => Object.values(Plugins) - .sort((a, b) => a.name.localeCompare(b.name)), []); + const sortedPlugins = useMemo(() => + Object.values(Plugins).sort((a, b) => a.name.localeCompare(b.name)), + [] + ); - const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL }); + const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL }); const search = searchValue.value.toLowerCase(); const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query })); @@ -254,9 +157,19 @@ export default function PluginSettings() { const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => { const { status } = searchValue; const enabled = Vencord.Plugins.isPluginEnabled(plugin.name); - if (enabled && status === SearchStatus.DISABLED) return false; - if (!enabled && status === SearchStatus.ENABLED) return false; - if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false; + + switch (status) { + case SearchStatus.DISABLED: + if (enabled) return false; + break; + case SearchStatus.ENABLED: + if (!enabled) return false; + break; + case SearchStatus.NEW: + if (!newPlugins?.includes(plugin.name)) return false; + break; + } + if (!search.length) return true; return ( @@ -306,7 +219,7 @@ export default function PluginSettings() { <PluginCard onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - onRestartNeeded={name => changes.handleChange(name)} + onRestartNeeded={(name, key) => changes.handleChange(`${name}.${key}`)} disabled={true} plugin={p} key={p.name} @@ -317,7 +230,7 @@ export default function PluginSettings() { } else { plugins.push( <PluginCard - onRestartNeeded={name => changes.handleChange(name)} + onRestartNeeded={(name, key) => changes.handleChange(`${name}.${key}`)} disabled={false} plugin={p} isNew={newPlugins?.includes(p.name)} @@ -386,9 +299,11 @@ export default function PluginSettings() { function makeDependencyList(deps: string[]) { return ( - <React.Fragment> + <> <Forms.FormText>This plugin is required by:</Forms.FormText> {deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)} - </React.Fragment> + </> ); } + +export default wrapTab(PluginSettings, "Plugins"); diff --git a/src/components/PluginSettings/styles.css b/src/components/settings/tabs/plugins/styles.css similarity index 98% rename from src/components/PluginSettings/styles.css rename to src/components/settings/tabs/plugins/styles.css index c3d97051..ff026192 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/settings/tabs/plugins/styles.css @@ -42,7 +42,7 @@ grid-template-columns: 1fr 150px; } -.vc-plugins-badge { +.vc-addon-badge { padding: 0 6px; font-family: var(--font-display); font-weight: 500; diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/settings/tabs/styles.css similarity index 100% rename from src/components/VencordSettings/settingsStyles.css rename to src/components/settings/tabs/styles.css diff --git a/src/components/VencordSettings/BackupAndRestoreTab.tsx b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx similarity index 93% rename from src/components/VencordSettings/BackupAndRestoreTab.tsx rename to src/components/settings/tabs/sync/BackupAndRestoreTab.tsx index a9a1c9fe..077b8792 100644 --- a/src/components/VencordSettings/BackupAndRestoreTab.tsx +++ b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx @@ -17,14 +17,13 @@ */ import { Flex } from "@components/Flex"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync"; import { Button, Card, Text } from "@webpack/common"; -import { SettingsTab, wrapTab } from "./shared"; - -function BackupRestoreTab() { +function BackupAndRestoreTab() { return ( <SettingsTab title="Backup & Restore"> <Card className={classes("vc-settings-card", "vc-backup-restore-card")}> @@ -64,4 +63,4 @@ function BackupRestoreTab() { ); } -export default wrapTab(BackupRestoreTab, "Backup & Restore"); +export default wrapTab(BackupAndRestoreTab, "Backup & Restore"); diff --git a/src/components/VencordSettings/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx similarity index 99% rename from src/components/VencordSettings/CloudTab.tsx rename to src/components/settings/tabs/sync/CloudTab.tsx index 809d4062..1d1fd978 100644 --- a/src/components/VencordSettings/CloudTab.tsx +++ b/src/components/settings/tabs/sync/CloudTab.tsx @@ -21,13 +21,12 @@ import { Settings, useSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Grid } from "@components/Grid"; import { Link } from "@components/Link"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { Margins } from "@utils/margins"; import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync"; import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common"; -import { SettingsTab, wrapTab } from "./shared"; - function validateUrl(url: string) { try { new URL(url); diff --git a/src/components/settings/tabs/themes/CspErrorCard.tsx b/src/components/settings/tabs/themes/CspErrorCard.tsx new file mode 100644 index 00000000..ee885647 --- /dev/null +++ b/src/components/settings/tabs/themes/CspErrorCard.tsx @@ -0,0 +1,86 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ErrorCard } from "@components/ErrorCard"; +import { Link } from "@components/Link"; +import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { relaunch } from "@utils/native"; +import { useForceUpdater } from "@utils/react"; +import { Alerts, Button, Forms } from "@webpack/common"; + +export function CspErrorCard() { + if (IS_WEB) return null; + + const errors = useCspErrors(); + const forceUpdate = useForceUpdater(); + + if (!errors.length) return null; + + const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/"); + + const allowUrl = async (url: string) => { + const { origin: baseUrl, host } = new URL(url); + + const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes"); + if (result !== "ok") return; + + CspBlockedUrls.forEach(url => { + if (new URL(url).host === host) { + CspBlockedUrls.delete(url); + } + }); + + forceUpdate(); + + Alerts.show({ + title: "Restart Required", + body: "A restart is required to apply this change", + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: relaunch + }); + }; + + const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); + + return ( + <ErrorCard className="vc-settings-card"> + <Forms.FormTitle tag="h5">Blocked Resources</Forms.FormTitle> + <Forms.FormText>Some images, styles, or fonts were blocked because they come from disallowed domains.</Forms.FormText> + <Forms.FormText>It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them.</Forms.FormText> + <Forms.FormText> + After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change. + </Forms.FormText> + + <Forms.FormTitle tag="h5" className={classes(Margins.top16, Margins.bottom8)}>Blocked URLs</Forms.FormTitle> + <div className="vc-settings-csp-list"> + {errors.map((url, i) => ( + <div key={url}> + {i !== 0 && <Forms.FormDivider className={Margins.bottom8} />} + <div className="vc-settings-csp-row"> + <Link href={url}>{url}</Link> + <Button color={Button.Colors.PRIMARY} onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}> + Allow + </Button> + </div> + </div> + ))} + </div> + + {hasImgurHtmlDomain && ( + <> + <Forms.FormDivider className={classes(Margins.top8, Margins.bottom16)} /> + <Forms.FormText> + Imgur links should be direct links in the form of <code>https://i.imgur.com/...</code> + </Forms.FormText> + <Forms.FormText>To obtain a direct link, right-click the image and select "Copy image address".</Forms.FormText> + </> + )} + </ErrorCard> + ); +} diff --git a/src/components/settings/tabs/themes/LocalThemesTab.tsx b/src/components/settings/tabs/themes/LocalThemesTab.tsx new file mode 100644 index 00000000..8d07a27c --- /dev/null +++ b/src/components/settings/tabs/themes/LocalThemesTab.tsx @@ -0,0 +1,170 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Settings, useSettings } from "@api/Settings"; +import { classNameFactory } from "@api/Styles"; +import { FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; +import { Link } from "@components/Link"; +import { QuickAction, QuickActionCard } from "@components/settings/QuickAction"; +import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal"; +import { UserThemeHeader } from "@main/themes"; +import { findLazy } from "@webpack"; +import { Card, Forms, useEffect, useRef, useState } from "@webpack/common"; +import ClientThemePlugin from "plugins/clientTheme"; +import type { ComponentType, Ref, SyntheticEvent } from "react"; + +import { ThemeCard } from "./ThemeCard"; + +const cl = classNameFactory("vc-settings-theme-"); + +type FileInput = ComponentType<{ + ref: Ref<HTMLInputElement>; + onChange: (e: SyntheticEvent<HTMLInputElement>) => void; + multiple?: boolean; + filters?: { name?: string; extensions: string[]; }[]; +}>; + +const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef); + +// When a local theme is enabled/disabled, update the settings +function onLocalThemeChange(fileName: string, value: boolean) { + if (value) { + if (Settings.enabledThemes.includes(fileName)) return; + Settings.enabledThemes = [...Settings.enabledThemes, fileName]; + } else { + Settings.enabledThemes = Settings.enabledThemes.filter(f => f !== fileName); + } +} + +async function onFileUpload(e: SyntheticEvent<HTMLInputElement>) { + e.stopPropagation(); + e.preventDefault(); + + if (!e.currentTarget?.files?.length) return; + const { files } = e.currentTarget; + + const uploads = Array.from(files, file => { + const { name } = file; + if (!name.endsWith(".css")) return; + + return new Promise<void>((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + VencordNative.themes.uploadTheme(name, reader.result as string) + .then(resolve) + .catch(reject); + }; + reader.readAsText(file); + }); + }); + + await Promise.all(uploads); +} + +export function LocalThemesTab() { + const settings = useSettings(["enabledThemes"]); + + const fileInputRef = useRef<HTMLInputElement>(null); + + const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null); + + useEffect(() => { + refreshLocalThemes(); + }, []); + + async function refreshLocalThemes() { + const themes = await VencordNative.themes.getThemesList(); + setUserThemes(themes); + } + + return ( + <> + <Card className="vc-settings-card"> + <Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle> + <div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}> + <Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes"> + BetterDiscord Themes + </Link> + <Link href="https://github.com/search?q=discord+theme">GitHub</Link> + </div> + <Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText> + </Card> + + <Card className="vc-settings-card"> + <Forms.FormTitle tag="h5">External Resources</Forms.FormTitle> + <Forms.FormText>For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.</Forms.FormText> + <Forms.FormText>Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</Forms.FormText> + </Card> + + <Forms.FormSection title="Local Themes"> + <QuickActionCard> + <> + {IS_WEB ? + ( + <QuickAction + text={ + <span style={{ position: "relative" }}> + Upload Theme + <FileInput + ref={fileInputRef} + onChange={async e => { + await onFileUpload(e); + refreshLocalThemes(); + }} + multiple={true} + filters={[{ extensions: ["css"] }]} + /> + </span> + } + Icon={PlusIcon} + /> + ) : ( + <QuickAction + text="Open Themes Folder" + action={() => VencordNative.themes.openFolder()} + Icon={FolderIcon} + /> + )} + <QuickAction + text="Load missing Themes" + action={refreshLocalThemes} + Icon={RestartIcon} + /> + <QuickAction + text="Edit QuickCSS" + action={() => VencordNative.quickCss.openEditor()} + Icon={PaintbrushIcon} + /> + + {Vencord.Plugins.isPluginEnabled(ClientThemePlugin.name) && ( + <QuickAction + text="Edit ClientTheme" + action={() => openPluginModal(ClientThemePlugin)} + Icon={PencilIcon} + /> + )} + </> + </QuickActionCard> + + <div className={cl("grid")}> + {userThemes?.map(theme => ( + <ThemeCard + key={theme.fileName} + enabled={settings.enabledThemes.includes(theme.fileName)} + onChange={enabled => onLocalThemeChange(theme.fileName, enabled)} + onDelete={async () => { + onLocalThemeChange(theme.fileName, false); + await VencordNative.themes.deleteTheme(theme.fileName); + refreshLocalThemes(); + }} + theme={theme} + /> + ))} + </div> + </Forms.FormSection> + </> + ); +} diff --git a/src/components/settings/tabs/themes/OnlineThemesTab.tsx b/src/components/settings/tabs/themes/OnlineThemesTab.tsx new file mode 100644 index 00000000..faca96dc --- /dev/null +++ b/src/components/settings/tabs/themes/OnlineThemesTab.tsx @@ -0,0 +1,56 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useSettings } from "@api/Settings"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { Card, Forms, TextArea, useState } from "@webpack/common"; + +export function OnlineThemesTab() { + const settings = useSettings(["themeLinks"]); + + const [themeText, setThemeText] = useState(settings.themeLinks.join("\n")); + + // When the user leaves the online theme textbox, update the settings + function onBlur() { + settings.themeLinks = [...new Set( + themeText + .trim() + .split(/\n+/) + .map(s => s.trim()) + .filter(Boolean) + )]; + } + + return ( + <> + <Card className={classes("vc-warning-card", Margins.bottom16)}> + <Forms.FormText> + This section is for advanced users. If you are having difficulties using it, use the + Local Themes tab instead. + </Forms.FormText> + </Card> + <Card className="vc-settings-card"> + <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> + <Forms.FormText>One link per line</Forms.FormText> + <Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText> + <Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText> + </Card> + + <Forms.FormSection title="Online Themes" tag="h5"> + <TextArea + value={themeText} + onChange={setThemeText} + className={"vc-settings-theme-links"} + placeholder="Enter Theme Links..." + spellCheck={false} + onBlur={onBlur} + rows={10} + /> + </Forms.FormSection> + </> + ); +} diff --git a/src/components/settings/tabs/themes/ThemeCard.tsx b/src/components/settings/tabs/themes/ThemeCard.tsx new file mode 100644 index 00000000..5d9b117b --- /dev/null +++ b/src/components/settings/tabs/themes/ThemeCard.tsx @@ -0,0 +1,56 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Flex } from "@components/Flex"; +import { DeleteIcon } from "@components/Icons"; +import { Link } from "@components/Link"; +import { AddonCard } from "@components/settings/AddonCard"; +import { UserThemeHeader } from "@main/themes"; +import { openInviteModal } from "@utils/discord"; +import { showToast } from "@webpack/common"; + +interface ThemeCardProps { + theme: UserThemeHeader; + enabled: boolean; + onChange: (enabled: boolean) => void; + onDelete: () => void; +} + +export function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) { + return ( + <AddonCard + name={theme.name} + description={theme.description} + author={theme.author} + enabled={enabled} + setEnabled={onChange} + infoButton={ + IS_WEB && ( + <div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}> + <DeleteIcon /> + </div> + ) + } + footer={ + <Flex flexDirection="row" style={{ gap: "0.2em" }}> + {!!theme.website && <Link href={theme.website}>Website</Link>} + {!!(theme.website && theme.invite) && " • "} + {!!theme.invite && ( + <Link + href={`https://discord.gg/${theme.invite}`} + onClick={async e => { + e.preventDefault(); + theme.invite != null && openInviteModal(theme.invite).catch(() => showToast("Invalid or expired invite")); + }} + > + Discord Server + </Link> + )} + </Flex> + } + /> + ); +} diff --git a/src/components/settings/tabs/themes/index.tsx b/src/components/settings/tabs/themes/index.tsx new file mode 100644 index 00000000..40653a4c --- /dev/null +++ b/src/components/settings/tabs/themes/index.tsx @@ -0,0 +1,85 @@ +/* + * 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 "./styles.css"; + +import { Link } from "@components/Link"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; +import { getStylusWebStoreUrl } from "@utils/web"; +import { Card, Forms, React, TabBar, useState } from "@webpack/common"; + +import { CspErrorCard } from "./CspErrorCard"; +import { LocalThemesTab } from "./LocalThemesTab"; +import { OnlineThemesTab } from "./OnlineThemesTab"; + +const enum ThemeTab { + LOCAL, + ONLINE +} + +function ThemesTab() { + const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); + + return ( + <SettingsTab title="Themes"> + <TabBar + type="top" + look="brand" + className="vc-settings-tab-bar" + selectedItem={currentTab} + onItemSelect={setCurrentTab} + > + <TabBar.Item + className="vc-settings-tab-bar-item" + id={ThemeTab.LOCAL} + > + Local Themes + </TabBar.Item> + <TabBar.Item + className="vc-settings-tab-bar-item" + id={ThemeTab.ONLINE} + > + Online Themes + </TabBar.Item> + </TabBar> + + <CspErrorCard /> + + {currentTab === ThemeTab.LOCAL && <LocalThemesTab />} + {currentTab === ThemeTab.ONLINE && <OnlineThemesTab />} + </SettingsTab> + ); +} + +function UserscriptThemesTab() { + return ( + <SettingsTab title="Themes"> + <Card className="vc-settings-card"> + <Forms.FormTitle tag="h5">Themes are not supported on the Userscript!</Forms.FormTitle> + + <Forms.FormText> + You can instead install themes with the <Link href={getStylusWebStoreUrl()}>Stylus extension</Link>! + </Forms.FormText> + </Card> + </SettingsTab> + ); +} + +export default IS_USERSCRIPT + ? wrapTab(UserscriptThemesTab, "Themes") + : wrapTab(ThemesTab, "Themes"); diff --git a/src/components/VencordSettings/themesStyles.css b/src/components/settings/tabs/themes/styles.css similarity index 100% rename from src/components/VencordSettings/themesStyles.css rename to src/components/settings/tabs/themes/styles.css diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx new file mode 100644 index 00000000..adc7e190 --- /dev/null +++ b/src/components/settings/tabs/updater/Components.tsx @@ -0,0 +1,148 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ErrorCard } from "@components/ErrorCard"; +import { Flex } from "@components/Flex"; +import { Link } from "@components/Link"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { relaunch } from "@utils/native"; +import { changes, checkForUpdates, update, updateError } from "@utils/updater"; +import { Alerts, Button, Card, Forms, React, Toasts, useState } from "@webpack/common"; + +import { runWithDispatch } from "./runWithDispatch"; + +export interface CommonProps { + repo: string; + repoPending: boolean; +} + +export function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) { + return ( + <Link href={`${repo}/commit/${hash}`} disabled={disabled}> + {hash} + </Link> + ); +} + +export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { + return ( + <Card style={{ padding: "0 0.5em" }}> + {updates.map(({ hash, author, message }) => ( + <div + key={hash} + style={{ + marginTop: "0.5em", + marginBottom: "0.5em" + }} + > + <code> + <HashLink {...{ repo, hash }} disabled={repoPending} /> + </code> + + <span style={{ + marginLeft: "0.5em", + color: "var(--text-default)" + }}> + {message} - {author} + </span> + </div> + ))} + </Card> + ); +} + +export function Newer(props: CommonProps) { + return ( + <> + <Forms.FormText className={Margins.bottom8}> + Your local copy has more recent commits. Please stash or reset them. + </Forms.FormText> + <Changes {...props} updates={changes} /> + </> + ); +} + +export function Updatable(props: CommonProps) { + const [updates, setUpdates] = useState(changes); + const [isChecking, setIsChecking] = useState(false); + const [isUpdating, setIsUpdating] = useState(false); + + const isOutdated = (updates?.length ?? 0) > 0; + + return ( + <> + {!updates && updateError ? ( + <> + <Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText> + <ErrorCard style={{ padding: "1em" }}> + <p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p> + </ErrorCard> + </> + ) : ( + <Forms.FormText className={Margins.bottom8}> + {isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"} + </Forms.FormText> + )} + + {isOutdated && <Changes updates={updates} {...props} />} + + <Flex className={classes(Margins.bottom8, Margins.top8)}> + {isOutdated && ( + <Button + size={Button.Sizes.SMALL} + disabled={isUpdating || isChecking} + onClick={runWithDispatch(setIsUpdating, async () => { + if (await update()) { + setUpdates([]); + + await new Promise<void>(r => { + Alerts.show({ + title: "Update Success!", + body: "Successfully updated. Restart now to apply the changes?", + confirmText: "Restart", + cancelText: "Not now!", + onConfirm() { + relaunch(); + r(); + }, + onCancel: r + }); + }); + } + })} + > + Update Now + </Button> + )} + <Button + size={Button.Sizes.SMALL} + disabled={isUpdating || isChecking} + onClick={runWithDispatch(setIsChecking, async () => { + const outdated = await checkForUpdates(); + + if (outdated) { + setUpdates(changes); + } else { + setUpdates([]); + + Toasts.show({ + message: "No updates found!", + id: Toasts.genId(), + type: Toasts.Type.MESSAGE, + options: { + position: Toasts.Position.BOTTOM + } + }); + } + })} + > + Check for Updates + </Button> + </Flex> + </> + ); +} diff --git a/src/components/settings/tabs/updater/index.tsx b/src/components/settings/tabs/updater/index.tsx new file mode 100644 index 00000000..81238617 --- /dev/null +++ b/src/components/settings/tabs/updater/index.tsx @@ -0,0 +1,115 @@ +/* + * 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 { useSettings } from "@api/Settings"; +import { Link } from "@components/Link"; +import { handleSettingsTabError, SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; +import { Margins } from "@utils/margins"; +import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { useAwaiter } from "@utils/react"; +import { getRepo, isNewer, UpdateLogger } from "@utils/updater"; +import { Forms, React, Switch } from "@webpack/common"; + +import gitHash from "~git-hash"; + +import { CommonProps, HashLink, Newer, Updatable } from "./Components"; + +function Updater() { + const settings = useSettings(["autoUpdate", "autoUpdateNotification"]); + + const [repo, err, repoPending] = useAwaiter(getRepo, { + fallbackValue: "Loading...", + onError: e => UpdateLogger.error("Failed to retrieve repo", err) + }); + + const commonProps: CommonProps = { + repo, + repoPending + }; + + return ( + <SettingsTab title="Vencord Updater"> + <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> + + <Switch + value={settings.autoUpdate} + onChange={(v: boolean) => settings.autoUpdate = v} + note="Automatically update Vencord without confirmation prompt" + > + Automatically update + </Switch> + <Switch + value={settings.autoUpdateNotification} + onChange={(v: boolean) => settings.autoUpdateNotification = v} + note="Show a notification when Vencord automatically updates" + disabled={!settings.autoUpdate} + > + Get notified when an automatic update completes + </Switch> + + <Forms.FormTitle tag="h5">Repo</Forms.FormTitle> + + <Forms.FormText> + {repoPending + ? repo + : err + ? "Failed to retrieve - check console" + : ( + <Link href={repo}> + {repo.split("/").slice(-2).join("/")} + </Link> + ) + } + {" "} + (<HashLink hash={gitHash} repo={repo} disabled={repoPending} />) + </Forms.FormText> + + <Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> + + <Forms.FormTitle tag="h5">Updates</Forms.FormTitle> + + {isNewer + ? <Newer {...commonProps} /> + : <Updatable {...commonProps} /> + } + </SettingsTab> + ); +} + +export default IS_UPDATER_DISABLED + ? null + : wrapTab(Updater, "Updater"); + +export const openUpdaterModal = IS_UPDATER_DISABLED + ? null + : function () { + const UpdaterTab = wrapTab(Updater, "Updater"); + + try { + openModal(wrapTab((modalProps: ModalProps) => ( + <ModalRoot {...modalProps} size={ModalSize.MEDIUM}> + <ModalContent className="vc-updater-modal"> + <ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" /> + <UpdaterTab /> + </ModalContent> + </ModalRoot> + ), "UpdaterModal")); + } catch { + handleSettingsTabError(); + } + }; diff --git a/src/components/settings/tabs/updater/runWithDispatch.tsx b/src/components/settings/tabs/updater/runWithDispatch.tsx new file mode 100644 index 00000000..719958e7 --- /dev/null +++ b/src/components/settings/tabs/updater/runWithDispatch.tsx @@ -0,0 +1,50 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ErrorCard } from "@components/ErrorCard"; +import { UpdateLogger } from "@utils/updater"; +import { Alerts, Parser } from "@webpack/common"; + +function getErrorMessage(e: any) { + if (!e?.code || !e.cmd) + return "An unknown error occurred.\nPlease try again or see the console for more info."; + + const { code, path, cmd, stderr } = e; + + if (code === "ENOENT") + return `Command \`${path}\` not found.\nPlease install it and try again.`; + + const extra = stderr || `Code \`${code}\`. See the console for more info.`; + + return `An error occurred while running \`${cmd}\`:\n${extra}`; +} + +export function runWithDispatch(dispatch: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) { + return async () => { + dispatch(true); + + try { + await action(); + } catch (e: any) { + UpdateLogger.error(e); + + const err = getErrorMessage(e); + + Alerts.show({ + title: "Oops!", + body: ( + <ErrorCard> + {err.split("\n").map((line, idx) => + <div key={idx}>{Parser.parse(line)}</div> + )} + </ErrorCard> + ) + }); + } finally { + dispatch(false); + } + }; +} diff --git a/src/components/settings/tabs/vencord/DonateButton.tsx b/src/components/settings/tabs/vencord/DonateButton.tsx new file mode 100644 index 00000000..df0bf408 --- /dev/null +++ b/src/components/settings/tabs/vencord/DonateButton.tsx @@ -0,0 +1,25 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import DonateButton from "@components/settings/DonateButton"; +import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants"; +import { Button, GuildMemberStore } from "@webpack/common"; +import BadgeAPI from "plugins/_api/badges"; + +export const isDonor = (userId: string) => !!( + BadgeAPI.getDonorBadges(userId)?.length > 0 + || GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) +); + +export function DonateButtonComponent() { + return ( + <DonateButton + look={Button.Looks.FILLED} + color={Button.Colors.WHITE} + style={{ marginTop: "1em" }} + /> + ); +} diff --git a/src/components/settings/tabs/vencord/MacVibrancySettings.tsx b/src/components/settings/tabs/vencord/MacVibrancySettings.tsx new file mode 100644 index 00000000..2f681064 --- /dev/null +++ b/src/components/settings/tabs/vencord/MacVibrancySettings.tsx @@ -0,0 +1,80 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useSettings } from "@api/Settings"; +import { Margins } from "@utils/margins"; +import { identity } from "@utils/misc"; +import { Forms, Select } from "@webpack/common"; + +export function VibrancySettings() { + const settings = useSettings(["macosVibrancyStyle"]); + + return ( + <> + <Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle> + <Select + className={Margins.bottom20} + placeholder="Window vibrancy style" + options={[ + // Sorted from most opaque to most transparent + { + label: "No vibrancy", value: undefined + }, + { + label: "Under Page (window tinting)", + value: "under-page" + }, + { + label: "Content", + value: "content" + }, + { + label: "Window", + value: "window" + }, + { + label: "Selection", + value: "selection" + }, + { + label: "Titlebar", + value: "titlebar" + }, + { + label: "Header", + value: "header" + }, + { + label: "Sidebar", + value: "sidebar" + }, + { + label: "Tooltip", + value: "tooltip" + }, + { + label: "Menu", + value: "menu" + }, + { + label: "Popover", + value: "popover" + }, + { + label: "Fullscreen UI (transparent but slightly muted)", + value: "fullscreen-ui" + }, + { + label: "HUD (Most transparent)", + value: "hud" + }, + ]} + select={v => settings.macosVibrancyStyle = v} + isSelected={v => settings.macosVibrancyStyle === v} + serialize={identity} /> + </> + ); +} diff --git a/src/components/VencordSettings/NotificationSettings.tsx b/src/components/settings/tabs/vencord/NotificationSettings.tsx similarity index 86% rename from src/components/VencordSettings/NotificationSettings.tsx rename to src/components/settings/tabs/vencord/NotificationSettings.tsx index c0fb393b..18b423cc 100644 --- a/src/components/VencordSettings/NotificationSettings.tsx +++ b/src/components/settings/tabs/vencord/NotificationSettings.tsx @@ -4,15 +4,46 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { useSettings } from "@api/Settings"; +import { ErrorCard } from "@components/ErrorCard"; +import { Flex } from "@components/Flex"; import { Margins } from "@utils/margins"; import { identity } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { Forms, Select, Slider, Text } from "@webpack/common"; +import { Button, Forms, Select, Slider, Text } from "@webpack/common"; -import { ErrorCard } from ".."; +export function NotificationSection() { + return ( + <Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5"> + <Flex> + <Button onClick={openNotificationSettingsModal}> + Notification Settings + </Button> + <Button onClick={openNotificationLogModal}> + View Notification Log + </Button> + </Flex> + </Forms.FormSection> + ); +} -export function NotificationSettings() { +export function openNotificationSettingsModal() { + openModal(props => ( + <ModalRoot {...props} size={ModalSize.MEDIUM}> + <ModalHeader> + <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Settings</Text> + <ModalCloseButton onClick={props.onClose} /> + </ModalHeader> + + <ModalContent> + <NotificationSettings /> + </ModalContent> + </ModalRoot> + )); +} + +function NotificationSettings() { const settings = useSettings().notifications; return ( @@ -89,18 +120,3 @@ export function NotificationSettings() { </div> ); } - -export function openNotificationSettingsModal() { - openModal(props => ( - <ModalRoot {...props} size={ModalSize.MEDIUM}> - <ModalHeader> - <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Settings</Text> - <ModalCloseButton onClick={props.onClose} /> - </ModalHeader> - - <ModalContent> - <NotificationSettings /> - </ModalContent> - </ModalRoot> - )); -} diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx new file mode 100644 index 00000000..19b8fe4f --- /dev/null +++ b/src/components/settings/tabs/vencord/index.tsx @@ -0,0 +1,211 @@ +/* + * 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 { openNotificationLogModal } from "@api/Notifications/notificationLog"; +import { useSettings } from "@api/Settings"; +import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/index"; +import { QuickAction, QuickActionCard } from "@components/settings/QuickAction"; +import { SpecialCard } from "@components/settings/SpecialCard"; +import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; +import { openContributorModal } from "@components/settings/tabs/plugins/ContributorModal"; +import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal"; +import { gitRemote } from "@shared/vencordUserAgent"; +import { IS_MAC, IS_WINDOWS } from "@utils/constants"; +import { Margins } from "@utils/margins"; +import { isPluginDev } from "@utils/misc"; +import { relaunch } from "@utils/native"; +import { Forms, React, Switch, useMemo, UserStore } from "@webpack/common"; + +import { DonateButtonComponent, isDonor } from "./DonateButton"; +import { VibrancySettings } from "./MacVibrancySettings"; +import { NotificationSection } from "./NotificationSettings"; + +const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; +const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png"; +const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png"; +const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png"; +const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048"; +const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048"; + +type KeysOfType<Object, Type> = { + [K in keyof Object]: Object[K] extends Type ? K : never; +}[keyof Object]; + +function Switches() { + const settings = useSettings(["useQuickCss", "enableReactDevtools", "frameless", "winNativeTitleBar", "transparent", "winCtrlQ", "disableMinSize"]); + + const Switches = [ + { + key: "useQuickCss", + title: "Enable Custom CSS", + note: "Loads your Custom CSS" + }, + !IS_WEB && { + key: "enableReactDevtools", + title: "Enable React Developer Tools", + note: "Requires a full restart" + }, + !IS_WEB && (!IS_DISCORD_DESKTOP || !IS_WINDOWS ? { + key: "frameless", + title: "Disable the window frame", + note: "Requires a full restart" + } : { + key: "winNativeTitleBar", + title: "Use Windows' native title bar instead of Discord's custom one", + note: "Requires a full restart" + }), + !IS_WEB && { + key: "transparent", + title: "Enable window transparency.", + note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart" + }, + !IS_WEB && IS_WINDOWS && { + key: "winCtrlQ", + title: "Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)", + note: "Requires a full restart" + }, + IS_DISCORD_DESKTOP && { + key: "disableMinSize", + title: "Disable minimum window size", + note: "Requires a full restart" + }, + ] satisfies Array<false | { + key: KeysOfType<typeof settings, boolean>; + title: string; + note: string; + }>; + + return Switches.map(s => s && ( + <Switch + key={s.key} + value={settings[s.key]} + onChange={v => settings[s.key] = v} + note={s.note} + > + {s.title} + </Switch> + )); +} + +function VencordSettings() { + const donateImage = useMemo(() => + Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, + [] + ); + + const needsVibrancySettings = IS_DISCORD_DESKTOP && IS_MAC; + + const user = UserStore.getCurrentUser(); + + return ( + <SettingsTab title="Vencord Settings"> + {isDonor(user?.id) + ? ( + <SpecialCard + title="Donations" + subtitle="Thank you for donating!" + description="You can manage your perks at any time by messaging @vending.machine." + cardImage={VENNIE_DONATOR_IMAGE} + backgroundImage={DONOR_BACKGROUND_IMAGE} + backgroundColor="#ED87A9" + > + <DonateButtonComponent /> + </SpecialCard> + ) + : ( + <SpecialCard + title="Support the Project" + description="Please consider supporting the development of Vencord by donating!" + cardImage={donateImage} + backgroundImage={DONOR_BACKGROUND_IMAGE} + backgroundColor="#c3a3ce" + > + <DonateButtonComponent /> + </SpecialCard> + ) + } + + {isPluginDev(user?.id) && ( + <SpecialCard + title="Contributions" + subtitle="Thank you for contributing!" + description="Since you've contributed to Vencord you now have a cool new badge!" + cardImage={COZY_CONTRIB_IMAGE} + backgroundImage={CONTRIB_BACKGROUND_IMAGE} + backgroundColor="#EDCC87" + buttonTitle="See what you've contributed to" + buttonOnClick={() => openContributorModal(user)} + /> + )} + + <Forms.FormSection title="Quick Actions"> + <QuickActionCard> + <QuickAction + Icon={LogIcon} + text="Notification Log" + action={openNotificationLogModal} + /> + <QuickAction + Icon={PaintbrushIcon} + text="Edit QuickCSS" + action={() => VencordNative.quickCss.openEditor()} + /> + {!IS_WEB && ( + <> + <QuickAction + Icon={RestartIcon} + text="Relaunch Discord" + action={relaunch} + /> + <QuickAction + Icon={FolderIcon} + text="Open Settings Folder" + action={() => VencordNative.settings.openFolder()} + /> + </> + )} + <QuickAction + Icon={GithubIcon} + text="View Source Code" + action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)} + /> + </QuickActionCard> + </Forms.FormSection> + + <Forms.FormDivider /> + + <Forms.FormSection className={Margins.top16} title="Settings" tag="h5"> + <Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}> + Hint: You can change the position of this settings section in the{" "} + <a onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}> + settings of the Settings plugin + </a>! + </Forms.FormText> + + <Switches /> + </Forms.FormSection> + + + {needsVibrancySettings && <VibrancySettings />} + + <NotificationSection /> + </SettingsTab> + ); +} + +export default wrapTab(VencordSettings, "Vencord Settings"); diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 8011e331..433c3080 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -19,11 +19,11 @@ import "./fixDiscordBadgePadding.css"; import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; -import DonateButton from "@components/DonateButton"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Heart } from "@components/Heart"; -import { openContributorModal } from "@components/PluginSettings/ContributorModal"; +import DonateButton from "@components/settings/DonateButton"; +import { openContributorModal } from "@components/settings/tabs"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 0eb8e13c..1f942d7d 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -17,13 +17,7 @@ */ import { Settings } from "@api/Settings"; -import BackupAndRestoreTab from "@components/VencordSettings/BackupAndRestoreTab"; -import CloudTab from "@components/VencordSettings/CloudTab"; -import PatchHelperTab from "@components/VencordSettings/PatchHelperTab"; -import PluginsTab from "@components/VencordSettings/PluginsTab"; -import ThemesTab from "@components/VencordSettings/ThemesTab"; -import UpdaterTab from "@components/VencordSettings/UpdaterTab"; -import VencordTab from "@components/VencordSettings/VencordTab"; +import { BackupAndRestoreTab, CloudTab, PatchHelperTab, PluginsTab, ThemesTab, UpdaterTab, VencordTab } from "@components/settings/tabs"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; @@ -90,7 +84,7 @@ export default definePlugin({ className: "vc-settings-header" }, { - section: "VencordSettings", + section: "settings/tabs", label: "Vencord", element: VencordTab, className: "vc-settings" @@ -120,7 +114,7 @@ export default definePlugin({ className: "vc-cloud" }, { - section: "VencordSettingsSync", + section: "settings/tabsSync", label: "Backup & Restore", element: BackupAndRestoreTab, className: "vc-backup-restore" diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 82e09b88..ff147216 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -21,7 +21,7 @@ import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; -import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; +import { openUpdaterModal } from "@components/settings/tabs/updater"; import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CATEGORY_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; @@ -87,7 +87,7 @@ async function generateDebugInfoMessage() { `v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` + `${SettingsPlugin.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, Client: `${RELEASE_CHANNEL} ~ ${client}`, - Platform: window.navigator.platform + Platform: navigator.platform }; if (IS_DISCORD_DESKTOP) { diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx index f3148c36..57e5ae19 100644 --- a/src/plugins/appleMusic.desktop/index.tsx +++ b/src/plugins/appleMusic.desktop/index.tsx @@ -5,7 +5,7 @@ */ import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; +import { Devs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; @@ -173,7 +173,7 @@ export default definePlugin({ name: "AppleMusicRichPresence", description: "Discord rich presence for your Apple Music!", authors: [Devs.RyanCaoDev], - hidden: !navigator.platform.startsWith("Mac"), + hidden: !IS_MAC, reporterTestable: ReporterTestable.None, settingsAboutComponent() { diff --git a/src/plugins/betterSettings/PluginsSubmenu.tsx b/src/plugins/betterSettings/PluginsSubmenu.tsx index ad7ded30..8a3e5b2a 100644 --- a/src/plugins/betterSettings/PluginsSubmenu.tsx +++ b/src/plugins/betterSettings/PluginsSubmenu.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { openPluginModal } from "@components/PluginSettings/PluginModal"; +import { openPluginModal } from "@components/settings/tabs"; import { getIntlMessage } from "@utils/discord"; import { isObjectEmpty } from "@utils/misc"; import { Alerts, Menu, useMemo, useState } from "@webpack/common"; diff --git a/src/plugins/consoleJanitor/index.tsx b/src/plugins/consoleJanitor/index.tsx index 7ef7ec9c..8a11cf01 100644 --- a/src/plugins/consoleJanitor/index.tsx +++ b/src/plugins/consoleJanitor/index.tsx @@ -5,7 +5,8 @@ */ import { definePluginSettings } from "@api/Settings"; -import { ErrorBoundary, Flex } from "@components/index"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import definePlugin, { defineDefault, OptionType, StartAt } from "@utils/types"; diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index b24f7a90..57a70d52 100644 --- a/src/plugins/ctrlEnterSend/index.ts +++ b/src/plugins/ctrlEnterSend/index.ts @@ -5,7 +5,7 @@ */ import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; +import { Devs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; export default definePlugin({ @@ -64,7 +64,7 @@ export default definePlugin({ result = event.shiftKey; break; case "ctrl+enter": - result = navigator.platform.includes("Mac") ? event.metaKey : event.ctrlKey; + result = IS_MAC ? event.metaKey : event.ctrlKey; break; case "enter": result = !event.shiftKey && !event.ctrlKey; diff --git a/src/plugins/customIdle/index.ts b/src/plugins/customIdle/index.ts index 9ffa889f..9a0a47a2 100644 --- a/src/plugins/customIdle/index.ts +++ b/src/plugins/customIdle/index.ts @@ -6,9 +6,8 @@ import { Notices } from "@api/index"; import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; import { FluxDispatcher } from "@webpack/common"; const settings = definePluginSettings({ diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx index 56c68410..d9cfd50c 100644 --- a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx @@ -12,8 +12,7 @@ import { classes, copyWithToast } from "@utils/misc"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { Queue } from "@utils/Queue"; import { User } from "@vencord/discord-types"; -import { findComponentByCodeLazy } from "@webpack"; -import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common"; +import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Decoration, getPresets, Preset } from "../../lib/api"; import { GUILD_ID, INVITE_KEY } from "../../lib/constants"; @@ -30,8 +29,6 @@ import SectionedGridList from "../components/SectionedGridList"; import { openCreateDecorationModal } from "./CreateDecorationModal"; import { openGuidelinesModal } from "./GuidelinesModal"; -const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); - function usePresets() { const [presets, setPresets] = useState<Preset[]>([]); useEffect(() => { getPresets().then(setPresets); }, []); diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 21c6088b..e916e158 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { ErrorCard } from "@components/ErrorCard"; -import { Devs } from "@utils/constants"; +import { Devs, IS_MAC } from "@utils/constants"; import { Margins } from "@utils/margins"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findLazy } from "@webpack"; @@ -31,9 +31,8 @@ import hideBugReport from "./hideBugReport.css?managed"; const KbdStyles = findByPropsLazy("key", "combo"); const BugReporterExperiment = findLazy(m => m?.definition?.id === "2024-09_bug_reporter"); -const isMacOS = navigator.platform.includes("Mac"); -const modKey = isMacOS ? "cmd" : "ctrl"; -const altKey = isMacOS ? "opt" : "alt"; +const modKey = IS_MAC ? "cmd" : "ctrl"; +const altKey = IS_MAC ? "opt" : "alt"; const settings = definePluginSettings({ toolbarDevMenu: { diff --git a/src/plugins/fixSpotifyEmbeds.desktop/index.ts b/src/plugins/fixSpotifyEmbeds.desktop/index.ts index 44199127..02a2845c 100644 --- a/src/plugins/fixSpotifyEmbeds.desktop/index.ts +++ b/src/plugins/fixSpotifyEmbeds.desktop/index.ts @@ -5,9 +5,8 @@ */ import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; // The entire code of this plugin can be found in ipcPlugins export default definePlugin({ diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 4fbfe3ff..3d25eda9 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -18,11 +18,10 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components"; import { debounce } from "@shared/debounce"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; import { createRoot, Menu } from "@webpack/common"; import { JSX } from "react"; import type { Root } from "react-dom/client"; diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index 33e99873..215268c6 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -17,14 +17,13 @@ */ import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; +import { Devs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { Message } from "@vencord/discord-types"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; import NoReplyMentionPlugin from "plugins/noReplyMention"; -const isMac = navigator.platform.includes("Mac"); // bruh let currentlyReplyingId: string | null = null; let currentlyEditingId: string | null = null; @@ -91,8 +90,8 @@ function onCreatePendingReply({ message, _isQuickReply }: { message: Message; _i currentlyReplyingId = message.id; } -const isCtrl = (e: KeyboardEvent) => isMac ? e.metaKey : e.ctrlKey; -const isAltOrMeta = (e: KeyboardEvent) => e.altKey || (!isMac && e.metaKey); +const isCtrl = (e: KeyboardEvent) => IS_MAC ? e.metaKey : e.ctrlKey; +const isAltOrMeta = (e: KeyboardEvent) => e.altKey || (!IS_MAC && e.metaKey); function onKeydown(e: KeyboardEvent) { const isUp = e.key === "ArrowUp"; diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 84b58199..34584242 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -18,10 +18,9 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; import { findByCodeLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, GuildRoleStore, GuildStore } from "@webpack/common"; diff --git a/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx b/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx index 926a6cae..b4682240 100644 --- a/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx +++ b/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx @@ -41,7 +41,6 @@ export interface HighlighterProps { lang?: string; content: string; isPreview: boolean; - tempSettings?: Record<string, any>; } export const createHighlighter = (props: HighlighterProps) => ( @@ -55,13 +54,12 @@ export const Highlighter = ({ lang, content, isPreview, - tempSettings, }: HighlighterProps) => { const { tryHljs, useDevIcon, bgOpacity, - } = useShikiSettings(["tryHljs", "useDevIcon", "bgOpacity"], tempSettings); + } = useShikiSettings(["tryHljs", "useDevIcon", "bgOpacity"]); const { id: currentThemeId, theme: currentTheme } = useTheme(); const shikiLang = lang ? resolveLang(lang) : null; diff --git a/src/plugins/shikiCodeblocks.desktop/hooks/useShikiSettings.ts b/src/plugins/shikiCodeblocks.desktop/hooks/useShikiSettings.ts index 22954ce1..020d77db 100644 --- a/src/plugins/shikiCodeblocks.desktop/hooks/useShikiSettings.ts +++ b/src/plugins/shikiCodeblocks.desktop/hooks/useShikiSettings.ts @@ -16,32 +16,27 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { PartialExcept } from "@utils/types"; import { React } from "@webpack/common"; import { shiki } from "../api/shiki"; import { settings as pluginSettings, ShikiSettings } from "../settings"; -export function useShikiSettings<F extends keyof ShikiSettings>(settingKeys: F[], overrides?: Partial<ShikiSettings>) { - const settings: Partial<ShikiSettings> = pluginSettings.use(settingKeys); +export function useShikiSettings<F extends keyof ShikiSettings>(settingKeys: F[]) { + const settings = pluginSettings.use([...settingKeys, "customTheme", "theme"]); const [isLoading, setLoading] = React.useState(false); - const withOverrides = { ...settings, ...overrides } as PartialExcept<ShikiSettings, F>; - const themeUrl = withOverrides.customTheme || withOverrides.theme; + const themeUrl = settings.customTheme || settings.theme; - if (overrides) { - const willChangeTheme = shiki.currentThemeUrl && themeUrl && themeUrl !== shiki.currentThemeUrl; - const noOverrides = Object.keys(overrides).length === 0; + const willChangeTheme = shiki.currentThemeUrl && themeUrl && themeUrl !== shiki.currentThemeUrl; - if (isLoading && (!willChangeTheme || noOverrides)) setLoading(false); - if (!isLoading && willChangeTheme) { - setLoading(true); - shiki.setTheme(themeUrl); - } + if (isLoading && (!willChangeTheme)) setLoading(false); + if (!isLoading && willChangeTheme) { + setLoading(true); + shiki.setTheme(themeUrl); } return { - ...withOverrides, - isThemeLoading: themeUrl !== shiki.currentThemeUrl, + ...settings, + isThemeLoading: isLoading, }; } diff --git a/src/plugins/shikiCodeblocks.desktop/index.ts b/src/plugins/shikiCodeblocks.desktop/index.ts index 1092f89a..670b2856 100644 --- a/src/plugins/shikiCodeblocks.desktop/index.ts +++ b/src/plugins/shikiCodeblocks.desktop/index.ts @@ -63,11 +63,10 @@ export default definePlugin({ shiki.destroy(); clearStyles(); }, - settingsAboutComponent: ({ tempSettings }) => createHighlighter({ + settingsAboutComponent: () => createHighlighter({ lang: "tsx", content: previewExampleText, - isPreview: true, - tempSettings, + isPreview: true }), // exports diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index ca90cc48..c0080a68 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -28,6 +28,7 @@ import definePlugin, { OptionType } from "@utils/types"; import { ConnectedAccount, User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { Tooltip, UserProfileStore } from "@webpack/common"; +import OpenInAppPlugin from "plugins/openInApp"; import { VerifiedIcon } from "./VerifiedIcon"; @@ -133,9 +134,8 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect rel="noreferrer" onClick={e => { if (Vencord.Plugins.isPluginEnabled("OpenInApp")) { - const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default; // handleLink will .preventDefault() if applicable - OpenInApp.handleLink(e.currentTarget, e); + OpenInAppPlugin.handleLink(e.currentTarget, e); } }} > diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 8f667de9..29cc3c21 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -24,12 +24,11 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; +import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:"); -const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const TypingStore = findStoreLazy("TypingStore"); const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); diff --git a/src/plugins/unlockedAvatarZoom/index.ts b/src/plugins/unlockedAvatarZoom/index.ts index 79c20474..dc8de74d 100644 --- a/src/plugins/unlockedAvatarZoom/index.ts +++ b/src/plugins/unlockedAvatarZoom/index.ts @@ -5,9 +5,8 @@ */ import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; const settings = definePluginSettings({ zoomMultiplier: { diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index 12d95242..d4d554ce 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { Channel } from "@vencord/discord-types"; import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack"; -import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores } from "@webpack/common"; +import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common"; const cl = classNameFactory("vc-uvs-"); @@ -20,7 +20,6 @@ const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", { const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({"); const VoiceStateStore = findStoreLazy("VoiceStateStore"); -const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const Avatar = findComponentByCodeLazy(".status)/2):0"); const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL"); diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index 117c1388..8931efe3 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -17,7 +17,7 @@ */ import { ErrorCard } from "@components/ErrorCard"; -import { Devs } from "@utils/constants"; +import { Devs, IS_LINUX } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { wordsToTitle } from "@utils/text"; @@ -44,9 +44,11 @@ const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentC // Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would // not say the second mute, which would lead you to believe they're unmuted -function speak(text: string, { volume, rate } = settings.store) { +function speak(text: string) { if (!text) return; + const { volume, rate } = settings.store; + const speech = new SpeechSynthesisUtterance(text); const voice = getCurrentVoice(); speech.voice = voice!; @@ -139,19 +141,17 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId, } */ -function playSample(tempSettings: any, type: string) { - const s = Object.assign({}, settings.plain, tempSettings); +function playSample(type: string) { const currentUser = UserStore.getCurrentUser(); const myGuildId = SelectedGuildStore.getGuildId(); speak(formatText( - s[type + "Message"], + settings.store[type + "Message"], currentUser.username, "general", currentUser.globalName ?? currentUser.username, - GuildMemberStore.getNick(myGuildId!, currentUser.id) ?? currentUser.username), - s - ); + GuildMemberStore.getNick(myGuildId!, currentUser.id) ?? currentUser.username + )); } export default definePlugin({ @@ -222,7 +222,7 @@ export default definePlugin({ }, - settingsAboutComponent({ tempSettings: s }) { + settingsAboutComponent() { const [hasVoices, hasEnglishVoices] = useMemo(() => { const voices = speechSynthesis.getVoices(); return [voices.length !== 0, voices.some(v => v.lang.startsWith("en"))]; @@ -236,7 +236,7 @@ export default definePlugin({ let errorComponent: ReactElement<any> | null = null; if (!hasVoices) { let error = "No narrator voices found. "; - error += navigator.platform?.toLowerCase().includes("linux") + error += IS_LINUX ? "Install speech-dispatcher or espeak and run Discord with the --enable-speech-dispatcher flag" : "Try installing some in the Narrator settings of your Operating System"; errorComponent = <ErrorCard>{error}</ErrorCard>; @@ -265,7 +265,7 @@ export default definePlugin({ className={"vc-narrator-buttons"} > {types.map(t => ( - <Button key={t} onClick={() => playSample(s, t)}> + <Button key={t} onClick={() => playSample(t)}> {wordsToTitle([t])} </Button> ))} diff --git a/src/plugins/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts index 62d2a2c1..664a59dd 100644 --- a/src/plugins/volumeBooster/index.ts +++ b/src/plugins/volumeBooster/index.ts @@ -17,9 +17,8 @@ */ import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { makeRange, OptionType } from "@utils/types"; const settings = definePluginSettings({ multiplier: { diff --git a/src/plugins/webKeybinds.web/index.ts b/src/plugins/webKeybinds.web/index.ts index a7435c95..6a0da357 100644 --- a/src/plugins/webKeybinds.web/index.ts +++ b/src/plugins/webKeybinds.web/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Devs } from "@utils/constants"; +import { Devs, IS_MAC } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common"; @@ -30,7 +30,7 @@ export default definePlugin({ enabledByDefault: true, onKey(e: KeyboardEvent) { - const hasCtrl = e.ctrlKey || (e.metaKey && navigator.platform.includes("Mac")); + const hasCtrl = e.ctrlKey || (e.metaKey && IS_MAC); if (hasCtrl) switch (e.key) { case "t": diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index a2826a2c..af6d8fdb 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -23,10 +23,9 @@ import { Queue } from "@utils/Queue"; import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types"; -import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common"; +import { findByPropsLazy } from "@webpack"; +import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common"; -const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); let Scroll: any = null; const queue = new Queue(); diff --git a/src/plugins/xsOverlay/index.tsx b/src/plugins/xsOverlay/index.tsx index 27faac93..ba184942 100644 --- a/src/plugins/xsOverlay/index.tsx +++ b/src/plugins/xsOverlay/index.tsx @@ -5,10 +5,9 @@ */ import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import definePlugin, { makeRange, OptionType, PluginNative, ReporterTestable } from "@utils/types"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "@vencord/discord-types"; import { findByCodeLazy, findLazy } from "@webpack"; import { Button, ChannelStore, GuildRoleStore, GuildStore, UserStore } from "@webpack/common"; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index afe1651f..962ea789 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -26,6 +26,11 @@ export const SUPPORT_CHANNEL_ID = "1026515880080842772"; export const SUPPORT_CATEGORY_ID = "1108135649699180705"; export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; +const platform = navigator.platform.toLowerCase(); +export const IS_WINDOWS = platform.startsWith("win"); +export const IS_MAC = platform.startsWith("mac"); +export const IS_LINUX = platform.startsWith("linux"); + export interface Dev { name: string; id: bigint; diff --git a/src/utils/react.tsx b/src/utils/react.tsx index f1c0f8f8..146725c5 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -145,3 +145,10 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) { return time; } + +export function useCleanupEffect( + effect: () => void, + deps?: React.DependencyList +): void { + useEffect(() => effect, deps); +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 1c53b971..2f1d6396 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -26,11 +26,18 @@ import { MessageClickListener, MessageEditListener, MessageSendListener } from " import { MessagePopoverButtonFactory } from "@api/MessagePopover"; import { Command, FluxEvents } from "@vencord/discord-types"; import { ReactNode } from "react"; -import { Promisable } from "type-fest"; // exists to export default definePlugin({...}) -export default function definePlugin<P extends PluginDef>(p: P & Record<string, any>) { - return p; +export default function definePlugin<P extends PluginDef>(p: P & Record<PropertyKey, any>) { + return p as typeof p & Plugin; +} + +export function makeRange(start: number, end: number, step = 1) { + const ranges: number[] = []; + for (let value = start; value <= end; value += step) { + ranges.push(Math.round(value * 100) / 100); + } + return ranges; } export type ReplaceFn = (match: string, ...groups: string[]) => string; @@ -135,18 +142,11 @@ export interface PluginDef { * Optionally provide settings that the user can configure in the Plugins tab of settings. */ settings?: DefinedSettings; - /** - * Check that this returns true before allowing a save to complete. - * If a string is returned, show the error to the user. - */ - beforeSave?(options: Record<string, any>): Promisable<true | string>; /** * Allows you to specify a custom Component that will be rendered in your * plugin's settings page */ - settingsAboutComponent?: React.ComponentType<{ - tempSettings?: Record<string, any>; - }>; + settingsAboutComponent?: React.ComponentType<{}>; /** * Allows you to subscribe to Flux events */ @@ -322,13 +322,6 @@ export interface IPluginOptionComponentProps { * NOTE: The user will still need to click save to apply these changes. */ setValue(newValue: any): void; - /** - * Set to true to prevent the user from saving. - * - * NOTE: This will not show the error to the user. It will only stop them saving. - * Make sure to show the error in your component. - */ - setError(error: boolean): void; /** * The options object */ diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index a392a02f..b25f3d4b 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -67,6 +67,7 @@ export const Avatar = waitForComponent<t.Avatar>("Avatar", filters.componentByCo export const ColorPicker = waitForComponent<t.ColorPicker>("ColorPicker", filters.componentByCode("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", "showEyeDropper")); +export const UserSummaryItem = waitForComponent("UserSummaryItem", filters.componentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin; export let scrollerClasses: Record<string, string>; From 828358bd2e28372ece3c19366c2446643e8ae24e Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:52:48 -0300 Subject: [PATCH 026/141] IrcColors: Fix chat colors broken patch --- src/plugins/ircColors/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index d0d020af..b6f18572 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -67,7 +67,7 @@ export default definePlugin({ find: '="SYSTEM_TAG"', replacement: { // Override colorString with our custom color and disable gradients if applying the custom color. - match: /(?<=colorString:\i,colorStrings:\i,colorRoleName:\i}=)(\i),/, + match: /(?<=colorString:\i,colorStrings:\i,colorRoleName:\i.*?}=)(\i),/, replace: "$self.wrapMessageColorProps($1, arguments[0])," } }, From d0869c41cd9668909f65e257d204a5591c79095d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:15:52 -0300 Subject: [PATCH 027/141] Settings: Improve layout of a setting section and error --- .../settings/tabs/plugins/PluginModal.tsx | 6 +++- .../plugins/components/BooleanSetting.tsx | 23 ++++-------- .../tabs/plugins/components/Common.tsx | 26 +++++++++----- .../tabs/plugins/components/NumberSetting.tsx | 2 +- .../tabs/plugins/components/SelectSetting.tsx | 4 +-- .../tabs/plugins/components/SliderSetting.tsx | 2 +- .../tabs/plugins/components/TextSetting.tsx | 4 +-- .../settings/tabs/plugins/components/index.ts | 2 ++ .../tabs/plugins/components/styles.css | 35 +++++++++++++++++++ .../settings/tabs/plugins/styles.css | 6 ++++ 10 files changed, 77 insertions(+), 33 deletions(-) create mode 100644 src/components/settings/tabs/plugins/components/styles.css diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index d34be261..55912d9d 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -116,7 +116,11 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti ); }); - return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>; + return ( + <div className="vc-plugins-settings"> + {options} + </div> + ); } function renderMoreUsers(_label: string, count: number) { diff --git a/src/components/settings/tabs/plugins/components/BooleanSetting.tsx b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx index c022fa2d..c5408cb9 100644 --- a/src/components/settings/tabs/plugins/components/BooleanSetting.tsx +++ b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx @@ -16,11 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { wordsFromCamel, wordsToTitle } from "@utils/text"; +import { Switch } from "@components/settings/Switch"; import { PluginOptionBoolean } from "@utils/types"; -import { Forms, React, Switch, useState } from "@webpack/common"; +import { React, useState } from "@webpack/common"; -import { resolveError, SettingProps } from "./Common"; +import { resolveError, SettingProps, SettingsSection } from "./Common"; export function BooleanSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps<PluginOptionBoolean>) { const def = pluginSettings[id] ?? option.default; @@ -40,20 +40,9 @@ export function BooleanSetting({ option, pluginSettings, definedSettings, id, on } return ( - <Forms.FormSection> - <Switch - value={state} - onChange={handleChange} - note={option.description} - disabled={option.disabled?.call(definedSettings) ?? false} - {...option.componentProps} - hideBorder - style={{ marginBottom: "0.5em" }} - > - {wordsToTitle(wordsFromCamel(id))} - </Switch> - {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} - </Forms.FormSection> + <SettingsSection name={id} description={option.description} error={error} inlineSetting> + <Switch checked={state} onChange={handleChange} /> + </SettingsSection> ); } diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx index 06083767..3d84859b 100644 --- a/src/components/settings/tabs/plugins/components/Common.tsx +++ b/src/components/settings/tabs/plugins/components/Common.tsx @@ -4,12 +4,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Margins } from "@utils/margins"; +import { classNameFactory } from "@api/Styles"; +import { classes } from "@utils/misc"; import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { DefinedSettings, PluginOptionBase } from "@utils/types"; -import { Forms } from "@webpack/common"; +import { Text } from "@webpack/common"; import { PropsWithChildren } from "react"; +export const cl = classNameFactory("vc-plugins-setting-"); + interface SettingBaseProps<T> { option: T; onChange(newValue: any): void; @@ -34,15 +37,20 @@ interface SettingsSectionProps extends PropsWithChildren { name: string; description: string; error: string | null; + inlineSetting?: boolean; } -export function SettingsSection({ name, description, error, children }: SettingsSectionProps) { +export function SettingsSection({ name, description, error, inlineSetting, children }: SettingsSectionProps) { return ( - <Forms.FormSection> - <Forms.FormTitle>{wordsToTitle(wordsFromCamel(name))}</Forms.FormTitle> - <Forms.FormText className={Margins.bottom20} type="description">{description}</Forms.FormText> - {children} - {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} - </Forms.FormSection> + <div className={cl("section")}> + <div className={classes(cl("content"), inlineSetting && cl("inline"))}> + <div className={cl("label")}> + {name && <Text className={cl("title")} variant="text-md/medium">{wordsToTitle(wordsFromCamel(name))}</Text>} + {description && <Text className={cl("description")} variant="text-sm/normal">{description}</Text>} + </div> + {children} + </div> + {error && <Text className={cl("error")} variant="text-sm/normal">{error}</Text>} + </div> ); } diff --git a/src/components/settings/tabs/plugins/components/NumberSetting.tsx b/src/components/settings/tabs/plugins/components/NumberSetting.tsx index 1b2e01a9..108ddc3e 100644 --- a/src/components/settings/tabs/plugins/components/NumberSetting.tsx +++ b/src/components/settings/tabs/plugins/components/NumberSetting.tsx @@ -53,9 +53,9 @@ export function NumberSetting({ option, pluginSettings, definedSettings, id, onC <TextInput type="number" pattern="-?[0-9]+" + placeholder={option.placeholder ?? "Enter a number"} value={state} onChange={handleChange} - placeholder={option.placeholder ?? "Enter a number"} disabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} /> diff --git a/src/components/settings/tabs/plugins/components/SelectSetting.tsx b/src/components/settings/tabs/plugins/components/SelectSetting.tsx index 35512a21..4c912c51 100644 --- a/src/components/settings/tabs/plugins/components/SelectSetting.tsx +++ b/src/components/settings/tabs/plugins/components/SelectSetting.tsx @@ -41,14 +41,14 @@ export function SelectSetting({ option, pluginSettings, definedSettings, onChang return ( <SettingsSection name={id} description={option.description} error={error}> <Select - isDisabled={option.disabled?.call(definedSettings) ?? false} - options={option.options} placeholder={option.placeholder ?? "Select an option"} + options={option.options} maxVisibleItems={5} closeOnSelect={true} select={handleChange} isSelected={v => v === state} serialize={v => String(v)} + isDisabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} /> </SettingsSection> diff --git a/src/components/settings/tabs/plugins/components/SliderSetting.tsx b/src/components/settings/tabs/plugins/components/SliderSetting.tsx index 936d8363..85a52da2 100644 --- a/src/components/settings/tabs/plugins/components/SliderSetting.tsx +++ b/src/components/settings/tabs/plugins/components/SliderSetting.tsx @@ -39,7 +39,6 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC return ( <SettingsSection name={id} description={option.description} error={error}> <Slider - disabled={option.disabled?.call(definedSettings) ?? false} markers={option.markers} minValue={option.markers[0]} maxValue={option.markers[option.markers.length - 1]} @@ -47,6 +46,7 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC onValueChange={handleChange} onValueRender={(v: number) => String(v.toFixed(2))} stickToMarkers={option.stickToMarkers ?? true} + disabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} /> </SettingsSection> diff --git a/src/components/settings/tabs/plugins/components/TextSetting.tsx b/src/components/settings/tabs/plugins/components/TextSetting.tsx index 759137f1..7eb4b9f9 100644 --- a/src/components/settings/tabs/plugins/components/TextSetting.tsx +++ b/src/components/settings/tabs/plugins/components/TextSetting.tsx @@ -40,11 +40,11 @@ export function TextSetting({ option, pluginSettings, definedSettings, id, onCha <SettingsSection name={id} description={option.description} error={error}> <TextInput type="text" + placeholder={option.placeholder ?? "Enter a value"} value={state} onChange={handleChange} - placeholder={option.placeholder ?? "Enter a value"} - disabled={option.disabled?.call(definedSettings) ?? false} maxLength={null} + disabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} /> </SettingsSection> diff --git a/src/components/settings/tabs/plugins/components/index.ts b/src/components/settings/tabs/plugins/components/index.ts index 6068dfef..0f1dd40c 100644 --- a/src/components/settings/tabs/plugins/components/index.ts +++ b/src/components/settings/tabs/plugins/components/index.ts @@ -16,6 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import "./styles.css"; + import { OptionType } from "@utils/types"; import { ComponentType } from "react"; diff --git a/src/components/settings/tabs/plugins/components/styles.css b/src/components/settings/tabs/plugins/components/styles.css new file mode 100644 index 00000000..9d72554e --- /dev/null +++ b/src/components/settings/tabs/plugins/components/styles.css @@ -0,0 +1,35 @@ +.vc-plugins-setting-section { + display: flex; + flex-direction: column; + gap: 0.5em; +} + +.vc-plugins-setting-content { + display: flex; + flex-direction: column; + gap: 0.5em; +} + +.vc-plugins-setting-inline { + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.vc-plugins-setting-label { + display: flex; + flex-direction: column; + gap: 0.25em; +} + +.vc-plugins-setting-title { + color: var(--header-primary); +} + +.vc-plugins-setting-description { + color: var(--header-secondary); +} + +.vc-plugins-setting-error { + color: var(--text-danger); +} diff --git a/src/components/settings/tabs/plugins/styles.css b/src/components/settings/tabs/plugins/styles.css index ff026192..7500054c 100644 --- a/src/components/settings/tabs/plugins/styles.css +++ b/src/components/settings/tabs/plugins/styles.css @@ -74,3 +74,9 @@ .vc-plugins-info-icon:not(:hover, :focus) { color: var(--text-muted); } + +.vc-plugins-settings { + display: flex; + flex-direction: column; + gap: 1.25em; +} From cdda1224ff6375d7b24014aaf1b40d57a1dff13f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 16 Jul 2025 20:23:06 -0300 Subject: [PATCH 028/141] UserVoiceShow: Improve icon in User Profile Modal V2 --- src/plugins/userVoiceShow/components.tsx | 10 +++++++--- src/plugins/userVoiceShow/index.tsx | 2 +- src/plugins/userVoiceShow/style.css | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index d4d554ce..9d1c7211 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -27,6 +27,7 @@ const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); interface IconProps extends React.ComponentPropsWithoutRef<"div"> { size?: number; + iconClassName?: string; } function SpeakerIcon(props: IconProps) { @@ -39,6 +40,7 @@ function SpeakerIcon(props: IconProps) { className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined, props.className)} > <svg + className={props.iconClassName} width={props.size} height={props.size} viewBox="0 0 24 24" @@ -129,13 +131,14 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) { export interface VoiceChannelIndicatorProps { userId: string; + isProfile?: boolean; isActionButton?: boolean; shouldHighlight?: boolean; } const clickTimers = {} as Record<string, any>; -export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { +export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); @@ -179,8 +182,9 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButto {props => { const iconProps: IconProps = { ...props, - className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight), - size: isActionButton ? 20 : undefined, + className: classes(isActionButton && ActionButtonClasses.actionButton, isActionButton && shouldHighlight && ActionButtonClasses.highlight), + iconClassName: classes(isProfile && cl("profile-speaker")), + size: isActionButton ? 20 : 16, onClick }; diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx index 0ee41414..bc1ce30b 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -60,7 +60,7 @@ export default definePlugin({ find: "#{intl::USER_PROFILE_LOAD_ERROR}", replacement: { match: /(\.fetchError.+?\?)null/, - replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})` + replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})` }, predicate: () => settings.store.showInUserProfileModal }, diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css index f9fd56fb..32b42208 100644 --- a/src/plugins/userVoiceShow/style.css +++ b/src/plugins/userVoiceShow/style.css @@ -13,6 +13,11 @@ color: var(--interactive-hover); } +.vc-uvs-profile-speaker { + width: var(--custom-nickname-icon-size); + height: var(--custom-nickname-icon-size); +} + .vc-uvs-tooltip-container { max-width: 300px; } From 09ed2c78ba0eda30d1ad621f49094bf290bd14d9 Mon Sep 17 00:00:00 2001 From: dorkbutt <git@nomadants.net> Date: Thu, 17 Jul 2025 19:19:09 -0400 Subject: [PATCH 029/141] updated to Vencord v1.12.6 --- src/plugins/usrbg-fork/index.tsx | 120 +++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/plugins/usrbg-fork/index.tsx diff --git a/src/plugins/usrbg-fork/index.tsx b/src/plugins/usrbg-fork/index.tsx new file mode 100644 index 00000000..d77e8780 --- /dev/null +++ b/src/plugins/usrbg-fork/index.tsx @@ -0,0 +1,120 @@ +/* + * 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 { Link } from "@components/Link"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const API_URL = "https://usrbg.dorkbutt.lol/users/users.json"; + +interface UsrbgApiReturn { + endpoint: string; + bucket: string; + prefix: string; + users: Record<string, string>; +} + +const settings = definePluginSettings({ + nitroFirst: { + description: "Banner to use if both Nitro and USRBG banners are present", + type: OptionType.SELECT, + options: [ + { label: "Nitro banner", value: true, default: true }, + { label: "USRBG banner", value: false }, + ] + }, + voiceBackground: { + description: "Use USRBG banners as voice chat backgrounds", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "FORKED - USRBG", + description: "Displays user banners from dorkbutt's USRBG server, allowing anyone to get a banner without Nitro", + authors: [Devs.dorkbutt, Devs.AutumnVN, Devs.katlyn, Devs.pylix, Devs.TheKodeToad], + settings, + patches: [ + { + find: '.banner)==null?"COMPLETE"', + replacement: { + match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, + replace: "$self.patchBannerUrl(arguments[0])||$&" + + } + }, + { + find: "\"data-selenium-video-tile\":", + predicate: () => settings.store.voiceBackground, + replacement: [ + { + match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, + replace: "$1.style=$self.getVoiceBackgroundStyles($1);" + } + ] + } + ], + + data: null as UsrbgApiReturn | null, + + settingsAboutComponent: () => { + return ( + "Message @dorkbutt about getting your personal banner added!" + ); + }, + + getVoiceBackgroundStyles({ className, participantUserId }: any) { + if (className.includes("tile_")) { + if (this.userHasBackground(participantUserId)) { + return { + backgroundImage: `url(${this.getImageUrl(participantUserId)})`, + backgroundSize: "cover", + backgroundPosition: "center", + backgroundRepeat: "no-repeat" + }; + } + } + }, + + patchBannerUrl({ displayProfile }: any) { + if (displayProfile?.banner && settings.store.nitroFirst) return; + if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId); + }, + + userHasBackground(userId: string) { + return !!this.data?.users[userId]; + }, + + getImageUrl(userId: string): string | null { + if (!this.userHasBackground(userId)) return null; + + // We can assert that data exists because userHasBackground returned true + const { endpoint, bucket, prefix, users: { [userId]: etag } } = this.data!; + return `${endpoint}/${bucket}/${prefix}${userId}?${etag}`; + }, + + async start() { + const res = await fetch(API_URL); + if (res.ok) { + this.data = await res.json(); + } + } +}); From bef4b013823954c81ef439f3a1c6d1e8e3d5c809 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:14:59 -0400 Subject: [PATCH 030/141] fix Expression Cloner, ServerInfo & SeeSummaries plugins (#3536) --- src/plugins/expressionCloner/index.tsx | 14 ++++++++++---- src/plugins/pauseInvitesForever/index.tsx | 6 +++--- src/plugins/seeSummaries/index.tsx | 8 +++++--- src/plugins/serverInfo/GuildInfoModal.tsx | 4 ++-- src/utils/discord.tsx | 13 +++++++++++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index c1244fae..6f84e7f7 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -20,12 +20,13 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { migratePluginSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Devs } from "@utils/constants"; +import { getGuildAcronym } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; -import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Guild } from "discord-types/general"; import { Promisable } from "type-fest"; @@ -250,13 +251,18 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) { width: "100%", height: "100%", }} - src={g.getIconURL(512, true)} + src={IconUtils.getGuildIconURL({ + id: g.id, + icon: g.icon, + canAnimate: true, + size: 512 + })} alt={g.name} /> ) : ( <Forms.FormText style={{ - fontSize: getFontSize(g.acronym), + fontSize: getFontSize(getGuildAcronym(g)), width: "100%", overflow: "hidden", whiteSpace: "nowrap", @@ -264,7 +270,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) { cursor: isCloning ? "not-allowed" : "pointer", }} > - {g.acronym} + {getGuildAcronym(g)} </Forms.FormText> )} </div> diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 432d1c1c..577d8bd7 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -18,7 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { getIntlMessage } from "@utils/discord"; +import { getIntlMessage, hasGuildFeature } from "@utils/discord"; import definePlugin from "@utils/types"; import { Constants, GuildStore, PermissionStore, RestAPI } from "@webpack/common"; @@ -27,8 +27,8 @@ function showDisableInvites(guildId: string) { if (!guild) return false; return ( - // @ts-ignore - !guild.hasFeature("INVITES_DISABLED") && + // @ts-expect-error + !hasGuildFeature(guild, "INVITES_DISABLED") && PermissionStore.getGuildPermissionProps(guild).canManageRoles ); } diff --git a/src/plugins/seeSummaries/index.tsx b/src/plugins/seeSummaries/index.tsx index de50e0a9..343348f1 100644 --- a/src/plugins/seeSummaries/index.tsx +++ b/src/plugins/seeSummaries/index.tsx @@ -7,6 +7,7 @@ import { DataStore } from "@api/index"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; +import { hasGuildFeature } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { ChannelStore, GuildStore } from "@webpack/common"; @@ -57,7 +58,7 @@ export default definePlugin({ { find: "SUMMARIZEABLE.has", replacement: { - match: /\i\.hasFeature\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g, + match: /\i\.features\.has\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g, replace: "true" } }, @@ -109,7 +110,8 @@ export default definePlugin({ const channel = ChannelStore.getChannel(channelId); // SUMMARIES_ENABLED feature is not in discord-types const guild = GuildStore.getGuild(channel.guild_id); - // @ts-ignore - return guild.hasFeature("SUMMARIES_ENABLED_GA"); + + // @ts-expect-error + return hasGuildFeature(guild, "SUMMARIES_ENABLED_GA"); } }); diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index c62352f2..359590cf 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -7,7 +7,7 @@ import "./styles.css"; import { classNameFactory } from "@api/Styles"; -import { openImageModal, openUserProfile } from "@utils/discord"; +import { getGuildAcronym, openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; @@ -103,7 +103,7 @@ function GuildInfoModal({ guild }: GuildProps) { width: 512, })} /> - : <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{guild.acronym}</div> + : <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{getGuildAcronym(guild)}</div> } <div className={cl("name-and-description")}> diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index e04ad201..fce909c3 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -19,6 +19,7 @@ import { MessageObject } from "@api/MessageEvents"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Channel, Guild, Message, User } from "discord-types/general"; +import GuildFeatures from "discord-types/other/Constants"; import { Except } from "type-fest"; import { runtimeHashMessageKey } from "./intlHash"; @@ -218,3 +219,15 @@ export function getEmojiURL(id: string, animated: boolean, size: number) { const url = IconUtils.getEmojiURL({ id, animated, size }); return animated ? url.replace(".webp", ".gif") : url; } + +// Discord has a similar function in their code +export function getGuildAcronym(guild: Guild): string { + return guild.name + .replaceAll("'s ", " ") + .replace(/\w+/g, m => m[0]) + .replace(/\s/g, ""); +} + +export function hasGuildFeature(guild: Guild, feature: keyof GuildFeatures["GuildFeatures"]): boolean { + return guild.features?.has(feature) ?? false; +} From 0e75d8a42f04ce1a376d3a4eeb2346e359c16db8 Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:04:12 +0200 Subject: [PATCH 031/141] Fix ImplicitRelationships & RelationshipNotifier (#3539) --- src/plugins/implicitRelationships/index.ts | 3 +-- src/plugins/relationshipNotifier/utils.ts | 4 ++-- src/webpack/common/types/stores.d.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index 4a8cf5fb..d57370a0 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -125,12 +125,11 @@ export default definePlugin({ // Implicit relationships are defined as users that you: // 1. Have an affinity for // 2. Do not have a relationship with - await this.refreshUserAffinities(); const userAffinities: Record<string, any>[] = UserAffinitiesStore.getUserAffinities(); const relationships = RelationshipStore.getMutableRelationships(); const nonFriendAffinities = userAffinities.filter(a => !RelationshipStore.getRelationshipType(a.otherUserId)); nonFriendAffinities.forEach(a => { - relationships[a.otherUserId] = 5; + relationships.set(a.otherUserId, 5); }); RelationshipStore.emitChange(); diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts index 75cf2d3a..09ef5cdd 100644 --- a/src/plugins/relationshipNotifier/utils.ts +++ b/src/plugins/relationshipNotifier/utils.ts @@ -173,8 +173,8 @@ export async function syncFriends() { friends.requests = []; const relationShips = RelationshipStore.getMutableRelationships(); - for (const id in relationShips) { - switch (relationShips[id]) { + for (const [id, type] of relationShips) { + switch (type) { case RelationshipType.FRIEND: friends.friends.push(id); break; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 9a2dd132..f0235986 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -255,5 +255,5 @@ export class RelationshipStore extends FluxStore { getSince(userId: string): string; /** @returns Format: [userId: Enum value from constants.RelationshipTypes] */ - getMutableRelationships(): Record<number, number>; + getMutableRelationships(): Map<string, number>; } From 2f6dfd9bee09f253d1e73509e8bf110a28240a8e Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:09:10 -0700 Subject: [PATCH 032/141] Experiments: fix edge case in experiment rollout detection (#3497) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/experiments/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 967197ad..eee732d0 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -68,8 +68,8 @@ export default definePlugin({ { find: 'type:"user",revision', replacement: { - match: /!(\i)&&"CONNECTION_OPEN".+?;/g, - replace: "$1=!0;" + match: /!(\i)(?=&&"CONNECTION_OPEN")/, + replace: "!($1=true)" } }, { From 34fa7cba208535e78c6350a4ce14a2b4f2bdcdd4 Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Tue, 8 Jul 2025 01:04:14 +0200 Subject: [PATCH 033/141] MutualGroupDMs: fix member count label (#3541) --- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 796a91db..b90eb7ed 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -24,7 +24,7 @@ import { isNonNullish } from "@utils/guards"; import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; +import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, Text, useMemo, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; import { JSX } from "react"; @@ -73,7 +73,7 @@ function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { </Avatar> <div className={MutualsListClasses.details}> <div className={MutualsListClasses.name}>{getGroupDMName(c)}</div> - <div className={MutualsListClasses.nick}>{c.recipients.length + 1} Members</div> + <Text variant="text-xs/medium">{c.recipients.length + 1} Members</Text> </div> </Clickable> )); From 3bbf885146072ce17bc06ebc71b42f64152ea2e3 Mon Sep 17 00:00:00 2001 From: nyx <60797172+verticalsync@users.noreply.github.com> Date: Tue, 8 Jul 2025 02:05:56 +0300 Subject: [PATCH 034/141] ShowMeYourName: fix gradient role compatibility (#3495) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/showMeYourName/styles.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/showMeYourName/styles.css b/src/plugins/showMeYourName/styles.css index 7a1455d9..e93fc451 100644 --- a/src/plugins/showMeYourName/styles.css +++ b/src/plugins/showMeYourName/styles.css @@ -1,5 +1,7 @@ .vc-smyn-suffix { color: var(--text-muted); + -webkit-text-fill-color: initial; + isolation: isolate; } .vc-smyn-suffix::before { From 619b0ef8581c3b4e822597c381f41f2aca1e348a Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Thu, 10 Jul 2025 00:38:39 +0200 Subject: [PATCH 035/141] refactor discord types into separate npm package (#3520) Co-authored-by: V <vendicated@riseup.net> Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com> Co-authored-by: ezzud <contact@ezzud.fr> --- package.json | 2 +- packages/discord-types/LICENSE | 165 +++++++++++ packages/discord-types/README.md | 42 +++ packages/discord-types/enums/commands.ts | 27 ++ packages/discord-types/enums/index.ts | 1 + packages/discord-types/package.json | 19 ++ packages/discord-types/src/classes.d.ts | 21 ++ .../discord-types/src/common/Channel.d.ts | 83 ++++++ packages/discord-types/src/common/Guild.d.ts | 64 +++++ .../discord-types/src/common/GuildMember.d.ts | 26 ++ packages/discord-types/src/common/Record.d.ts | 12 + packages/discord-types/src/common/Role.d.ts | 33 +++ packages/discord-types/src/common/User.d.ts | 66 +++++ packages/discord-types/src/common/index.d.ts | 7 + .../src/common/messages/Commands.d.ts | 61 +++++ .../src/common/messages/Embed.d.ts | 70 +++++ .../src/common/messages/Emoji.d.ts | 42 +++ .../src/common/messages/Message.d.ts | 191 +++++++++++++ .../src/common/messages/index.d.ts | 4 + .../discord-types/src}/components.d.ts | 22 +- packages/discord-types/src/flux.d.ts | 30 ++ packages/discord-types/src/fluxEvents.d.ts | 22 ++ packages/discord-types/src/index.d.ts | 9 + .../discord-types/src}/menu.d.ts | 20 +- .../src/stores/ChannelStore.d.ts | 24 ++ .../discord-types/src/stores/DraftStore.d.ts | 43 +++ .../discord-types/src/stores/EmojiStore.d.ts | 57 ++++ .../discord-types/src/stores/FluxStore.d.ts | 44 +++ .../src/stores/GuildMemberStore.d.ts | 27 ++ .../src/stores/GuildRoleStore.d.ts | 7 + .../discord-types/src/stores/GuildStore.d.ts | 8 + .../src/stores/MessageStore.d.ts | 13 + .../src/stores/RelationshipStore.d.ts | 21 ++ .../src/stores/SelectedChannelStore.d.ts | 14 + .../src/stores/SelectedGuildStore.d.ts | 14 + .../discord-types/src/stores/ThemeStore.d.ts | 18 ++ .../discord-types/src/stores/UserStore.d.ts | 10 + .../discord-types/src/stores/WindowStore.d.ts | 7 + packages/discord-types/src/stores/index.d.ts | 32 +++ .../discord-types/src}/utils.d.ts | 36 ++- .../discord-types/webpack/index.d.ts | 27 +- packages/vencord-types/package.json | 1 - pnpm-lock.yaml | 43 ++- scripts/build/build.mjs | 2 +- scripts/build/common.mjs | 2 +- src/api/ChatButtons.tsx | 2 +- src/api/Commands/commandHelpers.ts | 10 +- src/api/Commands/index.ts | 25 +- src/api/Commands/types.ts | 108 +------- src/api/DataStore/index.ts | 4 +- src/api/MemberListDecorators.tsx | 2 +- src/api/MessageDecorations.tsx | 2 +- src/api/MessageEvents.ts | 3 +- src/api/MessagePopover.tsx | 2 +- src/api/MessageUpdater.ts | 3 +- src/api/Settings.ts | 2 +- src/components/DonateButton.tsx | 2 +- .../PluginSettings/ContributorModal.tsx | 2 +- src/components/PluginSettings/PluginModal.tsx | 2 +- src/debug/loadLazyChunks.ts | 3 +- src/debug/runReporter.ts | 3 +- src/main/patcher.ts | 2 +- src/plugins/_api/badges/index.tsx | 2 +- src/plugins/_core/noTrack.ts | 2 +- src/plugins/_core/settings.tsx | 2 +- src/plugins/_core/supportHelper.tsx | 3 +- .../accountPanelServerProfile/index.tsx | 2 +- src/plugins/biggerStreamPreview/index.tsx | 2 +- .../webpack/types/stores.ts | 2 +- src/plugins/copyUserURLs/index.tsx | 2 +- .../decor/lib/stores/UsersDecorationsStore.ts | 2 +- .../decor/ui/modals/ChangeDecorationModal.tsx | 2 +- src/plugins/expressionCloner/index.tsx | 2 +- src/plugins/fakeNitro/index.tsx | 4 +- src/plugins/fakeProfileThemes/index.tsx | 8 +- src/plugins/favEmojiFirst/index.ts | 2 +- src/plugins/forceOwnerCrown/index.ts | 2 +- src/plugins/fullSearchContext/index.tsx | 2 +- src/plugins/greetStickerPicker/index.tsx | 2 +- src/plugins/hideAttachments/index.tsx | 4 +- src/plugins/index.ts | 2 +- src/plugins/invisibleChat.desktop/index.tsx | 2 +- src/plugins/memberCount/index.tsx | 2 +- src/plugins/mentionAvatars/index.tsx | 2 +- src/plugins/messageLatency/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 6 +- src/plugins/messageLogger/index.tsx | 2 +- src/plugins/messageTags/index.ts | 4 +- src/plugins/mutualGroupDMs/index.tsx | 2 +- src/plugins/newGuildSettings/index.tsx | 2 +- src/plugins/noBlockedMessages/index.ts | 2 +- src/plugins/noReplyMention/index.tsx | 2 +- src/plugins/onePingPerDM/index.ts | 2 +- src/plugins/pauseInvitesForever/index.tsx | 1 - .../components/RolesAndUsersPermissions.tsx | 3 +- .../components/UserPermissions.tsx | 2 +- src/plugins/permissionsViewer/index.tsx | 4 +- src/plugins/permissionsViewer/utils.ts | 2 +- src/plugins/petpet/index.ts | 7 +- src/plugins/pinDms/index.tsx | 2 +- src/plugins/platformIndicators/index.tsx | 2 +- src/plugins/previewMessage/index.tsx | 2 +- src/plugins/quickReply/index.ts | 2 +- .../readAllNotificationsButton/index.tsx | 2 +- src/plugins/relationshipNotifier/types.ts | 2 +- src/plugins/relationshipNotifier/utils.ts | 2 +- src/plugins/replyTimestamp/index.tsx | 2 +- src/plugins/reviewDB/index.tsx | 2 +- src/plugins/seeSummaries/index.tsx | 1 - src/plugins/serverInfo/GuildInfoModal.tsx | 2 +- src/plugins/serverInfo/index.tsx | 2 +- src/plugins/showConnections/index.tsx | 2 +- .../components/HiddenChannelLockScreen.tsx | 2 +- src/plugins/showHiddenChannels/index.tsx | 2 +- src/plugins/showMeYourName/index.tsx | 4 +- src/plugins/showTimeoutDuration/index.tsx | 2 +- src/plugins/sortFriendRequests/index.tsx | 2 +- src/plugins/spotifyShareCommands/index.ts | 3 +- src/plugins/themeAttributes/index.ts | 2 +- .../translate/TranslationAccessory.tsx | 2 +- src/plugins/typingIndicator/index.tsx | 2 +- src/plugins/typingTweaks/index.tsx | 2 +- src/plugins/unsuppressEmbeds/index.tsx | 2 +- .../PronounsChatComponent.tsx | 2 +- src/plugins/userVoiceShow/components.tsx | 2 +- src/plugins/validReply/index.ts | 3 +- src/plugins/vcNarrator/index.tsx | 11 +- src/plugins/viewIcons/index.tsx | 2 +- src/plugins/viewRaw/index.tsx | 2 +- src/plugins/whoReacted/index.tsx | 3 +- src/plugins/xsOverlay/index.tsx | 2 +- src/utils/apng-canvas.js | 19 +- src/utils/discord.tsx | 5 +- src/utils/types.ts | 3 +- src/webpack/common/classes.ts | 3 +- src/webpack/common/components.ts | 2 +- src/webpack/common/index.ts | 3 - src/webpack/common/menu.ts | 3 +- src/webpack/common/stores.ts | 24 +- src/webpack/common/types/classes.d.ts | 39 --- src/webpack/common/types/fluxEvents.d.ts | 40 --- src/webpack/common/types/index.d.ts | 24 -- src/webpack/common/types/stores.d.ts | 259 ------------------ src/webpack/common/utils.ts | 6 +- src/webpack/index.ts | 2 +- src/webpack/patchWebpack.ts | 3 +- src/webpack/types.ts | 28 ++ src/webpack/webpack.ts | 5 +- tsconfig.json | 3 +- 149 files changed, 1561 insertions(+), 751 deletions(-) create mode 100644 packages/discord-types/LICENSE create mode 100644 packages/discord-types/README.md create mode 100644 packages/discord-types/enums/commands.ts create mode 100644 packages/discord-types/enums/index.ts create mode 100644 packages/discord-types/package.json create mode 100644 packages/discord-types/src/classes.d.ts create mode 100644 packages/discord-types/src/common/Channel.d.ts create mode 100644 packages/discord-types/src/common/Guild.d.ts create mode 100644 packages/discord-types/src/common/GuildMember.d.ts create mode 100644 packages/discord-types/src/common/Record.d.ts create mode 100644 packages/discord-types/src/common/Role.d.ts create mode 100644 packages/discord-types/src/common/User.d.ts create mode 100644 packages/discord-types/src/common/index.d.ts create mode 100644 packages/discord-types/src/common/messages/Commands.d.ts create mode 100644 packages/discord-types/src/common/messages/Embed.d.ts create mode 100644 packages/discord-types/src/common/messages/Emoji.d.ts create mode 100644 packages/discord-types/src/common/messages/Message.d.ts create mode 100644 packages/discord-types/src/common/messages/index.d.ts rename {src/webpack/common/types => packages/discord-types/src}/components.d.ts (89%) create mode 100644 packages/discord-types/src/flux.d.ts create mode 100644 packages/discord-types/src/fluxEvents.d.ts create mode 100644 packages/discord-types/src/index.d.ts rename {src/webpack/common/types => packages/discord-types/src}/menu.d.ts (71%) create mode 100644 packages/discord-types/src/stores/ChannelStore.d.ts create mode 100644 packages/discord-types/src/stores/DraftStore.d.ts create mode 100644 packages/discord-types/src/stores/EmojiStore.d.ts create mode 100644 packages/discord-types/src/stores/FluxStore.d.ts create mode 100644 packages/discord-types/src/stores/GuildMemberStore.d.ts create mode 100644 packages/discord-types/src/stores/GuildRoleStore.d.ts create mode 100644 packages/discord-types/src/stores/GuildStore.d.ts create mode 100644 packages/discord-types/src/stores/MessageStore.d.ts create mode 100644 packages/discord-types/src/stores/RelationshipStore.d.ts create mode 100644 packages/discord-types/src/stores/SelectedChannelStore.d.ts create mode 100644 packages/discord-types/src/stores/SelectedGuildStore.d.ts create mode 100644 packages/discord-types/src/stores/ThemeStore.d.ts create mode 100644 packages/discord-types/src/stores/UserStore.d.ts create mode 100644 packages/discord-types/src/stores/WindowStore.d.ts create mode 100644 packages/discord-types/src/stores/index.d.ts rename {src/webpack/common/types => packages/discord-types/src}/utils.d.ts (92%) rename src/webpack/wreq.d.ts => packages/discord-types/webpack/index.d.ts (90%) delete mode 100644 src/webpack/common/types/classes.d.ts delete mode 100644 src/webpack/common/types/fluxEvents.d.ts delete mode 100644 src/webpack/common/types/index.d.ts delete mode 100644 src/webpack/common/types/stores.d.ts create mode 100644 src/webpack/types.ts diff --git a/package.json b/package.json index 8a6173ef..81b9a1c5 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@types/yazl": "^2.4.5", + "@vencord/discord-types": "link:packages/discord-types", "diff": "^7.0.0", - "discord-types": "^1.3.26", "esbuild": "^0.25.1", "eslint": "9.20.1", "eslint-import-resolver-alias": "^1.1.2", diff --git a/packages/discord-types/LICENSE b/packages/discord-types/LICENSE new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/packages/discord-types/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/packages/discord-types/README.md b/packages/discord-types/README.md new file mode 100644 index 00000000..82a98dd8 --- /dev/null +++ b/packages/discord-types/README.md @@ -0,0 +1,42 @@ +# Discord Types + +This package provides TypeScript types for the Webpack modules of Discord's web app. + +While it was primarily created for Vencord, other client mods could also benefit from this, so it is published as a standalone package! + +## Installation + +```bash +npm install -D @vencord/discord-types +yarn add -D @vencord/discord-types +pnpm add -D @vencord/discord-types +``` + +## Example Usage + +```ts +import type { UserStore } from "@vencord/discord-types"; + +const userStore: UserStore = findStore("UserStore"); // findStore is up to you to implement, this library only provides types and no runtime code +``` + +## Enums + +This library also exports some const enums that you can use from Typescript code: +```ts +import { ApplicationCommandType } from "@vencord/discord-types/enums"; + +console.log(ApplicationCommandType.CHAT_INPUT); // 1 +``` + +### License + +This package is licensed under the [LGPL-3.0](./LICENSE) (or later) license. + +A very short summary of the license is that you can use this package as a library in both open source and closed source projects, +similar to an MIT-licensed project. +However, if you modify the code of this package, you must release source code of your modified version under the same license. + +### Credit + +This package was inspired by Swishilicous' [discord-types](https://www.npmjs.com/package/discord-types) package. diff --git a/packages/discord-types/enums/commands.ts b/packages/discord-types/enums/commands.ts new file mode 100644 index 00000000..0556e72d --- /dev/null +++ b/packages/discord-types/enums/commands.ts @@ -0,0 +1,27 @@ +export const enum ApplicationCommandOptionType { + SUB_COMMAND = 1, + SUB_COMMAND_GROUP = 2, + STRING = 3, + INTEGER = 4, + BOOLEAN = 5, + USER = 6, + CHANNEL = 7, + ROLE = 8, + MENTIONABLE = 9, + NUMBER = 10, + ATTACHMENT = 11, +} + +export const enum ApplicationCommandInputType { + BUILT_IN = 0, + BUILT_IN_TEXT = 1, + BUILT_IN_INTEGRATION = 2, + BOT = 3, + PLACEHOLDER = 4, +} + +export const enum ApplicationCommandType { + CHAT_INPUT = 1, + USER = 2, + MESSAGE = 3, +} diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts new file mode 100644 index 00000000..62074d46 --- /dev/null +++ b/packages/discord-types/enums/index.ts @@ -0,0 +1 @@ +export * from "./commands"; diff --git a/packages/discord-types/package.json b/packages/discord-types/package.json new file mode 100644 index 00000000..7b8726fd --- /dev/null +++ b/packages/discord-types/package.json @@ -0,0 +1,19 @@ +{ + "name": "@vencord/discord-types", + "author": "Vencord Contributors", + "description": "Typescript definitions for the webpack modules of the Discord Web app", + "version": "1.0.0", + "license": "LGPL-3.0-or-later", + "types": "src/index.d.ts", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/Vendicated/Vencord.git", + "directory": "packages/discord-types" + }, + "dependencies": { + "@types/react": "^19.0.10", + "moment": "^2.22.2", + "type-fest": "^4.41.0" + } +} diff --git a/packages/discord-types/src/classes.d.ts b/packages/discord-types/src/classes.d.ts new file mode 100644 index 00000000..752ba3f0 --- /dev/null +++ b/packages/discord-types/src/classes.d.ts @@ -0,0 +1,21 @@ +export interface ImageModalClasses { + image: string, + modal: string, +} + +export interface ButtonWrapperClasses { + hoverScale: string; + buttonWrapper: string; + button: string; + iconMask: string; + buttonContent: string; + icon: string; + pulseIcon: string; + pulseButton: string; + notificationDot: string; + sparkleContainer: string; + sparkleStar: string; + sparklePlus: string; + sparkle: string; + active: string; +} diff --git a/packages/discord-types/src/common/Channel.d.ts b/packages/discord-types/src/common/Channel.d.ts new file mode 100644 index 00000000..7ad5ce53 --- /dev/null +++ b/packages/discord-types/src/common/Channel.d.ts @@ -0,0 +1,83 @@ +import { DiscordRecord } from "./Record"; + +export class Channel extends DiscordRecord { + constructor(channel: object); + application_id: number | undefined; + bitrate: number; + defaultAutoArchiveDuration: number | undefined; + flags: number; + guild_id: string; + icon: string; + id: string; + lastMessageId: string; + lastPinTimestamp: string | undefined; + member: unknown; + memberCount: number | undefined; + memberIdsPreview: string[] | undefined; + memberListId: unknown; + messageCount: number | undefined; + name: string; + nicks: Record<string, unknown>; + nsfw: boolean; + originChannelId: unknown; + ownerId: string; + parent_id: string; + permissionOverwrites: { + [role: string]: { + id: string; + type: number; + deny: bigint; + allow: bigint; + }; + }; + position: number; + rateLimitPerUser: number; + rawRecipients: { + id: string; + avatar: string; + username: string; + public_flags: number; + discriminator: string; + }[]; + recipients: string[]; + rtcRegion: string; + threadMetadata: { + locked: boolean; + archived: boolean; + invitable: boolean; + createTimestamp: string | undefined; + autoArchiveDuration: number; + archiveTimestamp: string | undefined; + }; + topic: string; + type: number; + userLimit: number; + videoQualityMode: undefined; + + get accessPermissions(): bigint; + get lastActiveTimestamp(): number; + + computeLurkerPermissionsAllowList(): unknown; + getApplicationId(): unknown; + getGuildId(): string; + getRecipientId(): unknown; + hasFlag(flag: number): boolean; + isActiveThread(): boolean; + isArchivedThread(): boolean; + isCategory(): boolean; + isDM(): boolean; + isDirectory(): boolean; + isForumChannel(): boolean; + isGroupDM(): boolean; + isGuildStageVoice(): boolean; + isGuildVoice(): boolean; + isListenModeCapable(): boolean; + isManaged(): boolean; + isMultiUserDM(): boolean; + isNSFW(): boolean; + isOwner(): boolean; + isPrivate(): boolean; + isSystemDM(): boolean; + isThread(): boolean; + isVocal(): boolean; +} diff --git a/packages/discord-types/src/common/Guild.d.ts b/packages/discord-types/src/common/Guild.d.ts new file mode 100644 index 00000000..5b5c3c74 --- /dev/null +++ b/packages/discord-types/src/common/Guild.d.ts @@ -0,0 +1,64 @@ +import { Role } from './Role'; +import { DiscordRecord } from './Record'; + +// copy(Object.keys(findByProps("CREATOR_MONETIZABLE")).map(JSON.stringify).join("|")) +export type GuildFeatures = + "INVITE_SPLASH" | "VIP_REGIONS" | "VANITY_URL" | "MORE_EMOJI" | "MORE_STICKERS" | "MORE_SOUNDBOARD" | "VERIFIED" | "COMMERCE" | "DISCOVERABLE" | "COMMUNITY" | "FEATURABLE" | "NEWS" | "HUB" | "PARTNERED" | "ANIMATED_ICON" | "BANNER" | "ENABLED_DISCOVERABLE_BEFORE" | "WELCOME_SCREEN_ENABLED" | "MEMBER_VERIFICATION_GATE_ENABLED" | "PREVIEW_ENABLED" | "ROLE_SUBSCRIPTIONS_ENABLED" | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" | "CREATOR_MONETIZABLE" | "CREATOR_MONETIZABLE_PROVISIONAL" | "CREATOR_MONETIZABLE_WHITEGLOVE" | "CREATOR_MONETIZABLE_DISABLED" | "CREATOR_MONETIZABLE_RESTRICTED" | "CREATOR_STORE_PAGE" | "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" | "PRODUCTS_AVAILABLE_FOR_PURCHASE" | "GUILD_WEB_PAGE_VANITY_URL" | "THREADS_ENABLED" | "THREADS_ENABLED_TESTING" | "NEW_THREAD_PERMISSIONS" | "ROLE_ICONS" | "TEXT_IN_STAGE_ENABLED" | "TEXT_IN_VOICE_ENABLED" | "HAS_DIRECTORY_ENTRY" | "ANIMATED_BANNER" | "LINKED_TO_HUB" | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" | "GUILD_HOME_DEPRECATION_OVERRIDE" | "GUILD_HOME_TEST" | "GUILD_HOME_OVERRIDE" | "GUILD_ONBOARDING" | "GUILD_ONBOARDING_EVER_ENABLED" | "GUILD_ONBOARDING_HAS_PROMPTS" | "GUILD_SERVER_GUIDE" | "INTERNAL_EMPLOYEE_ONLY" | "AUTO_MODERATION" | "INVITES_DISABLED" | "BURST_REACTIONS" | "SOUNDBOARD" | "SHARD" | "ACTIVITY_FEED_ENABLED_BY_USER" | "ACTIVITY_FEED_DISABLED_BY_USER" | "SUMMARIES_ENABLED_GA" | "LEADERBOARD_ENABLED" | "SUMMARIES_ENABLED_BY_USER" | "SUMMARIES_OPT_OUT_EXPERIENCE" | "CHANNEL_ICON_EMOJIS_GENERATED" | "NON_COMMUNITY_RAID_ALERTS" | "RAID_ALERTS_DISABLED" | "AUTOMOD_TRIGGER_USER_PROFILE" | "ENABLED_MODERATION_EXPERIENCE_FOR_NON_COMMUNITY" | "GUILD_PRODUCTS_ALLOW_ARCHIVED_FILE" | "CLAN" | "MEMBER_VERIFICATION_MANUAL_APPROVAL" | "FORWARDING_DISABLED" | "MEMBER_VERIFICATION_ROLLOUT_TEST" | "AUDIO_BITRATE_128_KBPS" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS" | "VIDEO_BITRATE_ENHANCED" | "MAX_FILE_SIZE_50_MB" | "MAX_FILE_SIZE_100_MB" | "GUILD_TAGS" | "ENHANCED_ROLE_COLORS" | "PREMIUM_TIER_3_OVERRIDE" | "REPORT_TO_MOD_PILOT" | "TIERLESS_BOOSTING_SYSTEM_MESSAGE"; +export type GuildPremiumFeatures = + "ANIMATED_ICON" | "STAGE_CHANNEL_VIEWERS_150" | "ROLE_ICONS" | "GUILD_TAGS" | "BANNER" | "MAX_FILE_SIZE_50_MB" | "VIDEO_QUALITY_720_60FPS" | "STAGE_CHANNEL_VIEWERS_50" | "VIDEO_QUALITY_1080_60FPS" | "MAX_FILE_SIZE_100_MB" | "VANITY_URL" | "VIDEO_BITRATE_ENHANCED" | "STAGE_CHANNEL_VIEWERS_300" | "AUDIO_BITRATE_128_KBPS" | "ANIMATED_BANNER" | "TIERLESS_BOOSTING" | "ENHANCED_ROLE_COLORS" | "INVITE_SPLASH" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS"; + +export class Guild extends DiscordRecord { + constructor(guild: object); + afkChannelId: string | undefined; + afkTimeout: number; + applicationCommandCounts: { + 0: number; + 1: number; + 2: number; + }; + application_id: unknown; + banner: string | undefined; + defaultMessageNotifications: number; + description: string | undefined; + discoverySplash: string | undefined; + explicitContentFilter: number; + features: Set<GuildFeatures>; + homeHeader: string | undefined; + hubType: unknown; + icon: string | undefined; + id: string; + joinedAt: Date; + latestOnboardingQuestionId: string | undefined; + maxMembers: number; + maxStageVideoChannelUsers: number; + maxVideoChannelUsers: number; + mfaLevel: number; + moderatorReporting: unknown; + name: string; + nsfwLevel: number; + ownerConfiguredContentLevel: number; + ownerId: string; + preferredLocale: string; + premiumFeatures: { + additionalEmojiSlots: number; + additionalSoundSlots: number; + additionalStickerSlots: number; + features: Array<GuildPremiumFeatures>; + }; + premiumProgressBarEnabled: boolean; + premiumSubscriberCount: number; + premiumTier: number; + profile: { + badge: string | undefined; + tag: string | undefined; + } | undefined; + publicUpdatesChannelId: string | undefined; + roles: Record<string, Role>; + rulesChannelId: string | undefined; + safetyAlertsChannelId: string | undefined; + splash: string | undefined; + systemChannelFlags: number; + systemChannelId: string | undefined; + vanityURLCode: string | undefined; + verificationLevel: number; +} diff --git a/packages/discord-types/src/common/GuildMember.d.ts b/packages/discord-types/src/common/GuildMember.d.ts new file mode 100644 index 00000000..5cd47682 --- /dev/null +++ b/packages/discord-types/src/common/GuildMember.d.ts @@ -0,0 +1,26 @@ +export interface GuildMember { + avatar: string | undefined; + avatarDecoration: string | undefined; + banner: string | undefined; + bio: string; + colorRoleId: string | undefined; + colorString: string; + colorStrings: { + primaryColor: string | undefined; + secondaryColor: string | undefined; + tertiaryColor: string | undefined; + }; + communicationDisabledUntil: string | undefined; + flags: number; + fullProfileLoadedTimestamp: number; + guildId: string; + highestRoleId: string; + hoistRoleId: string; + iconRoleId: string; + isPending: boolean | undefined; + joinedAt: string | undefined; + nick: string | undefined; + premiumSince: string | undefined; + roles: string[]; + userId: string; +} diff --git a/packages/discord-types/src/common/Record.d.ts b/packages/discord-types/src/common/Record.d.ts new file mode 100644 index 00000000..8f033058 --- /dev/null +++ b/packages/discord-types/src/common/Record.d.ts @@ -0,0 +1,12 @@ +type Updater = (value: any) => any; + +/** + * Common Record class extended by various Discord data structures, like User, Channel, Guild, etc. + */ +export class DiscordRecord { + toJS(): Record<string, any>; + + set(key: string, value: any): this; + merge(data: Record<string, any>): this; + update(key: string, defaultValueOrUpdater: Updater | any, updater?: Updater): this; +} diff --git a/packages/discord-types/src/common/Role.d.ts b/packages/discord-types/src/common/Role.d.ts new file mode 100644 index 00000000..7b038984 --- /dev/null +++ b/packages/discord-types/src/common/Role.d.ts @@ -0,0 +1,33 @@ +export interface Role { + color: number; + colorString: string | undefined; + colorStrings: { + primaryColor: string | undefined; + secondaryColor: string | undefined; + tertiaryColor: string | undefined; + }; + colors: { + primary_color: number | undefined; + secondary_color: number | undefined; + tertiary_color: number | undefined; + }; + flags: number; + hoist: boolean; + icon: string | undefined; + id: string; + managed: boolean; + mentionable: boolean; + name: string; + originalPosition: number; + permissions: bigint; + position: number; + /** + * probably incomplete + */ + tags: { + bot_id: string; + integration_id: string; + premium_subscriber: unknown; + } | undefined; + unicodeEmoji: string | undefined; +} diff --git a/packages/discord-types/src/common/User.d.ts b/packages/discord-types/src/common/User.d.ts new file mode 100644 index 00000000..a5503bcb --- /dev/null +++ b/packages/discord-types/src/common/User.d.ts @@ -0,0 +1,66 @@ +// TODO: a lot of optional params can also be null, not just undef + +import { DiscordRecord } from "./Record"; + +export class User extends DiscordRecord { + constructor(user: object); + accentColor: number; + avatar: string; + banner: string; + bio: string; + bot: boolean; + desktop: boolean; + discriminator: string; + email: string | undefined; + flags: number; + globalName: string | undefined; + guildMemberAvatars: Record<string, string>; + id: string; + mfaEnabled: boolean; + mobile: boolean; + nsfwAllowed: boolean | undefined; + phone: string | undefined; + premiumType: number | undefined; + premiumUsageFlags: number; + publicFlags: number; + purchasedFlags: number; + system: boolean; + username: string; + verified: boolean; + themeColors?: [number, number]; + + get createdAt(): Date; + get hasPremiumPerks(): boolean; + get tag(): string; + get usernameNormalized(): string; + + addGuildAvatarHash(guildId: string, avatarHash: string): User; + getAvatarSource(guildId: string, canAnimate?: boolean): { uri: string; }; + getAvatarURL(guildId?: string | null, t?: unknown, canAnimate?: boolean): string; + hasAvatarForGuild(guildId: string): boolean; + hasDisabledPremium(): boolean; + hasFlag(flag: number): boolean; + hasFreePremium(): boolean; + hasHadSKU(e: unknown): boolean; + hasPremiumUsageFlag(flag: number): boolean; + hasPurchasedFlag(flag: number): boolean; + hasUrgentMessages(): boolean; + isClaimed(): boolean; + isLocalBot(): boolean; + isNonUserBot(): boolean; + isPhoneVerified(): boolean; + isStaff(): boolean; + isSystemUser(): boolean; + isVerifiedBot(): boolean; + removeGuildAvatarHash(guildId: string): User; + toString(): string; +} + +export interface UserJSON { + avatar: string; + avatarDecoration: unknown | undefined; + discriminator: string; + id: string; + publicFlags: number; + username: string; +} diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts new file mode 100644 index 00000000..f85a7c72 --- /dev/null +++ b/packages/discord-types/src/common/index.d.ts @@ -0,0 +1,7 @@ +export * from "./Channel"; +export * from "./Guild"; +export * from "./GuildMember"; +export * from "./messages"; +export * from "./Role"; +export * from "./User"; +export * from "./Record"; diff --git a/packages/discord-types/src/common/messages/Commands.d.ts b/packages/discord-types/src/common/messages/Commands.d.ts new file mode 100644 index 00000000..8dc9f9a7 --- /dev/null +++ b/packages/discord-types/src/common/messages/Commands.d.ts @@ -0,0 +1,61 @@ +import { Channel } from "../Channel"; +import { Guild } from "../Guild"; +import { Promisable } from "type-fest"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "../../../enums"; + +export interface CommandContext { + channel: Channel; + guild?: Guild; +} + +export interface CommandOption { + name: string; + displayName?: string; + type: ApplicationCommandOptionType; + description: string; + displayDescription?: string; + required?: boolean; + options?: CommandOption[]; + choices?: Array<ChoicesOption>; +} + +export interface ChoicesOption { + label: string; + value: string; + name: string; + displayName?: string; +} + +export interface CommandReturnValue { + content: string; + // TODO: implement + // cancel?: boolean; +} + +export interface CommandArgument { + type: ApplicationCommandOptionType; + name: string; + value: string; + focused: undefined; + options: CommandArgument[]; +} + +export interface Command { + id?: string; + applicationId?: string; + type?: ApplicationCommandType; + inputType?: ApplicationCommandInputType; + plugin?: string; + + name: string; + untranslatedName?: string; + displayName?: string; + description: string; + untranslatedDescription?: string; + displayDescription?: string; + + options?: CommandOption[]; + predicate?(ctx: CommandContext): boolean; + + execute(args: CommandArgument[], ctx: CommandContext): Promisable<void | CommandReturnValue>; +} diff --git a/packages/discord-types/src/common/messages/Embed.d.ts b/packages/discord-types/src/common/messages/Embed.d.ts new file mode 100644 index 00000000..4edd9ced --- /dev/null +++ b/packages/discord-types/src/common/messages/Embed.d.ts @@ -0,0 +1,70 @@ +export interface Embed { + author?: { + name: string; + url: string; + iconURL: string | undefined; + iconProxyURL: string | undefined; + }; + color: string; + fields: []; + id: string; + image?: { + height: number; + width: number; + url: string; + proxyURL: string; + }; + provider?: { + name: string; + url: string | undefined; + }; + rawDescription: string; + rawTitle: string; + referenceId: unknown; + timestamp: string; + thumbnail?: { + height: number; + proxyURL: string | undefined; + url: string; + width: number; + }; + type: string; + url: string | undefined; + video?: { + height: number; + width: number; + url: string; + proxyURL: string | undefined; + }; +} + +export interface EmbedJSON { + author?: { + name: string; + url: string; + icon_url: string; + proxy_icon_url: string; + }; + title: string; + color: string; + description: string; + type: string; + url: string | undefined; + provider?: { + name: string; + url: string; + }; + timestamp: string; + thumbnail?: { + height: number; + width: number; + url: string; + proxy_url: string | undefined; + }; + video?: { + height: number; + width: number; + url: string; + proxy_url: string | undefined; + }; +} diff --git a/packages/discord-types/src/common/messages/Emoji.d.ts b/packages/discord-types/src/common/messages/Emoji.d.ts new file mode 100644 index 00000000..793e90f8 --- /dev/null +++ b/packages/discord-types/src/common/messages/Emoji.d.ts @@ -0,0 +1,42 @@ +export type Emoji = CustomEmoji | UnicodeEmoji; + +export interface CustomEmoji { + type: 1; + allNamesString: string; + animated: boolean; + available: boolean; + guildId: string; + id: string; + managed: boolean; + name: string; + originalName?: string; + require_colons: boolean; + roles: string[]; +} + +export interface UnicodeEmoji { + type: 0; + diversityChildren: Record<any, any>; + emojiObject: { + names: string[]; + surrogates: string; + unicodeVersion: number; + }; + index: number; + surrogates: string; + uniqueName: string; + useSpriteSheet: boolean; + get allNamesString(): string; + get animated(): boolean; + get defaultDiversityChild(): any; + get hasDiversity(): boolean | undefined; + get hasDiversityParent(): boolean | undefined; + get hasMultiDiversity(): boolean | undefined; + get hasMultiDiversityParent(): boolean | undefined; + get managed(): boolean; + get name(): string; + get names(): string[]; + get optionallyDiverseSequence(): string | undefined; + get unicodeVersion(): number; + get url(): string; +} diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts new file mode 100644 index 00000000..38010caf --- /dev/null +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -0,0 +1,191 @@ +import { CommandOption } from './Commands'; +import { User, UserJSON } from '../User'; +import { Embed, EmbedJSON } from './Embed'; +import { DiscordRecord } from "../Record"; + +/** + * TODO: looks like discord has moved over to Date instead of Moment; + */ +export class Message extends DiscordRecord { + constructor(message: object); + activity: unknown; + application: unknown; + applicationId: string | unknown; + attachments: MessageAttachment[]; + author: User; + blocked: boolean; + bot: boolean; + call: { + duration: moment.Duration; + endedTimestamp: moment.Moment; + participants: string[]; + }; + channel_id: string; + /** + * NOTE: not fully typed + */ + codedLinks: { + code?: string; + type: string; + }[]; + colorString: unknown; + components: unknown[]; + content: string; + customRenderedContent: unknown; + editedTimestamp: Date; + embeds: Embed[]; + flags: number; + giftCodes: string[]; + id: string; + interaction: { + id: string; + name: string; + type: number; + user: User; + }[] | undefined; + interactionData: { + application_command: { + application_id: string; + default_member_permissions: unknown; + default_permission: boolean; + description: string; + dm_permission: unknown; + id: string; + name: string; + options: CommandOption[]; + permissions: unknown[]; + type: number; + version: string; + }; + attachments: MessageAttachment[]; + guild_id: string | undefined; + id: string; + name: string; + options: { + focused: unknown; + name: string; + type: number; + value: string; + }[]; + type: number; + version: string; + }[]; + interactionError: unknown[]; + isSearchHit: boolean; + loggingName: unknown; + mentionChannels: string[]; + mentionEveryone: boolean; + mentionRoles: string[]; + mentioned: boolean; + mentions: string[]; + messageReference: { + guild_id?: string; + channel_id: string; + message_id: string; + } | undefined; + nick: unknown; // probably a string + nonce: string | undefined; + pinned: boolean; + reactions: MessageReaction[]; + state: string; + stickerItems: { + format_type: number; + id: string; + name: string; + }[]; + stickers: unknown[]; + timestamp: moment.Moment; + tts: boolean; + type: number; + webhookId: string | undefined; + + /** + * Doesn't actually update the original message; it just returns a new message instance with the added reaction. + */ + addReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; + /** + * Searches each reaction and if the provided string has an index above -1 it'll return the reaction object. + */ + getReaction(name: string): MessageReaction; + /** + * Doesn't actually update the original message; it just returns the message instance without the reaction searched with the provided emoji object. + */ + removeReactionsForEmoji(emoji: ReactionEmoji): Message; + /** + * Doesn't actually update the original message; it just returns the message instance without the reaction. + */ + removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; + + getChannelId(): string; + hasFlag(flag: number): boolean; + isCommandType(): boolean; + isEdited(): boolean; + isSystemDM(): boolean; +} + +/** A smaller Message object found in FluxDispatcher and elsewhere. */ +export interface MessageJSON { + attachments: MessageAttachment[]; + author: UserJSON; + channel_id: string; + components: unknown[]; + content: string; + edited_timestamp: string; + embeds: EmbedJSON[]; + flags: number; + guild_id: string | undefined; + id: string; + loggingName: unknown; + member: { + avatar: string | undefined; + communication_disabled_until: string | undefined; + deaf: boolean; + hoisted_role: string | undefined; + is_pending: boolean; + joined_at: string; + mute: boolean; + nick: string | boolean; + pending: boolean; + premium_since: string | undefined; + roles: string[]; + } | undefined; + mention_everyone: boolean; + mention_roles: string[]; + mentions: UserJSON[]; + message_reference: { + guild_id?: string; + channel_id: string; + message_id: string; + } | undefined; + nonce: string | undefined; + pinned: boolean; + referenced_message: MessageJSON | undefined; + state: string; + timestamp: string; + tts: boolean; + type: number; +} + +export interface MessageAttachment { + filename: string; + id: string; + proxy_url: string; + size: number; + spoiler: boolean; + url: string; + content_type?: string; + width?: number; + height?: number; +} + +export interface ReactionEmoji { + id: string | undefined; + name: string; + animated: boolean; +} + +export interface MessageReaction { + count: number; + emoji: ReactionEmoji; + me: boolean; +} diff --git a/packages/discord-types/src/common/messages/index.d.ts b/packages/discord-types/src/common/messages/index.d.ts new file mode 100644 index 00000000..245e971e --- /dev/null +++ b/packages/discord-types/src/common/messages/index.d.ts @@ -0,0 +1,4 @@ +export * from "./Commands"; +export * from "./Message"; +export * from "./Embed"; +export * from "./Emoji"; diff --git a/src/webpack/common/types/components.d.ts b/packages/discord-types/src/components.d.ts similarity index 89% rename from src/webpack/common/types/components.d.ts rename to packages/discord-types/src/components.d.ts index 783663cb..8b2fd1cf 100644 --- a/src/webpack/common/types/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -1,25 +1,7 @@ -/* - * 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 type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react"; - -export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; +// copy(find(m => Array.isArray(m) && m.includes("heading-sm/normal")).map(JSON.stringify).join("|")) +export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-sm/extrabold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-md/extrabold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-lg/extrabold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/semibold" | "heading-xl/bold" | "heading-xl/extrabold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/semibold" | "heading-xxl/bold" | "heading-xxl/extrabold" | "eyebrow" | "heading-deprecated-12/normal" | "heading-deprecated-12/medium" | "heading-deprecated-12/semibold" | "heading-deprecated-12/bold" | "heading-deprecated-12/extrabold" | "redesign/heading-18/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "redesign/message-preview/normal" | "redesign/message-preview/medium" | "redesign/message-preview/semibold" | "redesign/message-preview/bold" | "redesign/channel-title/normal" | "redesign/channel-title/medium" | "redesign/channel-title/semibold" | "redesign/channel-title/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; diff --git a/packages/discord-types/src/flux.d.ts b/packages/discord-types/src/flux.d.ts new file mode 100644 index 00000000..bb5600dd --- /dev/null +++ b/packages/discord-types/src/flux.d.ts @@ -0,0 +1,30 @@ +import { FluxStore } from "./stores/FluxStore"; + +export class FluxEmitter { + constructor(); + + changeSentinel: number; + changedStores: Set<FluxStore>; + isBatchEmitting: boolean; + isDispatching: boolean; + isPaused: boolean; + pauseTimer: NodeJS.Timeout | null; + reactChangedStores: Set<FluxStore>; + + batched(batch: (...args: any[]) => void): void; + destroy(): void; + emit(): void; + emitNonReactOnce(): void; + emitReactOnce(): void; + getChangeSentinel(): number; + getIsPaused(): boolean; + injectBatchEmitChanges(batch: (...args: any[]) => void): void; + markChanged(store: FluxStore): void; + pause(): void; + resume(): void; +} + +export interface Flux { + Store: typeof FluxStore; + Emitter: FluxEmitter; +} diff --git a/packages/discord-types/src/fluxEvents.d.ts b/packages/discord-types/src/fluxEvents.d.ts new file mode 100644 index 00000000..c210f4b9 --- /dev/null +++ b/packages/discord-types/src/fluxEvents.d.ts @@ -0,0 +1,22 @@ +/* +function makeFluxEventList() { + // prefill MESSAGE_CREATE so that typescript infers this is a String Set + // without explicitly typing so that this function is also valid javascript + const events = new Set(["MESSAGE_CREATE"]); + + const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; + for (const nodeId in nodes) { + for (const event in nodes[nodeId].actionHandler) { + events.add(event); + } + } + for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { + events.add(event); + } + + return Array.from(events, e => JSON.stringify(e)).sort().join("|"); +} +copy(makeFluxEventList()) +*/ + +export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_CONTRAST" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CROSSFADES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACKNOWLEDGE_CHANNEL_SAFETY_WARNING_TOOLTIP" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_AV_ERRORS_CHANGED" | "ACTIVE_BOGO_PROMOTION_FETCH" | "ACTIVE_BOGO_PROMOTION_FETCH_FAIL" | "ACTIVE_BOGO_PROMOTION_FETCH_SUCCESS" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_INVITE_MODAL_QUERY" | "ACTIVITY_INVITE_MODAL_SEND" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_LAYOUT_MODE_UPDATE" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_POPOUT_WINDOW_OPEN" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "ADYEN_CASH_APP_PAY_SUBMIT_SUCCESS" | "ADYEN_CREATE_CASH_APP_PAY_COMPONENT_SUCCESS" | "ADYEN_CREATE_CLIENT_SUCCESS" | "ADYEN_TEARDOWN_CLIENT" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "APEX_EXPERIMENT_CLEAR_SERVER_ASSIGNMENTS" | "APEX_EXPERIMENT_OVERRIDE_CLEAR" | "APEX_EXPERIMENT_OVERRIDE_CREATE" | "APEX_EXPERIMENT_OVERRIDE_DELETE" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_ASSETS_FETCH" | "APPLICATION_ASSETS_FETCH_SUCCESS" | "APPLICATION_ASSETS_UPDATE" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_EXECUTE_BAD_VERSION" | "APPLICATION_COMMAND_INDEX_FETCH_FAILURE" | "APPLICATION_COMMAND_INDEX_FETCH_REQUEST" | "APPLICATION_COMMAND_INDEX_FETCH_SUCCESS" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_DIRECTORY_FETCH_APPLICATION" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_FAILURE" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_CATEGORIES_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SEARCH" | "APPLICATION_DIRECTORY_FETCH_SEARCH_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SEARCH_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_SUCCESS" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCHING" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_FAILED" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_SUCCESS" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLICATION_UPDATE" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_RESET" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_DM_OPEN" | "APP_ICON_EDITOR_RESET" | "APP_ICON_TRACK_IMPRESSION" | "APP_ICON_UPDATED" | "APP_LAUNCHER_ADD_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_DISMISS" | "APP_LAUNCHER_REMOVE_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_SET_ACTIVE_COMMAND" | "APP_LAUNCHER_SHOW" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_FAILURE" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_SUCCESS" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ACTIVE_INPUT_PROFILE" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_BYPASS_SYSTEM_INPUT_PROCESSING" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_KRISP_MODEL_OVERRIDE" | "AUDIO_SET_KRISP_SUPPRESSION_LEVEL" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SELF_MUTE" | "AUDIO_SET_SIDECHAIN_COMPRESSION" | "AUDIO_SET_SIDECHAIN_COMPRESSION_STRENGTH" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_LOCAL_SOUNDBOARD_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_TARGET" | "AUDIT_LOG_FILTER_BY_USER" | "AUTHENTICATOR_CREATE" | "AUTHENTICATOR_DELETE" | "AUTHENTICATOR_UPDATE" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "AUTO_UPDATER_QUIT_AND_INSTALL" | "BACKGROUND_SYNC" | "BACKGROUND_SYNC_CHANNEL_MESSAGES" | "BASIC_GUILD_FETCH" | "BASIC_GUILD_FETCH_FAILURE" | "BASIC_GUILD_FETCH_SUCCESS" | "BILLING_CREATE_REFERRAL_SUCCESS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_IP_LOCATION_FAILURE" | "BILLING_IP_LOCATION_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_NITRO_AFFINITY_FETCHED" | "BILLING_NITRO_AFFINITY_FETCH_SUCCEEDED" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PREVIOUS_PREMIUM_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_REFERRALS_REMAINING_FETCH_FAIL" | "BILLING_REFERRALS_REMAINING_FETCH_START" | "BILLING_REFERRALS_REMAINING_FETCH_SUCCESS" | "BILLING_REFERRAL_RESOLVE_FAIL" | "BILLING_REFERRAL_RESOLVE_SUCCESS" | "BILLING_REFERRAL_TRIAL_OFFER_UPDATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_IP_LOCATION" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_FAILURE" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_START" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_OFFER_FETCH_FAIL" | "BILLING_USER_OFFER_FETCH_START" | "BILLING_USER_OFFER_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_FAIL" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_FAIL" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_PICKER_ANIMATION_ADD" | "BURST_REACTION_PICKER_ANIMATION_CLEAR" | "CACHED_EMOJIS_LOADED" | "CACHED_STICKERS_LOADED" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CACHE_LOADED_LAZY_NO_CACHE" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CONNECT_MULTIPLE" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_FETCH_FAILED" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_MARK_SEEN" | "CHANGE_LOG_RESOLVED" | "CHANGE_LOG_SET_CONFIG" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MEMBER_COUNT_UPDATE" | "CHANNEL_MUTE_EXPIRED" | "CHANNEL_PERMISSIONS_DELETE_OVERWRITE_SUCCESS" | "CHANNEL_PERMISSIONS_PUT_OVERWRITE_SUCCESS" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_JUMP_TO_VOICE_CHANNEL_MESSAGE" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTCIPANTS_LIST_OPEN" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SAFETY_WARNING_FEEDBACK" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_STATUSES" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_TOGGLE_SUMMARIES_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECKOUT_RECOVERY_STATUS_FETCH" | "CHECKOUT_RECOVERY_STATUS_FETCH_FAILURE" | "CHECKOUT_RECOVERY_STATUS_FETCH_SUCCESS" | "CHECK_LAUNCHABLE_GAME" | "CLEAR_CACHES" | "CLEAR_CHANNEL_SAFETY_WARNINGS" | "CLEAR_CONSUMED_ENTITLEMENT" | "CLEAR_CONVERSATION_SUMMARIES" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_LAST_SESSION_VOICE_CHANNEL_ID" | "CLEAR_MENTIONS" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLEAR_THEME_OVERRIDE" | "CLEAR_VIDEO_STREAM_READY_TIMEOUT" | "CLICKER_GAME_ADD_POINTS" | "CLICKER_GAME_PURCHASE_ITEM" | "CLICKER_GAME_PURCHASE_ITEM_UPGRADE" | "CLICKER_GAME_REDEEM_PRIZE_FAIL" | "CLICKER_GAME_REDEEM_PRIZE_START" | "CLICKER_GAME_REDEEM_PRIZE_SUCCESS" | "CLICKER_GAME_RESET" | "CLICKER_GAME_SET_MUTED" | "CLICKER_GAME_SET_VOLUME" | "CLICKER_GAME_UNLOCK_ACHIEVEMENT" | "CLICKER_GAME_UPDATE_ITEM_METADATA" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIPS_ALLOW_VOICE_RECORDING_UPDATE" | "CLIPS_CLASSIFY_HARDWARE" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_CLEAR_NEW_CLIP_IDS" | "CLIPS_DELETE_CLIP" | "CLIPS_DISMISS_EDUCATION" | "CLIPS_INIT" | "CLIPS_INIT_FAILURE" | "CLIPS_LOAD_DIRECTORY_SUCCESS" | "CLIPS_RESTART" | "CLIPS_SAVE_ANIMATION_END" | "CLIPS_SAVE_CLIP" | "CLIPS_SAVE_CLIP_ERROR" | "CLIPS_SAVE_CLIP_PLACEHOLDER" | "CLIPS_SAVE_CLIP_PLACEHOLDER_ERROR" | "CLIPS_SAVE_CLIP_START" | "CLIPS_SETTINGS_UPDATE" | "CLIPS_SHOW_CALL_WARNING" | "CLIPS_UPDATE_METADATA" | "CLOSE_AGE_VERIFICATION_MODAL" | "CLOSE_SUSPENDED_USER" | "COLLECTIBLES_CATEGORIES_FETCH" | "COLLECTIBLES_CATEGORIES_FETCH_FAILURE" | "COLLECTIBLES_CATEGORIES_FETCH_SUCCESS" | "COLLECTIBLES_CLAIM" | "COLLECTIBLES_CLAIM_FAILURE" | "COLLECTIBLES_CLAIM_SUCCESS" | "COLLECTIBLES_MARKETING_FETCH" | "COLLECTIBLES_MARKETING_FETCH_SUCCESS" | "COLLECTIBLES_PRODUCT_DETAILS_OPEN" | "COLLECTIBLES_PRODUCT_FETCH" | "COLLECTIBLES_PRODUCT_FETCH_FAILURE" | "COLLECTIBLES_PRODUCT_FETCH_SUCCESS" | "COLLECTIBLES_PURCHASES_FETCH" | "COLLECTIBLES_PURCHASES_FETCH_FAILURE" | "COLLECTIBLES_PURCHASES_FETCH_SUCCESS" | "COLLECTIBLES_SET_SHOP_HOME_CONFIG_OVERRIDE" | "COLLECTIBLES_SHOP_CLOSE" | "COLLECTIBLES_SHOP_HOME_FETCH" | "COLLECTIBLES_SHOP_HOME_FETCH_FAILURE" | "COLLECTIBLES_SHOP_HOME_FETCH_SUCCESS" | "COLLECTIBLES_SHOP_OPEN" | "COLLECTIBLES_SKIP_NUM_CATEGORIES" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "COMPLETE_NEW_MEMBER_ACTION" | "CONNECTED_DEVICE_DONT_SWITCH" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SWITCH" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_STATE_UPDATE" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONSUMABLES_CLEAR_ERROR" | "CONSUMABLES_ENTITLEMENT_FETCH_COMPLETED" | "CONSUMABLES_ENTITLEMENT_FETCH_FAILED" | "CONSUMABLES_ENTITLEMENT_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_FAILED" | "CONSUMABLES_PRICE_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_SUCCEEDED" | "CONTENT_INVENTORY_CLEAR_DELETE_HISTORY_ERROR" | "CONTENT_INVENTORY_CLEAR_FEED" | "CONTENT_INVENTORY_DEBUG_CLEAR_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_LOG_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_TOGGLE_FAST_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DEBUG_TOGGLE_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_FAILURE" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_START" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_SUCCESS" | "CONTENT_INVENTORY_FETCH_OUTBOX_FAILURE" | "CONTENT_INVENTORY_FETCH_OUTBOX_START" | "CONTENT_INVENTORY_FETCH_OUTBOX_SUCCESS" | "CONTENT_INVENTORY_FORCE_SHOW_GAME_SHARING" | "CONTENT_INVENTORY_INBOX_STALE" | "CONTENT_INVENTORY_MANUAL_REFRESH" | "CONTENT_INVENTORY_SET_FEED" | "CONTENT_INVENTORY_SET_FEED_STATE" | "CONTENT_INVENTORY_SET_FILTERS" | "CONTENT_INVENTORY_TOGGLE_FEED_HIDDEN" | "CONTENT_INVENTORY_TRACK_ITEM_IMPRESSIONS" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CONVERSATION_SUMMARY_UPDATE" | "CREATE_PENDING_REPLY" | "CREATE_PENDING_SCHEDULED_MESSAGE" | "CREATE_REFERRALS_SUCCESS" | "CREATE_SHALLOW_PENDING_REPLY" | "CREATOR_MONETIZATION_NAG_ACTIVATE_ELIGIBLITY_FETCH_SUCCESS" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_FAILURE" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_SUCCESS" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "CUSTOM_ACTIVITY_LINK_FETCH_SUCCESS" | "DCF_DAILY_CAP_OVERRIDE" | "DCF_EVENT_LOGGED" | "DCF_HANDLE_DC_DISMISSED" | "DCF_HANDLE_DC_SHOWN" | "DCF_NEW_USER_MIN_AGE_REQUIRED_OVERRIDE" | "DCF_OVERRIDE_LAST_DC_DISMISSED" | "DCF_RESET" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DELETE_PENDING_SCHEDULED_MESSAGE" | "DELETE_SUMMARY" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_FAILURE" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_SUCCESS" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_UPDATE_FILTER" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_DESIGN_TOGGLE_SET" | "DEV_TOOLS_DESIGN_TOGGLE_WEB_SET" | "DEV_TOOLS_DEV_SETTING_SET" | "DEV_TOOLS_FRIENDS_LIST_GIFT_INTENTS_SHOWN_RESET" | "DEV_TOOLS_FRIENDS_TAB_BADGE_COOLDOWN_RESET" | "DEV_TOOLS_GIFT_MESSAGE_COOLDOWN_RESET" | "DEV_TOOLS_SETTINGS_UPDATE" | "DEV_TOOLS_SET_FRIEND_ANNIVERSARY_COUNT" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISMISS_CHANNEL_SAFETY_WARNINGS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_MEDIA_POST_SHARE_PROMPT" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_FOCUSED_LAYOUT" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMBEDDED_ACTIVITY_SET_PANEL_MODE" | "EMBEDDED_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_UPDATE_POPOUT_WINDOW_LAYOUT" | "EMBEDDED_ACTIVITY_UPDATE_V2" | "EMOJI_AUTOSUGGESTION_UPDATE" | "EMOJI_DELETE" | "EMOJI_FETCH_FAILURE" | "EMOJI_FETCH_SUCCESS" | "EMOJI_INTERACTION_INITIATED" | "EMOJI_TRACK_USAGE" | "EMOJI_UPLOAD_START" | "EMOJI_UPLOAD_STOP" | "ENABLE_AUTOMATIC_ACK" | "ENTITLEMENTS_FETCH_FOR_USER_FAIL" | "ENTITLEMENTS_FETCH_FOR_USER_START" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "FAMILY_CENTER_FETCH_START" | "FAMILY_CENTER_HANDLE_TAB_SELECT" | "FAMILY_CENTER_INITIAL_LOAD" | "FAMILY_CENTER_LINKED_USERS_FETCH_SUCCESS" | "FAMILY_CENTER_LINK_CODE_FETCH_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_REMOVE_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_UPDATE_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_FETCH_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_MORE_FETCH_SUCCESS" | "FEEDBACK_OVERRIDE_CLEAR" | "FEEDBACK_OVERRIDE_SET" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_CHAT_WALLPAPERS_FAILURE" | "FETCH_CHAT_WALLPAPERS_START" | "FETCH_CHAT_WALLPAPERS_SUCCESS" | "FETCH_GUILD_EVENT" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_FAILURE" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_SUCCESS" | "FETCH_SCHEDULED_MESSAGES" | "FETCH_SCHEDULED_MESSAGES_FAILURE" | "FETCH_SCHEDULED_MESSAGES_SUCCESS" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_LIST_GIFT_INTENTS_SHOWN" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIENDS_TAB_BADGE_DISMISS" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_CONSOLE_SELECT_DEVICE" | "GAME_DETECTION_DEBUGGING_START" | "GAME_DETECTION_DEBUGGING_STOP" | "GAME_DETECTION_DEBUGGING_TICK" | "GAME_DETECTION_WATCH_CANDIDATE_GAMES_START" | "GAME_ICON_UPDATE" | "GAME_INVITE_CLEAR_UNSEEN" | "GAME_INVITE_CREATE" | "GAME_INVITE_DELETE" | "GAME_INVITE_DELETE_MANY" | "GAME_INVITE_UPDATE_STATUS" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GAME_PROFILE_OPEN" | "GAME_RELATIONSHIP_ADD" | "GAME_RELATIONSHIP_REMOVE" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIFT_INTENT_FLOW_PURCHASED_GIFT" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_CLEAR" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_LAYOUT_RESET" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_SUCCESS" | "GUILD_ACK" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_SUCCESS" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLIED_BOOSTS_UPDATE" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FOLDER_COLLAPSE" | "GUILD_FOLDER_CREATE_LOCAL" | "GUILD_FOLDER_DELETE_LOCAL" | "GUILD_FOLDER_EDIT_LOCAL" | "GUILD_GEO_RESTRICTED" | "GUILD_HOME_SETTINGS_FETCH_FAIL" | "GUILD_HOME_SETTINGS_FETCH_START" | "GUILD_HOME_SETTINGS_FETCH_SUCCESS" | "GUILD_HOME_SETTINGS_TOGGLE_ENABLED" | "GUILD_HOME_SETTINGS_UPDATE_FAIL" | "GUILD_HOME_SETTINGS_UPDATE_START" | "GUILD_HOME_SETTINGS_UPDATE_SUCCESS" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR_DECORATION" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PROFILE_EFFECT_ID" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_TAB" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_BY_ID_FETCH_SUCCESS" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_LOCAL_RING_START" | "GUILD_MEMBERS_CHUNK_BATCH" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_REMOVE_LOCAL" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE_BY_ID" | "GUILD_MUTE_EXPIRED" | "GUILD_NEW_MEMBER_ACTIONS_DELETE_SUCCESS" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_FAIL" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_START" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_SUCCESS" | "GUILD_NEW_MEMBER_ACTION_UPDATE_SUCCESS" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POWERUPS_ACK_NOTIFICATION" | "GUILD_POWERUPS_RESET_NOTIFICATIONS" | "GUILD_POWERUP_CATALOG_FETCH_SUCCESS" | "GUILD_POWERUP_ENTITLEMENTS_CREATE" | "GUILD_POWERUP_ENTITLEMENTS_DELETE" | "GUILD_PRODUCTS_FETCH" | "GUILD_PRODUCTS_FETCH_FAILURE" | "GUILD_PRODUCTS_FETCH_SUCCESS" | "GUILD_PRODUCT_CREATE" | "GUILD_PRODUCT_DELETE" | "GUILD_PRODUCT_FETCH" | "GUILD_PRODUCT_FETCH_FAILURE" | "GUILD_PRODUCT_FETCH_SUCCESS" | "GUILD_PRODUCT_UPDATE" | "GUILD_PROFILE_FETCH" | "GUILD_PROFILE_FETCH_FAILURE" | "GUILD_PROFILE_FETCH_SUCCESS" | "GUILD_PROFILE_UPDATE" | "GUILD_PROFILE_UPDATE_FAILURE" | "GUILD_PROFILE_UPDATE_SUCCESS" | "GUILD_PROFILE_UPDATE_VISIBILITY" | "GUILD_PROFILE_UPDATE_VISIBILITY_FAILURE" | "GUILD_PROFILE_UPDATE_VISIBILITY_SUCCESS" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RESOURCE_CHANNEL_UPDATE_SUCCESS" | "GUILD_RING_START" | "GUILD_RING_STOP" | "GUILD_ROLE_CONNECTIONS_CONFIGURATIONS_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTIONS_MODAL_SHOW" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_TEMPLATES" | "GUILD_ROLE_SUBSCRIPTIONS_STASH_TEMPLATE_CHANNELS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTIONS_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_COUNTS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SEARCH_RECENT_MEMBERS" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_RESET" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_FAILED" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SUBMIT" | "GUILD_SETTINGS_DEFAULT_CHANNELS_TOGGLE" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_JOIN_RULES_APPLY_SET_PENDING_FORM_FIELDS" | "GUILD_SETTINGS_JOIN_RULES_INVITE_SET_PENDING_RULES" | "GUILD_SETTINGS_JOIN_RULES_SET_CONTENT_LEVEL" | "GUILD_SETTINGS_JOIN_RULES_SET_SELECTED_TYPE" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_BANS_BATCH" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_ADD_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_ADD_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DELETE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_DELETE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DISMISS_RESOURCE_CHANNEL_SUGGESTION" | "GUILD_SETTINGS_ONBOARDING_EDUCATION_UPSELL_DISMISSED" | "GUILD_SETTINGS_ONBOARDING_HOME_SETTINGS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_EDIT" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_ERRORS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_FAILED" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SUBMIT" | "GUILD_SETTINGS_ONBOARDING_REORDER_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_REORDER_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_SET_MODE" | "GUILD_SETTINGS_ONBOARDING_STEP" | "GUILD_SETTINGS_ONBOARDING_UPDATE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_UPDATE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_UPDATE_WELCOME_MESSAGE" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_PROFILE_UPDATE" | "GUILD_SETTINGS_ROLES_CLEAR_PERMISSIONS" | "GUILD_SETTINGS_ROLES_INIT" | "GUILD_SETTINGS_ROLES_ROLE_STYLE_UPDATE" | "GUILD_SETTINGS_ROLES_SAVE_FAIL" | "GUILD_SETTINGS_ROLES_SAVE_SUCCESS" | "GUILD_SETTINGS_ROLES_SORT_UPDATE" | "GUILD_SETTINGS_ROLES_SUBMITTING" | "GUILD_SETTINGS_ROLES_UPDATE_COLOR" | "GUILD_SETTINGS_ROLES_UPDATE_COLORS" | "GUILD_SETTINGS_ROLES_UPDATE_DESCRIPTION" | "GUILD_SETTINGS_ROLES_UPDATE_NAME" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSIONS" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSION_SET" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_CONNECTION_CONFIGURATIONS" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_ICON" | "GUILD_SETTINGS_ROLES_UPDATE_SETTINGS" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAFETY_PAGE" | "GUILD_SETTINGS_SAFETY_SET_SUBSECTION" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SETTINGS_VANITY_URL_ERROR" | "GUILD_SETTINGS_VANITY_URL_RESET" | "GUILD_SETTINGS_VANITY_URL_SET" | "GUILD_SETTINGS_WIDGET_UPDATE" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_SOUNDS_UPDATE" | "GUILD_SOUNDBOARD_SOUND_CREATE" | "GUILD_SOUNDBOARD_SOUND_DELETE" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_LOCALLY" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_SOUND_UPDATE" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_ADD_MEMBER_UPDATES" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_SUBSCRIPTIONS_REMOVE_MEMBER_UPDATES" | "GUILD_TAG_CHANGED_COACHMARK_SEEN" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_TOP_READ_CHANNELS_FETCH_SUCCESS" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UNLOCKED_POWERUPS_FETCH_SUCCESS" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HABITUAL_DND_CLEAR" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIGH_FIVE_COMPLETE" | "HIGH_FIVE_COMPLETE_CLEAR" | "HIGH_FIVE_QUEUE" | "HIGH_FIVE_REMOVE" | "HIGH_FIVE_SET_ENABLED" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INITIALIZE_MEMBER_SAFETY_STORE" | "INITIATE_AGE_VERIFICATION" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CLEAR" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_CREATE" | "INTEGRATION_DELETE" | "INTEGRATION_PERMISSION_SETTINGS_APPLICATION_PERMISSIONS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_CLEAR" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_SUCCESS" | "INTEGRATION_PERMISSION_SETTINGS_COMMAND_UPDATE" | "INTEGRATION_PERMISSION_SETTINGS_EDIT" | "INTEGRATION_PERMISSION_SETTINGS_INIT" | "INTEGRATION_PERMISSION_SETTINGS_RESET" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_COMMAND" | "INTEGRATION_SETTINGS_START_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_COMMAND" | "INTEGRATION_SETTINGS_STOP_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_INTEGRATION" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_IFRAME_MODAL_CLOSE" | "INTERACTION_IFRAME_MODAL_CREATE" | "INTERACTION_IFRAME_MODAL_KEY_CREATE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "INVITE_SUGGESTIONS_SEARCH" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAB_FEATURE_TOGGLE" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_SET_WIDGET_META" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_CHANNELS" | "LOAD_DATA_HARVEST_TYPE_FAILURE" | "LOAD_DATA_HARVEST_TYPE_START" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_ICYMI_HYDRATED" | "LOAD_INVITE_SUGGESTIONS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES_V2" | "LOAD_USER_AFFINITIES_V2_FAILURE" | "LOAD_USER_AFFINITIES_V2_SUCCESS" | "LOCAL_ACTIVITY_UPDATE" | "LOCAL_MESSAGES_LOADED" | "LOCAL_MESSAGE_CREATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_STEP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGIN_SUSPENDED_USER" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_CONNECTION_STATS" | "MEDIA_ENGINE_CONNECTION_STATS_HISTORY_RESET" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_ENABLE_HARDWARE_MUTE_NOTICE" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_GO_LIVE_SOURCE" | "MEDIA_ENGINE_SET_HARDWARE_ENCODING" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_USE_SYSTEM_SCREENSHARE_PICKER" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEDIA_ENGINE_VOICE_ACTIVITY_DETECTION_ERROR" | "MEDIA_PLAYBACK_POSITION_UPDATE" | "MEDIA_PLAYBACK_RATE_UPDATE" | "MEDIA_POST_EMBED_FETCH" | "MEDIA_POST_EMBED_FETCH_FAILURE" | "MEDIA_POST_EMBED_FETCH_SUCCESS" | "MEDIA_SESSION_JOINED" | "MEMBER_SAFETY_GUILD_MEMBER_SEARCH_SUCCESS" | "MEMBER_SAFETY_GUILD_MEMBER_UPDATE_BATCH" | "MEMBER_SAFETY_NEW_MEMBER_TIMESTAMP_REFRESH" | "MEMBER_SAFETY_PAGINATION_TOKEN_UPDATE" | "MEMBER_SAFETY_PAGINATION_UPDATE" | "MEMBER_SAFETY_SEARCH_STATE_UPDATE" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MESSAGE_ACK" | "MESSAGE_ACKED" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_EXPLICIT_CONTENT_FP_CREATE" | "MESSAGE_EXPLICIT_CONTENT_FP_SUBMIT" | "MESSAGE_EXPLICIT_CONTENT_SCAN_TIMEOUT" | "MESSAGE_GIFT_INTENT_SHOWN" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_NOTIFICATION_SHOWN" | "MESSAGE_PREVIEWS_LOADED" | "MESSAGE_PREVIEWS_LOCALLY_LOADED" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_MANY" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REMINDER_DUE" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REQUEST_ACK" | "MESSAGE_REQUEST_CLEAR_ACK" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_SUCCESS" | "MFA_SEEN_BACKUP_CODE_PROMPT" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MOBILE_NATIVE_UPDATE_CHECK_FINISHED" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MOD_VIEW_SEARCH_FINISH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NATIVE_APP_MODAL_OPENED" | "NATIVE_APP_MODAL_OPENING" | "NATIVE_APP_MODAL_OPEN_FAILED" | "NATIVE_SCREEN_SHARE_PICKER_CANCEL" | "NATIVE_SCREEN_SHARE_PICKER_ERROR" | "NATIVE_SCREEN_SHARE_PICKER_PRESENT" | "NATIVE_SCREEN_SHARE_PICKER_RELEASE" | "NATIVE_SCREEN_SHARE_PICKER_UPDATE" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_CLEAR_GUILD_MENTIONS" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_REFRESH" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CENTER_TAB_FOCUSED" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOTIFICATION_SETTINGS_UPDATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_FORCE_RENDER_MODE" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_MESSAGE_EVENT_ACTION" | "OVERLAY_NOTIFICATION_EVENT" | "OVERLAY_READY" | "OVERLAY_RELOAD" | "OVERLAY_RENDER_DEBUG_CLEAR_TRACKED_PIDS" | "OVERLAY_RENDER_DEBUG_MODE" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_ASSOCIATED_GAME" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_CLICK_ZONES" | "OVERLAY_SET_DISABLE_CLICKABLE_REGIONS" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_GAME_INVITE_NOTIFICATION" | "OVERLAY_SET_GPU_BOOST_REQUESTED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_INVITE_MESSAGE" | "OVERLAY_SET_LIMITED_INTERACTION_OVERRIDE" | "OVERLAY_SET_NOTIFICATION_DISABLED_SETTING" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_SHOW_KEYBIND_INDICATORS" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SOUNDBOARD_SOUNDS_FETCH_REQUEST" | "OVERLAY_START_SESSION" | "OVERLAY_SUCCESSFULLY_SHOWN" | "OVERLAY_UPDATE_OVERLAY_METHOD" | "OVERLAY_UPDATE_OVERLAY_STATE" | "OVERLAY_WIDGET_CHANGED" | "PASSIVE_UPDATE_V2" | "PASSWORDLESS_FAILURE" | "PASSWORDLESS_START" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_RESIZE" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_ADD_STYLESHEET" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "POTIONS_SET_CONFETTI_MODE" | "POTIONS_TRIGGER_MESSAGE_CONFETTI" | "PREMIUM_MARKETING_DATA_READY" | "PREMIUM_MARKETING_PREVIEW" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_SUBSCRIPTIONS_ADD" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_EFFECTS_SET_TRY_IT_OUT" | "PROXY_BLOCKED_REQUEST" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_FAILURE" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_SUCCESS" | "PURCHASED_ITEMS_FESTIVITY_IS_FETCHING_WOW_MOMENT_MEDIA" | "PURCHASED_ITEMS_FESTIVITY_SET_CAN_PLAY_WOW_MOMENT" | "PUSH_NOTIFICATION_CLICK" | "QUESTS_CLAIM_REWARD_BEGIN" | "QUESTS_CLAIM_REWARD_FAILURE" | "QUESTS_CLAIM_REWARD_SUCCESS" | "QUESTS_DELIVERY_OVERRIDE" | "QUESTS_DISMISS_CONTENT_BEGIN" | "QUESTS_DISMISS_CONTENT_FAILURE" | "QUESTS_DISMISS_CONTENT_SUCCESS" | "QUESTS_DISMISS_PROGRESS_TRACKING_FAILURE_NOTICE" | "QUESTS_ENROLL_BEGIN" | "QUESTS_ENROLL_FAILURE" | "QUESTS_ENROLL_SUCCESS" | "QUESTS_FETCH_CLAIMED_QUESTS_BEGIN" | "QUESTS_FETCH_CLAIMED_QUESTS_FAILURE" | "QUESTS_FETCH_CLAIMED_QUESTS_SUCCESS" | "QUESTS_FETCH_CURRENT_QUESTS_BEGIN" | "QUESTS_FETCH_CURRENT_QUESTS_FAILURE" | "QUESTS_FETCH_CURRENT_QUESTS_SUCCESS" | "QUESTS_FETCH_QUEST_TO_DELIVER_BEGIN" | "QUESTS_FETCH_QUEST_TO_DELIVER_FAILURE" | "QUESTS_FETCH_QUEST_TO_DELIVER_SUCCESS" | "QUESTS_FETCH_REWARD_CODE_BEGIN" | "QUESTS_FETCH_REWARD_CODE_FAILURE" | "QUESTS_FETCH_REWARD_CODE_SUCCESS" | "QUESTS_PREVIEW_UPDATE_SUCCESS" | "QUESTS_SELECT_TASK_PLATFORM" | "QUESTS_SEND_HEARTBEAT_FAILURE" | "QUESTS_SEND_HEARTBEAT_SUCCESS" | "QUESTS_UPDATE_OPTIMISTIC_PROGRESS" | "QUESTS_USER_COMPLETION_UPDATE" | "QUESTS_USER_STATUS_UPDATE" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECEIVE_CHANNEL_AFFINITIES" | "RECEIVE_CHANNEL_SUMMARIES" | "RECEIVE_CHANNEL_SUMMARIES_BULK" | "RECEIVE_CHANNEL_SUMMARY" | "RECENT_MENTION_DELETE" | "RECOMPUTE_READ_STATES" | "REFERRALS_FETCH_ELIGIBLE_USER_FAIL" | "REFERRALS_FETCH_ELIGIBLE_USER_START" | "REFERRALS_FETCH_ELIGIBLE_USER_SUCCESS" | "REGISTER" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_IGNORE_USER_SUCCESS" | "RELATIONSHIP_PENDING_INCOMING_REMOVED" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REPORT_AV_ERROR" | "REPORT_TO_MOD_REPORT_MESSAGE_SUCCESS" | "REQUEST_CHANNEL_AFFINITIES" | "REQUEST_CHANNEL_SUMMARIES" | "REQUEST_CHANNEL_SUMMARIES_BULK" | "REQUEST_CHANNEL_SUMMARY" | "REQUEST_FORUM_UNREADS" | "REQUEST_SOUNDBOARD_SOUNDS" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESET_PREVIEW_CLIENT_THEME" | "RESET_SOCKET" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_CLIENT_CONNECT" | "RTC_CONNECTION_CLIENT_DISCONNECT" | "RTC_CONNECTION_FLAGS" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_PLATFORM" | "RTC_CONNECTION_REMOTE_VIDEO_SINK_WANTS" | "RTC_CONNECTION_ROSTER_MAP_UPDATE" | "RTC_CONNECTION_SECURE_FRAMES_UPDATE" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_USERS_MERGED" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_DEBUG_SET_SIMULCAST_OVERRIDE" | "RTC_LATENCY_TEST_COMPLETE" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_DETECTION" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAFETY_HUB_APPEAL_CLOSE" | "SAFETY_HUB_APPEAL_OPEN" | "SAFETY_HUB_APPEAL_SIGNAL_CUSTOM_INPUT_CHANGE" | "SAFETY_HUB_APPEAL_SIGNAL_SELECT" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_CLOSE" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_OPEN" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_START_POLL" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_SUBMIT_SUCCESS" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_FETCH_CLASSIFICATION_FAILURE" | "SAFETY_HUB_FETCH_CLASSIFICATION_START" | "SAFETY_HUB_FETCH_CLASSIFICATION_SUCCESS" | "SAFETY_HUB_FETCH_FAILURE" | "SAFETY_HUB_FETCH_START" | "SAFETY_HUB_FETCH_SUCCESS" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_REQUEST_REVIEW_FAILURE" | "SAFETY_HUB_REQUEST_REVIEW_START" | "SAFETY_HUB_REQUEST_REVIEW_SUCCESS" | "SAVED_MESSAGES_UPDATE" | "SAVED_MESSAGE_CREATE" | "SAVED_MESSAGE_DELETE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SCHEDULED_MESSAGES_CREATE_SUCCESS" | "SCHEDULED_MESSAGES_DELETE_FAILURE" | "SCHEDULED_MESSAGES_DELETE_START" | "SCHEDULED_MESSAGES_DELETE_SUCCESS" | "SEARCH_ADD_HISTORY" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MESSAGES_CLEAR_ALL" | "SEARCH_MESSAGES_FAILURE" | "SEARCH_MESSAGES_INDEXING" | "SEARCH_MESSAGES_START" | "SEARCH_MESSAGES_SUCCESS" | "SEARCH_RECENT_MESSAGES_CLEAR" | "SEARCH_REMOVE_HISTORY" | "SEARCH_RESULTS_QUERY_UPDATE" | "SEARCH_SCREEN_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_SET_SHOW_NO_RESULTS_ALT" | "SEARCH_START" | "SECURE_FRAMES_SETTINGS_UPDATE" | "SECURE_FRAMES_TRANSIENT_KEY_CREATE" | "SECURE_FRAMES_TRANSIENT_KEY_DELETE" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_ADD" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_CLEAR" | "SECURE_FRAMES_USER_VERIFIED_KEYS_DELETE" | "SECURE_FRAMES_VERIFIED_KEY_CREATE" | "SECURE_FRAMES_VERIFIED_KEY_DELETE" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELECT_HOME_RESOURCE_CHANNEL" | "SELECT_NEW_MEMBER_ACTION_CHANNEL" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_CREATED_AT_OVERRIDE" | "SET_GUILD_FOLDER_EXPANDED" | "SET_GUILD_LEADERBOARD" | "SET_HIGHLIGHTED_SUMMARY" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_PREMIUM_TYPE_OVERRIDE" | "SET_PREVIOUS_GO_LIVE_SETTINGS" | "SET_RECENTLY_ACTIVE_COLLAPSED" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_RPC_NOTIFICATION_SETTINGS" | "SET_SELECTED_SUMMARY" | "SET_SOUNDPACK" | "SET_STREAM_APP_INTENT" | "SET_SUMMARY_FEEDBACK" | "SET_THEME_OVERRIDE" | "SET_TTS_SPEECH_RATE" | "SET_USER_LEADERBOARD_LAST_UPDATE_REQUESTED" | "SET_VAD_PERMISSION" | "SHARED_CANVAS_CLEAR_DRAWABLES" | "SHARED_CANVAS_DRAW_LINE_POINT" | "SHARED_CANVAS_SET_DRAW_MODE" | "SHARED_CANVAS_UPDATE_EMOJI_HOSE" | "SHARED_CANVAS_UPDATE_LINE_POINTS" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CLOSE_GUILD" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SIDEBAR_VIEW_GUILD" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_AWAIT_CONFIRMATION" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH" | "SKU_PURCHASE_PREVIEW_FETCH_FAILURE" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS_SUCCESS" | "SOUNDBOARD_MUTE_JOIN_SOUND" | "SOUNDBOARD_SET_OVERLAY_ENABLED" | "SOUNDBOARD_SOUNDS_RECEIVED" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPELLCHECK_UNLEARN_WORD" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_NEW_TRACK" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "STAGE_MUSIC_MUTE" | "STAGE_MUSIC_PLAY" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_LISTINGS_FETCH_FAIL" | "STORE_LISTINGS_FETCH_START" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SURVEY_SEEN" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_EXPANDED_STATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOGGLE_OVERLAY_CANVAS" | "TOGGLE_TOPICS_BAR" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TRY_ACK" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPCOMING_GUILD_EVENT_NOTICE_HIDE" | "UPCOMING_GUILD_EVENT_NOTICE_SEEN" | "UPDATE_AVAILABLE" | "UPDATE_BACKGROUND_GRADIENT_PRESET" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CHANNEL_LIST_SUBTITLES" | "UPDATE_CHAT_WALLPAPER_FLAG_COMPLETE" | "UPDATE_CHAT_WALLPAPER_FLAG_START" | "UPDATE_CHAT_WALLPAPER_OVERRIDES" | "UPDATE_CLIENT_PREMIUM_TYPE" | "UPDATE_CONSENTS" | "UPDATE_DATA_HARVEST_TYPE" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_MANUALLY" | "UPDATE_MOBILE_PENDING_THEME_INDEX" | "UPDATE_NOT_AVAILABLE" | "UPDATE_STRANGER_STATUS" | "UPDATE_THEME_PREFERENCES" | "UPDATE_TOKEN" | "UPDATE_VISIBLE_MESSAGES" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_FILE_UPDATE" | "UPLOAD_ITEM_CANCEL_REQUEST" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLICATION_REMOVE" | "USER_APPLICATION_UPDATE" | "USER_APPLIED_BOOSTS_FETCH_START" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_REQUEST" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_CALLBACK" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_INTEGRATION_JOINING_ERROR" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_COACHMARK_CLEAR" | "USER_GUILD_JOIN_REQUEST_COACHMARK_SHOW" | "USER_GUILD_JOIN_REQUEST_COOLDOWN_FETCH" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_BROWSER_CHECKOUT_DONE" | "USER_PAYMENT_BROWSER_CHECKOUT_STARTED" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_EFFECTS_FETCH" | "USER_PROFILE_EFFECTS_FETCH_FAILURE" | "USER_PROFILE_EFFECTS_FETCH_SUCCESS" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_PIN_BADGES_ON_CLIENT" | "USER_PROFILE_SIDEBAR_TOGGLE_SECTION" | "USER_PROFILE_UPDATE_FAILURE" | "USER_PROFILE_UPDATE_START" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_RESET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME" | "USER_SETTINGS_ACCOUNT_SET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_NAMEPLATE" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_SINGLE_TRY_IT_OUT_COLLECTIBLES_ITEM" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PRESET" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_RESET_PENDING_PRIMARY_GUILD_CHANGES" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SETTINGS_SET_PENDING_PRIMARY_GUILD_ID" | "USER_SOUNDBOARD_SET_VOLUME" | "USER_UPDATE" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIDEO_SIZE_UPDATE" | "VIDEO_STREAM_READY_TIMEOUT" | "VIEW_HISTORY_MARK_VIEW" | "VIRTUAL_CURRENCY_BALANCE_FETCH" | "VIRTUAL_CURRENCY_BALANCE_FETCH_FAIL" | "VIRTUAL_CURRENCY_BALANCE_FETCH_SUCCESS" | "VIRTUAL_CURRENCY_BALANCE_UPDATE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_CLOSE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_RESET" | "VIRTUAL_CURRENCY_REDEEM_FAIL" | "VIRTUAL_CURRENCY_REDEEM_START" | "VIRTUAL_CURRENCY_REDEEM_SUCCESS" | "VIRTUAL_CURRENCY_SET_BALANCE_PILL_OVERLAY" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_CHANNEL_STATUS_UPDATE" | "VOICE_FILTER_APPLIED" | "VOICE_FILTER_APPLY_FAILED" | "VOICE_FILTER_CATALOG_FETCH_FAILED" | "VOICE_FILTER_CATALOG_FETCH_SUCCESS" | "VOICE_FILTER_DEV_TOOLS_SET_UPDATE_TIME" | "VOICE_FILTER_DOWNLOAD_FAILED" | "VOICE_FILTER_DOWNLOAD_PROGRESS" | "VOICE_FILTER_DOWNLOAD_STARTED" | "VOICE_FILTER_FILE_READY" | "VOICE_FILTER_LAGGING" | "VOICE_FILTER_LOOPBACK_TOGGLE" | "VOICE_FILTER_NATIVE_MODULE_STATE_CHANGE" | "VOICE_FILTER_REQUEST_SWITCH" | "VOICE_FILTER_UPDATE_LIMITED_TIME_VOICES" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SETTINGS_CLEAR" | "WELCOME_SCREEN_SETTINGS_RESET" | "WELCOME_SCREEN_SETTINGS_UPDATE" | "WELCOME_SCREEN_SUBMIT" | "WELCOME_SCREEN_SUBMIT_FAILURE" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD" | "WINDOW_VISIBILITY_CHANGE" | "WRITE_CACHES"; diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts new file mode 100644 index 00000000..6d9356b4 --- /dev/null +++ b/packages/discord-types/src/index.d.ts @@ -0,0 +1,9 @@ +export * from "./common"; +export * from "./classes"; +export * from "./components"; +export * from "./flux"; +export * from "./fluxEvents"; +export * from "./menu"; +export * from "./stores"; +export * from "./utils"; +export * as Webpack from "../webpack"; diff --git a/src/webpack/common/types/menu.d.ts b/packages/discord-types/src/menu.d.ts similarity index 71% rename from src/webpack/common/types/menu.d.ts rename to packages/discord-types/src/menu.d.ts index 5ae9062c..0866e1c3 100644 --- a/src/webpack/common/types/menu.d.ts +++ b/packages/discord-types/src/menu.d.ts @@ -1,21 +1,3 @@ -/* - * 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 type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>; @@ -73,7 +55,7 @@ export interface Menu { renderValue?(value: number): string, }>; MenuSearchControl: RC<{ - query: string + query: string; onChange(query: string): void; placeholder?: string; }>; diff --git a/packages/discord-types/src/stores/ChannelStore.d.ts b/packages/discord-types/src/stores/ChannelStore.d.ts new file mode 100644 index 00000000..1507ba5e --- /dev/null +++ b/packages/discord-types/src/stores/ChannelStore.d.ts @@ -0,0 +1,24 @@ +import { Channel, FluxStore } from ".."; + +export class ChannelStore extends FluxStore { + getChannel(channelId: string): Channel; + getBasicChannel(channelId: string): Channel | undefined; + hasChannel(channelId: string): boolean; + + getChannelIds(guildId?: string | null): string[]; + getMutableBasicGuildChannelsForGuild(guildId: string): Record<string, Channel>; + getMutableGuildChannelsForGuild(guildId: string): Record<string, Channel>; + getAllThreadsForGuild(guildId: string): Channel[]; + getAllThreadsForParent(channelId: string): Channel[]; + + getDMFromUserId(userId: string): string; + getDMChannelFromUserId(userId: string): Channel | undefined; + getDMUserIds(): string[]; + getMutableDMsByUserIds(): Record<string, string>; + getMutablePrivateChannels(): Record<string, Channel>; + getSortedPrivateChannels(): Channel[]; + + getGuildChannelsVersion(guildId: string): number; + getPrivateChannelsVersion(): number; + getInitialOverlayState(): Record<string, Channel>; +} diff --git a/packages/discord-types/src/stores/DraftStore.d.ts b/packages/discord-types/src/stores/DraftStore.d.ts new file mode 100644 index 00000000..98b34cdf --- /dev/null +++ b/packages/discord-types/src/stores/DraftStore.d.ts @@ -0,0 +1,43 @@ +import { FluxStore } from ".."; + +export enum DraftType { + ChannelMessage = 0, + ThreadSettings = 1, + FirstThreadMessage = 2, + ApplicationLauncherCommand = 3, + Poll = 4, + SlashCommand = 5, + ForwardContextMessage = 6 +} + +export interface Draft { + timestamp: number; + draft: string; +} + +export interface ThreadSettingsDraft { + timestamp: number; + parentMessageId?: string; + name?: string; + isPrivate?: boolean; + parentChannelId?: string; + location?: string; +} + +export type ChannelDrafts = { + [DraftType.ThreadSettings]: ThreadSettingsDraft; +} & { + [key in Exclude<DraftType, DraftType.ThreadSettings>]: Draft; +}; + +export type UserDrafts = Partial<Record<string, ChannelDrafts>>; +export type DraftState = Partial<Record<string, UserDrafts>>; + +export class DraftStore extends FluxStore { + getState(): DraftState; + getRecentlyEditedDrafts(type: DraftType): Array<Draft & { channelId: string; }>; + getDraft(channelId: string, type: DraftType): string; + + getThreadSettings(channelId: string): ThreadSettingsDraft | null | undefined; + getThreadDraftWithParentMessageId(parentMessageId: string): ThreadSettingsDraft | null | undefined; +} diff --git a/packages/discord-types/src/stores/EmojiStore.d.ts b/packages/discord-types/src/stores/EmojiStore.d.ts new file mode 100644 index 00000000..93161b2f --- /dev/null +++ b/packages/discord-types/src/stores/EmojiStore.d.ts @@ -0,0 +1,57 @@ +import { Channel, CustomEmoji, Emoji, FluxStore } from ".."; + +export class EmojiStore extends FluxStore { + getCustomEmojiById(id?: string | null): CustomEmoji | undefined; + getUsableCustomEmojiById(id?: string | null): CustomEmoji | undefined; + getGuilds(): Record<string, { + id: string; + get emojis(): CustomEmoji[]; + get rawEmojis(): CustomEmoji[]; + get usableEmojis(): CustomEmoji[]; + get emoticons(): any[]; + getEmoji(id: string): CustomEmoji | undefined; + isUsable(emoji: CustomEmoji): boolean; + }>; + getGuildEmoji(guildId?: string | null): CustomEmoji[]; + getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[]; + getTopEmoji(guildId?: string | null): CustomEmoji[]; + getTopEmojisMetadata(guildId?: string | null): { + emojiIds: string[]; + topEmojisTTL: number; + }; + hasPendingUsage(): boolean; + hasUsableEmojiInAnyGuild(): boolean; + searchWithoutFetchingLatest(data: any): any; + getSearchResultsOrder(...args: any[]): any; + getState(): { + pendingUsages: { key: string, timestamp: number; }[]; + }; + searchWithoutFetchingLatest(data: { + channel: Channel; + query: string; + count?: number; + intention: number; + includeExternalGuilds?: boolean; + matchComparator?(name: string): boolean; + }): Record<"locked" | "unlocked", Emoji[]>; + + getDisambiguatedEmojiContext(): { + backfillTopEmojis: Record<any, any>; + customEmojis: Record<string, CustomEmoji>; + emojisById: Record<string, CustomEmoji>; + emojisByName: Record<string, CustomEmoji>; + emoticonRegex: RegExp | null; + emoticonsByName: Record<string, any>; + escapedEmoticonNames: string; + favoriteNamesAndIds?: any; + favorites?: any; + frequentlyUsed?: any; + groupedCustomEmojis: Record<string, CustomEmoji[]>; + guildId?: string; + isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean; + newlyAddedEmoji: Record<string, CustomEmoji[]>; + topEmojis?: any; + unicodeAliases: Record<string, string>; + get favoriteEmojisWithoutFetchingLatest(): Emoji[]; + }; +} diff --git a/packages/discord-types/src/stores/FluxStore.d.ts b/packages/discord-types/src/stores/FluxStore.d.ts new file mode 100644 index 00000000..da55ac0b --- /dev/null +++ b/packages/discord-types/src/stores/FluxStore.d.ts @@ -0,0 +1,44 @@ +import { FluxDispatcher, FluxEvents } from ".."; + +type Callback = () => void; + +/* + For some reason, this causes type errors when you try to destructure it: + ```ts + interface FluxEvent { + type: FluxEvents; + [key: string]: any; + } + ``` + */ +export type FluxEvent = any; + +export type ActionHandler = (event: FluxEvent) => void; +export type ActionHandlers = Partial<Record<FluxEvents, ActionHandler>>; + +export class FluxStore { + constructor(dispatcher: FluxDispatcher, actionHandlers?: ActionHandlers); + + getName(): string; + + addChangeListener(callback: Callback): void; + /** Listener will be removed once the callback returns false. */ + addConditionalChangeListener(callback: () => boolean, preemptive?: boolean): void; + addReactChangeListener(callback: Callback): void; + removeChangeListener(callback: Callback): void; + removeReactChangeListener(callback: Callback): void; + + doEmitChanges(event: FluxEvent): void; + emitChange(): void; + + getDispatchToken(): string; + initialize(): void; + initializeIfNeeded(): void; + /** this is a setter */ + mustEmitChanges(actionHandler: ActionHandler | undefined): void; + registerActionHandlers(actionHandlers: ActionHandlers): void; + syncWith(stores: FluxStore[], callback: Callback, timeout?: number): void; + waitFor(...stores: FluxStore[]): void; + + static getAll(): FluxStore[]; +} diff --git a/packages/discord-types/src/stores/GuildMemberStore.d.ts b/packages/discord-types/src/stores/GuildMemberStore.d.ts new file mode 100644 index 00000000..9dec139a --- /dev/null +++ b/packages/discord-types/src/stores/GuildMemberStore.d.ts @@ -0,0 +1,27 @@ +import { FluxStore, GuildMember } from ".."; + +export class GuildMemberStore extends FluxStore { + /** @returns Format: [guildId-userId: Timestamp (string)] */ + getCommunicationDisabledUserMap(): Record<string, string>; + getCommunicationDisabledVersion(): number; + + getMutableAllGuildsAndMembers(): Record<string, Record<string, GuildMember>>; + + getMember(guildId: string, userId: string): GuildMember | null; + getTrueMember(guildId: string, userId: string): GuildMember | null; + getMemberIds(guildId: string): string[]; + getMembers(guildId: string): GuildMember[]; + + getCachedSelfMember(guildId: string): GuildMember | null; + getSelfMember(guildId: string): GuildMember | null; + getSelfMemberJoinedAt(guildId: string): Date | null; + + getNick(guildId: string, userId: string): string | null; + getNicknameGuildsMapping(userId: string): Record<string, string[]>; + getNicknames(userId: string): string[]; + + isMember(guildId: string, userId: string): boolean; + isMember(guildId: string, userId: string): boolean; + isGuestOrLurker(guildId: string, userId: string): boolean; + isCurrentUserGuest(guildId: string): boolean; +} diff --git a/packages/discord-types/src/stores/GuildRoleStore.d.ts b/packages/discord-types/src/stores/GuildRoleStore.d.ts new file mode 100644 index 00000000..bf0d4042 --- /dev/null +++ b/packages/discord-types/src/stores/GuildRoleStore.d.ts @@ -0,0 +1,7 @@ +import { FluxStore, Role } from ".."; + +export class GuildRoleStore extends FluxStore { + getRole(guildId: string, roleId: string): Role; + getRoles(guildId: string): Record<string, Role>; + getAllGuildRoles(): Record<string, Record<string, Role>>; +} diff --git a/packages/discord-types/src/stores/GuildStore.d.ts b/packages/discord-types/src/stores/GuildStore.d.ts new file mode 100644 index 00000000..d1a3b9b3 --- /dev/null +++ b/packages/discord-types/src/stores/GuildStore.d.ts @@ -0,0 +1,8 @@ +import { Guild, FluxStore } from ".."; + +export class GuildStore extends FluxStore { + getGuild(guildId: string): Guild; + getGuildCount(): number; + getGuilds(): Record<string, Guild>; + getGuildIds(): string[]; +} diff --git a/packages/discord-types/src/stores/MessageStore.d.ts b/packages/discord-types/src/stores/MessageStore.d.ts new file mode 100644 index 00000000..d4823fc8 --- /dev/null +++ b/packages/discord-types/src/stores/MessageStore.d.ts @@ -0,0 +1,13 @@ +import { MessageJSON, FluxStore, Message } from ".."; + +export class MessageStore extends FluxStore { + getMessage(channelId: string, messageId: string): Message; + /** @returns This return object is fucking huge; I'll type it later. */ + getMessages(channelId: string): unknown; + getRawMessages(channelId: string): Record<string | number, MessageJSON>; + hasCurrentUserSentMessage(channelId: string): boolean; + hasPresent(channelId: string): boolean; + isLoadingMessages(channelId: string): boolean; + jumpedMessageId(channelId: string): string | undefined; + whenReady(channelId: string, callback: () => void): void; +} diff --git a/packages/discord-types/src/stores/RelationshipStore.d.ts b/packages/discord-types/src/stores/RelationshipStore.d.ts new file mode 100644 index 00000000..5d1a08af --- /dev/null +++ b/packages/discord-types/src/stores/RelationshipStore.d.ts @@ -0,0 +1,21 @@ +import { FluxStore } from ".."; + +export class RelationshipStore extends FluxStore { + getFriendIDs(): string[]; + getIgnoredIDs(): string[]; + getBlockedIDs(): string[]; + + getPendingCount(): number; + getRelationshipCount(): number; + + /** Related to friend nicknames. */ + getNickname(userId: string): string; + /** @returns Enum value from constants.RelationshipTypes */ + getRelationshipType(userId: string): number; + isFriend(userId: string): boolean; + isBlocked(userId: string): boolean; + isIgnored(userId: string): boolean; + getSince(userId: string): string; + + getMutableRelationships(): Map<string, number>; +} diff --git a/packages/discord-types/src/stores/SelectedChannelStore.d.ts b/packages/discord-types/src/stores/SelectedChannelStore.d.ts new file mode 100644 index 00000000..13ac98ac --- /dev/null +++ b/packages/discord-types/src/stores/SelectedChannelStore.d.ts @@ -0,0 +1,14 @@ +import { FluxStore } from ".."; + +export class SelectedChannelStore extends FluxStore { + getChannelId(guildId?: string | null): string; + getVoiceChannelId(): string | undefined; + getCurrentlySelectedChannelId(guildId?: string): string | undefined; + getMostRecentSelectedTextChannelId(guildId: string): string | undefined; + getLastSelectedChannelId(guildId?: string): string; + // yes this returns a string + getLastSelectedChannels(guildId?: string): string; + + /** If you follow an announcement channel, this will return whichever channel you chose as destination */ + getLastChannelFollowingDestination(): { guildId?: string; channelId?: string; } | undefined; +} diff --git a/packages/discord-types/src/stores/SelectedGuildStore.d.ts b/packages/discord-types/src/stores/SelectedGuildStore.d.ts new file mode 100644 index 00000000..0ee9c207 --- /dev/null +++ b/packages/discord-types/src/stores/SelectedGuildStore.d.ts @@ -0,0 +1,14 @@ +import { FluxStore } from ".."; + +export interface SelectedGuildState { + selectedGuildTimestampMillis: Record<string | number, number>; + selectedGuildId: string | null; + lastSelectedGuildId: string | null; +} + +export class SelectedGuildStore extends FluxStore { + getGuildId(): string | null; + getLastSelectedGuildId(): string | null; + getLastSelectedTimestamp(guildId: string): number | null; + getState(): SelectedGuildState | undefined; +} diff --git a/packages/discord-types/src/stores/ThemeStore.d.ts b/packages/discord-types/src/stores/ThemeStore.d.ts new file mode 100644 index 00000000..2900f7f6 --- /dev/null +++ b/packages/discord-types/src/stores/ThemeStore.d.ts @@ -0,0 +1,18 @@ +import { FluxStore } from ".."; + +export type ThemePreference = "dark" | "light" | "unknown"; +export type SystemTheme = "dark" | "light"; +export type Theme = "light" | "dark" | "darker" | "midnight"; + +export interface ThemeState { + theme: Theme; + status: 0 | 1; + preferences: Record<ThemePreference, Theme>; +} +export class ThemeStore extends FluxStore { + get theme(): Theme; + get darkSidebar(): boolean; + get systemTheme(): SystemTheme; + themePreferenceForSystemTheme(preference: ThemePreference): Theme; + getState(): ThemeState; +} diff --git a/packages/discord-types/src/stores/UserStore.d.ts b/packages/discord-types/src/stores/UserStore.d.ts new file mode 100644 index 00000000..323a1df0 --- /dev/null +++ b/packages/discord-types/src/stores/UserStore.d.ts @@ -0,0 +1,10 @@ +import { FluxStore, User } from ".."; + +export class UserStore extends FluxStore { + filter(filter: (user: User) => boolean, sort?: boolean): Record<string, User>; + findByTag(username: string, discriminator: string): User; + forEach(action: (user: User) => void): void; + getCurrentUser(): User; + getUser(userId: string): User; + getUsers(): Record<string, User>; +} diff --git a/packages/discord-types/src/stores/WindowStore.d.ts b/packages/discord-types/src/stores/WindowStore.d.ts new file mode 100644 index 00000000..53a40e96 --- /dev/null +++ b/packages/discord-types/src/stores/WindowStore.d.ts @@ -0,0 +1,7 @@ +import { FluxStore } from ".."; + +export class WindowStore extends FluxStore { + isElementFullScreen(): boolean; + isFocused(): boolean; + windowSize(): Record<"width" | "height", number>; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts new file mode 100644 index 00000000..b6254844 --- /dev/null +++ b/packages/discord-types/src/stores/index.d.ts @@ -0,0 +1,32 @@ +// please keep in alphabetical order +export * from "./ChannelStore"; +export * from "./DraftStore"; +export * from "./EmojiStore"; +export * from "./FluxStore"; +export * from "./GuildMemberStore"; +export * from "./GuildRoleStore"; +export * from "./GuildStore"; +export * from "./MessageStore"; +export * from "./RelationshipStore"; +export * from "./SelectedChannelStore"; +export * from "./SelectedGuildStore"; +export * from "./ThemeStore"; +export * from "./UserStore"; +export * from "./WindowStore"; + +/** + * React hook that returns stateful data for one or more stores + * You might need a custom comparator (4th argument) if your store data is an object + * @param stores The stores to listen to + * @param mapper A function that returns the data you need + * @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook + * @param isEqual A custom comparator for the data returned by mapper + * + * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); + */ +export type useStateFromStores = <T>( + stores: any[], + mapper: () => T, + dependencies?: any, + isEqual?: (old: T, newer: T) => boolean +) => T; diff --git a/src/webpack/common/types/utils.d.ts b/packages/discord-types/src/utils.d.ts similarity index 92% rename from src/webpack/common/types/utils.d.ts rename to packages/discord-types/src/utils.d.ts index cfea5d76..77b6f88b 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -1,22 +1,4 @@ -/* - * 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 { Channel, Guild, GuildMember, Message, User } from "discord-types/general"; +import { Channel, Guild, GuildMember, Message, User } from "."; import type { ReactNode } from "react"; import { LiteralUnion } from "type-fest"; @@ -335,3 +317,19 @@ export interface DateUtils { dateFormat(date: Date, format: string): string; diffAsUnits(start: Date, end: Date, stopAtOneSecond?: boolean): Record<"days" | "hours" | "minutes" | "seconds", number>; } + +export interface CommandOptions { + type: number; + name: string; + description: string; + required?: boolean; + choices?: { + name: string; + values: string | number; + }[]; + options?: CommandOptions[]; + channel_types?: number[]; + min_value?: number; + max_value?: number; + autocomplete?: boolean; +} diff --git a/src/webpack/wreq.d.ts b/packages/discord-types/webpack/index.d.ts similarity index 90% rename from src/webpack/wreq.d.ts rename to packages/discord-types/webpack/index.d.ts index 2b356f9d..4a0011e8 100644 --- a/src/webpack/wreq.d.ts +++ b/packages/discord-types/webpack/index.d.ts @@ -1,11 +1,9 @@ /* - * Vencord, a Discord client mod + * @vencord/discord-types * Copyright (c) 2024 Vendicated, Nuckyz and contributors - * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-License-Identifier: LGPL-3.0-or-later */ -import { SYM_ORIGINAL_FACTORY, SYM_PATCHED_BY, SYM_PATCHED_SOURCE } from "./patchWebpack"; - export type ModuleExports = any; export type Module = { @@ -215,24 +213,3 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & { /** rspack unique id */ ruid: string; }; - -// Utility section for Vencord - -export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & { - /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ - m: Record<PropertyKey, AnyModuleFactory>; -}; - -/** exports can be anything, however initially it is always an empty object */ -export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & { - [SYM_PATCHED_SOURCE]?: string; - [SYM_PATCHED_BY]?: Set<string>; -}; - -export type PatchedModuleFactory = AnyModuleFactory & { - [SYM_ORIGINAL_FACTORY]: AnyModuleFactory; - [SYM_PATCHED_SOURCE]?: string; - [SYM_PATCHED_BY]?: Set<string>; -}; - -export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory; diff --git a/packages/vencord-types/package.json b/packages/vencord-types/package.json index b3bbe315..64586919 100644 --- a/packages/vencord-types/package.json +++ b/packages/vencord-types/package.json @@ -21,7 +21,6 @@ "@types/node": "^22.13.4", "@types/react": "18.3.1", "@types/react-dom": "18.3.1", - "discord-types": "^1.3.26", "standalone-electron-types": "^34.2.0", "type-fest": "^4.35.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e493972..cde57a1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,12 +65,12 @@ importers: '@types/yazl': specifier: ^2.4.5 version: 2.4.6 + '@vencord/discord-types': + specifier: link:packages/discord-types + version: link:packages/discord-types diff: specifier: ^7.0.0 version: 7.0.0 - discord-types: - specifier: ^1.3.26 - version: 1.3.26 esbuild: specifier: ^0.25.1 version: 0.25.1 @@ -141,6 +141,18 @@ importers: specifier: ^0.3.5 version: 0.3.5 + packages/discord-types: + dependencies: + '@types/react': + specifier: ^19.0.10 + version: 19.0.12 + moment: + specifier: ^2.22.2 + version: 2.30.1 + type-fest: + specifier: ^4.41.0 + version: 4.41.0 + packages/vencord-types: dependencies: '@types/lodash': @@ -155,9 +167,6 @@ importers: '@types/react-dom': specifier: 18.3.1 version: 18.3.1 - discord-types: - specifier: ^1.3.26 - version: 1.3.26 standalone-electron-types: specifier: ^34.2.0 version: 34.2.0 @@ -522,9 +531,6 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react@17.0.2': - resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==} - '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} @@ -956,9 +962,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - discord-types@1.3.26: - resolution: {integrity: sha512-ToG51AOCH+JTQf7b+8vuYQe5Iqwz7nZ7StpECAZ/VZcI1ZhQk13pvt9KkRTfRv1xNvwJ2qib4e3+RifQlo8VPQ==} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -2328,6 +2331,10 @@ packages: resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==} engines: {node: '>=16'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2764,11 +2771,6 @@ snapshots: dependencies: '@types/react': 19.0.12 - '@types/react@17.0.2': - dependencies: - '@types/prop-types': 15.7.14 - csstype: 3.1.3 - '@types/react@18.3.1': dependencies: '@types/prop-types': 15.7.14 @@ -3255,11 +3257,6 @@ snapshots: dependencies: path-type: 4.0.0 - discord-types@1.3.26: - dependencies: - '@types/react': 17.0.2 - moment: 2.30.1 - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4923,6 +4920,8 @@ snapshots: type-fest@4.38.0: {} + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 0ee24a31..db0a1ce0 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -51,7 +51,7 @@ const nodeCommonOpts = { format: "cjs", platform: "node", target: ["esnext"], - // @ts-ignore this is never undefined + // @ts-expect-error this is never undefined external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external] }; diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 5c1732aa..516f6a1b 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -363,6 +363,6 @@ export const commonRendererPlugins = [ banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), - // @ts-ignore this is never undefined + // @ts-expect-error this is never undefined ...commonOpts.plugins ]; diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 6f4285ff..1cacd06b 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -8,9 +8,9 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; +import { Channel } from "@vencord/discord-types"; import { waitFor } from "@webpack"; import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index ac1dafc9..8ec23135 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -17,13 +17,11 @@ */ import { mergeDefaults } from "@utils/mergeDefaults"; +import { CommandArgument, Message } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { MessageActions, SnowflakeUtils } from "@webpack/common"; -import { Message } from "discord-types/general"; import type { PartialDeep } from "type-fest"; -import { Argument } from "./types"; - const createBotMessage = findByCodeLazy('username:"Clyde"'); export function generateId() { @@ -51,8 +49,8 @@ export function sendBotMessage(channelId: string, message: PartialDeep<Message>) * @param fallbackValue Fallback value in case this option wasn't passed * @returns Value */ -export function findOption<T>(args: Argument[], name: string): T & {} | undefined; -export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {}; -export function findOption(args: Argument[], name: string, fallbackValue?: any) { +export function findOption<T>(args: CommandArgument[], name: string): T & {} | undefined; +export function findOption<T>(args: CommandArgument[], name: string, fallbackValue: T): T & {}; +export function findOption(args: CommandArgument[], name: string, fallbackValue?: any) { return (args.find(a => a.name === name)?.value ?? fallbackValue) as any; } diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index af6a6fdf..231b3daf 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -18,38 +18,39 @@ import { Logger } from "@utils/Logger"; import { makeCodeblock } from "@utils/text"; +import { CommandArgument, CommandContext, CommandOption } from "@vencord/discord-types"; import { sendBotMessage } from "./commandHelpers"; -import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, VencordCommand } from "./types"; export * from "./commandHelpers"; export * from "./types"; -export let BUILT_IN: Command[]; -export const commands = {} as Record<string, Command>; +export let BUILT_IN: VencordCommand[]; +export const commands = {} as Record<string, VencordCommand>; // hack for plugins being evaluated before we can grab these from webpack -const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option; -const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option; +const OptPlaceholder = Symbol("OptionalMessageOption") as any as CommandOption; +const ReqPlaceholder = Symbol("RequiredMessageOption") as any as CommandOption; /** * Optional message option named "message" you can use in commands. * Used in "tableflip" or "shrug" * @see {@link RequiredMessageOption} */ -export let OptionalMessageOption: Option = OptPlaceholder; +export let OptionalMessageOption: CommandOption = OptPlaceholder; /** * Required message option named "message" you can use in commands. * Used in "me" * @see {@link OptionalMessageOption} */ -export let RequiredMessageOption: Option = ReqPlaceholder; +export let RequiredMessageOption: CommandOption = ReqPlaceholder; // Discord's command list has random gaps for some reason, which can cause issues while rendering the commands // Add this offset to every added command to keep them unique let commandIdOffset: number; -export const _init = function (cmds: Command[]) { +export const _init = function (cmds: VencordCommand[]) { try { BUILT_IN = cmds; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; @@ -61,7 +62,7 @@ export const _init = function (cmds: Command[]) { return cmds; } as never; -export const _handleCommand = function (cmd: Command, args: Argument[], ctx: CommandContext) { +export const _handleCommand = function (cmd: VencordCommand, args: CommandArgument[], ctx: CommandContext) { if (!cmd.isVencordCommand) return cmd.execute(args, ctx); @@ -92,7 +93,7 @@ export const _handleCommand = function (cmd: Command, args: Argument[], ctx: Com * Prepare a Command Option for Discord by filling missing fields * @param opt */ -export function prepareOption<O extends Option | Command>(opt: O): O { +export function prepareOption<O extends CommandOption | VencordCommand>(opt: O): O { opt.displayName ||= opt.name; opt.displayDescription ||= opt.description; opt.options?.forEach((opt, i, opts) => { @@ -109,7 +110,7 @@ export function prepareOption<O extends Option | Command>(opt: O): O { // Yes, Discord registers individual commands for each subcommand // TODO: This probably doesn't support nested subcommands. If that is ever needed, // investigate -function registerSubCommands(cmd: Command, plugin: string) { +function registerSubCommands(cmd: VencordCommand, plugin: string) { cmd.options?.forEach(o => { if (o.type !== ApplicationCommandOptionType.SUB_COMMAND) throw new Error("When specifying sub-command options, all options must be sub-commands."); @@ -132,7 +133,7 @@ function registerSubCommands(cmd: Command, plugin: string) { }); } -export function registerCommand<C extends Command>(command: C, plugin: string) { +export function registerCommand<C extends VencordCommand>(command: C, plugin: string) { if (!BUILT_IN) { console.warn( "[CommandsAPI]", diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts index 70b73775..c5ecb35e 100644 --- a/src/api/Commands/types.ts +++ b/src/api/Commands/types.ts @@ -1,106 +1,12 @@ /* - * 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) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ -import { Channel, Guild } from "discord-types/general"; -import { Promisable } from "type-fest"; +import { Command } from "@vencord/discord-types"; +export { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@vencord/discord-types/enums"; -export interface CommandContext { - channel: Channel; - guild?: Guild; -} - -export const enum ApplicationCommandOptionType { - SUB_COMMAND = 1, - SUB_COMMAND_GROUP = 2, - STRING = 3, - INTEGER = 4, - BOOLEAN = 5, - USER = 6, - CHANNEL = 7, - ROLE = 8, - MENTIONABLE = 9, - NUMBER = 10, - ATTACHMENT = 11, -} - -export const enum ApplicationCommandInputType { - BUILT_IN = 0, - BUILT_IN_TEXT = 1, - BUILT_IN_INTEGRATION = 2, - BOT = 3, - PLACEHOLDER = 4, -} - -export interface Option { - name: string; - displayName?: string; - type: ApplicationCommandOptionType; - description: string; - displayDescription?: string; - required?: boolean; - options?: Option[]; - choices?: Array<ChoicesOption>; -} - -export interface ChoicesOption { - label: string; - value: string; - name: string; - displayName?: string; -} - -export const enum ApplicationCommandType { - CHAT_INPUT = 1, - USER = 2, - MESSAGE = 3, -} - -export interface CommandReturnValue { - content: string; - /** TODO: implement */ - cancel?: boolean; -} - -export interface Argument { - type: ApplicationCommandOptionType; - name: string; - value: string; - focused: undefined; - options: Argument[]; -} - -export interface Command { - id?: string; - applicationId?: string; - type?: ApplicationCommandType; - inputType?: ApplicationCommandInputType; - plugin?: string; +export interface VencordCommand extends Command { isVencordCommand?: boolean; - - name: string; - untranslatedName?: string; - displayName?: string; - description: string; - untranslatedDescription?: string; - displayDescription?: string; - - options?: Option[]; - predicate?(ctx: CommandContext): boolean; - - execute(args: Argument[], ctx: CommandContext): Promisable<void | CommandReturnValue>; } diff --git a/src/api/DataStore/index.ts b/src/api/DataStore/index.ts index 47ae39db..80ebb065 100644 --- a/src/api/DataStore/index.ts +++ b/src/api/DataStore/index.ts @@ -22,9 +22,9 @@ export function promisifyRequest<T = undefined>( request: IDBRequest<T> | IDBTransaction, ): Promise<T> { return new Promise<T>((resolve, reject) => { - // @ts-ignore - file size hacks + // @ts-expect-error - file size hacks request.oncomplete = request.onsuccess = () => resolve(request.result); - // @ts-ignore - file size hacks + // @ts-expect-error - file size hacks request.onabort = request.onerror = () => reject(request.error); }); } diff --git a/src/api/MemberListDecorators.tsx b/src/api/MemberListDecorators.tsx index ada60776..2367e4cf 100644 --- a/src/api/MemberListDecorators.tsx +++ b/src/api/MemberListDecorators.tsx @@ -17,7 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { Channel, User } from "discord-types/general/index.js"; +import { Channel, User } from "@vencord/discord-types"; import { JSX } from "react"; interface DecoratorProps { diff --git a/src/api/MessageDecorations.tsx b/src/api/MessageDecorations.tsx index 1b94c18d..8cf492c7 100644 --- a/src/api/MessageDecorations.tsx +++ b/src/api/MessageDecorations.tsx @@ -17,7 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { Channel, Message } from "discord-types/general/index.js"; +import { Channel, Message } from "@vencord/discord-types"; import { JSX } from "react"; export interface MessageDecorationProps { diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 1b55ff34..8b1d9e78 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -17,9 +17,8 @@ */ import { Logger } from "@utils/Logger"; +import type { Channel, CustomEmoji, Message } from "@vencord/discord-types"; import { MessageStore } from "@webpack/common"; -import { CustomEmoji } from "@webpack/types"; -import type { Channel, Message } from "discord-types/general"; import type { Promisable } from "type-fest"; const MessageEventsLogger = new Logger("MessageEvents", "#e5c890"); diff --git a/src/api/MessagePopover.tsx b/src/api/MessagePopover.tsx index 71787954..c7b2a090 100644 --- a/src/api/MessagePopover.tsx +++ b/src/api/MessagePopover.tsx @@ -18,7 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; -import { Channel, Message } from "discord-types/general"; +import { Channel, Message } from "@vencord/discord-types"; import type { ComponentType, MouseEventHandler } from "react"; const logger = new Logger("MessagePopover"); diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts index 284a2088..7c4b3d5c 100644 --- a/src/api/MessageUpdater.ts +++ b/src/api/MessageUpdater.ts @@ -4,9 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { FluxStore, Message } from "@vencord/discord-types"; import { MessageCache, MessageStore } from "@webpack/common"; -import { FluxStore } from "@webpack/types"; -import { Message } from "discord-types/general"; /** * Update and re-render a message diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 08d2f8ca..5c8965bf 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -268,7 +268,7 @@ type ResolveUseSettings<T extends object> = { [Key in keyof T]: Key extends string ? T[Key] extends Record<string, unknown> - // @ts-ignore "Type instantiation is excessively deep and possibly infinite" + // @ts-expect-error "Type instantiation is excessively deep and possibly infinite" ? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never : Key : never; diff --git a/src/components/DonateButton.tsx b/src/components/DonateButton.tsx index ee2f3ed3..92af9831 100644 --- a/src/components/DonateButton.tsx +++ b/src/components/DonateButton.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { ButtonProps } from "@vencord/discord-types"; import { Button } from "@webpack/common"; -import { ButtonProps } from "@webpack/types"; import { Heart } from "./Heart"; diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx index fe0e987c..378ee34c 100644 --- a/src/components/PluginSettings/ContributorModal.tsx +++ b/src/components/PluginSettings/ContributorModal.tsx @@ -14,8 +14,8 @@ import { DevsById } from "@utils/constants"; import { fetchUserProfile } from "@utils/discord"; import { classes, pluralise } from "@utils/misc"; import { ModalContent, ModalRoot, openModal } from "@utils/modal"; +import { User } from "@vencord/discord-types"; import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; -import { User } from "discord-types/general"; import Plugins from "~plugins"; diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 17ab2662..29ce90c9 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -29,9 +29,9 @@ import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; -import { User } from "discord-types/general"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index f8c71cae..fcb33d38 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -6,9 +6,10 @@ import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; +import { ModuleFactory } from "@vencord/discord-types/webpack"; import * as Webpack from "@webpack"; import { wreq } from "@webpack"; -import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d"; +import { AnyModuleFactory } from "webpack"; export async function loadLazyChunks() { const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index 4ee2d394..39071a0c 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -28,7 +28,7 @@ async function runReporter() { } }, "Vencord Reporter"); - // @ts-ignore + // @ts-expect-error Vencord.Webpack._initReporter = function () { // initReporter is called in the patched entry point of Discord // setImmediate to only start searching for lazy chunks after Discord initialized the app @@ -83,7 +83,6 @@ async function runReporter() { result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports); if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail"); } else { - // @ts-ignore result = Webpack[method](...args); } diff --git a/src/main/patcher.ts b/src/main/patcher.ts index 60b169af..8868caa7 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -38,7 +38,7 @@ const asarPath = join(dirname(injectorPath), "..", asarName); const discordPkg = require(join(asarPath, "package.json")); require.main!.filename = join(asarPath, discordPkg.main); -// @ts-ignore Untyped method? Dies from cringe +// @ts-expect-error Untyped method? Dies from cringe app.setAppPath(asarPath); if (!IS_VANILLA) { diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index b00df6b0..8011e331 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -30,8 +30,8 @@ import { Margins } from "@utils/margins"; import { shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; +import { User } from "@vencord/discord-types"; import { Forms, Toasts, UserStore } from "@webpack/common"; -import { User } from "discord-types/general"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index 30920a06..ad1f255b 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { WebpackRequire } from "@webpack/wreq.d"; +import { WebpackRequire } from "@vencord/discord-types/webpack"; const settings = definePluginSettings({ disableAnalytics: { diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index a9e34f78..0eb8e13c 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -213,7 +213,7 @@ export default definePlugin({ get chromiumVersion() { try { return VencordNative.native.getVersions().chrome - // @ts-ignore Typescript will add userAgentData IMMEDIATELY + // @ts-expect-error Typescript will add userAgentData IMMEDIATELY || navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version || null; } catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 825bbc20..956343f6 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -32,8 +32,8 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; +import { Channel } from "@vencord/discord-types"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -196,7 +196,6 @@ export default definePlugin({ } } - // @ts-ignore outdated type const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles; if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index f871b8ee..ce9f6b51 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -9,9 +9,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common"; -import { User } from "discord-types/general"; interface UserProfileProps { popoutProps: Record<string, any>; diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx index 92b6f57f..e77e3a02 100644 --- a/src/plugins/biggerStreamPreview/index.tsx +++ b/src/plugins/biggerStreamPreview/index.tsx @@ -21,8 +21,8 @@ import { ScreenshareIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { openImageModal } from "@utils/discord"; import definePlugin from "@utils/types"; +import { Channel, User } from "@vencord/discord-types"; import { Menu } from "@webpack/common"; -import { Channel, User } from "discord-types/general"; import { ApplicationStreamingStore, ApplicationStreamPreviewStore } from "./webpack/stores"; import { ApplicationStream, Stream } from "./webpack/types/stores"; diff --git a/src/plugins/biggerStreamPreview/webpack/types/stores.ts b/src/plugins/biggerStreamPreview/webpack/types/stores.ts index 0265986f..e11ede50 100644 --- a/src/plugins/biggerStreamPreview/webpack/types/stores.ts +++ b/src/plugins/biggerStreamPreview/webpack/types/stores.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { FluxStore } from "@webpack/types"; +import { FluxStore } from "@vencord/discord-types"; export interface ApplicationStreamPreviewStore extends FluxStore { getIsPreviewLoading: (guildId: string | bigint | null, channelId: string | bigint, ownerId: string | bigint) => boolean; diff --git a/src/plugins/copyUserURLs/index.tsx b/src/plugins/copyUserURLs/index.tsx index 9e15cc82..744f7cca 100644 --- a/src/plugins/copyUserURLs/index.tsx +++ b/src/plugins/copyUserURLs/index.tsx @@ -21,8 +21,8 @@ import { LinkIcon } from "@components/Icons"; import { copyToClipboard } from "@utils/clipboard"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import type { Channel, User } from "@vencord/discord-types"; import { Menu } from "@webpack/common"; -import type { Channel, User } from "discord-types/general"; interface UserContextProps { channel: Channel; diff --git a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts index f47ccbc5..588bbdf9 100644 --- a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts +++ b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts @@ -6,8 +6,8 @@ import { debounce } from "@shared/debounce"; import { proxyLazy } from "@utils/lazy"; +import { User } from "@vencord/discord-types"; import { useEffect, useState, zustandCreate } from "@webpack/common"; -import { User } from "discord-types/general"; import { AvatarDecoration } from "../../"; import { getUsersDecorations } from "../api"; diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx index a3edc097..56c68410 100644 --- a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx @@ -11,9 +11,9 @@ import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { Queue } from "@utils/Queue"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common"; -import { User } from "discord-types/general"; import { Decoration, getPresets, Preset } from "../../lib/api"; import { GUILD_ID, INVITE_KEY } from "../../lib/constants"; diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index 6f84e7f7..60a95336 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -25,9 +25,9 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; +import { Guild } from "@vencord/discord-types"; import { findByCodeLazy, findStoreLazy } from "@webpack"; import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; -import { Guild } from "discord-types/general"; import { Promisable } from "type-fest"; const StickersStore = findStoreLazy("StickersStore"); diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index b3909595..380147fe 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -23,10 +23,9 @@ import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { getCurrentGuild, getEmojiURL } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, Patch } from "@utils/types"; +import type { Emoji, Message } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; -import type { Emoji } from "@webpack/types"; -import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import type { ReactElement, ReactNode } from "react"; @@ -813,7 +812,6 @@ export default definePlugin({ let isUsableTwitchSubEmote = false; if (e.managed && e.guildId) { - // @ts-ignore outdated type const myRoles = GuildMemberStore.getSelfMember(e.guildId)?.roles ?? []; isUsableTwitchSubEmote = e.roles.some(r => myRoles.includes(r)); } diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 61ab1731..a84722c1 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -25,16 +25,12 @@ import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; -import { User } from "discord-types/general"; import { ReactElement } from "react"; import virtualMerge from "virtual-merge"; -interface UserProfile extends User { - themeColors?: Array<number>; -} - interface Colors { primary: number; accent: number; @@ -220,7 +216,7 @@ export default definePlugin({ </Forms.FormSection>); }, settings, - colorDecodeHook(user: UserProfile) { + colorDecodeHook(user: User) { if (user) { // don't replace colors if already set with nitro if (settings.store.nitroFirst && user.themeColors) return user; diff --git a/src/plugins/favEmojiFirst/index.ts b/src/plugins/favEmojiFirst/index.ts index ebb89f7e..e9a9bc58 100644 --- a/src/plugins/favEmojiFirst/index.ts +++ b/src/plugins/favEmojiFirst/index.ts @@ -18,8 +18,8 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Emoji } from "@vencord/discord-types"; import { EmojiStore } from "@webpack/common"; -import { Emoji } from "@webpack/types"; interface EmojiAutocompleteState { query?: { diff --git a/src/plugins/forceOwnerCrown/index.ts b/src/plugins/forceOwnerCrown/index.ts index bf115c64..f7e4b470 100644 --- a/src/plugins/forceOwnerCrown/index.ts +++ b/src/plugins/forceOwnerCrown/index.ts @@ -18,8 +18,8 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Channel, User } from "@vencord/discord-types"; import { GuildStore } from "@webpack/common"; -import { Channel, User } from "discord-types/general"; export default definePlugin({ name: "ForceOwnerCrown", diff --git a/src/plugins/fullSearchContext/index.tsx b/src/plugins/fullSearchContext/index.tsx index 7c0dbea6..d5b9c8a1 100644 --- a/src/plugins/fullSearchContext/index.tsx +++ b/src/plugins/fullSearchContext/index.tsx @@ -22,9 +22,9 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { NoopComponent } from "@utils/react"; import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { filters, findByCodeLazy, waitFor } from "@webpack"; import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:"); diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 3ab5fca4..b2454f9c 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -19,9 +19,9 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { Channel, Message } from "@vencord/discord-types"; import { findLazy } from "@webpack"; import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common"; -import { Channel, Message } from "discord-types/general"; interface Sticker { id: string; diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx index fe9e6899..0198a3e1 100644 --- a/src/plugins/hideAttachments/index.tsx +++ b/src/plugins/hideAttachments/index.tsx @@ -25,8 +25,8 @@ import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; +import { MessageSnapshot } from "@vencord/discord-types"; import { ChannelStore } from "@webpack/common"; -import { MessageSnapshot } from "@webpack/types"; const KEY = "HideAttachments_HiddenIds"; @@ -56,7 +56,7 @@ export default definePlugin({ }], renderMessagePopoverButton(msg) { - // @ts-ignore - discord-types lags behind discord. + // @ts-expect-error - discord-types lags behind discord. const hasAttachmentsInShapshots = msg.messageSnapshots.some( (snapshot: MessageSnapshot) => snapshot?.message.attachments.length ); diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 4a268868..8288935b 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -30,9 +30,9 @@ import { disableStyle, enableStyle } from "@api/Styles"; import { Logger } from "@utils/Logger"; import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; +import { FluxEvents } from "@vencord/discord-types"; import { FluxDispatcher } from "@webpack/common"; import { patches } from "@webpack/patcher"; -import { FluxEvents } from "@webpack/types"; import Plugins from "~plugins"; diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index ab124192..a19a4615 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -23,8 +23,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getStegCloak } from "@utils/dependencies"; import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common"; -import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; import { buildEncModal } from "./components/EncryptionModal"; diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index ad7491cc..97f6f23c 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -23,8 +23,8 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { FluxStore } from "@vencord/discord-types"; import { findStoreLazy } from "@webpack"; -import { FluxStore } from "@webpack/types"; import { MemberCount } from "./MemberCount"; diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx index 8f18567e..11316ef8 100644 --- a/src/plugins/mentionAvatars/index.tsx +++ b/src/plugins/mentionAvatars/index.tsx @@ -10,8 +10,8 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { GuildRoleStore, SelectedGuildStore, useState } from "@webpack/common"; -import { User } from "discord-types/general"; const settings = definePluginSettings({ showAtSymbol: { diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index 460b95a8..cd331b9d 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -9,9 +9,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { SnowflakeUtils, Tooltip } from "@webpack/common"; -import { Message } from "discord-types/general"; type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted"); type Fill = [FillValue, FillValue, FillValue]; diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 1e4f68e0..663fa349 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -24,6 +24,7 @@ import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin, { OptionType } from "@utils/types"; +import { Channel, Message } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, @@ -39,7 +40,6 @@ import { Text, UserStore } from "@webpack/common"; -import { Channel, Message } from "discord-types/general"; import { JSX } from "react"; const messageCache = new Map<string, { @@ -217,7 +217,7 @@ function withEmbeddedBy(message: Message, embeddedBy: string[]) { return new Proxy(message, { get(_, prop) { if (prop === "vencordEmbeddedBy") return embeddedBy; - // @ts-ignore ts so bad + // @ts-expect-error ts so bad return Reflect.get(...arguments); } }); @@ -225,7 +225,7 @@ function withEmbeddedBy(message: Message, embeddedBy: string[]) { function MessageEmbedAccessory({ message }: { message: Message; }) { - // @ts-ignore + // @ts-expect-error const embeddedBy: string[] = message.vencordEmbeddedBy ?? []; const accessories = [] as (JSX.Element | null)[]; diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 3c7aecae..0be4a0f5 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -28,9 +28,9 @@ import { getIntlMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common"; -import { Message } from "discord-types/general"; import overlayStyle from "./deleteStyleOverlay.css?managed"; import textStyle from "./deleteStyleText.css?managed"; diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts index 49e88c42..90ce0ceb 100644 --- a/src/plugins/messageTags/index.ts +++ b/src/plugins/messageTags/index.ts @@ -92,7 +92,7 @@ export default definePlugin({ // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed const oldTags = await DataStore.get<Tag[]>(DATA_KEY); if (oldTags != null) { - // @ts-ignore + // @ts-expect-error settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag]))); await DataStore.del(DATA_KEY); } @@ -211,7 +211,7 @@ export default definePlugin({ description: Object.values(getTags()) .map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`) .join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`, - // @ts-ignore + // @ts-expect-error color: 0xd77f7f, type: "rich", } diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index b90eb7ed..8d81e43d 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -23,9 +23,9 @@ import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; +import { Channel, User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, Text, useMemo, UserStore } from "@webpack/common"; -import { Channel, User } from "discord-types/general"; import { JSX } from "react"; const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); diff --git a/src/plugins/newGuildSettings/index.tsx b/src/plugins/newGuildSettings/index.tsx index f8a517fa..fba0c1c3 100644 --- a/src/plugins/newGuildSettings/index.tsx +++ b/src/plugins/newGuildSettings/index.tsx @@ -24,9 +24,9 @@ import { definePluginSettings } from "@api/Settings"; import { CogWheel } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { Guild } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { Menu } from "@webpack/common"; -import { Guild } from "discord-types/general"; const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", { diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index 973e3796..1a4d691f 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -21,8 +21,8 @@ import { Devs } from "@utils/constants"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { i18n, RelationshipStore } from "@webpack/common"; -import { Message } from "discord-types/general"; interface MessageDeleteProps { // Internal intl message for BLOCKED_MESSAGE_COUNT diff --git a/src/plugins/noReplyMention/index.tsx b/src/plugins/noReplyMention/index.tsx index 16b3a3e0..b4de573f 100644 --- a/src/plugins/noReplyMention/index.tsx +++ b/src/plugins/noReplyMention/index.tsx @@ -19,7 +19,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import type { Message } from "discord-types/general"; +import type { Message } from "@vencord/discord-types"; const settings = definePluginSettings({ userList: { diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts index e9cde652..0ef81781 100644 --- a/src/plugins/onePingPerDM/index.ts +++ b/src/plugins/onePingPerDM/index.ts @@ -7,8 +7,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { MessageJSON } from "@vencord/discord-types"; import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common"; -import { MessageJSON } from "discord-types/general"; const enum ChannelType { DM = 1, diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 577d8bd7..7ffba7c5 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -27,7 +27,6 @@ function showDisableInvites(guildId: string) { if (!guild) return false; return ( - // @ts-expect-error !hasGuildFeature(guild, "INVITES_DISABLED") && PermissionStore.getGuildPermissionProps(guild).canManageRoles ); diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 46ddb146..28beabe1 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -22,10 +22,9 @@ import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { copyToClipboard } from "@utils/clipboard"; import { getIntlMessage, getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { Guild, Role, UnicodeEmoji, User } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; -import { UnicodeEmoji } from "@webpack/types"; -import type { Guild, Role, User } from "discord-types/general"; import { settings } from ".."; import { cl, getGuildPermissionSpecMap } from "../utils"; diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index 85b1d418..5185abb3 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -19,9 +19,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { getIntlMessage } from "@utils/discord"; import { classes } from "@utils/misc"; +import type { Guild, GuildMember } from "@vencord/discord-types"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common"; -import type { Guild, GuildMember } from "discord-types/general"; import { PermissionsSortOrder, settings } from ".."; import { cl, getGuildPermissionSpecMap, getSortedRoles, sortUserRoles } from "../utils"; diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index cc942f09..ed2471fb 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -25,9 +25,9 @@ import { SafetyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import type { Guild, GuildMember } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, useRef, UserStore } from "@webpack/common"; -import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; import UserPermissions from "./components/UserPermissions"; @@ -71,7 +71,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { const { permissions, header } = match(type) .returnType<{ permissions: RoleOrUserPermission[], header: string; }>() .with(MenuItemParentType.User, () => { - const member = GuildMemberStore.getMember(guildId, id!); + const member = GuildMemberStore.getMember(guildId, id!)!; const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member) .map(role => ({ diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts index 409c0896..022dca6e 100644 --- a/src/plugins/permissionsViewer/utils.ts +++ b/src/plugins/permissionsViewer/utils.ts @@ -17,9 +17,9 @@ */ import { classNameFactory } from "@api/Styles"; +import { Guild, GuildMember, Role } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { GuildRoleStore } from "@webpack/common"; -import { Guild, GuildMember, Role } from "discord-types/general"; import { PermissionsSortOrder, settings } from "."; import { PermissionType } from "./components/RolesAndUsersPermissions"; diff --git a/src/plugins/petpet/index.ts b/src/plugins/petpet/index.ts index 708c6f0c..40fd0af8 100644 --- a/src/plugins/petpet/index.ts +++ b/src/plugins/petpet/index.ts @@ -16,10 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import { makeLazy } from "@utils/lazy"; import definePlugin from "@utils/types"; +import { CommandArgument, CommandContext } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DraftType, UploadHandler, UploadManager, UserUtils } from "@webpack/common"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; @@ -54,7 +55,7 @@ function loadImage(source: File | string) { }); } -async function resolveImage(options: Argument[], ctx: CommandContext, noServerPfp: boolean): Promise<File | string | null> { +async function resolveImage(options: CommandArgument[], ctx: CommandContext, noServerPfp: boolean): Promise<File | string | null> { for (const opt of options) { switch (opt.name) { case "image": @@ -177,6 +178,8 @@ export default definePlugin({ } gif.finish(); + // @ts-ignore This causes a type error on *only some* typescript versions. + // usage adheres to mdn https://developer.mozilla.org/en-US/docs/Web/API/File/File#parameters const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" }); // Immediately after the command finishes, Discord clears all input, including pending attachments. // Thus, setTimeout is needed to make this execute after Discord cleared the input diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index 59fee9c0..53524257 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -11,9 +11,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; +import { Channel } from "@vencord/discord-types"; import { findByPropsLazy, findStoreLazy } from "@webpack"; import { Clickable, ContextMenuApi, FluxDispatcher, Menu, React } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { contextMenus } from "./components/contextMenu"; import { openCategoryModal, requireSettingsMenu } from "./components/CreateCategoryModal"; diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index 7829295a..f804e987 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -25,9 +25,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; -import { User } from "discord-types/general"; export interface Session { sessionId: string; diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index ded408f9..1f64c714 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -20,9 +20,9 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; +import { MessageAttachment } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; -import { MessageAttachment } from "discord-types/general"; const UploadStore = findByPropsLazy("getUploads"); diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index dcd2038c..33e99873 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -19,8 +19,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; import NoReplyMentionPlugin from "plugins/noReplyMention"; diff --git a/src/plugins/readAllNotificationsButton/index.tsx b/src/plugins/readAllNotificationsButton/index.tsx index 419e93af..274e98d3 100644 --- a/src/plugins/readAllNotificationsButton/index.tsx +++ b/src/plugins/readAllNotificationsButton/index.tsx @@ -22,9 +22,9 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Channel } from "@vencord/discord-types"; import { findStoreLazy } from "@webpack"; import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common"; -import { Channel } from "discord-types/general"; interface ThreadJoined { channel: Channel; diff --git a/src/plugins/relationshipNotifier/types.ts b/src/plugins/relationshipNotifier/types.ts index c60d5397..f2e482d6 100644 --- a/src/plugins/relationshipNotifier/types.ts +++ b/src/plugins/relationshipNotifier/types.ts @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Channel } from "discord-types/general"; +import { Channel } from "@vencord/discord-types"; export interface ChannelDelete { type: "CHANNEL_DELETE"; diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts index 09ef5cdd..aaef783b 100644 --- a/src/plugins/relationshipNotifier/utils.ts +++ b/src/plugins/relationshipNotifier/utils.ts @@ -19,9 +19,9 @@ import { DataStore, Notices } from "@api/index"; import { showNotification } from "@api/Notifications"; import { getUniqueUsername, openUserProfile } from "@utils/discord"; +import { FluxStore } from "@vencord/discord-types"; import { findStoreLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common"; -import { FluxStore } from "@webpack/types"; import settings from "./settings"; import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types"; diff --git a/src/plugins/replyTimestamp/index.tsx b/src/plugins/replyTimestamp/index.tsx index 0be5dfec..dcf70c70 100644 --- a/src/plugins/replyTimestamp/index.tsx +++ b/src/plugins/replyTimestamp/index.tsx @@ -9,9 +9,9 @@ import "./style.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import type { Message } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DateUtils, Timestamp } from "@webpack/common"; -import type { Message } from "discord-types/general"; import type { HTMLAttributes } from "react"; const MessageClasses = findByPropsLazy("separator", "latin24CompactTimeStamp"); diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 32546b9b..d8374e6f 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -24,9 +24,9 @@ import { NotesIcon, OpenExternalIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; +import { Guild, User } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { Alerts, Button, Menu, Parser, TooltipContainer } from "@webpack/common"; -import { Guild, User } from "discord-types/general"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; diff --git a/src/plugins/seeSummaries/index.tsx b/src/plugins/seeSummaries/index.tsx index 343348f1..a5af9bc4 100644 --- a/src/plugins/seeSummaries/index.tsx +++ b/src/plugins/seeSummaries/index.tsx @@ -111,7 +111,6 @@ export default definePlugin({ // SUMMARIES_ENABLED feature is not in discord-types const guild = GuildStore.getGuild(channel.guild_id); - // @ts-expect-error return hasGuildFeature(guild, "SUMMARIES_ENABLED_GA"); } }); diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 359590cf..2fa687e8 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -11,9 +11,9 @@ import { getGuildAcronym, openImageModal, openUserProfile } from "@utils/discord import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; +import { Guild, User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildRoleStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; -import { Guild, User } from "discord-types/general"; const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const FriendRow = findComponentByCodeLazy("discriminatorClass:", ".isMobileOnline", "getAvatarURL"); diff --git a/src/plugins/serverInfo/index.tsx b/src/plugins/serverInfo/index.tsx index 2a3f3adf..98b9b2d5 100644 --- a/src/plugins/serverInfo/index.tsx +++ b/src/plugins/serverInfo/index.tsx @@ -7,8 +7,8 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Guild } from "@vencord/discord-types"; import { Menu } from "@webpack/common"; -import { Guild } from "discord-types/general"; import { openGuildInfoModal } from "./GuildInfoModal"; diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index f99c0be9..882c869e 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -25,9 +25,9 @@ import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { Tooltip, UserProfileStore } from "@webpack/common"; -import { User } from "discord-types/general"; import { VerifiedIcon } from "./VerifiedIcon"; diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index bbe286af..4543301b 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -20,9 +20,9 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { formatDuration } from "@utils/text"; +import type { Channel } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; -import type { Channel } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; import { sortPermissionOverwrites } from "../../permissionsViewer/utils"; diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 149d422b..c99afc84 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -25,9 +25,9 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; +import type { Channel, Role } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { ChannelStore, PermissionsBits, PermissionStore, Tooltip } from "@webpack/common"; -import type { Channel, Role } from "discord-types/general"; import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index ba92e82a..b9b10714 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -10,7 +10,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Message, User } from "discord-types/general"; +import { Message, User } from "@vencord/discord-types"; interface UsernameProps { author: { nick: string; }; @@ -62,7 +62,7 @@ export default definePlugin({ const user = userOverride ?? message.author; let { username } = user; if (settings.store.displayNames) - username = (user as any).globalName || username; + username = user.globalName || username; const { nick } = author; const prefix = withMentionPrefix ? "@" : ""; diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx index 2c7c8a85..1bd68a0c 100644 --- a/src/plugins/showTimeoutDuration/index.tsx +++ b/src/plugins/showTimeoutDuration/index.tsx @@ -12,9 +12,9 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { findComponentLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, Text, Tooltip } from "@webpack/common"; -import { Message } from "discord-types/general"; import { FunctionComponent, ReactNode } from "react"; const countDownFilter = canonicalizeMatch("#{intl::MAX_AGE_NEVER}"); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index d8b64cf9..42af9a3a 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -23,8 +23,8 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { DateUtils, RelationshipStore, Text, TooltipContainer } from "@webpack/common"; -import { User } from "discord-types/general"; import { PropsWithChildren } from "react"; const formatter = new Intl.DateTimeFormat(undefined, { diff --git a/src/plugins/spotifyShareCommands/index.ts b/src/plugins/spotifyShareCommands/index.ts index ed793963..e11864ae 100644 --- a/src/plugins/spotifyShareCommands/index.ts +++ b/src/plugins/spotifyShareCommands/index.ts @@ -16,10 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { ApplicationCommandInputType, Command, findOption, OptionalMessageOption, sendBotMessage } from "@api/Commands"; +import { ApplicationCommandInputType, findOption, OptionalMessageOption, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import definePlugin from "@utils/types"; +import { Command } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, MessageActions } from "@webpack/common"; diff --git a/src/plugins/themeAttributes/index.ts b/src/plugins/themeAttributes/index.ts index 7d904e7e..ca54ea27 100644 --- a/src/plugins/themeAttributes/index.ts +++ b/src/plugins/themeAttributes/index.ts @@ -6,8 +6,8 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; export default definePlugin({ diff --git a/src/plugins/translate/TranslationAccessory.tsx b/src/plugins/translate/TranslationAccessory.tsx index db9461d8..00e35689 100644 --- a/src/plugins/translate/TranslationAccessory.tsx +++ b/src/plugins/translate/TranslationAccessory.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { Message } from "@vencord/discord-types"; import { Parser, useEffect, useState } from "@webpack/common"; -import { Message } from "discord-types/general"; import { TranslateIcon } from "./TranslateIcon"; import { cl, TranslationValue } from "./utils"; diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 160b002d..8f667de9 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -56,7 +56,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s return oldKeys.length === currentKeys.length && currentKeys.every(key => old[key] != null); } ); - const currentChannelId: string = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId()); + const currentChannelId = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId()); if (!settings.store.includeMutedChannels) { const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId); diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index fe7acd48..bcfea898 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -21,8 +21,8 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; -import { User } from "discord-types/general"; import { PropsWithChildren } from "react"; import managedStyle from "./style.css?managed"; diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index 2df64b72..e5f8557f 100644 --- a/src/plugins/unsuppressEmbeds/index.tsx +++ b/src/plugins/unsuppressEmbeds/index.tsx @@ -20,8 +20,8 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { MessageSnapshot } from "@vencord/discord-types"; import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; -import { MessageSnapshot } from "@webpack/types"; const EMBED_SUPPRESSED = 1 << 2; diff --git a/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx b/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx index c2a54f14..b4c32638 100644 --- a/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx +++ b/src/plugins/userMessagesPronouns/PronounsChatComponent.tsx @@ -20,9 +20,9 @@ import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { getIntlMessage } from "@utils/discord"; import { classes } from "@utils/misc"; +import { Message } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { Tooltip, UserStore } from "@webpack/common"; -import { Message } from "discord-types/general"; import { settings } from "./settings"; import { useFormattedPronouns } from "./utils"; diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index 9029cdc5..12d95242 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -7,9 +7,9 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; +import { Channel } from "@vencord/discord-types"; import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores } from "@webpack/common"; -import { Channel } from "discord-types/general"; const cl = classNameFactory("vc-uvs-"); diff --git a/src/plugins/validReply/index.ts b/src/plugins/validReply/index.ts index 989513d0..0f0a198d 100644 --- a/src/plugins/validReply/index.ts +++ b/src/plugins/validReply/index.ts @@ -6,10 +6,9 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Channel, Message, User } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { FluxDispatcher, RestAPI } from "@webpack/common"; -import { Message, User } from "discord-types/general"; -import { Channel } from "discord-types/general/index.js"; const enum ReferencedMessageState { Loaded, diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index 02eb216c..117c1388 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -144,7 +144,14 @@ function playSample(tempSettings: any, type: string) { const currentUser = UserStore.getCurrentUser(); const myGuildId = SelectedGuildStore.getGuildId(); - speak(formatText(s[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), s); + speak(formatText( + s[type + "Message"], + currentUser.username, + "general", + currentUser.globalName ?? currentUser.username, + GuildMemberStore.getNick(myGuildId!, currentUser.id) ?? currentUser.username), + s + ); } export default definePlugin({ @@ -177,7 +184,7 @@ export default definePlugin({ const template = settings.store[type + "Message"]; const user = isMe && !settings.store.sayOwnName ? "" : UserStore.getUser(userId).username; const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user); - const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user); + const nickname = user && (GuildMemberStore.getNick(myGuildId!, userId) ?? user); const channel = ChannelStore.getChannel(id).name; speak(formatText(template, user, channel, displayName, nickname)); diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 2afdb2d5..3b8485fc 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -22,8 +22,8 @@ import { ImageIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { openImageModal } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import type { Channel, Guild, User } from "@vencord/discord-types"; import { GuildMemberStore, IconUtils, Menu } from "@webpack/common"; -import type { Channel, Guild, User } from "discord-types/general"; interface UserContextProps { diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index 7eb7b78f..e6781784 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -27,8 +27,8 @@ import { Margins } from "@utils/margins"; import { copyWithToast } from "@utils/misc"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { Button, ChannelStore, Forms, GuildRoleStore, Menu, Text } from "@webpack/common"; -import { Message } from "discord-types/general"; const CopyIcon = () => { diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index f14de9f5..a2826a2c 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -22,10 +22,9 @@ import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; +import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common"; -import { CustomEmoji } from "@webpack/types"; -import { Message, ReactionEmoji, User } from "discord-types/general"; const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); diff --git a/src/plugins/xsOverlay/index.tsx b/src/plugins/xsOverlay/index.tsx index 1ffbd3ff..27faac93 100644 --- a/src/plugins/xsOverlay/index.tsx +++ b/src/plugins/xsOverlay/index.tsx @@ -9,9 +9,9 @@ import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import type { Channel, Embed, GuildMember, MessageAttachment, User } from "@vencord/discord-types"; import { findByCodeLazy, findLazy } from "@webpack"; import { Button, ChannelStore, GuildRoleStore, GuildStore, UserStore } from "@webpack/common"; -import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10); diff --git a/src/utils/apng-canvas.js b/src/utils/apng-canvas.js index 5149dcc0..62203a75 100644 --- a/src/utils/apng-canvas.js +++ b/src/utils/apng-canvas.js @@ -1,6 +1,5 @@ /* eslint-disable */ -const self = module.exports; /** * apng-canvas v2.1.2 * @@ -549,11 +548,7 @@ const self = module.exports; }).call( this, Y("VCmEsw"), - "undefined" != typeof self - ? self - : "undefined" != typeof window - ? window - : {} + module.exports ); }, { VCmEsw: 2 }, @@ -880,11 +875,7 @@ const self = module.exports; }); }).call( this, - "undefined" != typeof self - ? self - : "undefined" != typeof window - ? window - : {} + module.exports ); }, { "./loader": 6, "./parser": 7, "./support-test": 8 }, @@ -1153,11 +1144,7 @@ const self = module.exports; }; }).call( this, - "undefined" != typeof self - ? self - : "undefined" != typeof window - ? window - : {} + module.exports ); }, { "es6-promise": 1 }, diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index fce909c3..4a652a29 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,9 +17,8 @@ */ import { MessageObject } from "@api/MessageEvents"; +import { Channel, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; -import { Channel, Guild, Message, User } from "discord-types/general"; -import GuildFeatures from "discord-types/other/Constants"; import { Except } from "type-fest"; import { runtimeHashMessageKey } from "./intlHash"; @@ -228,6 +227,6 @@ export function getGuildAcronym(guild: Guild): string { .replace(/\s/g, ""); } -export function hasGuildFeature(guild: Guild, feature: keyof GuildFeatures["GuildFeatures"]): boolean { +export function hasGuildFeature(guild: Guild, feature: GuildFeatures): boolean { return guild.features?.has(feature) ?? false; } diff --git a/src/utils/types.ts b/src/utils/types.ts index 3ad63ea1..1c53b971 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -18,14 +18,13 @@ import { ProfileBadge } from "@api/Badges"; import { ChatBarButtonFactory } from "@api/ChatButtons"; -import { Command } from "@api/Commands"; import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { MemberListDecoratorFactory } from "@api/MemberListDecorators"; import { MessageAccessoryFactory } from "@api/MessageAccessories"; import { MessageDecorationFactory } from "@api/MessageDecorations"; import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents"; import { MessagePopoverButtonFactory } from "@api/MessagePopover"; -import { FluxEvents } from "@webpack/types"; +import { Command, FluxEvents } from "@vencord/discord-types"; import { ReactNode } from "react"; import { Promisable } from "type-fest"; diff --git a/src/webpack/common/classes.ts b/src/webpack/common/classes.ts index ca3d75f5..85b90537 100644 --- a/src/webpack/common/classes.ts +++ b/src/webpack/common/classes.ts @@ -16,9 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import * as t from "@vencord/discord-types"; import { findByPropsLazy, findLazy } from "@webpack"; -import * as t from "./types/classes"; - export const ModalImageClasses: t.ImageModalClasses = findLazy(m => m.image && m.modal && !m.applicationIcon); export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent"); diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index bcce3cbb..a392a02f 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -17,10 +17,10 @@ */ import { LazyComponent } from "@utils/lazyReact"; +import * as t from "@vencord/discord-types"; import { filters, mapMangledModuleLazy, waitFor } from "@webpack"; import { waitForComponent } from "./internal"; -import * as t from "./types/components"; const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"')); diff --git a/src/webpack/common/index.ts b/src/webpack/common/index.ts index 4193330c..80223b9a 100644 --- a/src/webpack/common/index.ts +++ b/src/webpack/common/index.ts @@ -21,8 +21,5 @@ export * from "./components"; export * from "./menu"; export * from "./react"; export * from "./stores"; -export * as ComponentTypes from "./types/components.d"; -export * as MenuTypes from "./types/menu.d"; -export * as UtilTypes from "./types/utils.d"; export * from "./userSettings"; export * from "./utils"; diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index 2bb05f36..e4c5d0a3 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -16,10 +16,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import type * as t from "@vencord/discord-types"; import { filters, mapMangledModuleLazy, waitFor, wreq } from "@webpack"; -import type * as t from "./types/menu"; - export const Menu = {} as t.Menu; // Relies on .name properties added by the MenuItemDemanglerAPI diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 712f5bdc..be72d47c 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,11 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import * as t from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack"; -import type * as Stores from "discord-types/stores"; import { waitForStore } from "./internal"; -import * as t from "./types/stores"; export const Flux: t.Flux = findByPropsLazy("connectStores"); @@ -28,7 +27,7 @@ export type GenericStore = t.FluxStore & Record<string, any>; export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand"); -export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & GenericStore & { +export let MessageStore: Omit<t.MessageStore, "getMessages"> & GenericStore & { getMessages(chanId: string): any; }; @@ -41,12 +40,12 @@ export let PresenceStore: GenericStore; export let GuildStore: t.GuildStore; export let GuildRoleStore: t.GuildRoleStore; -export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; -export let UserStore: Stores.UserStore & t.FluxStore; +export let GuildMemberStore: t.GuildMemberStore; +export let UserStore: t.UserStore; export let UserProfileStore: GenericStore; -export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore; -export let SelectedGuildStore: t.FluxStore & Record<string, any>; -export let ChannelStore: Stores.ChannelStore & t.FluxStore; +export let SelectedChannelStore: t.SelectedChannelStore; +export let SelectedGuildStore: t.SelectedGuildStore; +export let ChannelStore: t.ChannelStore; export let RelationshipStore: t.RelationshipStore; export let EmojiStore: t.EmojiStore; @@ -55,14 +54,7 @@ export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; /** - * React hook that returns stateful data for one or more stores - * You might need a custom comparator (4th argument) if your store data is an object - * @param stores The stores to listen to - * @param mapper A function that returns the data you need - * @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook - * @param isEqual A custom comparator for the data returned by mapper - * - * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); + * @see jsdoc of {@link t.useStateFromStores} */ export const useStateFromStores: t.useStateFromStores = findByCodeLazy("useStateFromStores"); diff --git a/src/webpack/common/types/classes.d.ts b/src/webpack/common/types/classes.d.ts deleted file mode 100644 index b6066177..00000000 --- a/src/webpack/common/types/classes.d.ts +++ /dev/null @@ -1,39 +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 interface ImageModalClasses { - image: string, - modal: string, -} - -export interface ButtonWrapperClasses { - hoverScale: string; - buttonWrapper: string; - button: string; - iconMask: string; - buttonContent: string; - icon: string; - pulseIcon: string; - pulseButton: string; - notificationDot: string; - sparkleContainer: string; - sparkleStar: string; - sparklePlus: string; - sparkle: string; - active: string; -} diff --git a/src/webpack/common/types/fluxEvents.d.ts b/src/webpack/common/types/fluxEvents.d.ts deleted file mode 100644 index 1bd50f46..00000000 --- a/src/webpack/common/types/fluxEvents.d.ts +++ /dev/null @@ -1,40 +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/>. -*/ - -/* -function makeFluxEventList() { - // prefill MESSAGE_CREATE so that typescript infers this is a String Set - // without explicitly typing so that this function is also valid javascript - const events = new Set(["MESSAGE_CREATE"]); - - const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; - for (const nodeId in nodes) { - for (const event in nodes[nodeId].actionHandler) { - events.add(event); - } - } - for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { - events.add(event); - } - - return Array.from(events, e => JSON.stringify(e)).sort().join("|"); -} -*/ - -// 46kb worth of events ??????? -export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_CONTRAST" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CROSSFADES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACKNOWLEDGE_CHANNEL_SAFETY_WARNING_TOOLTIP" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_BOGO_PROMOTION_FETCH" | "ACTIVE_BOGO_PROMOTION_FETCH_FAIL" | "ACTIVE_BOGO_PROMOTION_FETCH_SUCCESS" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITIES_WHATS_NEW_ACKNOWLEDGE_SECTION" | "ACTIVITIES_WHATS_NEW_MARK_OPENED_SECTION" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_LAYOUT_MODE_UPDATE" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "ADYEN_CASH_APP_PAY_SUBMIT_SUCCESS" | "ADYEN_CREATE_CASH_APP_PAY_COMPONENT_SUCCESS" | "ADYEN_CREATE_CLIENT_SUCCESS" | "ADYEN_TEARDOWN_CLIENT" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATIONS_SHELF_FETCH_FAIL" | "APPLICATIONS_SHELF_FETCH_START" | "APPLICATIONS_SHELF_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_ASSETS_FETCH" | "APPLICATION_ASSETS_FETCH_SUCCESS" | "APPLICATION_ASSETS_UPDATE" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_EXECUTE_BAD_VERSION" | "APPLICATION_COMMAND_INDEX_FETCH_FAILURE" | "APPLICATION_COMMAND_INDEX_FETCH_REQUEST" | "APPLICATION_COMMAND_INDEX_FETCH_SUCCESS" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_DIRECTORY_FETCH_APPLICATION" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_FAILURE" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_CATEGORIES_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SEARCH" | "APPLICATION_DIRECTORY_FETCH_SEARCH_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SEARCH_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_SUCCESS" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_ICON_EDITOR_CLOSE" | "APP_ICON_EDITOR_OPEN" | "APP_ICON_TRACK_IMPRESSION" | "APP_ICON_UPDATED" | "APP_LAUNCHER_DISMISS_APP_DETAIL" | "APP_LAUNCHER_DISMISS_POPUP" | "APP_LAUNCHER_SET_ACTIVE_COMMAND" | "APP_LAUNCHER_SHOW_APP_DETAIL" | "APP_LAUNCHER_SHOW_POPUP" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SELF_MUTE" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_LOCAL_SOUNDBOARD_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_TARGET" | "AUDIT_LOG_FILTER_BY_USER" | "AUTHENTICATOR_CREATE" | "AUTHENTICATOR_DELETE" | "AUTHENTICATOR_UPDATE" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "BACKGROUND_SYNC" | "BACKGROUND_SYNC_CHANNEL_MESSAGES" | "BILLING_ANNUAL_USER_OFFER_FETCH_FAIL" | "BILLING_ANNUAL_USER_OFFER_FETCH_SUCCESS" | "BILLING_CREATE_REFERRAL_PREVIEW_FAIL" | "BILLING_CREATE_REFERRAL_PREVIEW_START" | "BILLING_CREATE_REFERRAL_PREVIEW_SUCCESS" | "BILLING_CREATE_REFERRAL_SUCCESS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_NITRO_AFFINITY_FETCHED" | "BILLING_NITRO_AFFINITY_FETCH_SUCCEEDED" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_PERKS_RELEVANCE_FETCH_FAIL" | "BILLING_PERKS_RELEVANCE_FETCH_START" | "BILLING_PERKS_RELEVANCE_FETCH_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PREVIOUS_PREMIUM_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_REFERRALS_REMAINING_FETCH_FAIL" | "BILLING_REFERRALS_REMAINING_FETCH_START" | "BILLING_REFERRALS_REMAINING_FETCH_SUCCESS" | "BILLING_REFERRAL_RESOLVE_FAIL" | "BILLING_REFERRAL_RESOLVE_SUCCESS" | "BILLING_REFERRAL_TRIAL_OFFER_UPDATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_OFFER_FETCH_FAIL" | "BILLING_USER_OFFER_FETCH_SUCCESS" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH_ERROR" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BLOCKED_DOMAIN_LIST_FETCHED" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_FAIL" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_FAIL" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROADCASTER_BUCKETS_RECEIVED" | "BROADCAST_START" | "BROADCAST_STOP" | "BROADCAST_VIEWERS_UPDATE" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_PICKER_ANIMATION_ADD" | "BURST_REACTION_PICKER_ANIMATION_CLEAR" | "CACHED_EMOJIS_LOADED" | "CACHED_STICKERS_LOADED" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CACHE_LOADED_LAZY_NO_CACHE" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CONNECT_MULTIPLE" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_FETCH_FAILED" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_MARK_SEEN" | "CHANGE_LOG_SET_CONFIG" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MUTE_EXPIRED" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SAFETY_WARNING_FEEDBACK" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OPEN" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_STATUSES" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_TOGGLE_SUMMARIES_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECK_LAUNCHABLE_GAME" | "CLAN_SETUP_ERROR" | "CLAN_SETUP_RESET" | "CLAN_SETUP_SUBMIT" | "CLAN_SETUP_SUCCESS" | "CLAN_SETUP_UPDATE" | "CLEAR_AUTHENTICATION_ERRORS" | "CLEAR_CACHES" | "CLEAR_CHANNEL_SAFETY_WARNINGS" | "CLEAR_CONVERSATION_SUMMARIES" | "CLEAR_HANG_STATUS" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_LAST_SESSION_VOICE_CHANNEL_ID" | "CLEAR_MENTIONS" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIENT_THEMES_EDITOR_OPEN" | "CLIPS_CLASSIFY_HARDWARE" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_CLEAR_NEW_CLIP_IDS" | "CLIPS_DELETE_CLIP" | "CLIPS_DISMISS_EDUCATION" | "CLIPS_INIT" | "CLIPS_INIT_FAILURE" | "CLIPS_LOAD_DIRECTORY_SUCCESS" | "CLIPS_RESTART" | "CLIPS_SAVE_ANIMATION_END" | "CLIPS_SAVE_CLIP" | "CLIPS_SAVE_CLIP_ERROR" | "CLIPS_SAVE_CLIP_PLACEHOLDER" | "CLIPS_SAVE_CLIP_PLACEHOLDER_ERROR" | "CLIPS_SAVE_CLIP_START" | "CLIPS_SETTINGS_UPDATE" | "CLIPS_SHOW_CALL_WARNING" | "CLIPS_UPDATE_METADATA" | "CLOSE_SUSPENDED_USER" | "COLLECTIBLES_CATEGORIES_FETCH" | "COLLECTIBLES_CATEGORIES_FETCH_FAILURE" | "COLLECTIBLES_CATEGORIES_FETCH_SUCCESS" | "COLLECTIBLES_CATEGORY_ITEMS_VIEWED" | "COLLECTIBLES_CLAIM" | "COLLECTIBLES_CLAIM_FAILURE" | "COLLECTIBLES_CLAIM_SUCCESS" | "COLLECTIBLES_PRODUCT_DETAILS_OPEN" | "COLLECTIBLES_PRODUCT_FETCH" | "COLLECTIBLES_PRODUCT_FETCH_FAILURE" | "COLLECTIBLES_PRODUCT_FETCH_SUCCESS" | "COLLECTIBLES_PURCHASES_FETCH" | "COLLECTIBLES_PURCHASES_FETCH_FAILURE" | "COLLECTIBLES_PURCHASES_FETCH_SUCCESS" | "COLLECTIBLES_SHOP_CLOSE" | "COLLECTIBLES_SHOP_OPEN" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "COMPLETE_NEW_MEMBER_ACTION" | "COMPLETE_SIGN_UP" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SET" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_INTERRUPTED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONTENT_INVENTORY_CLEAR_FEED" | "CONTENT_INVENTORY_DEBUG_CLEAR_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_LOG_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_TOGGLE_FAST_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DEBUG_TOGGLE_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_MANUAL_REFRESH" | "CONTENT_INVENTORY_SET_FEED" | "CONTENT_INVENTORY_SET_FILTERS" | "CONTENT_INVENTORY_TOGGLE_FEED_HIDDEN" | "CONTENT_INVENTORY_TOGGLE_REPLY_MODE" | "CONTENT_INVENTORY_TRACK_ITEM_IMPRESSIONS" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CONVERSATION_SUMMARY_UPDATE" | "CREATE_PENDING_REPLY" | "CREATE_REFERRALS_SUCCESS" | "CREATE_SHALLOW_PENDING_REPLY" | "CREATOR_MONETIZATION_NAG_ACTIVATE_ELIGIBLITY_FETCH_SUCCESS" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_FAILURE" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_SUCCESS" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "DCF_DAILY_CAP_OVERRIDE" | "DCF_HANDLE_DC_DISMISSED" | "DCF_HANDLE_DC_SHOWN" | "DCF_RESET" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DELETE_SUMMARY" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_FAILURE" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_SUCCESS" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_UPDATE_FILTER" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_DESIGN_TOGGLE_SET" | "DEV_TOOLS_DESIGN_TOGGLE_WEB_SET" | "DEV_TOOLS_DEV_SETTING_SET" | "DEV_TOOLS_SETTINGS_UPDATE" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISMISS_CHANNEL_SAFETY_WARNINGS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_MEDIA_POST_SHARE_PROMPT" | "DISMISS_SIGN_UP" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "DRAWER_CLOSE" | "DRAWER_OPEN" | "DRAWER_SELECT_TAB" | "DROPS_ELIGIBILITY_FETCH_SUCCESS" | "DROPS_ENROLLED_USER_FETCH_SUCCESS" | "DROPS_FETCH_PROGRESS_FAILURE" | "DROPS_FETCH_PROGRESS_SUCCESS" | "DROPS_HEARTBEAT_FAILURE" | "DROPS_HEARTBEAT_SUCCESS" | "DROPS_PLATFORM_AVAILABILITY_SUCCESS" | "DROPS_UNENROLL_USER" | "DROPS_USER_STATUS_FETCH_FAILURE" | "DROPS_USER_STATUS_FETCH_SUCCESS" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_DISCONNECT" | "EMBEDDED_ACTIVITY_DISMISS_NEW_INDICATOR" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_FOCUSED_LAYOUT" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMBEDDED_ACTIVITY_SET_PANEL_MODE" | "EMBEDDED_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_UPDATE_V2" | "EMOJI_AUTOSUGGESTION_UPDATE" | "EMOJI_CAPTIONS_FETCH" | "EMOJI_CAPTIONS_FETCH_ERROR" | "EMOJI_CAPTIONS_FETCH_SUCCESS" | "EMOJI_INTERACTION_INITIATED" | "EMOJI_TRACK_USAGE" | "ENABLE_AUTOMATIC_ACK" | "ENABLE_GUILD_SIGN_UP" | "ENABLE_USER_SIGN_UP" | "ENTITLEMENTS_FETCH_FOR_USER_FAIL" | "ENTITLEMENTS_FETCH_FOR_USER_START" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "EXPERIMENT_REGISTER_LEGACY" | "FAMILY_CENTER_FETCH_START" | "FAMILY_CENTER_HANDLE_TAB_SELECT" | "FAMILY_CENTER_INITIAL_LOAD" | "FAMILY_CENTER_LINKED_USERS_FETCH_SUCCESS" | "FAMILY_CENTER_LINK_CODE_FETCH_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_REMOVE_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_UPDATE_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_FETCH_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_MORE_FETCH_SUCCESS" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_CLAN_DISCOVERY_SEARCH_RESULT_SUCCESS" | "FETCH_GUILD_EVENT" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_FAILURE" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_SUCCESS" | "FETCH_PRIVATE_CHANNEL_INTEGRATIONS_FAIL" | "FETCH_PRIVATE_CHANNEL_INTEGRATIONS_START" | "FETCH_PRIVATE_CHANNEL_INTEGRATIONS_SUCCESS" | "FETCH_STATIC_CLAN_LIST_SUCCESS" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_CONSOLE_SELECT_DEVICE" | "GAME_DETECTION_WATCH_CANDIDATE_GAMES_START" | "GAME_ICON_UPDATE" | "GAME_INVITE_CLEAR_UNSEEN" | "GAME_INVITE_CREATE" | "GAME_INVITE_DELETE" | "GAME_INVITE_DELETE_MANY" | "GAME_INVITE_UPDATE_STATUS" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GUILD_ACK" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_CLEAR_SEARCH" | "GUILD_DISCOVERY_CLEAR_SEEN_GUILDS" | "GUILD_DISCOVERY_FETCH_FAILURE" | "GUILD_DISCOVERY_FETCH_START" | "GUILD_DISCOVERY_FETCH_SUCCESS" | "GUILD_DISCOVERY_GUILD_SEEN" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_POPULAR_FETCH_FAILURE" | "GUILD_DISCOVERY_POPULAR_FETCH_START" | "GUILD_DISCOVERY_POPULAR_FETCH_SUCCESS" | "GUILD_DISCOVERY_SEARCH_COUNTS_FAIL" | "GUILD_DISCOVERY_SEARCH_FETCH_FAILURE" | "GUILD_DISCOVERY_SEARCH_FETCH_START" | "GUILD_DISCOVERY_SEARCH_FETCH_SUCCESS" | "GUILD_DISCOVERY_SEARCH_INIT" | "GUILD_DISCOVERY_SEARCH_UPDATE_COUNTS" | "GUILD_DISCOVERY_SELECT_CATEGORY" | "GUILD_DISCOVERY_SLUG_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FEED_FEATURED_ITEMS_FETCH_FAILURE" | "GUILD_FEED_FEATURED_ITEMS_FETCH_SUCCESS" | "GUILD_FEED_FEATURE_ITEM" | "GUILD_FEED_FETCH_FAILURE" | "GUILD_FEED_FETCH_FRESH_START" | "GUILD_FEED_FETCH_PAGE_START" | "GUILD_FEED_FETCH_SUCCESS" | "GUILD_FEED_ITEM_HIDE" | "GUILD_FEED_ITEM_READ_ACK" | "GUILD_FEED_ITEM_REMOVE" | "GUILD_FEED_ITEM_UNHIDE" | "GUILD_FEED_UNFEATURE_ITEM" | "GUILD_FOLDER_COLLAPSE" | "GUILD_FOLDER_CREATE_LOCAL" | "GUILD_FOLDER_DELETE_LOCAL" | "GUILD_FOLDER_EDIT_LOCAL" | "GUILD_GEO_RESTRICTED" | "GUILD_HOME_ENSURE_HOME_SESSION" | "GUILD_HOME_SETTINGS_FETCH_FAIL" | "GUILD_HOME_SETTINGS_FETCH_START" | "GUILD_HOME_SETTINGS_FETCH_SUCCESS" | "GUILD_HOME_SETTINGS_TOGGLE_ENABLED" | "GUILD_HOME_SETTINGS_UPDATE_SUCCESS" | "GUILD_HOME_SET_SCROLL_POSITION" | "GUILD_HOME_SET_SOURCE" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_CLOSE" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR_DECORATION" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PROFILE_EFFECT_ID" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_TAB" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_BY_ID_FETCH_SUCCESS" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_MEMBERS_CHUNK_BATCH" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE_BY_ID" | "GUILD_MUTE_EXPIRED" | "GUILD_NEW_MEMBER_ACTIONS_DELETE_SUCCESS" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_FAIL" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_START" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_SUCCESS" | "GUILD_NEW_MEMBER_ACTION_UPDATE_SUCCESS" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POPOUT_FETCH_FAILURE" | "GUILD_POPOUT_FETCH_START" | "GUILD_POPOUT_FETCH_SUCCESS" | "GUILD_PRODUCTS_FETCH" | "GUILD_PRODUCTS_FETCH_FAILURE" | "GUILD_PRODUCTS_FETCH_SUCCESS" | "GUILD_PRODUCT_CREATE" | "GUILD_PRODUCT_DELETE" | "GUILD_PRODUCT_FETCH" | "GUILD_PRODUCT_FETCH_FAILURE" | "GUILD_PRODUCT_FETCH_SUCCESS" | "GUILD_PRODUCT_UPDATE" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RECOMMENDATION_FETCH" | "GUILD_RECOMMENDATION_FETCH_FAILURE" | "GUILD_RECOMMENDATION_FETCH_SUCCESS" | "GUILD_RESOURCE_CHANNEL_UPDATE_SUCCESS" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_TEMPLATES" | "GUILD_ROLE_SUBSCRIPTIONS_STASH_TEMPLATE_CHANNELS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTIONS_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_COUNTS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SEARCH_RECENT_MEMBERS" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_BANS_BATCH" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_SET_MODE" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_SOUNDS_UPDATE" | "GUILD_SOUNDBOARD_SOUND_CREATE" | "GUILD_SOUNDBOARD_SOUND_DELETE" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_LOCALLY" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_SOUND_UPDATE" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_ADD_MEMBER_UPDATES" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_SUBSCRIPTIONS_REMOVE_MEMBER_UPDATES" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HABITUAL_DND_CLEAR" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIGH_FIVE_COMPLETE" | "HIGH_FIVE_COMPLETE_CLEAR" | "HIGH_FIVE_QUEUE" | "HIGH_FIVE_REMOVE" | "HIGH_FIVE_SET_ENABLED" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "I18N_LOAD_ERROR" | "I18N_LOAD_START" | "I18N_LOAD_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INITIALIZE_MEMBER_SAFETY_STORE" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CLEAR" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_CREATE" | "INTEGRATION_DELETE" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_IFRAME_MODAL_CLOSE" | "INTERACTION_IFRAME_MODAL_CREATE" | "INTERACTION_IFRAME_MODAL_KEY_CREATE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIGHTNING_CHECKOUT_CLOSE" | "LIGHTNING_CHECKOUT_OPEN" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_CHANNELS" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES" | "LOAD_USER_AFFINITIES_FAILURE" | "LOAD_USER_AFFINITIES_SUCCESS" | "LOCAL_ACTIVITY_UPDATE" | "LOCAL_MESSAGES_LOADED" | "LOCAL_MESSAGE_CREATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_FAILURE" | "LOGIN_MFA_SMS" | "LOGIN_MFA_SMS_FAILURE" | "LOGIN_MFA_SMS_REQUEST_SUCCESS" | "LOGIN_MFA_STEP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGIN_SUSPENDED_USER" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_GO_LIVE_SOURCE" | "MEDIA_ENGINE_SET_HARDWARE_H264" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEDIA_POST_EMBED_FETCH" | "MEDIA_POST_EMBED_FETCH_FAILURE" | "MEDIA_POST_EMBED_FETCH_SUCCESS" | "MEDIA_SESSION_JOINED" | "MEMBER_SAFETY_GUILD_MEMBER_SEARCH_SUCCESS" | "MEMBER_SAFETY_GUILD_MEMBER_UPDATE_BATCH" | "MEMBER_SAFETY_NEW_MEMBER_TIMESTAMP_REFRESH" | "MEMBER_SAFETY_PAGINATION_TOKEN_UPDATE" | "MEMBER_SAFETY_PAGINATION_UPDATE" | "MEMBER_SAFETY_SEARCH_STATE_UPDATE" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MENTION_MODAL_CLOSE" | "MENTION_MODAL_OPEN" | "MESSAGE_ACK" | "MESSAGE_ACKED" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_EXPLICIT_CONTENT_FP_CREATE" | "MESSAGE_EXPLICIT_CONTENT_FP_SUBMIT" | "MESSAGE_EXPLICIT_CONTENT_SCAN_TIMEOUT" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_NOTIFICATION_SHOWN" | "MESSAGE_PREVIEWS_LOADED" | "MESSAGE_PREVIEWS_LOCALLY_LOADED" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_MANY" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REMINDER_NOTIFIED" | "MESSAGE_REMINDER_TOGGLE" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_EMAIL_TOKEN" | "MFA_ENABLE_SUCCESS" | "MFA_SEEN_BACKUP_CODE_PROMPT" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MFA_WEBAUTHN_CREDENTIALS_LOADING" | "MOBILE_NATIVE_UPDATE_CHECK_FINISHED" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MOD_VIEW_SEARCH_FINISH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_CLEAR_GUILD_MENTIONS" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_REFRESH" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CENTER_TAB_FOCUSED" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOTIFICATION_SETTINGS_UPDATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_MESSAGE_EVENT_ACTION" | "OVERLAY_NOTIFICATION_EVENT" | "OVERLAY_NOTIFY_READY_TO_SHOW" | "OVERLAY_READY" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_ASSOCIATED_GAME" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_CLICK_ZONES" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_SHOW_KEYBIND_INDICATORS" | "OVERLAY_SET_TEXT_CHAT_NOTIFICATION_MODE" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SET_UI_LOCKED" | "OVERLAY_SOUNDBOARD_SOUNDS_FETCH_REQUEST" | "OVERLAY_START_SESSION" | "OVERLAY_SUCCESSFULLY_SHOWN" | "OVERLAY_WIDGET_CHANGED" | "PASSIVE_UPDATE_V1" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "PREMIUM_MARKETING_DATA_READY" | "PREMIUM_MARKETING_PREVIEW" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_PERKS_DEMOS_FETCH_FAILURE" | "PREMIUM_PERKS_DEMOS_FETCH_SUCCESS" | "PREMIUM_PERKS_DEMO_ACTIVATE_FAILURE" | "PREMIUM_PERKS_DEMO_ACTIVATE_SUCCESS" | "PREMIUM_PERKS_DEMO_COMPLETE" | "PREMIUM_PERKS_DEMO_OVERRIDE" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_INTEGRATION_CREATE" | "PRIVATE_CHANNEL_INTEGRATION_DELETE" | "PRIVATE_CHANNEL_INTEGRATION_UPDATE" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_EFFECTS_SET_TRY_IT_OUT" | "PROFILE_PANEL_TOGGLE_SECTION" | "PROXY_BLOCKED_REQUEST" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_SUCCESS" | "PURCHASED_ITEMS_FESTIVITY_IS_FETCHING_WOW_MOMENT_MEDIA" | "PURCHASED_ITEMS_FESTIVITY_SET_CAN_PLAY_WOW_MOMENT" | "PURCHASE_CONFIRMATION_MODAL_CLOSE" | "PURCHASE_CONFIRMATION_MODAL_OPEN" | "PUSH_NOTIFICATION_CLICK" | "QUESTS_CLAIM_REWARD_BEGIN" | "QUESTS_CLAIM_REWARD_CODE_BEGIN" | "QUESTS_CLAIM_REWARD_CODE_FAILURE" | "QUESTS_CLAIM_REWARD_CODE_SUCCESS" | "QUESTS_CLAIM_REWARD_FAILURE" | "QUESTS_CLAIM_REWARD_SUCCESS" | "QUESTS_DELIVERY_OVERRIDE" | "QUESTS_DISMISS_CONTENT_BEGIN" | "QUESTS_DISMISS_CONTENT_FAILURE" | "QUESTS_DISMISS_CONTENT_SUCCESS" | "QUESTS_DISMISS_PROGRESS_TRACKING_FAILURE_NOTICE" | "QUESTS_ENROLL_BEGIN" | "QUESTS_ENROLL_FAILURE" | "QUESTS_ENROLL_SUCCESS" | "QUESTS_FETCH_CURRENT_QUESTS_BEGIN" | "QUESTS_FETCH_CURRENT_QUESTS_FAILURE" | "QUESTS_FETCH_CURRENT_QUESTS_SUCCESS" | "QUESTS_FETCH_REWARD_CODE_BEGIN" | "QUESTS_FETCH_REWARD_CODE_FAILURE" | "QUESTS_FETCH_REWARD_CODE_SUCCESS" | "QUESTS_OPTIMISTIC_PROGRESS_UPDATE" | "QUESTS_PREVIEW_UPDATE_SUCCESS" | "QUESTS_SEND_HEARTBEAT_FAILURE" | "QUESTS_SEND_HEARTBEAT_SUCCESS" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECEIVE_CHANNEL_AFFINITIES" | "RECEIVE_CHANNEL_SUMMARIES" | "RECEIVE_CHANNEL_SUMMARIES_BULK" | "RECEIVE_CHANNEL_SUMMARY" | "RECENT_MENTION_DELETE" | "RECOMPUTE_READ_STATES" | "REFERRALS_FETCH_ELIGIBLE_USER_FAIL" | "REFERRALS_FETCH_ELIGIBLE_USER_START" | "REFERRALS_FETCH_ELIGIBLE_USER_SUCCESS" | "REGISTER" | "REGISTER_FAILURE" | "REGISTER_SAVE_FORM" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_PENDING_INCOMING_REMOVED" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REQUEST_CHANNEL_AFFINITIES" | "REQUEST_CHANNEL_SUMMARIES" | "REQUEST_CHANNEL_SUMMARIES_BULK" | "REQUEST_CHANNEL_SUMMARY" | "REQUEST_FORUM_UNREADS" | "REQUEST_SOUNDBOARD_SOUNDS" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESET_PREVIEW_CLIENT_THEME" | "RESET_SOCKET" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_FLAGS" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_PLATFORM" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_USER_CREATE" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_LATENCY_TEST_COMPLETE" | "RTC_SPEED_TEST_START_TEST" | "RTC_SPEED_TEST_STOP_TEST" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_DETECTION" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAFETY_HUB_APPEAL_CLOSE" | "SAFETY_HUB_APPEAL_OPEN" | "SAFETY_HUB_APPEAL_SIGNAL_CUSTOM_INPUT_CHANGE" | "SAFETY_HUB_APPEAL_SIGNAL_SELECT" | "SAFETY_HUB_FETCH_CLASSIFICATION_FAILURE" | "SAFETY_HUB_FETCH_CLASSIFICATION_START" | "SAFETY_HUB_FETCH_CLASSIFICATION_SUCCESS" | "SAFETY_HUB_FETCH_FAILURE" | "SAFETY_HUB_FETCH_START" | "SAFETY_HUB_FETCH_SUCCESS" | "SAFETY_HUB_REQUEST_REVIEW_FAILURE" | "SAFETY_HUB_REQUEST_REVIEW_START" | "SAFETY_HUB_REQUEST_REVIEW_SUCCESS" | "SAVED_MESSAGES_UPDATE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SEARCH_ADD_HISTORY" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MODAL_CLOSE" | "SEARCH_MODAL_OPEN" | "SEARCH_REMOVE_HISTORY" | "SEARCH_SCREEN_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_START" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELECT_HOME_RESOURCE_CHANNEL" | "SELECT_NEW_MEMBER_ACTION_CHANNEL" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_CREATED_AT_OVERRIDE" | "SET_GUILD_FOLDER_EXPANDED" | "SET_HIGHLIGHTED_SUMMARY" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_LOGIN_CREDENTIALS" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_PREMIUM_TYPE_OVERRIDE" | "SET_RECENTLY_ACTIVE_COLLAPSED" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_SELECTED_SUMMARY" | "SET_SOUNDPACK" | "SET_STREAM_APP_INTENT" | "SET_SUMMARY_FEEDBACK" | "SET_TTS_SPEECH_RATE" | "SET_VAD_PERMISSION" | "SHARED_CANVAS_CLEAR_DRAWABLES" | "SHARED_CANVAS_DRAW_LINE_POINT" | "SHARED_CANVAS_SET_DRAW_MODE" | "SHARED_CANVAS_UPDATE_EMOJI_HOSE" | "SHARED_CANVAS_UPDATE_LINE_POINTS" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CLOSE_GUILD" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SIDEBAR_VIEW_GUILD" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_AWAIT_CONFIRMATION" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH" | "SKU_PURCHASE_PREVIEW_FETCH_FAILURE" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS_SUCCESS" | "SOUNDBOARD_MUTE_JOIN_SOUND" | "SOUNDBOARD_SET_OVERLAY_ENABLED" | "SOUNDBOARD_SOUNDS_RECEIVED" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPEED_TEST_CREATE" | "SPEED_TEST_DELETE" | "SPEED_TEST_SERVER_UPDATE" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPELLCHECK_UNLEARN_WORD" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_NEW_TRACK" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "STAGE_MUSIC_MUTE" | "STAGE_MUSIC_PLAY" | "START_BROADCAST_STREAM" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STATS_UPDATE" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SURVEY_SEEN" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOGGLE_OVERLAY_CANVAS" | "TOGGLE_TOPICS_BAR" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TRY_ACK" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNREAD_SETTING_NOTICE_CHANNEL_VISIT" | "UNREAD_SETTING_NOTICE_RENDERED" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPCOMING_GUILD_EVENT_NOTICE_HIDE" | "UPCOMING_GUILD_EVENT_NOTICE_SEEN" | "UPDATE_AVAILABLE" | "UPDATE_BACKGROUND_GRADIENT_PRESET" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CHANNEL_LIST_SUBTITLES" | "UPDATE_CLIENT_PREMIUM_TYPE" | "UPDATE_CONSENTS" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_HANG_STATUS" | "UPDATE_HANG_STATUS_CUSTOM" | "UPDATE_MANUALLY" | "UPDATE_MOBILE_PENDING_THEME_INDEX" | "UPDATE_NOT_AVAILABLE" | "UPDATE_TOKEN" | "UPDATE_VISIBLE_MESSAGES" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_FILE_UPDATE" | "UPLOAD_ITEM_CANCEL_REQUEST" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACHIEVEMENT_UPDATE" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLICATION_REMOVE" | "USER_APPLICATION_UPDATE" | "USER_APPLIED_BOOSTS_FETCH_START" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_INTEGRATION_JOINING_ERROR" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOADED" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_BROWSER_CHECKOUT_DONE" | "USER_PAYMENT_BROWSER_CHECKOUT_STARTED" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_ACCESSIBILITY_TOOLTIP_VIEWED" | "USER_PROFILE_EFFECTS_FETCH" | "USER_PROFILE_EFFECTS_FETCH_FAILURE" | "USER_PROFILE_EFFECTS_FETCH_SUCCESS" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_UPDATE_FAILURE" | "USER_PROFILE_UPDATE_START" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_RECENT_GAMES_FETCH_ERROR" | "USER_RECENT_GAMES_FETCH_START" | "USER_RECENT_GAMES_FETCH_SUCCESS" | "USER_RECENT_GAMES_UPDATE_LOCAL" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_SINGLE_TRY_IT_OUT_COLLECTIBLES_ITEM" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SOUNDBOARD_SET_VOLUME" | "USER_TENURE_REWARD_STATUS_DELETE" | "USER_TENURE_REWARD_STATUS_RESET" | "USER_TENURE_REWARD_SYNC_START" | "USER_TENURE_REWARD_SYNC_SUCCESS" | "USER_UPDATE" | "VERIFY_FAILURE" | "VERIFY_SUCCESS" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIEW_HISTORY_MARK_VIEW" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_CHANNEL_STATUS_UPDATE" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD" | "WINDOW_VISIBILITY_CHANGE" | "WRITE_CACHES"; diff --git a/src/webpack/common/types/index.d.ts b/src/webpack/common/types/index.d.ts deleted file mode 100644 index 77148dd0..00000000 --- a/src/webpack/common/types/index.d.ts +++ /dev/null @@ -1,24 +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 * from "./classes"; -export * from "./components"; -export * from "./fluxEvents"; -export * from "./menu"; -export * from "./stores"; -export * from "./utils"; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts deleted file mode 100644 index f0235986..00000000 --- a/src/webpack/common/types/stores.d.ts +++ /dev/null @@ -1,259 +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 { Channel, Guild, Role } from "discord-types/general"; - -import { FluxDispatcher, FluxEvents } from "./utils"; - -type GenericFunction = (...args: any[]) => any; - -export class FluxStore { - constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>); - - addChangeListener(callback: () => void): void; - addReactChangeListener(callback: () => void): void; - removeChangeListener(callback: () => void): void; - removeReactChangeListener(callback: () => void): void; - emitChange(): void; - getDispatchToken(): string; - getName(): string; - initialize(): void; - initializeIfNeeded(): void; - registerActionHandlers: GenericFunction; - syncWith: GenericFunction; - waitFor: GenericFunction; - __getLocalVars(): Record<string, any>; - - static getAll(): FluxStore[]; -} - -export class FluxEmitter { - constructor(); - - changeSentinel: number; - changedStores: Set<FluxStore>; - isBatchEmitting: boolean; - isDispatching: boolean; - isPaused: boolean; - pauseTimer: NodeJS.Timeout | null; - reactChangedStores: Set<FluxStore>; - - batched(batch: (...args: any[]) => void): void; - destroy(): void; - emit(): void; - emitNonReactOnce(): void; - emitReactOnce(): void; - getChangeSentinel(): number; - getIsPaused(): boolean; - injectBatchEmitChanges(batch: (...args: any[]) => void): void; - markChanged(store: FluxStore): void; - pause(): void; - resume(): void; -} - -export interface Flux { - Store: typeof FluxStore; - Emitter: FluxEmitter; -} - -export class WindowStore extends FluxStore { - isElementFullScreen(): boolean; - isFocused(): boolean; - windowSize(): Record<"width" | "height", number>; -} - -type Emoji = CustomEmoji | UnicodeEmoji; -export interface CustomEmoji { - allNamesString: string; - animated: boolean; - available: boolean; - guildId: string; - id: string; - managed: boolean; - name: string; - originalName?: string; - require_colons: boolean; - roles: string[]; - type: 1; -} - -export interface UnicodeEmoji { - diversityChildren: Record<any, any>; - emojiObject: { - names: string[]; - surrogates: string; - unicodeVersion: number; - }; - index: number; - surrogates: string; - type: 0; - uniqueName: string; - useSpriteSheet: boolean; - get allNamesString(): string; - get animated(): boolean; - get defaultDiversityChild(): any; - get hasDiversity(): boolean | undefined; - get hasDiversityParent(): boolean | undefined; - get hasMultiDiversity(): boolean | undefined; - get hasMultiDiversityParent(): boolean | undefined; - get managed(): boolean; - get name(): string; - get names(): string[]; - get optionallyDiverseSequence(): string | undefined; - get unicodeVersion(): number; - get url(): string; -} - -export class EmojiStore extends FluxStore { - getCustomEmojiById(id?: string | null): CustomEmoji; - getUsableCustomEmojiById(id?: string | null): CustomEmoji; - getGuilds(): Record<string, { - id: string; - _emojiMap: Record<string, CustomEmoji>; - _emojis: CustomEmoji[]; - get emojis(): CustomEmoji[]; - get rawEmojis(): CustomEmoji[]; - _usableEmojis: CustomEmoji[]; - get usableEmojis(): CustomEmoji[]; - _emoticons: any[]; - get emoticons(): any[]; - }>; - getGuildEmoji(guildId?: string | null): CustomEmoji[]; - getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[]; - getTopEmoji(guildId?: string | null): CustomEmoji[]; - getTopEmojisMetadata(guildId?: string | null): { - emojiIds: string[]; - topEmojisTTL: number; - }; - hasPendingUsage(): boolean; - hasUsableEmojiInAnyGuild(): boolean; - searchWithoutFetchingLatest(data: any): any; - getSearchResultsOrder(...args: any[]): any; - getState(): { - pendingUsages: { key: string, timestamp: number; }[]; - }; - searchWithoutFetchingLatest(data: { - channel: Channel, - query: string; - count?: number; - intention: number; - includeExternalGuilds?: boolean; - matchComparator?(name: string): boolean; - }): Record<"locked" | "unlocked", Emoji[]>; - - getDisambiguatedEmojiContext(): { - backfillTopEmojis: Record<any, any>; - customEmojis: Record<string, CustomEmoji>; - emojisById: Record<string, CustomEmoji>; - emojisByName: Record<string, CustomEmoji>; - emoticonRegex: RegExp | null; - emoticonsByName: Record<string, any>; - escapedEmoticonNames: string; - favoriteNamesAndIds?: any; - favorites?: any; - frequentlyUsed?: any; - groupedCustomEmojis: Record<string, CustomEmoji[]>; - guildId?: string; - isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean; - newlyAddedEmoji: Record<string, CustomEmoji[]>; - topEmojis?: any; - unicodeAliases: Record<string, string>; - get favoriteEmojisWithoutFetchingLatest(): Emoji[]; - }; -} - -export interface DraftObject { - channelId: string; - timestamp: number; - draft: string; -} - -interface DraftState { - [userId: string]: { - [channelId: string]: { - [key in DraftType]?: Omit<DraftObject, "channelId">; - } | undefined; - } | undefined; -} - - -export class DraftStore extends FluxStore { - getDraft(channelId: string, type: DraftType): string; - getRecentlyEditedDrafts(type: DraftType): DraftObject[]; - getState(): DraftState; - getThreadDraftWithParentMessageId?(arg: any): any; - getThreadSettings(channelId: string): any | null; -} - -export enum DraftType { - ChannelMessage, - ThreadSettings, - FirstThreadMessage, - ApplicationLauncherCommand, - Poll, - SlashCommand, -} - -export class GuildStore extends FluxStore { - getGuild(guildId: string): Guild; - getGuildCount(): number; - getGuilds(): Record<string, Guild>; - getGuildIds(): string[]; -} - -export class GuildRoleStore extends FluxStore { - getRole(guildId: string, roleId: string): Role; - getRoles(guildId: string): Record<string, Role>; - getAllGuildRoles(): Record<string, Record<string, Role>>; -} - -export class ThemeStore extends FluxStore { - theme: "light" | "dark" | "darker" | "midnight"; - darkSidebar: boolean; - isSystemThemeAvailable: boolean; - systemPrefersColorScheme: "light" | "dark"; - systemTheme: null; -} - -export type useStateFromStores = <T>( - stores: any[], - mapper: () => T, - dependencies?: any, - isEqual?: (old: T, newer: T) => boolean -) => T; - -export class RelationshipStore extends FluxStore { - getFriendIDs(): string[]; - getIgnoredIDs(): string[]; - getBlockedIDs(): string[]; - - getPendingCount(): number; - getRelationshipCount(): number; - - /** Related to friend nicknames. */ - getNickname(userId: string): string; - /** @returns Enum value from constants.RelationshipTypes */ - getRelationshipType(userId: string): number; - isFriend(userId: string): boolean; - isBlocked(userId: string): boolean; - isIgnored(userId: string): boolean; - getSince(userId: string): string; - - /** @returns Format: [userId: Enum value from constants.RelationshipTypes] */ - getMutableRelationships(): Map<string, number>; -} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 5c54c95a..1d653dea 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,10 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import type * as t from "@vencord/discord-types"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; -import type { Channel } from "discord-types/general"; - -import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; waitFor(["dispatch", "subscribe"], m => { @@ -138,7 +136,7 @@ export const UserUtils = { export const UploadManager = findByPropsLazy("clearAll", "addFile"); export const UploadHandler = { - promptToUpload: findByCodeLazy("=!0,showLargeMessageDialog:") as (files: File[], channel: Channel, draftType: Number) => void + promptToUpload: findByCodeLazy("=!0,showLargeMessageDialog:") as (files: File[], channel: t.Channel, draftType: Number) => void }; export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size must === [", { diff --git a/src/webpack/index.ts b/src/webpack/index.ts index 6f1fd25b..37dda6fe 100644 --- a/src/webpack/index.ts +++ b/src/webpack/index.ts @@ -17,5 +17,5 @@ */ export * as Common from "./common"; +export * from "./types"; export * from "./webpack"; -export * from "./wreq.d"; diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index d9b35f01..767fa20d 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -10,10 +10,11 @@ import { Logger } from "@utils/Logger"; import { interpolateIfDefined } from "@utils/misc"; import { canonicalizeReplacement } from "@utils/patches"; import { Patch, PatchReplacement } from "@utils/types"; +import { WebpackRequire } from "@vencord/discord-types/webpack"; import { traceFunctionWithResults } from "../debug/Tracer"; +import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory } from "./types"; import { _blacklistBadModules, _initWebpack, factoryListeners, findModuleFactory, moduleListeners, waitForSubscriptions, wreq } from "./webpack"; -import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, PatchedModuleFactory, WebpackRequire } from "./wreq.d"; export const patches = [] as Patch[]; diff --git a/src/webpack/types.ts b/src/webpack/types.ts new file mode 100644 index 00000000..328aa8bc --- /dev/null +++ b/src/webpack/types.ts @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated, Nuckyz and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Module, ModuleExports, WebpackRequire } from "@vencord/discord-types/webpack"; + +import { SYM_ORIGINAL_FACTORY, SYM_PATCHED_BY, SYM_PATCHED_SOURCE } from "./patchWebpack"; + +export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & { + /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ + m: Record<PropertyKey, AnyModuleFactory>; +}; + +/** exports can be anything, however initially it is always an empty object */ +export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & { + [SYM_PATCHED_SOURCE]?: string; + [SYM_PATCHED_BY]?: Set<string>; +}; + +export type PatchedModuleFactory = AnyModuleFactory & { + [SYM_ORIGINAL_FACTORY]: AnyModuleFactory; + [SYM_PATCHED_SOURCE]?: string; + [SYM_PATCHED_BY]?: Set<string>; +}; + +export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 98e7d4d1..247ea3f4 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -20,11 +20,12 @@ import { makeLazy, proxyLazy } from "@utils/lazy"; import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; -import { FluxStore } from "@webpack/types"; +import { FluxStore } from "@vencord/discord-types"; +import { ModuleExports, WebpackRequire } from "@vencord/discord-types/webpack"; import { traceFunction } from "../debug/Tracer"; import { Flux } from "./common"; -import { AnyModuleFactory, AnyWebpackRequire, ModuleExports, WebpackRequire } from "./wreq"; +import { AnyModuleFactory, AnyWebpackRequire } from "./types"; const logger = new Logger("Webpack"); diff --git a/tsconfig.json b/tsconfig.json index d2a42bd5..0123021c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, - "skipLibCheck": true, + "skipLibCheck": false, "allowJs": true, "lib": [ "DOM", @@ -27,7 +27,6 @@ "@components/*": ["./components/*"], "@utils/*": ["./utils/*"], "@shared/*": ["./shared/*"], - "@webpack/types": ["./webpack/common/types"], "@webpack/common": ["./webpack/common"], "@webpack": ["./webpack/webpack"], "@webpack/patcher": ["./webpack/patchWebpack"], From c2fbcec3bda495ae8618d3062c8de2bc1c4ca0b6 Mon Sep 17 00:00:00 2001 From: Cookie <52550063+Covkie@users.noreply.github.com> Date: Wed, 9 Jul 2025 18:42:38 -0400 Subject: [PATCH 036/141] Settings: remove outdated style (#3490) --- src/components/PluginSettings/PluginModal.tsx | 6 +++--- src/components/VencordSettings/PatchHelperTab.tsx | 2 +- src/components/VencordSettings/ThemesTab.tsx | 2 +- src/components/VencordSettings/UpdaterTab.tsx | 2 +- src/components/VencordSettings/settingsStyles.css | 9 --------- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 29ce90c9..ddbcf8b8 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -26,7 +26,7 @@ import { Flex } from "@components/Flex"; import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; -import { classes, isObjectEmpty } from "@utils/misc"; +import { isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; @@ -212,7 +212,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti const pluginMeta = PluginMeta[plugin.name]; return ( - <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable"> + <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}> <ModalHeader separator={false}> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text> @@ -268,7 +268,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti </div> </Forms.FormSection> {!!plugin.settingsAboutComponent && ( - <div className={classes(Margins.bottom8, "vc-text-selectable")}> + <div className={Margins.bottom8}> <Forms.FormSection> <ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component"> <plugin.settingsAboutComponent tempSettings={tempSettings} /> diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index accccd49..83364d19 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -194,7 +194,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) { error={error ?? replacementError} /> {!isFunc && ( - <div className="vc-text-selectable"> + <div> <Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle> {Object.entries({ "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 692e20bf..f557adf1 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -309,7 +309,7 @@ function ThemesTab() { function renderOnlineThemes() { return ( <> - <Card className="vc-settings-card vc-text-selectable"> + <Card className="vc-settings-card"> <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> <Forms.FormText>One link per line</Forms.FormText> <Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText> diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx index 9871bfcc..f5e3d4b2 100644 --- a/src/components/VencordSettings/UpdaterTab.tsx +++ b/src/components/VencordSettings/UpdaterTab.tsx @@ -225,7 +225,7 @@ function Updater() { <Forms.FormTitle tag="h5">Repo</Forms.FormTitle> - <Forms.FormText className="vc-text-selectable"> + <Forms.FormText> {repoPending ? repo : err diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 35c12197..0161ff4e 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -60,15 +60,6 @@ background-color: var(--button-danger-background); } -.vc-text-selectable, -.vc-text-selectable :where([class*="text" i], [class*="title" i]) { - /* make text selectable, silly discord makes the entirety of settings not selectable */ - user-select: text; - - /* discord also sets cursor: default which prevents the cursor from showing as text */ - cursor: initial; -} - .vc-updater-modal { padding: 1.5em !important; } From c2d7b68950c11a1602c72c61e6a0f075b6bf3b08 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 10 Jul 2025 01:37:13 +0200 Subject: [PATCH 037/141] Online Themes: fix & improve ui --- src/components/PluginSettings/index.tsx | 2 +- src/components/PluginSettings/styles.css | 7 -- src/components/VencordSettings/ThemesTab.tsx | 67 +++---------------- .../VencordSettings/settingsStyles.css | 20 ++++-- .../VencordSettings/specialCard.css | 5 -- src/plugins/_core/supportHelper.tsx | 2 +- src/plugins/voiceMessages/index.tsx | 2 +- src/utils/cspViolations.ts | 2 +- 8 files changed, 26 insertions(+), 81 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 27eb10a3..4d4e41e7 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -62,7 +62,7 @@ function showErrorToast(message: string) { function ReloadRequiredCard({ required }: { required: boolean; }) { return ( - <Card className={cl("info-card", { "restart-card": required })}> + <Card className={classes(cl("info-card"), required && "vc-warning-card")}> {required ? ( <> <Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle> diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index ed5e9aa1..c3d97051 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -66,13 +66,6 @@ gap: 0.25em; } -.vc-plugins-restart-card { - padding: 1em; - background: var(--info-warning-background); - border: 1px solid var(--info-warning-foreground); - color: var(--info-warning-foreground); -} - .vc-plugins-restart-button { margin-top: 0.5em; background: var(--info-warning-foreground) !important; diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index f557adf1..6d41ab86 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -29,7 +29,7 @@ import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; -import { useAwaiter, useForceUpdater } from "@utils/react"; +import { useForceUpdater } from "@utils/react"; import { getStylusWebStoreUrl } from "@utils/web"; import { findLazy } from "@webpack"; import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; @@ -52,62 +52,6 @@ const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue & const cl = classNameFactory("vc-settings-theme-"); -function Validator({ link }: { link: string; }) { - const [res, err, pending] = useAwaiter(() => fetch(link).then(res => { - if (res.status > 300) throw `${res.status} ${res.statusText}`; - const contentType = res.headers.get("Content-Type"); - if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain")) - throw "Not a CSS file. Remember to use the raw link!"; - - return "Okay!"; - })); - - const text = pending - ? "Checking..." - : err - ? `Error: ${err instanceof Error ? err.message : String(err)}` - : "Valid!"; - - return <Forms.FormText style={{ - color: pending ? "var(--text-muted)" : err ? "var(--text-danger)" : "var(--status-positive)" - }}>{text}</Forms.FormText>; -} - -function Validators({ themeLinks }: { themeLinks: string[]; }) { - if (!themeLinks.length) return null; - - return ( - <> - <Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle> - <Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText> - <div> - {themeLinks.map(rawLink => { - const { label, link } = (() => { - const match = /^@(light|dark) (.*)/.exec(rawLink); - if (!match) return { label: rawLink, link: rawLink }; - - const [, mode, link] = match; - return { label: `[${mode} mode only] ${link}`, link }; - })(); - - return <Card style={{ - padding: ".5em", - marginBottom: ".5em", - marginTop: ".5em" - }} key={link}> - <Forms.FormTitle tag="h5" style={{ - overflowWrap: "break-word" - }}> - {label} - </Forms.FormTitle> - <Validator link={link} /> - </Card>; - })} - </div> - </> - ); -} - interface ThemeCardProps { theme: UserThemeHeader; enabled: boolean; @@ -309,6 +253,12 @@ function ThemesTab() { function renderOnlineThemes() { return ( <> + <Card className={classes("vc-warning-card", Margins.bottom16)}> + <Forms.FormText> + This section is for advanced users. If you are having difficulties using it, use the + Local Themes tab instead. + </Forms.FormText> + </Card> <Card className="vc-settings-card"> <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> <Forms.FormText>One link per line</Forms.FormText> @@ -321,12 +271,11 @@ function ThemesTab() { value={themeText} onChange={setThemeText} className={"vc-settings-theme-links"} - placeholder="Theme Links" + placeholder="Enter Theme Links..." spellCheck={false} onBlur={onBlur} rows={10} /> - <Validators themeLinks={settings.themeLinks} /> </Forms.FormSection> </> ); diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 0161ff4e..c92d588f 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -1,7 +1,7 @@ .vc-settings-tab-bar { margin-top: 20px; margin-bottom: 10px; - border-bottom: 2px solid var(--background-modifier-accent); + border-bottom: 1px solid var(--border-subtle); } .vc-settings-tab-bar-item { @@ -20,6 +20,13 @@ margin-bottom: 1em; } +.vc-warning-card { + padding: 1em; + background: var(--info-warning-background); + border: 1px solid var(--info-warning-foreground); + color: var(--info-warning-foreground); +} + .vc-backup-restore-card { background-color: var(--info-warning-background); border-color: var(--info-warning-foreground); @@ -30,19 +37,20 @@ /* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */ display: inline-block !important; color: var(--text-default) !important; - padding: 0.5em; - border: 1px solid var(--background-modifier-accent); + padding: 0.5em 1em; + border: 1px solid var(--input-border); max-height: unset; background-color: transparent; box-sizing: border-box; - font-size: 12px; - line-height: 14px; resize: none; width: 100%; + font-size: 1em; + line-height: 2em; + white-space: nowrap; } .vc-settings-theme-links::placeholder { - color: var(--header-secondary); + color: var(--text-muted) !important; } .vc-settings-theme-links:focus { diff --git a/src/components/VencordSettings/specialCard.css b/src/components/VencordSettings/specialCard.css index 07b628f5..fb868283 100644 --- a/src/components/VencordSettings/specialCard.css +++ b/src/components/VencordSettings/specialCard.css @@ -12,11 +12,6 @@ position: relative; } -.vc-settings-card { - padding: 1em; - margin-bottom: 1em; -} - .vc-special-card-special { padding: 1em 1.5em; margin-bottom: 1em; diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 956343f6..82e09b88 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -318,7 +318,7 @@ export default definePlugin({ if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null; return ( - <Card className={`vc-plugins-restart-card ${Margins.top8}`}> + <Card className={`vc-warning-card ${Margins.top8}`}> Please do not private message Vencord plugin developers for support! <br /> Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")} diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 8365bb51..74f39ea3 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -219,7 +219,7 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) { /> {isUnsupportedFormat && ( - <Card className={`vc-plugins-restart-card ${Margins.top16}`}> + <Card className={`vc-warning-card ${Margins.top16}`}> <Forms.FormText>Voice Messages have to be OggOpus to be playable on iOS. This file is <code>{blob.type}</code> so it will not be playable on iOS.</Forms.FormText> <Forms.FormText className={Margins.top8}> diff --git a/src/utils/cspViolations.ts b/src/utils/cspViolations.ts index 9477bcaa..b1acc4c6 100644 --- a/src/utils/cspViolations.ts +++ b/src/utils/cspViolations.ts @@ -8,7 +8,7 @@ import { useLayoutEffect } from "@webpack/common"; import { useForceUpdater } from "./react"; -const cssRelevantDirectives = ["style-src", "img-src", "font-src"] as const; +const cssRelevantDirectives = ["style-src", "style-src-elem", "img-src", "font-src"] as const; export const CspBlockedUrls = new Set<string>(); const CspErrorListeners = new Set<() => void>(); From 4c315b6886532f0109487ffa80634955f045968f Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 10 Jul 2025 01:37:32 +0200 Subject: [PATCH 038/141] fix various plugins still using outdated Discord classes --- src/plugins/clientTheme/clientTheme.css | 2 +- src/plugins/reviewDB/style.css | 2 +- src/plugins/sendTimestamps/styles.css | 4 ++-- src/plugins/serverInfo/styles.css | 2 +- src/plugins/spotifyControls/spotifyStyles.css | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css index 49cc3e15..828703e5 100644 --- a/src/plugins/clientTheme/clientTheme.css +++ b/src/plugins/clientTheme/clientTheme.css @@ -16,7 +16,7 @@ } .vc-clientTheme-container [class^="swatch"] { - border: thin solid var(--background-modifier-accent) !important; + border: thin solid var(--input-border) !important; } .vc-clientTheme-buttons-container { diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css index 50499b29..47ccd091 100644 --- a/src/plugins/reviewDB/style.css +++ b/src/plugins/reviewDB/style.css @@ -123,7 +123,7 @@ display: block; width: 100%; height: 100%; - background-color: var(--background-modifier-accent); + background-color: var(--border-subtle); } .vc-rdb-block-modal-username { diff --git a/src/plugins/sendTimestamps/styles.css b/src/plugins/sendTimestamps/styles.css index d96e886a..7d82b800 100644 --- a/src/plugins/sendTimestamps/styles.css +++ b/src/plugins/sendTimestamps/styles.css @@ -15,11 +15,11 @@ .vc-st-format-select { margin-bottom: 1em; - --background-modifier-accent: transparent; + --border-subtle: transparent; } .vc-st-format-label { - --background-modifier-accent: transparent; + --border-subtle: transparent; } .vc-st-modal-header { diff --git a/src/plugins/serverInfo/styles.css b/src/plugins/serverInfo/styles.css index 0fc44e02..f10fb6ac 100644 --- a/src/plugins/serverInfo/styles.css +++ b/src/plugins/serverInfo/styles.css @@ -38,7 +38,7 @@ } .vc-gp-tab-bar { - border-bottom: 2px solid var(--background-modifier-accent); + border-bottom: 1px solid var(--border-subtle); margin: 20px 12px 0; display: flex; gap: 40px; diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 58d74504..d4afbc83 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -1,6 +1,6 @@ #vc-spotify-player { padding: 0.375rem 0.5rem; - border-bottom: 1px solid var(--background-modifier-accent); + border-bottom: 1px solid var(--border-subtle); --vc-spotify-green: var(--spotify, #1db954); /* so custom themes can easily change it */ --vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%); From 8ec5b0a8d86dc99aca099774386b5872eef27e25 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:56:12 -0400 Subject: [PATCH 039/141] fix ImageZoom & FakeProfileThemes (#3546) Co-authored-by: Vendicated <vendicated@riseup.net> --- src/plugins/fakeProfileThemes/index.tsx | 2 +- src/plugins/imageZoom/index.tsx | 11 +++-------- src/plugins/showHiddenChannels/index.tsx | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index a84722c1..1de38ab4 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -122,7 +122,7 @@ export default definePlugin({ { find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}", replacement: { - match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\)}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, + match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\).+?}\)(?=\])(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" } } diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 56e1d4a2..4fbfe3ff 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -171,14 +171,9 @@ export default definePlugin({ replace: `id:"${ELEMENT_ID}",$&` }, { - // This patch needs to be above the next one as it uses the zoomed class as an anchor - match: /\.zoomed]:.+?,(?=children:)/, - replace: "$&onClick:()=>{}," - }, - { - match: /className:\i\(\)\(\i\.wrapper,.+?}\),/, - replace: "" - }, + match: /(?<=null!=(\i)\?.{0,20})\i\.\i,{children:\1/, + replace: "'div',{onClick:e=>e.stopPropagation(),children:$1" + } ] }, // Make media viewer options not hide when zoomed in with the default Discord feature diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index c99afc84..a0ef38a6 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -250,7 +250,7 @@ export default definePlugin({ replace: (m, channel) => `${m}if($self.isHiddenChannel(${channel}))break;` }, { - match: /(?<="renderHeaderBar",\(\)=>{.+?hideSearch:(\i)\.isDirectory\(\))/, + match: /(?<="renderHeaderBar",\i=>{.+?hideSearch:(\i)\.isDirectory\(\))/, replace: (_, channel) => `||$self.isHiddenChannel(${channel})` }, { From 8411026c512506d3a9630fe1a167b29caac1154c Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:38:27 -0400 Subject: [PATCH 040/141] FakeProfileThemes: fix error when own profile is not loaded (#3514) Co-authored-by: V <vendicated@riseup.net> --- packages/discord-types/enums/commands.ts | 5 + .../discord-types/src/common/Application.d.ts | 23 ++ packages/discord-types/src/common/User.d.ts | 3 +- packages/discord-types/src/common/index.d.ts | 1 + .../src/stores/UserProfileStore.d.ts | 151 +++++++++++++ packages/discord-types/src/stores/index.d.ts | 1 + src/plugins/fakeProfileThemes/index.tsx | 202 +++++++++--------- src/plugins/showConnections/index.tsx | 15 +- src/plugins/validUser/index.tsx | 18 +- src/utils/discord.tsx | 2 +- src/webpack/common/stores.ts | 2 +- 11 files changed, 297 insertions(+), 126 deletions(-) create mode 100644 packages/discord-types/src/common/Application.d.ts create mode 100644 packages/discord-types/src/stores/UserProfileStore.d.ts diff --git a/packages/discord-types/enums/commands.ts b/packages/discord-types/enums/commands.ts index 0556e72d..298f9b7a 100644 --- a/packages/discord-types/enums/commands.ts +++ b/packages/discord-types/enums/commands.ts @@ -25,3 +25,8 @@ export const enum ApplicationCommandType { USER = 2, MESSAGE = 3, } + +export const enum ApplicationIntegrationType { + GUILD_INSTALL = 0, + USER_INSTALL = 1 +} diff --git a/packages/discord-types/src/common/Application.d.ts b/packages/discord-types/src/common/Application.d.ts new file mode 100644 index 00000000..d2ec1e7e --- /dev/null +++ b/packages/discord-types/src/common/Application.d.ts @@ -0,0 +1,23 @@ +import { User } from "./User"; + +export interface Application { + id: string; + name: string; + description?: string | null; + type: number | null; + icon: string | null | undefined; + is_discoverable: boolean; + is_monetized: boolean; + is_verified: boolean; + bot?: User; + deeplink_uri?: string; + flags?: number; + privacy_policy_url?: string; + terms_of_service_url?: string; + install_params?: ApplicationInstallParams; +} + +export interface ApplicationInstallParams { + permissions: string | null; + scopes: string[]; +} diff --git a/packages/discord-types/src/common/User.d.ts b/packages/discord-types/src/common/User.d.ts index a5503bcb..07eb4718 100644 --- a/packages/discord-types/src/common/User.d.ts +++ b/packages/discord-types/src/common/User.d.ts @@ -6,7 +6,7 @@ export class User extends DiscordRecord { constructor(user: object); accentColor: number; avatar: string; - banner: string; + banner: string | null | undefined; bio: string; bot: boolean; desktop: boolean; @@ -27,7 +27,6 @@ export class User extends DiscordRecord { system: boolean; username: string; verified: boolean; - themeColors?: [number, number]; get createdAt(): Date; get hasPremiumPerks(): boolean; diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts index f85a7c72..5e50e96e 100644 --- a/packages/discord-types/src/common/index.d.ts +++ b/packages/discord-types/src/common/index.d.ts @@ -1,3 +1,4 @@ +export * from "./Application"; export * from "./Channel"; export * from "./Guild"; export * from "./GuildMember"; diff --git a/packages/discord-types/src/stores/UserProfileStore.d.ts b/packages/discord-types/src/stores/UserProfileStore.d.ts new file mode 100644 index 00000000..d9efc47b --- /dev/null +++ b/packages/discord-types/src/stores/UserProfileStore.d.ts @@ -0,0 +1,151 @@ +import { FluxStore, Guild, User, Application, ApplicationInstallParams } from ".."; +import { ApplicationIntegrationType } from "../../enums"; + +export interface MutualFriend { + /** + * the userid of the mutual friend + */ + key: string; + /** + * the status of the mutual friend + */ + status: "online" | "offline" | "idle" | "dnd"; + /** + * the user object of the mutual friend + */ + user: User; +} + +export interface MutualGuild { + /** + * the guild object of the mutual guild + */ + guild: Guild; + /** + * the user's nickname in the guild, if any + */ + nick: string | null; + +} + +export interface ProfileBadge { + id: string; + description: string; + icon: string; + link?: string; +} + +export interface ConnectedAccount { + type: "twitch" | "youtube" | "skype" | "steam" | "leagueoflegends" | "battlenet" | "bluesky" | "bungie" | "reddit" | "twitter" | "twitter_legacy" | "spotify" | "facebook" | "xbox" | "samsung" | "contacts" | "instagram" | "mastodon" | "soundcloud" | "github" | "playstation" | "playstation-stg" | "epicgames" | "riotgames" | "roblox" | "paypal" | "ebay" | "tiktok" | "crunchyroll" | "domain" | "amazon-music"; + /** + * underlying id of connected account + * eg. account uuid + */ + id: string; + /** + * display name of connected account + */ + name: string; + verified: boolean; + metadata?: Record<string, string>; +} + +export interface ProfileApplication { + id: string; + customInstallUrl: string | undefined; + installParams: ApplicationInstallParams | undefined; + flags: number; + popularApplicationCommandIds?: string[]; + integrationTypesConfig: Record<ApplicationIntegrationType, Partial<{ + oauth2_install_params: ApplicationInstallParams; + }>>; + primarySkuId: string | undefined; + storefront_available: boolean; +} + +export interface UserProfileBase extends Pick<User, "banner"> { + accentColor: number | null; + /** + * often empty for guild profiles, get the user profile for badges + */ + badges: ProfileBadge[]; + bio: string | undefined; + popoutAnimationParticleType: string | null; + profileEffectExpiresAt: number | Date | undefined; + profileEffectId: undefined | string; + /** + * often an empty string when not set + */ + pronouns: string | "" | undefined; + themeColors: [number, number] | undefined; + userId: string; +} + +export interface ApplicationRoleConnection { + application: Application; + application_metadata: Record<string, any>; + metadata: Record<string, any>; + platform_name: string; + platform_username: string; +} + +export interface UserProfile extends UserProfileBase, Pick<User, "premiumType"> { + /** If this is a bot user profile, this will be its application */ + application: ProfileApplication | null; + applicationRoleConnections: ApplicationRoleConnection[] | undefined; + connectedAccounts: ConnectedAccount[] | undefined; + fetchStartedAt: number; + fetchEndedAt: number; + legacyUsername: string | undefined; + premiumGuildSince: Date | null; + premiumSince: Date | null; +} + +export class UserProfileStore extends FluxStore { + /** + * @param userId the user ID of the profile being fetched. + * @param guildId the guild ID to of the profile being fetched. + * defaults to the internal symbol `NO GUILD ID` if nullish + * + * @returns true if the profile is being fetched, false otherwise. + */ + isFetchingProfile(userId: string, guildId?: string): boolean; + /** + * Check if mutual friends for {@link userId} are currently being fetched. + * + * @param userId the user ID of the mutual friends being fetched. + * + * @returns true if mutual friends are being fetched, false otherwise. + */ + isFetchingFriends(userId: string): boolean; + + get isSubmitting(): boolean; + + getUserProfile(userId: string): UserProfile | undefined; + + getGuildMemberProfile(userId: string, guildId: string | undefined): UserProfileBase | null; + /** + * Get the mutual friends of a user. + * + * @param userId the user ID of the user to get the mutual friends of. + * + * @returns an array of mutual friends, or undefined if the user has no mutual friends + */ + getMutualFriends(userId: string): MutualFriend[] | undefined; + /** + * Get the count of mutual friends for a user. + * + * @param userId the user ID of the user to get the mutual friends count of. + * + * @returns the count of mutual friends, or undefined if the user has no mutual friends + */ + getMutualFriendsCount(userId: string): number | undefined; + /** + * Get the mutual guilds of a user. + * + * @param userId the user ID of the user to get the mutual guilds of. + * + * @returns an array of mutual guilds, or undefined if the user has no mutual guilds + */ + getMutualGuilds(userId: string): MutualGuild[] | undefined; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index b6254844..23045832 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -11,6 +11,7 @@ export * from "./RelationshipStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; export * from "./ThemeStore"; +export * from "./UserProfileStore"; export * from "./UserStore"; export * from "./WindowStore"; diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 1de38ab4..5d16a842 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -22,13 +22,14 @@ import "./index.css"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { fetchUserProfile } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; +import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { User } from "@vencord/discord-types"; +import { User, UserProfile } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; -import { ReactElement } from "react"; import virtualMerge from "virtual-merge"; interface Colors { @@ -81,14 +82,6 @@ const settings = definePluginSettings({ } }); -interface ColorPickerProps { - color: number | null; - label: ReactElement<any>; - showEyeDropper?: boolean; - suggestedColors?: string[]; - onChange(value: number | null): void; -} - // I can't be bothered to figure out the semantics of this component. The // functions surely get some event argument sent to them and they likely aren't // all required. If anyone who wants to use this component stumbles across this @@ -106,6 +99,100 @@ interface ProfileModalProps { const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER"); +function SettingsAboutComponentWrapper() { + const [, , userProfileLoading] = useAwaiter(() => fetchUserProfile(UserStore.getCurrentUser().id)); + + return !userProfileLoading && <SettingsAboutComponent />; +} + +function SettingsAboutComponent() { + const existingColors = decode( + UserProfileStore.getUserProfile(UserStore.getCurrentUser().id)?.bio ?? "" + ) ?? [0, 0]; + const [color1, setColor1] = useState(existingColors[0]); + const [color2, setColor2] = useState(existingColors[1]); + + return ( + <Forms.FormSection> + <Forms.FormTitle tag="h3">Usage</Forms.FormTitle> + <Forms.FormText> + After enabling this plugin, you will see custom colors in + the profiles of other people using compatible plugins.{" "} + <br /> + To set your own colors: + <ul> + <li> + • use the color pickers below to choose your colors + </li> + <li>• click the "Copy 3y3" button</li> + <li>• paste the invisible text anywhere in your bio</li> + </ul><br /> + <Forms.FormDivider + className={classes(Margins.top8, Margins.bottom8)} + /> + <Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle> + <Flex + direction={Flex.Direction.HORIZONTAL} + style={{ gap: "1rem" }} + > + <ColorPicker + color={color1} + label={ + <Text + variant={"text-xs/normal"} + style={{ marginTop: "4px" }} + > + Primary + </Text> + } + onChange={(color: number) => { + setColor1(color); + }} + /> + <ColorPicker + color={color2} + label={ + <Text + variant={"text-xs/normal"} + style={{ marginTop: "4px" }} + > + Accent + </Text> + } + onChange={(color: number) => { + setColor2(color); + }} + /> + <Button + onClick={() => { + const colorString = encode(color1, color2); + copyWithToast(colorString); + }} + color={Button.Colors.PRIMARY} + size={Button.Sizes.XLARGE} + > + Copy 3y3 + </Button> + </Flex> + <Forms.FormDivider + className={classes(Margins.top8, Margins.bottom8)} + /> + <Forms.FormTitle tag="h3">Preview</Forms.FormTitle> + <div className="vc-fpt-preview"> + <ProfileModal + user={UserStore.getCurrentUser()} + pendingThemeColors={[color1, color2]} + onAvatarChange={() => { }} + onBannerChange={() => { }} + canUsePremiumCustomization={true} + hideExampleButton={true} + hideFakeActivity={true} + isTryItOutFlow={true} + /> + </div> + </Forms.FormText> + </Forms.FormSection>); +} export default definePlugin({ name: "FakeProfileThemes", @@ -117,7 +204,7 @@ export default definePlugin({ replacement: { match: /(?<=getUserProfile\(\i\){return )(.+?)(?=})/, replace: "$self.colorDecodeHook($1)" - } + }, }, { find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}", @@ -127,97 +214,12 @@ export default definePlugin({ } } ], - settingsAboutComponent: () => { - const existingColors = decode( - UserProfileStore.getUserProfile(UserStore.getCurrentUser().id).bio - ) ?? [0, 0]; - const [color1, setColor1] = useState(existingColors[0]); - const [color2, setColor2] = useState(existingColors[1]); - return ( - <Forms.FormSection> - <Forms.FormTitle tag="h3">Usage</Forms.FormTitle> - <Forms.FormText> - After enabling this plugin, you will see custom colors in - the profiles of other people using compatible plugins.{" "} - <br /> - To set your own colors: - <ul> - <li> - • use the color pickers below to choose your colors - </li> - <li>• click the "Copy 3y3" button</li> - <li>• paste the invisible text anywhere in your bio</li> - </ul><br /> - <Forms.FormDivider - className={classes(Margins.top8, Margins.bottom8)} - /> - <Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle> - <Flex - direction={Flex.Direction.HORIZONTAL} - style={{ gap: "1rem" }} - > - <ColorPicker - color={color1} - label={ - <Text - variant={"text-xs/normal"} - style={{ marginTop: "4px" }} - > - Primary - </Text> - } - onChange={(color: number) => { - setColor1(color); - }} - /> - <ColorPicker - color={color2} - label={ - <Text - variant={"text-xs/normal"} - style={{ marginTop: "4px" }} - > - Accent - </Text> - } - onChange={(color: number) => { - setColor2(color); - }} - /> - <Button - onClick={() => { - const colorString = encode(color1, color2); - copyWithToast(colorString); - }} - color={Button.Colors.PRIMARY} - size={Button.Sizes.XLARGE} - > - Copy 3y3 - </Button> - </Flex> - <Forms.FormDivider - className={classes(Margins.top8, Margins.bottom8)} - /> - <Forms.FormTitle tag="h3">Preview</Forms.FormTitle> - <div className="vc-fpt-preview"> - <ProfileModal - user={UserStore.getCurrentUser()} - pendingThemeColors={[color1, color2]} - onAvatarChange={() => { }} - onBannerChange={() => { }} - canUsePremiumCustomization={true} - hideExampleButton={true} - hideFakeActivity={true} - isTryItOutFlow={true} - /> - </div> - </Forms.FormText> - </Forms.FormSection>); - }, + settingsAboutComponent: SettingsAboutComponentWrapper, + settings, - colorDecodeHook(user: User) { - if (user) { + colorDecodeHook(user: UserProfile) { + if (user?.bio) { // don't replace colors if already set with nitro if (settings.store.nitroFirst && user.themeColors) return user; const colors = decode(user.bio); diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index 882c869e..ca90cc48 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -25,7 +25,7 @@ import { CopyIcon, LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { User } from "@vencord/discord-types"; +import { ConnectedAccount, User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { Tooltip, UserProfileStore } from "@webpack/common"; @@ -60,15 +60,8 @@ const settings = definePluginSettings({ } }); -interface Connection { - type: string; - id: string; - name: string; - verified: boolean; -} - interface ConnectionPlatform { - getPlatformUserUrl(connection: Connection): string; + getPlatformUserUrl(connection: ConnectedAccount): string; icon: { lightSVG: string, darkSVG: string; }; } @@ -88,7 +81,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { if (!profile) return null; - const connections: Connection[] = profile.connectedAccounts; + const connections = profile.connectedAccounts; if (!connections?.length) return null; @@ -102,7 +95,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { ); } -function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) { +function CompactConnectionComponent({ connection, theme }: { connection: ConnectedAccount, theme: string; }) { const platform = platforms.get(useLegacyPlatformType(connection.type)); const url = platform.getPlatformUserUrl?.(connection); diff --git a/src/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index 4825cdaa..488b6bd6 100644 --- a/src/plugins/validUser/index.tsx +++ b/src/plugins/validUser/index.tsx @@ -22,6 +22,7 @@ import { isNonNullish } from "@utils/guards"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin from "@utils/types"; +import { ProfileBadge } from "@vencord/discord-types"; import { Constants, FluxDispatcher, RestAPI, UserProfileStore, UserStore, useState } from "@webpack/common"; import { type ComponentType, type ReactNode } from "react"; @@ -47,13 +48,6 @@ const badges: Record<string, ProfileBadge> = { const fetching = new Set<string>(); const queue = new Queue(5); -interface ProfileBadge { - id: string; - description: string; - icon: string; - link?: string; -} - interface MentionProps { data: { userId?: string; @@ -102,10 +96,12 @@ async function getUser(id: string) { // Fill in what we can deduce const profile = UserProfileStore.getUserProfile(id); - profile.accentColor = user.accent_color; - profile.badges = fakeBadges; - profile.banner = user.banner; - profile.premiumType = user.premium_type; + if (profile) { + profile.accentColor = user.accent_color; + profile.badges = fakeBadges; + profile.banner = user.banner; + profile.premiumType = user.premium_type; + } return userObj; } diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 4a652a29..d7a38ebb 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -194,7 +194,7 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt }); FluxDispatcher.dispatch({ type: "USER_UPDATE", user: body.user }); - await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", ...body }); + await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", userProfile: body }); if (options?.guild_id && body.guild_member) FluxDispatcher.dispatch({ type: "GUILD_MEMBER_PROFILE_UPDATE", guildId: options.guild_id, guildMember: body.guild_member }); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index be72d47c..4165af8e 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -42,7 +42,7 @@ export let GuildStore: t.GuildStore; export let GuildRoleStore: t.GuildRoleStore; export let GuildMemberStore: t.GuildMemberStore; export let UserStore: t.UserStore; -export let UserProfileStore: GenericStore; +export let UserProfileStore: t.UserProfileStore; export let SelectedChannelStore: t.SelectedChannelStore; export let SelectedGuildStore: t.SelectedGuildStore; export let ChannelStore: t.ChannelStore; From aa52e1a42b975c157f0b4147dcdcb7cc8ae9fcaf Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:07:22 -0300 Subject: [PATCH 041/141] Fix MessagePopoverAPI incorrectly hiding the emoji button --- src/plugins/_api/messagePopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index a297fc87..a49658fb 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?children:\[(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},` } From 2a52efbd97aa478267228cc6d0e7394572678cc5 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:07:43 -0300 Subject: [PATCH 042/141] Fix Experiments embed patches --- src/plugins/experiments/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index eee732d0..21c6088b 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -116,11 +116,19 @@ export default definePlugin({ }, // Fix some tricky experiments name causing a client crash { - match: /.getRegisteredExperiments\(\)(?<=(\i)=.+?).+?if\(null==(\i)(?=\)return null;)/, - replace: "$&||!Object.hasOwn($1,$2)" + match: /.getExperimentBucketName.+?if\(null==(\i)\|\|null==\i(?=\)return null;)/, + replace: "$&||({})[$1]!=null" } ] }, + // Fix another function which cases crashes with tricky experiment names and the experiment embed + { + find: "}getServerAssignment(", + replacement: { + match: /}getServerAssignment\((\i),\i,\i\){/, + replace: "$&if($1==null)return;" + } + } ], start: () => !BugReporterExperiment.getCurrentConfig().hasBugReporterAccess && enableStyle(hideBugReport), From ec6fbb190f486001915ad66549df7661b15d14cc Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:09:21 -0300 Subject: [PATCH 043/141] Attempt to make OverrideForumDefaults not always slow --- src/plugins/overrideForumDefaults/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/overrideForumDefaults/index.tsx b/src/plugins/overrideForumDefaults/index.tsx index be106ac8..a377fff5 100644 --- a/src/plugins/overrideForumDefaults/index.tsx +++ b/src/plugins/overrideForumDefaults/index.tsx @@ -36,11 +36,11 @@ export default definePlugin({ find: "getDefaultLayout(){", replacement: [ { - match: /getDefaultLayout\(\){/, + match: /}getDefaultLayout\(\){/, replace: "$&return $self.getLayout();" }, { - match: /getDefaultSortOrder\(\){/, + match: /}getDefaultSortOrder\(\){/, replace: "$&return $self.getSortOrder();" } ] From 3abb5fcda8eb449ca0b7fd4345253f33d5b105b1 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:02:44 -0300 Subject: [PATCH 044/141] Fix Plugin Settings broken webpack find --- src/components/PluginSettings/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 4d4e41e7..acfb0609 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -45,7 +45,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => const cl = classNameFactory("vc-plugins-"); const logger = new Logger("PluginSettings", "#a6d189"); -const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error"); +const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); @@ -349,7 +349,7 @@ export default function PluginSettings() { select={onStatusChange} isSelected={v => v === searchValue.status} closeOnSelect={true} - className={InputStyles.inputDefault} + className={InputStyles.input} /> </div> </div> From 35509f74122533ba0315cc8520f469103eee00b9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:05:04 -0300 Subject: [PATCH 045/141] Bump to v1.12.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81b9a1c5..577bf39d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.5", + "version": "1.12.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From f6f0624e52ea460fbff0ad5294fb31c4090366c6 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:05:42 -0300 Subject: [PATCH 046/141] Fix broken Decor style classes find --- src/plugins/decor/ui/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/decor/ui/index.ts b/src/plugins/decor/ui/index.ts index c7846364..f8ac3746 100644 --- a/src/plugins/decor/ui/index.ts +++ b/src/plugins/decor/ui/index.ts @@ -8,7 +8,7 @@ import { classNameFactory } from "@api/Styles"; import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack"; export const cl = classNameFactory("vc-decor-"); -export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton"); +export const DecorationModalStyles = findByPropsLazy("modalPreview", "modalCloseButton", "spinner", "modal"); export const requireAvatarDecorationModal = extractAndLoadChunksLazy([".COLLECTIBLES_SHOP_FULLSCREEN&&"]); export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]); From 25cd6b7069e21bb1a50d54d4402ff8e051a4a9f3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 17 Jul 2025 22:06:22 -0300 Subject: [PATCH 047/141] Remove unused and non unique webpack common style classes find --- packages/discord-types/src/classes.d.ts | 5 ----- src/webpack/common/classes.ts | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/discord-types/src/classes.d.ts b/packages/discord-types/src/classes.d.ts index 752ba3f0..a7260048 100644 --- a/packages/discord-types/src/classes.d.ts +++ b/packages/discord-types/src/classes.d.ts @@ -1,8 +1,3 @@ -export interface ImageModalClasses { - image: string, - modal: string, -} - export interface ButtonWrapperClasses { hoverScale: string; buttonWrapper: string; diff --git a/src/webpack/common/classes.ts b/src/webpack/common/classes.ts index 85b90537..daaf023c 100644 --- a/src/webpack/common/classes.ts +++ b/src/webpack/common/classes.ts @@ -17,7 +17,6 @@ */ import * as t from "@vencord/discord-types"; -import { findByPropsLazy, findLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; -export const ModalImageClasses: t.ImageModalClasses = findLazy(m => m.image && m.modal && !m.applicationIcon); export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent"); From 113a1f4b863bbddf522ba7265e9cb56b04ca9c2e Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 21 Jul 2025 12:09:36 +0200 Subject: [PATCH 048/141] fix ShowMeYourName --- src/plugins/showMeYourName/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index b9b10714..5affa885 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -50,8 +50,8 @@ export default definePlugin({ { find: '="SYSTEM_TAG"', replacement: { - match: /(?<=onContextMenu:\i,children:)\i/, - replace: "$self.renderUsername(arguments[0])" + match: /(?<=onContextMenu:\i,children:)\i\?/, + replace: "$self.renderUsername(arguments[0]),_oldChildren:$&" } }, ], From 4ab084c0aca819f29c5397ea57387caa51a95695 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 21 Jul 2025 12:13:13 +0200 Subject: [PATCH 049/141] make ShowMeYourName patch safer --- src/plugins/showMeYourName/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 5affa885..018fa305 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -50,7 +50,8 @@ export default definePlugin({ { find: '="SYSTEM_TAG"', replacement: { - match: /(?<=onContextMenu:\i,children:)\i\?/, + // The field is named "userName", but as this is unusual casing, the regex also matches username, in case they change it + match: /(?<=onContextMenu:\i,children:)\i\?(?=.{0,100}?user[Nn]ame:)/, replace: "$self.renderUsername(arguments[0]),_oldChildren:$&" } }, From 50e2ad776baec3144dabd9ac6fd91e9890f52abd Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 21 Jul 2025 12:32:14 +0200 Subject: [PATCH 050/141] Plugin Settings: improve headings --- .../settings/tabs/plugins/PluginModal.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index 55912d9d..3332b1fc 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -27,7 +27,7 @@ import { debounce } from "@shared/debounce"; import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; -import { isObjectEmpty } from "@utils/misc"; +import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; @@ -147,11 +147,12 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti return ( <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}> - <ModalHeader separator={false}> - <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text> + <ModalHeader separator={false} className={Margins.bottom8}> + <Text variant="heading-xl/bold" style={{ flexGrow: 1 }}>{plugin.name}</Text> <ModalCloseButton onClick={onClose} /> </ModalHeader> - <ModalContent> + + <ModalContent className={Margins.bottom16}> <Forms.FormSection> <Flex className={cl("info")}> <Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText> @@ -168,8 +169,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti </div> )} </Flex> - <Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle> - <div style={{ width: "fit-content", marginBottom: 8 }}> + <Text variant="heading-lg/semibold" className={classes(Margins.top8, Margins.bottom8)}>Authors</Text> + <div style={{ width: "fit-content" }}> <UserSummaryItem users={authors} guildId={undefined} @@ -195,7 +196,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti </Forms.FormSection> {!!plugin.settingsAboutComponent && ( - <div className={Margins.bottom8}> + <div className={Margins.top16}> <Forms.FormSection> <ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component"> <plugin.settingsAboutComponent /> @@ -204,8 +205,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti </div> )} - <Forms.FormSection className={Margins.bottom16}> - <Forms.FormTitle tag="h3">Settings</Forms.FormTitle> + <Forms.FormSection> + <Text variant="heading-lg/semibold" className={classes(Margins.top16, Margins.bottom8)}>Settings</Text> {renderSettings()} </Forms.FormSection> </ModalContent> From 8b3601326430deb60179b80d4399600a15550e4a Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:35:35 -0400 Subject: [PATCH 051/141] fix GreetStickerPicker (#3561) --- src/plugins/greetStickerPicker/index.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index b2454f9c..4ea1f25d 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -23,13 +23,6 @@ import { Channel, Message } from "@vencord/discord-types"; import { findLazy } from "@webpack"; import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common"; -interface Sticker { - id: string; - format_type: number; - description: string; - name: string; -} - enum GreetMode { Greet = "Greet", NormalMessage = "Message" @@ -168,7 +161,7 @@ export default definePlugin({ { find: "#{intl::WELCOME_CTA_LABEL}", replacement: { - match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/, + match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).+?)/, replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1)," } } From 6fb685b9595e88de42d09a97a722b0aeb2a407fa Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 22 Jul 2025 22:43:31 +0200 Subject: [PATCH 052/141] Fix Plugin settings page Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com> --- src/webpack/common/components.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index b25f3d4b..bdf0b2c4 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -51,7 +51,7 @@ const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { export const Tooltip = LazyComponent(() => Tooltips.Tooltip); export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer); -export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.componentByCode(".error]:this.hasError()")); +export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.componentByCode("#{intl::MAXIMUM_LENGTH_ERROR}", '"input"')); export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:")); export const Text = waitForComponent<t.Text>("Text", filters.componentByCode('case"always-white"')); export const Heading = waitForComponent<t.Heading>("Heading", filters.componentByCode(">6?{", "variant:")); From 03fe7d15cf9c51f02922e23f3071b84c865d327c Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 22 Jul 2025 22:45:28 +0200 Subject: [PATCH 053/141] bump to v1.12.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 577bf39d..b3401cc5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.6", + "version": "1.12.7", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 29a2bcbf0749618b2bc4d469bb1d8830df3e8521 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 26 Jul 2025 11:31:30 -0300 Subject: [PATCH 054/141] Fix BetterUploadButton not working --- src/plugins/betterUploadButton/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index 29827a5e..bfdb3afe 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -34,7 +34,7 @@ export default definePlugin({ noWarn: true }, { - match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/, + match: /\.attachButtonInner,.+?onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/, replace: "$&,onClick:$1,onContextMenu:$2.onClick,", }, ] From cb36cf5706b68e28aa5857e21269ca9f81c1634e Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 31 Jul 2025 16:31:33 -0400 Subject: [PATCH 055/141] fix Plugins broken by recent Discord changes (#3569) Co-authored-by: Vendicated <vendicated@riseup.net> --- .../src/stores/GuildRoleStore.d.ts | 5 +++-- src/plugins/ircColors/index.ts | 4 ++-- .../components/RolesAndUsersPermissions.tsx | 2 +- .../components/UserPermissions.tsx | 4 ++-- src/plugins/permissionsViewer/index.tsx | 6 +++--- src/plugins/permissionsViewer/utils.ts | 13 ++++++------- src/plugins/serverInfo/GuildInfoModal.tsx | 2 +- src/plugins/showHiddenChannels/index.tsx | 6 +++--- src/plugins/showHiddenThings/index.ts | 17 ++++++++++++++--- src/plugins/typingIndicator/index.tsx | 2 +- src/webpack/common/stores.ts | 7 ++----- 11 files changed, 38 insertions(+), 30 deletions(-) diff --git a/packages/discord-types/src/stores/GuildRoleStore.d.ts b/packages/discord-types/src/stores/GuildRoleStore.d.ts index bf0d4042..faf586a0 100644 --- a/packages/discord-types/src/stores/GuildRoleStore.d.ts +++ b/packages/discord-types/src/stores/GuildRoleStore.d.ts @@ -1,7 +1,8 @@ import { FluxStore, Role } from ".."; +// TODO: add the rest of the methods for GuildRoleStore export class GuildRoleStore extends FluxStore { getRole(guildId: string, roleId: string): Role; - getRoles(guildId: string): Record<string, Role>; - getAllGuildRoles(): Record<string, Record<string, Role>>; + getSortedRoles(guildId: string): Role[]; + getRolesSnapshot(guildId: string): Record<string, Role>; } diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index b6f18572..ce007a57 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -74,8 +74,8 @@ export default definePlugin({ { find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /(?<=roleName:\i,)color:/, - replace: "color:$self.calculateNameColorForListContext(arguments[0]),originalColor:" + match: /(?<=roleName:\i,)(colorString:)/, + replace: "$1$self.calculateNameColorForListContext(arguments[0]),originalColor:" }, predicate: () => settings.store.memberListColors } diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 28beabe1..608aa74f 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -84,7 +84,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea const [selectedItemIndex, selectItem] = useState(0); const selectedItem = permissions[selectedItemIndex]; - const roles = GuildRoleStore.getRoles(guild.id); + const roles = GuildRoleStore.getRolesSnapshot(guild.id); return ( <ModalRoot diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index 5185abb3..30589616 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -24,7 +24,7 @@ import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common"; import { PermissionsSortOrder, settings } from ".."; -import { cl, getGuildPermissionSpecMap, getSortedRoles, sortUserRoles } from "../utils"; +import { cl, getGuildPermissionSpecMap, getSortedRolesForMember, sortUserRoles } from "../utils"; import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermission } from "./RolesAndUsersPermissions"; interface UserPermission { @@ -94,7 +94,7 @@ function UserPermissionsComponent({ guild, guildMember, closePopout }: { guild: const [rolePermissions, userPermissions] = useMemo(() => { const userPermissions: UserPermissions = []; - const userRoles = getSortedRoles(guild, guildMember); + const userRoles = getSortedRolesForMember(guild, guildMember); const rolePermissions: Array<RoleOrUserPermission> = userRoles.map(role => ({ type: PermissionType.Role, diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index ed2471fb..1f96e66e 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -31,7 +31,7 @@ import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildSt import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; import UserPermissions from "./components/UserPermissions"; -import { getSortedRoles, sortPermissionOverwrites } from "./utils"; +import { getSortedRolesForMember, sortPermissionOverwrites } from "./utils"; const PopoutClasses = findByPropsLazy("container", "scroller", "list"); const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "banner"); @@ -73,7 +73,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { .with(MenuItemParentType.User, () => { const member = GuildMemberStore.getMember(guildId, id!)!; - const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member) + const permissions: RoleOrUserPermission[] = getSortedRolesForMember(guild, member) .map(role => ({ type: PermissionType.Role, ...role @@ -107,7 +107,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { }; }) .otherwise(() => { - const permissions = Object.values(GuildRoleStore.getRoles(guild.id)).map(role => ({ + const permissions = GuildRoleStore.getSortedRoles(guild.id).map(role => ({ type: PermissionType.Role, ...role })); diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts index 022dca6e..27e7494b 100644 --- a/src/plugins/permissionsViewer/utils.ts +++ b/src/plugins/permissionsViewer/utils.ts @@ -28,12 +28,11 @@ export const { getGuildPermissionSpecMap } = findByPropsLazy("getGuildPermission export const cl = classNameFactory("vc-permviewer-"); -export function getSortedRoles({ id }: Guild, member: GuildMember) { - const roles = GuildRoleStore.getRoles(id); - - return [...member.roles, id] - .map(id => roles[id]) - .sort((a, b) => b.position - a.position); +export function getSortedRolesForMember({ id: guildId }: Guild, member: GuildMember) { + // the guild id is the @everyone role + return GuildRoleStore + .getSortedRoles(guildId) + .filter(role => role.id === guildId || member.roles.includes(role.id)); } export function sortUserRoles(roles: Role[]) { @@ -48,7 +47,7 @@ export function sortUserRoles(roles: Role[]) { } export function sortPermissionOverwrites<T extends { id: string; type: number; }>(overwrites: T[], guildId: string) { - const roles = GuildRoleStore.getRoles(guildId); + const roles = GuildRoleStore.getRolesSnapshot(guildId); return overwrites.sort((a, b) => { if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0; diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 2fa687e8..ac57e976 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -200,7 +200,7 @@ function ServerInfoTab({ guild }: GuildProps) { "Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?", "Server Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`, "Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category - "Roles": Object.keys(GuildRoleStore.getRoles(guild.id)).length - 1, // - @everyone + "Roles": GuildRoleStore.getSortedRoles(guild.id).length - 1, // - @everyone }; return ( diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index a0ef38a6..d582c8c7 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -178,7 +178,7 @@ export default definePlugin({ }, // Add the hidden eye icon if the channel is hidden { - match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/, + match: /\.Children\.count.+?:null(?<=,channel:(\i).+?)/, replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null` }, // Make voice channels also appear as muted if they are muted @@ -302,8 +302,8 @@ export default definePlugin({ }, { // Include the @everyone role in the allowed roles list for Hidden Channels - match: /sortBy.{0,30}?\.filter\(\i=>(?<=channel:(\i).+?)/, - replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?true:` + match: /getSortedRoles.+?\.filter\(\i=>(?=!)/, + replace: m => `${m}$self.isHiddenChannel(arguments[0].channel)?true:` }, { // If the @everyone role has the required permissions, make the array only contain it diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index d80691fe..96ebf169 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -18,7 +18,9 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, PluginSettingDef } from "@utils/types"; +import { GuildMember, Role } from "@vencord/discord-types"; const opt = (description: string) => ({ type: OptionType.BOOLEAN, @@ -70,8 +72,8 @@ export default definePlugin({ find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:", predicate: () => settings.store.showModView, replacement: { - match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/, - replace: "$1$2arguments[0].member.highestRoleId]", + match: /(role:)\i(?<=\i\.roles,\i\.highestRoleId,(\i)\].+)/, + replace: "$1$self.findHighestRole(arguments[0],$2)", } }, // allows you to open mod view on yourself @@ -83,5 +85,14 @@ export default definePlugin({ replace: "false" } } - ] + ], + + findHighestRole({ member }: { member: GuildMember; }, roles: Role[]): Role | undefined { + try { + return roles.find(role => role.id === member.highestRoleId); + } catch (e) { + new Logger("ShowHiddenThings").error("Failed to find highest role", e); + return undefined; + } + } }); diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 29cc3c21..0a0cd53a 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -173,7 +173,7 @@ export default definePlugin({ { find: "UNREAD_IMPORTANT:", replacement: { - match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/, + match: /\.Children\.count.+?:null(?<=,channel:(\i).+?)/, replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())" } }, diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 4165af8e..71abd729 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -17,7 +17,7 @@ */ import * as t from "@vencord/discord-types"; -import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { waitForStore } from "./internal"; @@ -71,6 +71,7 @@ waitForStore("PermissionStore", m => PermissionStore = m); waitForStore("PresenceStore", m => PresenceStore = m); waitForStore("ReadStateStore", m => ReadStateStore = m); waitForStore("GuildChannelStore", m => GuildChannelStore = m); +waitForStore("GuildRoleStore", m => GuildRoleStore = m); waitForStore("MessageStore", m => MessageStore = m); waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); @@ -79,7 +80,3 @@ waitForStore("ThemeStore", m => { // Importing this directly can easily cause circular imports. For this reason, use a non import access here. Vencord.QuickCss.initQuickCssThemeStore(); }); - -// GuildRoleStore is new, this code is for stable + canary compatibility -// TODO: Change to waitForStore once GuildRoleStore is on stable -waitFor(["getRole", "getRoles"], m => GuildRoleStore = m); From fa672a347d06ac56582db1fd319481e421940059 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Fri, 1 Aug 2025 01:51:29 +0200 Subject: [PATCH 056/141] BetterSessions: fix ui --- src/plugins/betterSessions/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index b40c3c1e..14a25577 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -92,7 +92,7 @@ export default definePlugin({ <span>{title}</span> {(savedSession == null || savedSession.isNew) && ( <div - className="vc-plugins-badge" + className="vc-addon-badge" style={{ backgroundColor: "#ED4245", marginLeft: "2px" @@ -123,6 +123,7 @@ export default definePlugin({ return ( <BlobMask + isFolder style={{ cursor: "unset" }} selected={false} lowerBadge={ @@ -153,7 +154,7 @@ export default definePlugin({ className={SessionIconClasses.sessionIcon} style={{ backgroundColor: GetOsColor(session.client_info.os) }} > - <DeviceIcon width={28} height={28} color="currentColor" /> + <DeviceIcon size="md" color="currentColor" /> </div> </BlobMask> ); From 22d3fb10e9c0527a112b7f9f804b21a6f9302898 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Sun, 3 Aug 2025 09:05:26 -0400 Subject: [PATCH 057/141] fix plugins broken by latest Discord update (#3574) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/_api/dynamicImageModalApi.ts | 5 +++-- src/plugins/_api/messageEvents.ts | 2 +- src/plugins/decor/index.tsx | 2 +- src/plugins/favGifSearch/index.tsx | 4 ++-- src/plugins/showHiddenChannels/index.tsx | 14 ++++++++++++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/plugins/_api/dynamicImageModalApi.ts b/src/plugins/_api/dynamicImageModalApi.ts index d91a5a93..114b4836 100644 --- a/src/plugins/_api/dynamicImageModalApi.ts +++ b/src/plugins/_api/dynamicImageModalApi.ts @@ -16,8 +16,9 @@ export default definePlugin({ { find: ".dimensionlessImage,", replacement: { - match: /(?<="IMAGE"===\i&&\(\i=)\i(?=\?)/, - replace: "true" + // widthAndHeightPassed = w != null && w !== 0 && h == null || h === 0 + match: /(?<=\i=)(null!=\i&&0!==\i)&&(null!=\i&&0!==\i)/, + replace: "($1)||($2)" } } ] diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts index 9dfc55e2..e5192763 100644 --- a/src/plugins/_api/messageEvents.ts +++ b/src/plugins/_api/messageEvents.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::EDIT_TEXTAREA_HELP}", replacement: { - match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/, + match: /(?<=,channel:\i\}\)\.then\().+?\i\.content!==this\.props\.message\.content&&\i\((.+?)\)\}(?=return)/, replace: (match, args) => "" + `async ${match}` + `if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` + diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 0a6dd85d..a141f2b5 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -71,7 +71,7 @@ export default definePlugin({ }, // Remove NEW label from decor avatar decorations { - match: /(?<=\.\i\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/, + match: /(?<=\.\i\.PURCHASE)(?=,)(?<=avatarDecoration:(\i).+?)/, replace: "||$1.skuId===$self.SKU_ID" } ] diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index 5e302912..d88ced34 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -35,7 +35,7 @@ interface SearchBarComponentProps { } type TSearchBarComponent = - React.FC<SearchBarComponentProps> & { Sizes: Record<"SMALL" | "MEDIUM" | "LARGE", string>; }; + React.FC<SearchBarComponentProps>; interface Gif { format: number; @@ -182,7 +182,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc ref={ref} autoFocus={true} className={containerClasses.searchBar} - size={SearchBarComponent.Sizes.MEDIUM} + size="md" onChange={onChange} onClear={() => { setQuery(""); diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index d582c8c7..2d0ea368 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -292,8 +292,8 @@ export default definePlugin({ replacement: [ { // Change the role permission check to CONNECT if the channel is locked - match: /ADMINISTRATOR\)\|\|(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/, - replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):` + match: /\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\)(?<=context:(\i)}.+?)/, + replace: (m, channel) => `$self.fixPermCheck(${m},${channel})` }, { // Change the permissionOverwrite check to CONNECT if the channel is locked @@ -492,6 +492,16 @@ export default definePlugin({ } ], + + fixPermCheck(originalPerms: bigint, channel: Channel) { + if (!PermissionStore.can(PermissionsBits.CONNECT, channel)) { + originalPerms &= ~PermissionsBits.VIEW_CHANNEL; + originalPerms |= PermissionsBits.CONNECT; + } + + return originalPerms; + }, + isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) { try { if (channel == null || Object.hasOwn(channel, "channelId") && channel.channelId == null) return false; From 38e46f89cfb67e321fe5aeeb178ca674671647cd Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 3 Aug 2025 16:24:00 +0200 Subject: [PATCH 058/141] BetterUploadButton: fix right click action not working --- src/plugins/betterUploadButton/index.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index bfdb3afe..788721a9 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -25,17 +25,11 @@ export default definePlugin({ description: "Upload with a single click, open menu with right click", patches: [ { - find: '"ChannelAttachButton"', + find: ".CHAT_INPUT_BUTTON_NOTIFICATION,", replacement: [ { - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, - replace: "$&onClick:$1,onContextMenu:$2.onClick,", - noWarn: true - }, - { - match: /\.attachButtonInner,.+?onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/, - replace: "$&,onClick:$1,onContextMenu:$2.onClick,", + match: /onClick:(\i\?void 0:\i)(?=,onDoubleClick:(\i\?void 0:\i))/, + replace: "onContextMenu:$1,onClick:$2", }, ] }, From a02d1afdf095bb8c58a74b29df1cb08bd500c1a8 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 3 Aug 2025 16:26:08 +0200 Subject: [PATCH 059/141] bump to v1.12.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3401cc5..57aa36f0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.7", + "version": "1.12.8", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 1ebd4123925cfbbd62b805505cc2ad20c0a1646a Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 3 Aug 2025 17:04:50 +0200 Subject: [PATCH 060/141] BetterUploadButton: don't affect other chat buttons --- package.json | 2 +- src/plugins/betterUploadButton/index.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 57aa36f0..5438d37f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.8", + "version": "1.12.9", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index 788721a9..9639dc60 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -28,10 +28,19 @@ export default definePlugin({ find: ".CHAT_INPUT_BUTTON_NOTIFICATION,", replacement: [ { - match: /onClick:(\i\?void 0:\i)(?=,onDoubleClick:(\i\?void 0:\i))/, - replace: "onContextMenu:$1,onClick:$2", + match: /onClick:(\i\?void 0:\i)(?=,onDoubleClick:(\i\?void 0:\i),)/, + replace: "$&,...$self.getOverrides(arguments[0],$1,$2)", }, ] }, ], + + getOverrides(props: any, onClick: any, onDoubleClick: any) { + if (!props?.className?.includes("attachButton")) return {}; + + return { + onClick: onDoubleClick, + onContextMenu: onClick + }; + } }); From 6380111f326e0778629b62165d14892d661d3f16 Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Tue, 5 Aug 2025 19:20:28 +0200 Subject: [PATCH 061/141] Notification Log: fix lag if there are too many entries (#3584) Use Discord's lazy list implementation for only rendering what's on screen --- packages/discord-types/src/components.d.ts | 57 +++++++++++++++++-- .../Notifications/NotificationComponent.tsx | 4 +- src/api/Notifications/notificationLog.tsx | 41 ++++++------- src/api/Notifications/styles.css | 35 +++++++++--- src/webpack/common/components.ts | 9 +++ 5 files changed, 106 insertions(+), 40 deletions(-) diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index eff9d73a..1df03735 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -474,19 +474,66 @@ export type MaskedLink = ComponentType<PropsWithChildren<{ channelId?: string; }>>; -export type ScrollerThin = ComponentType<PropsWithChildren<{ +export interface ScrollerBaseProps { className?: string; style?: CSSProperties; - dir?: "ltr"; - orientation?: "horizontal" | "vertical" | "auto"; paddingFix?: boolean; - fade?: boolean; - onClose?(): void; onScroll?(): void; +} + +export type ScrollerThin = ComponentType<PropsWithChildren<ScrollerBaseProps & { + orientation?: "horizontal" | "vertical" | "auto"; + fade?: boolean; }>>; +interface BaseListItem { + anchorId: any; + listIndex: number; + offsetTop: number; + section: number; +} +interface ListSection extends BaseListItem { + type: "section"; +} +interface ListRow extends BaseListItem { + type: "row"; + row: number; + rowIndex: number; +} + +export type ListScrollerThin = ComponentType<ScrollerBaseProps & { + sections: number[]; + renderSection?: (item: ListSection) => React.ReactNode; + renderRow: (item: ListRow) => React.ReactNode; + renderFooter?: (item: any) => React.ReactNode; + renderSidebar?: (listVisible: boolean, sidebarVisible: boolean) => React.ReactNode; + wrapSection?: (section: number, children: React.ReactNode) => React.ReactNode; + + sectionHeight: number; + rowHeight: number; + footerHeight?: number; + sidebarHeight?: number; + + chunkSize?: number; + + paddingTop?: number; + paddingBottom?: number; + fade?: boolean; + onResize?: Function; + getAnchorId?: any; + + innerTag?: string; + innerId?: string; + innerClassName?: string; + innerRole?: string; + innerAriaLabel?: string; + // Yes, Discord uses this casing + innerAriaMultiselectable?: boolean; + innerAriaOrientation?: "vertical" | "horizontal"; +}>; + export type Clickable = <T extends "a" | "div" | "span" | "li" = "div">(props: PropsWithChildren<ComponentPropsWithRef<T>> & { tag?: T; }) => ReactNode; diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx index d07143c4..52e56d5d 100644 --- a/src/api/Notifications/NotificationComponent.tsx +++ b/src/api/Notifications/NotificationComponent.tsx @@ -104,9 +104,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({ </svg> </button> </div> - <div> - {richBody ?? <p className="vc-notification-p">{body}</p>} - </div> + {richBody ?? <p className="vc-notification-p">{body}</p>} </div> </div> {image && <img className="vc-notification-img" src={image} alt="" />} diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index a943db07..dca7679e 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -21,9 +21,9 @@ import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; -import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; @@ -103,21 +103,9 @@ export function useLogs() { function NotificationEntry({ data }: { data: PersistentNotificationData; }) { const [removing, setRemoving] = useState(false); - const ref = React.useRef<HTMLDivElement>(null); - - useEffect(() => { - const div = ref.current!; - - const setHeight = () => { - if (div.clientHeight === 0) return requestAnimationFrame(setHeight); - div.style.height = `${div.clientHeight}px`; - }; - - setHeight(); - }, []); return ( - <div className={cl("wrapper", { removing })} ref={ref}> + <div className={cl("wrapper", { removing })}> <NotificationComponent {...data} permanent={true} @@ -129,13 +117,13 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) { setTimeout(() => deleteNotification(data.timestamp), 200); }} richBody={ - <div className={cl("body")}> - {data.body} + <div className={cl("body-wrapper")}> + <div className={cl("body")}>{data.body}</div> <Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} /> </div> } /> - </div> + </div > ); } @@ -151,9 +139,14 @@ export function NotificationLog({ log, pending }: { log: PersistentNotificationD ); return ( - <div className={cl("container")}> - {log.map(n => <NotificationEntry data={n} key={n.id} />)} - </div> + <ListScrollerThin + className={cl("container")} + sections={[log.length]} + sectionHeight={0} + rowHeight={120} + renderSection={() => null} + renderRow={item => <NotificationEntry data={log[item.row]} key={log[item.row].id} />} + /> ); } @@ -161,15 +154,15 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void const [log, pending] = useLogs(); return ( - <ModalRoot {...modalProps} size={ModalSize.LARGE}> + <ModalRoot {...modalProps} size={ModalSize.LARGE} className={cl("modal")}> <ModalHeader> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Log</Text> <ModalCloseButton onClick={close} /> </ModalHeader> - <ModalContent> + <div style={{ width: "100%" }}> <NotificationLog log={log} pending={pending} /> - </ModalContent> + </div> <ModalFooter> <Flex> diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index 10d3c0cf..52cbc805 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -32,6 +32,7 @@ .vc-notification-content { width: 100%; + overflow: hidden; } .vc-notification-header { @@ -81,6 +82,11 @@ width: 100%; } +.vc-notification-log-modal { + max-width: 962px; + width: clamp(var(--modal-width-large, 800px), 962px, 85vw); +} + .vc-notification-log-empty { height: 218px; background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat; @@ -88,19 +94,23 @@ } .vc-notification-log-container { - display: flex; - flex-direction: column; padding: 1em; - overflow: hidden; + max-height: min(750px, 75vh); + width: 100%; } .vc-notification-log-wrapper { + height: 120px; + width: 100%; + padding-bottom: 16px; + box-sizing: border-box; transition: 200ms ease; transition-property: height, opacity; -} -.vc-notification-log-wrapper:not(:last-child) { - margin-bottom: 1em; + /* stylelint-disable-next-line no-descending-specificity */ + .vc-notification-root { + height: 104px; + } } .vc-notification-log-removing { @@ -109,9 +119,18 @@ margin-bottom: 1em; } -.vc-notification-log-body { +.vc-notification-log-body-wrapper { display: flex; flex-direction: column; + width: 100%; + box-sizing: border-box; +} + +.vc-notification-log-body { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + line-height: 1.2em; } .vc-notification-log-timestamp { @@ -123,4 +142,4 @@ .vc-notification-log-danger-btn { color: var(--white-500); background-color: var(--button-danger-background); -} +} \ No newline at end of file diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index bdf0b2c4..bf7c9dd1 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -70,14 +70,23 @@ export const ColorPicker = waitForComponent<t.ColorPicker>("ColorPicker", filter export const UserSummaryItem = waitForComponent("UserSummaryItem", filters.componentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin; +export let createListScroller: (scrollBarClassName: string, fadeClassName: string, someOtherClassIdkMan: string, resizeObserverClass: typeof ResizeObserver) => t.ListScrollerThin; export let scrollerClasses: Record<string, string>; +export let listScrollerClasses: Record<string, string>; + waitFor(filters.byCode('="ltr",orientation:', "customTheme:", "forwardRef"), m => createScroller = m); +waitFor(filters.byCode("getScrollerNode:", "resizeObserver:", "sectionHeight:"), m => createListScroller = m); waitFor(["thin", "auto", "customTheme"], m => scrollerClasses = m); +waitFor(m => m.thin && m.auto && !m.customTheme, m => listScrollerClasses = m); export const ScrollerNone = LazyComponent(() => createScroller(scrollerClasses.none, scrollerClasses.fade, scrollerClasses.customTheme)); export const ScrollerThin = LazyComponent(() => createScroller(scrollerClasses.thin, scrollerClasses.fade, scrollerClasses.customTheme)); export const ScrollerAuto = LazyComponent(() => createScroller(scrollerClasses.auto, scrollerClasses.fade, scrollerClasses.customTheme)); +export const ListScrollerNone = LazyComponent(() => createListScroller(listScrollerClasses.none, listScrollerClasses.fade, "", ResizeObserver)); +export const ListScrollerThin = LazyComponent(() => createListScroller(listScrollerClasses.thin, listScrollerClasses.fade, "", ResizeObserver)); +export const ListScrollerAuto = LazyComponent(() => createListScroller(listScrollerClasses.auto, listScrollerClasses.fade, "", ResizeObserver)); + const { FocusLock_ } = mapMangledModuleLazy('document.getElementById("app-mount"))', { FocusLock_: filters.componentByCode(".containerRef") }) as { From a2253cb4aeec1c4e6a69a95d2e3dacb0f0c1fa48 Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Tue, 5 Aug 2025 23:57:51 +0200 Subject: [PATCH 062/141] remove old discord ui workarounds & legacy code (#3585) Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com> Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- .vscode/settings.json | 9 ++- src/api/Notifications/styles.css | 6 +- src/components/CheckedTextInput.tsx | 1 - src/components/settings/AddonCard.css | 16 +--- src/components/settings/QuickAction.css | 14 +--- .../tabs/plugins/ContributorModal.css | 6 +- .../settings/tabs/plugins/LinkIconButton.css | 4 +- src/components/settings/tabs/styles.css | 4 +- src/plugins/_api/chatButtons.ts | 7 +- src/plugins/_core/settings.tsx | 3 +- src/plugins/betterFolders/style.css | 4 +- src/plugins/clientTheme/utils/styleUtils.ts | 4 +- src/plugins/ctrlEnterSend/index.ts | 5 +- src/plugins/decor/index.tsx | 12 --- src/plugins/fakeNitro/index.tsx | 7 +- src/plugins/ircColors/index.ts | 4 +- src/plugins/memberCount/index.tsx | 6 -- src/plugins/messageTags/index.ts | 9 --- src/plugins/openInApp/index.ts | 5 +- src/plugins/permissionFreeWill/index.ts | 5 +- src/plugins/permissionsViewer/utils.ts | 2 +- src/plugins/pinDms/data.ts | 29 ------- src/plugins/showHiddenChannels/index.tsx | 38 ++++----- src/plugins/showHiddenThings/index.ts | 6 +- .../spotifyControls/PlayerComponent.tsx | 1 - src/plugins/spotifyControls/spotifyStyles.css | 60 +++++++++++---- .../visualRefreshSpotifyStyles.css | 77 ------------------- src/plugins/startupTimings/index.tsx | 10 +-- src/plugins/textReplace/index.tsx | 16 ---- src/plugins/userMessagesPronouns/index.ts | 7 -- src/plugins/viewIcons/index.tsx | 6 -- src/utils/constants.ts | 6 +- src/webpack/common/stores.ts | 2 - 33 files changed, 105 insertions(+), 286 deletions(-) delete mode 100644 src/plugins/spotifyControls/visualRefreshSpotifyStyles.css diff --git a/.vscode/settings.json b/.vscode/settings.json index fa543b38..fe123188 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,11 +13,14 @@ "typescript.format.semicolons": "insert", "typescript.preferences.quoteStyle": "double", "javascript.preferences.quoteStyle": "double", - "gitlens.remotes": [ { "domain": "codeberg.org", "type": "Gitea" } - ] -} + ], + "css.format.spaceAroundSelectorSeparator": true, + "[css]": { + "editor.defaultFormatter": "vscode.css-language-features" + } +} \ No newline at end of file diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index 52cbc805..471fbf01 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -4,17 +4,13 @@ display: flex; flex-direction: column; color: var(--text-default); - background-color: var(--background-base-lower-alt); + background-color: var(--background-base-low); border-radius: 6px; overflow: hidden; cursor: pointer; width: 100%; } -.visual-refresh .vc-notification-root { - background-color: var(--background-base-low); -} - .vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) { position: absolute; z-index: 2147483647; diff --git a/src/components/CheckedTextInput.tsx b/src/components/CheckedTextInput.tsx index cf4aa119..8d37a560 100644 --- a/src/components/CheckedTextInput.tsx +++ b/src/components/CheckedTextInput.tsx @@ -18,7 +18,6 @@ import { React, TextInput } from "@webpack/common"; -// TODO: Refactor settings to use this as well interface TextInputProps { /** * WARNING: Changing this between renders will have no effect! diff --git a/src/components/settings/AddonCard.css b/src/components/settings/AddonCard.css index c8a2a81e..d5e3b4d6 100644 --- a/src/components/settings/AddonCard.css +++ b/src/components/settings/AddonCard.css @@ -1,6 +1,7 @@ .vc-addon-card { - background-color: var(--background-base-lower-alt); + background-color: var(--card-primary-bg); color: var(--interactive-active); + border: 1px solid var(--border-subtle); border-radius: 8px; display: block; height: 100%; @@ -11,26 +12,15 @@ box-sizing: border-box; } -.visual-refresh .vc-addon-card { - background-color: var(--card-primary-bg); - border: 1px solid var(--border-subtle); -} - .vc-addon-card-disabled { opacity: 0.6; } .vc-addon-card:hover { - background-color: var(--background-tertiary); transform: translateY(-1px); box-shadow: var(--elevation-high); } -.visual-refresh .vc-addon-card:hover { - /* same as non-hover, here to overwrite the non-refresh hover background */ - background-color: var(--card-primary-bg); -} - .vc-addon-header { margin-top: auto; display: flex; @@ -104,4 +94,4 @@ .vc-addon-title:hover { overflow: visible; animation: vc-addon-title var(--duration) linear infinite; -} +} \ No newline at end of file diff --git a/src/components/settings/QuickAction.css b/src/components/settings/QuickAction.css index f65fba21..8b94bd2d 100644 --- a/src/components/settings/QuickAction.css +++ b/src/components/settings/QuickAction.css @@ -14,7 +14,7 @@ .vc-settings-quickActions-pill { all: unset; - background: var(--background-base-lower); + background: var(--button-secondary-background); color: var(--header-secondary); display: flex; align-items: center; @@ -26,7 +26,7 @@ } .vc-settings-quickActions-pill:hover { - background: var(--background-base-lower-alt); + background: var(--button-secondary-background-hover); transform: translateY(-1px); box-shadow: var(--elevation-high); } @@ -36,15 +36,7 @@ outline-offset: 2px; } -.visual-refresh .vc-settings-quickActions-pill { - background: var(--button-secondary-background); -} - -.visual-refresh .vc-settings-quickActions-pill:hover { - background: var(--button-secondary-background-hover); -} - .vc-settings-quickActions-img { width: 24px; height: 24px; -} +} \ No newline at end of file diff --git a/src/components/settings/tabs/plugins/ContributorModal.css b/src/components/settings/tabs/plugins/ContributorModal.css index 3a698e2c..ef3b2dde 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.css +++ b/src/components/settings/tabs/plugins/ContributorModal.css @@ -11,7 +11,7 @@ .vc-author-modal-name { text-transform: none; flex-grow: 0; - background: var(--background-tertiary); + background: var(--background-base-lowest); border-radius: 0 9999px 9999px 0; padding: 6px 0.8em 6px 0.5em; font-size: 20px; @@ -26,7 +26,7 @@ position: absolute; height: 100%; width: 32px; - background: var(--background-tertiary); + background: var(--background-base-lowest); z-index: -1; left: -32px; top: 0; @@ -48,4 +48,4 @@ display: grid; gap: 0.5em; margin-top: 0.75em; -} +} \ No newline at end of file diff --git a/src/components/settings/tabs/plugins/LinkIconButton.css b/src/components/settings/tabs/plugins/LinkIconButton.css index 1055d6c7..97db0780 100644 --- a/src/components/settings/tabs/plugins/LinkIconButton.css +++ b/src/components/settings/tabs/plugins/LinkIconButton.css @@ -2,11 +2,11 @@ height: 32px; width: 32px; border-radius: 50%; - border: 4px solid var(--background-tertiary); + border: 4px solid var(--background-base-lowest); box-sizing: border-box } .vc-settings-modal-links { display: flex; gap: 0.2em; -} +} \ No newline at end of file diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css index c92d588f..2ff8cb9f 100644 --- a/src/components/settings/tabs/styles.css +++ b/src/components/settings/tabs/styles.css @@ -54,7 +54,7 @@ } .vc-settings-theme-links:focus { - background-color: var(--background-tertiary); + background-color: var(--background-base-lowest); } .vc-cloud-settings-sync-grid { @@ -74,4 +74,4 @@ .vc-updater-modal-close-button { float: right; -} +} \ No newline at end of file diff --git a/src/plugins/_api/chatButtons.ts b/src/plugins/_api/chatButtons.ts index d4e1ea8b..26312253 100644 --- a/src/plugins/_api/chatButtons.ts +++ b/src/plugins/_api/chatButtons.ts @@ -16,11 +16,8 @@ export default definePlugin({ { find: '"sticker")', replacement: { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(.+?(\i)\.push)/, - replace: (m, not, children) => not - ? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&` - : `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||` + match: /return\(\i\.\i\|\|(?=\(.+?(\i)\.push)/, + replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),false)||" } } ] diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 1f942d7d..4a0f297e 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -59,8 +59,7 @@ export default definePlugin({ replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` }, { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/, + match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?\(\)=>)\2/, replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})` } ] diff --git a/src/plugins/betterFolders/style.css b/src/plugins/betterFolders/style.css index a3c82dcb..a0a04966 100644 --- a/src/plugins/betterFolders/style.css +++ b/src/plugins/betterFolders/style.css @@ -3,11 +3,11 @@ } /* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */ -.visual-refresh .vc-betterFolders-sidebar-grid { +.vc-betterFolders-sidebar-grid { /* stylelint-disable-next-line value-keyword-case */ grid-template-columns: [start] min-content [guildsEnd] min-content [sidebarEnd] min-content [channelsEnd] 1fr [end]; grid-template-areas: "titleBar titleBar titleBar titleBar" "guildsList betterFoldersSidebar notice notice" "guildsList betterFoldersSidebar channelsList page"; -} +} \ No newline at end of file diff --git a/src/plugins/clientTheme/utils/styleUtils.ts b/src/plugins/clientTheme/utils/styleUtils.ts index bc6169d4..ca36730a 100644 --- a/src/plugins/clientTheme/utils/styleUtils.ts +++ b/src/plugins/clientTheme/utils/styleUtils.ts @@ -75,8 +75,8 @@ function createColorsOverrides(styles: string) { const darkThemeBaseLightness = visualRefreshColorsLightness["--neutral-69-hsl"]; createOrUpdateStyle(OVERRIDES_STYLE_ID, [ - `.visual-refresh.theme-light {\n ${generateNewColorVars(visualRefreshColorsLightness, lightThemeBaseLightness)} \n}`, - `.visual-refresh.theme-dark {\n ${generateNewColorVars(visualRefreshColorsLightness, darkThemeBaseLightness)} \n}`, + `.theme-light {\n ${generateNewColorVars(visualRefreshColorsLightness, lightThemeBaseLightness)} \n}`, + `.theme-dark {\n ${generateNewColorVars(visualRefreshColorsLightness, darkThemeBaseLightness)} \n}`, ].join("\n\n")); } diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index 57a70d52..7b0b26b9 100644 --- a/src/plugins/ctrlEnterSend/index.ts +++ b/src/plugins/ctrlEnterSend/index.ts @@ -44,9 +44,8 @@ export default definePlugin({ { find: ".selectPreviousCommandOption(", replacement: { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/, - replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})` + match: /(?<=(\i)\.which!==\i\.\i.ENTER\|\|).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=\|\|\(\i\.preventDefault)/, + replace: "!$self.shouldSubmit($1,$2)" } }, { diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index a141f2b5..528e9154 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -49,18 +49,6 @@ export default definePlugin({ { find: ".decorationGridItem,", replacement: [ - { - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/, - replace: "$self.DecorationGridItem=$&", - noWarn: true - }, - { - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /(?<==)\i=>{let{user:\i,avatarDecoration/, - replace: "$self.DecorationGridDecoration=$&", - noWarn: true - }, { match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/, replace: "$self.DecorationGridItem=$&", diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 380147fe..f697c3fd 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -255,11 +255,8 @@ export default definePlugin({ }, { // Disallow the emoji for premium locked if the intention doesn't allow it - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/, - replace: (m, not) => not - ? `(${m}&&!${IS_BYPASSEABLE_INTENTION})` - : `(${m}||${IS_BYPASSEABLE_INTENTION})` + match: /!(\i\.\i\.canUseEmojisEverywhere\(\i\))/, + replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})` }, { // Allow animated emojis to be used if the intention allows it diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index ce007a57..ebfa735e 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -74,8 +74,8 @@ export default definePlugin({ { find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /(?<=roleName:\i,)(colorString:)/, - replace: "$1$self.calculateNameColorForListContext(arguments[0]),originalColor:" + match: /(?<=roleName:\i,)colorString:/, + replace: "colorString:$self.calculateNameColorForListContext(arguments[0]),originalColor:" }, predicate: () => settings.store.memberListColors } diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 97f6f23c..7fa6fbcd 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -66,12 +66,6 @@ export default definePlugin({ { find: "{isSidebarVisible:", replacement: [ - { - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, - replace: ":[$1?.startsWith('members')?$self.render():null,$2", - noWarn: true - }, { match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, replace: ":[$1?.startsWith('members')?$self.render():null,$2", diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts index 90ce0ceb..08b035b1 100644 --- a/src/plugins/messageTags/index.ts +++ b/src/plugins/messageTags/index.ts @@ -17,7 +17,6 @@ */ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands"; -import * as DataStore from "@api/DataStore"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -89,14 +88,6 @@ export default definePlugin({ settings, async start() { - // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed - const oldTags = await DataStore.get<Tag[]>(DATA_KEY); - if (oldTags != null) { - // @ts-expect-error - settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag]))); - await DataStore.del(DATA_KEY); - } - const tags = getTags(); for (const tagName in tags) { createTagCommand(tags[tagName]); diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index e1133234..04c4f441 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -100,9 +100,8 @@ export default definePlugin({ replace: "true" }, { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(!)?\(0,\i\.isDesktop\)\(\)/, - replace: (_, not) => not ? "false" : "true" + match: /\(0,\i\.isDesktop\)\(\)/, + replace: "true" } ] }, diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts index 8a613514..767d4f98 100644 --- a/src/plugins/permissionFreeWill/index.ts +++ b/src/plugins/permissionFreeWill/index.ts @@ -46,9 +46,8 @@ export default definePlugin({ find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}", replacement: [ { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/, - replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)") + match: /{(?:\i:\(\)=>\i,?){2}}/, + replace: m => m.replaceAll(canonicalizeMatch(/\(\)=>\i/g), "()=>Promise.resolve(true)") } ], predicate: () => settings.store.onboarding diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts index 27e7494b..c3f61672 100644 --- a/src/plugins/permissionsViewer/utils.ts +++ b/src/plugins/permissionsViewer/utils.ts @@ -29,7 +29,7 @@ export const { getGuildPermissionSpecMap } = findByPropsLazy("getGuildPermission export const cl = classNameFactory("vc-permviewer-"); export function getSortedRolesForMember({ id: guildId }: Guild, member: GuildMember) { - // the guild id is the @everyone role + // The guild id is the @everyone role return GuildRoleStore .getSortedRoles(guildId) .filter(role => role.id === guildId || member.roles.includes(role.id)); diff --git a/src/plugins/pinDms/data.ts b/src/plugins/pinDms/data.ts index d689bd2a..18782299 100644 --- a/src/plugins/pinDms/data.ts +++ b/src/plugins/pinDms/data.ts @@ -4,8 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import * as DataStore from "@api/DataStore"; -import { Settings } from "@api/Settings"; import { useForceUpdater } from "@utils/react"; import { UserStore } from "@webpack/common"; @@ -28,8 +26,6 @@ let forceUpdateDms: (() => void) | undefined = undefined; export let currentUserCategories: Category[] = []; export async function init() { - await migrateData(); - const userId = UserStore.getCurrentUser()?.id; if (userId == null) return; @@ -154,28 +150,3 @@ export function moveChannel(channelId: string, direction: -1 | 1) { swapElementsInArray(category.channels, a, b); } - -// TODO(OptionType.CUSTOM Related): Remove DataStore PinnedDms migration once enough time has passed -async function migrateData() { - if (Settings.plugins.PinDMs.dmSectioncollapsed != null) { - settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed; - delete Settings.plugins.PinDMs.dmSectioncollapsed; - } - - const dataStoreKeys = await DataStore.keys(); - const pinDmsKeys = dataStoreKeys.map(key => String(key)).filter(key => key.startsWith(CATEGORY_BASE_KEY)); - - if (pinDmsKeys.length === 0) return; - - for (const pinDmsKey of pinDmsKeys) { - const categories = await DataStore.get<Category[]>(pinDmsKey); - if (categories == null) continue; - - const userId = pinDmsKey.replace(CATEGORY_BASE_KEY, ""); - settings.store.userBasedCategoryList[userId] = categories; - - await DataStore.del(pinDmsKey); - } - - await Promise.all([DataStore.del(CATEGORY_MIGRATED_PINDMS_KEY), DataStore.del(CATEGORY_MIGRATED_KEY), DataStore.del(OLD_CATEGORY_KEY)]); -} diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 2d0ea368..43cd677e 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -112,11 +112,8 @@ export default definePlugin({ }, { // Prevent Discord from trying to connect to hidden voice channels - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(?=(\|\||&&)\i\.\i\.selectVoiceChannel\((\i)\.id\))/, - replace: (_, condition, channel) => condition === "||" - ? `||$self.isHiddenChannel(${channel})` - : `&&!$self.isHiddenChannel(${channel})` + match: /(?=\|\|\i\.\i\.selectVoiceChannel\((\i)\.id\))/, + replace: (_, channel) => `||$self.isHiddenChannel(${channel})` }, { // Make Discord show inside the channel if clicking on a hidden or locked channel @@ -129,11 +126,8 @@ export default definePlugin({ { find: ".AUDIENCE),{isSubscriptionGated", replacement: { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, - replace: (m, not, channel) => not - ? `${m}&&!$self.isHiddenChannel(${channel})` - : `${m}||$self.isHiddenChannel(${channel})` + match: /(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, + replace: (m, channel) => `${m}||$self.isHiddenChannel(${channel})` } }, { @@ -183,11 +177,8 @@ export default definePlugin({ }, // Make voice channels also appear as muted if they are muted { - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)(if\()?(\i)(?:\)return |\?)(\i\.MUTED)/, - replace: (_, otherClasses, isIf, isMuted, mutedClassExpression) => isIf - ? `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""` - : `${isMuted}?${mutedClassExpression}:"",${otherClasses}${isMuted}?""` + match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)(?:\)return |\?)(\i\.MUTED)/, + replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""` } ] }, @@ -197,8 +188,7 @@ export default definePlugin({ { // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, - // FIXME(Bundler change related): Remove old compatiblity once enough time has passed - match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/, + match: /(?<=\.LOCKED;if\()(?<={channel:(\i).+?)/, replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&` }, { @@ -292,8 +282,8 @@ export default definePlugin({ replacement: [ { // Change the role permission check to CONNECT if the channel is locked - match: /\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\)(?<=context:(\i)}.+?)/, - replace: (m, channel) => `$self.fixPermCheck(${m},${channel})` + match: /(forceRoles:.+?)(\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\))(?<=context:(\i)}.+?)/, + replace: (_, rest, mergedPermissions, channel) => `${rest}$self.swapViewChannelWithConnectPermission(${mergedPermissions},${channel})` }, { // Change the permissionOverwrite check to CONNECT if the channel is locked @@ -303,7 +293,7 @@ export default definePlugin({ { // Include the @everyone role in the allowed roles list for Hidden Channels match: /getSortedRoles.+?\.filter\(\i=>(?=!)/, - replace: m => `${m}$self.isHiddenChannel(arguments[0].channel)?true:` + replace: m => `${m}$self.isHiddenChannel(arguments[0]?.channel)?true:` }, { // If the @everyone role has the required permissions, make the array only contain it @@ -493,13 +483,13 @@ export default definePlugin({ ], - fixPermCheck(originalPerms: bigint, channel: Channel) { + swapViewChannelWithConnectPermission(mergedPermissions: bigint, channel: Channel) { if (!PermissionStore.can(PermissionsBits.CONNECT, channel)) { - originalPerms &= ~PermissionsBits.VIEW_CHANNEL; - originalPerms |= PermissionsBits.CONNECT; + mergedPermissions &= ~PermissionsBits.VIEW_CHANNEL; + mergedPermissions |= PermissionsBits.CONNECT; } - return originalPerms; + return mergedPermissions; }, isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) { diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index 96ebf169..c39dccc4 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -72,8 +72,8 @@ export default definePlugin({ find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:", predicate: () => settings.store.showModView, replacement: { - match: /(role:)\i(?<=\i\.roles,\i\.highestRoleId,(\i)\].+)/, - replace: "$1$self.findHighestRole(arguments[0],$2)", + match: /(?<=\.highestRole\),)role:\i(?<=\[\i\.roles,\i\.highestRoleId,(\i)\].+)/, + replace: "role:$self.getHighestRole(arguments[0],$2)", } }, // allows you to open mod view on yourself @@ -87,7 +87,7 @@ export default definePlugin({ } ], - findHighestRole({ member }: { member: GuildMember; }, roles: Role[]): Role | undefined { + getHighestRole({ member }: { member: GuildMember; }, roles: Role[]): Role | undefined { try { return roles.find(role => role.id === member.highestRoleId); } catch (e) { diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 78a69a14..32c78c36 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -17,7 +17,6 @@ */ import "./spotifyStyles.css"; -import "./visualRefreshSpotifyStyles.css"; // TODO: merge with spotifyStyles.css and remove when old UI is discontinued import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index d4afbc83..e9dfeffd 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -1,8 +1,13 @@ #vc-spotify-player { - padding: 0.375rem 0.5rem; + padding: 12px; + background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt))); + margin: 0; + border-top-left-radius: 10px; + border-top-right-radius: 10px; border-bottom: 1px solid var(--border-subtle); - --vc-spotify-green: var(--spotify, #1db954); /* so custom themes can easily change it */ + /* so custom themes can easily change it */ + --vc-spotify-green: var(--spotify, #1db954); --vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%); --vc-spotify-green-80: color-mix(in hsl, var(--vc-spotify-green), transparent 80%); } @@ -16,12 +21,13 @@ } .vc-spotify-button { + margin: 0 2px; + border-radius: var(--radius-sm); background: none; color: var(--interactive-normal); padding: 0; width: 32px; height: 32px; - border-radius: 100%; display: flex; justify-content: center; align-items: center; @@ -47,13 +53,16 @@ filter: brightness(1.3); } */ -.vc-spotify-shuffle-on, .vc-spotify-repeat-context, .vc-spotify-repeat-track, -.vc-spotify-shuffle-on:hover, +.vc-spotify-shuffle-on { + background-color: var(--vc-spotify-green-90); +} + .vc-spotify-repeat-context:hover, -.vc-spotify-repeat-track:hover { - color: var(--vc-spotify-green); +.vc-spotify-repeat-track:hover, +.vc-spotify-shuffle-on:hover { + background-color: var(--vc-spotify-green-80); } .vc-spotify-tooltip-text { @@ -74,6 +83,15 @@ .vc-spotify-button-row { justify-content: center; + margin-top: 14px; +} + +.vc-spotify-secondary-song-info { + font-size: 12px; +} + +.vc-spotify-song-info-prefix { + display: none; } #vc-spotify-info-wrapper { @@ -127,7 +145,7 @@ .vc-spotify-album { font-size: 12px; text-decoration: none; - color: var(--header-secondary); + color: var(--header-primary); } .vc-spotify-comma { @@ -155,16 +173,26 @@ padding: 0 !important; } -#vc-spotify-progress-bar > [class^="slider"] [class^="bar-"] { - height: 4px !important; +#vc-spotify-progress-bar > [class^="slider"] [class^="bar"] { + height: 3px !important; + top: calc(12px - 4px / 2 + var(--bar-offset)); +} + +#vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] { + background-color: var(--interactive-active); +} + +#vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] { + background-color: var(--vc-spotify-green); } #vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] { /* these importants are necessary, it applies a width and height through inline styles */ - height: 10px !important; - width: 10px !important; - margin-top: 4px; - background-color: var(--interactive-normal); + height: 16px !important; + width: 16px !important; + margin-top: calc(17px/-2 + var(--bar-offset)/2); + margin-left: -0.5px; + background-color: var(--interactive-active); border-color: var(--interactive-normal); color: var(--interactive-normal); opacity: 0; @@ -183,6 +211,8 @@ font-size: 12px; top: 10px; position: absolute; + margin-top: 8px; + font-family: var(--font-code); } .vc-spotify-time-left { @@ -196,4 +226,4 @@ .vc-spotify-fallback { padding: 0.5em; color: var(--text-default); -} +} \ No newline at end of file diff --git a/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css b/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css deleted file mode 100644 index 0d6d30ba..00000000 --- a/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css +++ /dev/null @@ -1,77 +0,0 @@ -/* TODO: merge with spotifyStyles.css and remove when old UI is discontinued */ -.visual-refresh { - #vc-spotify-player { - padding: 12px; - background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt))); - margin: 0; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - } - - .vc-spotify-song-info-prefix { - display: none; - } - - .vc-spotify-artist, .vc-spotify-album { - color: var(--header-primary); - } - - .vc-spotify-secondary-song-info { - font-size: 12px; - } - - #vc-spotify-progress-bar { - position: relative; - color: var(--text-default); - width: 100%; - } - - #vc-spotify-progress-bar > [class^="slider"] { - flex-grow: 1; - width: 100%; - padding: 0 !important; - } - - #vc-spotify-progress-bar > [class^="slider"] [class^="bar"] { - height: 3px !important; - top: calc(12px - 4px / 2 + var(--bar-offset)); - } - - #vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] { - background-color: var(--interactive-active); - } - - #vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] { - background-color: var(--vc-spotify-green); - } - - #vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] { - background-color: var(--interactive-active); - width: 16px !important; - height: 16px !important; - margin-top: calc(17px/-2 + var(--bar-offset)/2); - margin-left: -0.5px; - } - - .vc-spotify-progress-time { - margin-top: 8px; - font-family: var(--font-code); - } - - .vc-spotify-button-row { - margin-top: 14px; - } - - .vc-spotify-button { - margin: 0 2px; - border-radius: var(--radius-sm); - } - - .vc-spotify-repeat-context, .vc-spotify-repeat-track, .vc-spotify-shuffle-on { - background-color: var(--vc-spotify-green-90); - } - - .vc-spotify-repeat-context:hover, .vc-spotify-repeat-track:hover, .vc-spotify-shuffle-on:hover { - background-color: var(--vc-spotify-green-80); - } -} diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx index ac7f3f0c..747850f1 100644 --- a/src/plugins/startupTimings/index.tsx +++ b/src/plugins/startupTimings/index.tsx @@ -25,17 +25,10 @@ export default definePlugin({ name: "StartupTimings", description: "Adds Startup Timings to the Settings menu", authors: [Devs.Megu], + patches: [{ find: "#{intl::ACTIVITY_SETTINGS}", replacement: [ - { - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/, - replace: (_, commaOrSemi, settings, elements) => "" + - `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` + - `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`, - noWarn: true - }, { match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/, replace: (_, commaOrSemi, settings, elements) => "" + @@ -44,5 +37,6 @@ export default definePlugin({ }, ] }], + StartupTimingPage }); diff --git a/src/plugins/textReplace/index.tsx b/src/plugins/textReplace/index.tsx index d5d6f4dc..756541cc 100644 --- a/src/plugins/textReplace/index.tsx +++ b/src/plugins/textReplace/index.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { DataStore } from "@api/index"; import { definePluginSettings } from "@api/Settings"; import { Flex } from "@components/Flex"; import { DeleteIcon } from "@components/Icons"; @@ -241,20 +240,5 @@ export default definePlugin({ // Channel used for sharing rules, applying rules here would be messy if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return; msg.content = applyRules(msg.content); - }, - - async start() { - // TODO(OptionType.CUSTOM Related): Remove DataStore rules migrations once enough time has passed - const oldStringRules = await DataStore.get<Rule[]>(STRING_RULES_KEY); - if (oldStringRules != null) { - settings.store.stringRules = oldStringRules; - await DataStore.del(STRING_RULES_KEY); - } - - const oldRegexRules = await DataStore.get<Rule[]>(REGEX_RULES_KEY); - if (oldRegexRules != null) { - settings.store.regexRules = oldRegexRules; - await DataStore.del(REGEX_RULES_KEY); - } } }); diff --git a/src/plugins/userMessagesPronouns/index.ts b/src/plugins/userMessagesPronouns/index.ts index 1699251e..9eaab235 100644 --- a/src/plugins/userMessagesPronouns/index.ts +++ b/src/plugins/userMessagesPronouns/index.ts @@ -42,13 +42,6 @@ export default definePlugin({ { find: '="SYSTEM_TAG"', replacement: [ - { - // Add next to username (compact mode) - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g, - replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),", - noWarn: true - }, { // Add next to username (compact mode) match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g, diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 3b8485fc..a523a681 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -199,12 +199,6 @@ export default definePlugin({ { find: ".overlay:void 0,status:", replacement: [ - { - // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert - match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, - replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", - noWarn: true - }, { match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/, replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},", diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 962ea789..eb19bf86 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -93,9 +93,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Mai", id: 722647978577363026n }, - echo: { - name: "ECHO", - id: 712639419785412668n + amy: { + name: "Amy", + id: 603229858612510720n }, katlyn: { name: "katlyn", diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 71abd729..ca867c70 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -31,8 +31,6 @@ export let MessageStore: Omit<t.MessageStore, "getMessages"> & GenericStore & { getMessages(chanId: string): any; }; -// TODO: The correct name for this is ChannelActionCreators and it has already been exported again from utils. Remove this export once enough time has passed -export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel"); export let PermissionStore: GenericStore; export let GuildChannelStore: GenericStore; export let ReadStateStore: GenericStore; From 36c15d619e99645b0f8c93a9b0fae9ae8a54a2f0 Mon Sep 17 00:00:00 2001 From: Zordan <92729699+zordythezordan@users.noreply.github.com> Date: Wed, 6 Aug 2025 17:26:00 +0300 Subject: [PATCH 063/141] HideAttachments: correctly support forwarded Messages (#3587) Co-authored-by: V <vendicated@riseup.net> --- .../discord-types/src/common/messages/Message.d.ts | 3 +++ src/plugins/hideAttachments/index.tsx | 11 ++++------- src/plugins/unsuppressEmbeds/index.tsx | 11 ++++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts index 38010caf..41de8816 100644 --- a/packages/discord-types/src/common/messages/Message.d.ts +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -83,6 +83,9 @@ export class Message extends DiscordRecord { channel_id: string; message_id: string; } | undefined; + messageSnapshots: { + message: Message; + }[]; nick: unknown; // probably a string nonce: string | undefined; pinned: boolean; diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx index 0198a3e1..38213057 100644 --- a/src/plugins/hideAttachments/index.tsx +++ b/src/plugins/hideAttachments/index.tsx @@ -25,7 +25,7 @@ import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; -import { MessageSnapshot } from "@vencord/discord-types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore } from "@webpack/common"; const KEY = "HideAttachments_HiddenIds"; @@ -41,6 +41,8 @@ const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids); migratePluginSettings("HideMedia", "HideAttachments"); +const hasMedia = (msg: Message) => msg.attachments.length > 0 || msg.embeds.length > 0 || msg.stickerItems.length > 0; + export default definePlugin({ name: "HideMedia", description: "Hide attachments and embeds for individual messages via hover button", @@ -56,12 +58,7 @@ export default definePlugin({ }], renderMessagePopoverButton(msg) { - // @ts-expect-error - discord-types lags behind discord. - const hasAttachmentsInShapshots = msg.messageSnapshots.some( - (snapshot: MessageSnapshot) => snapshot?.message.attachments.length - ); - - if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null; + if (!hasMedia(msg) && !msg.messageSnapshots.some(s => hasMedia(s.message))) return null; const isHidden = hiddenMessages.has(msg.id); diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index e5f8557f..9eecd86d 100644 --- a/src/plugins/unsuppressEmbeds/index.tsx +++ b/src/plugins/unsuppressEmbeds/index.tsx @@ -20,17 +20,18 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { MessageSnapshot } from "@vencord/discord-types"; +import { Channel, Message } from "@vencord/discord-types"; import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; const EMBED_SUPPRESSED = 1 << 2; -const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => { +const messageContextMenuPatch: NavContextMenuPatchCallback = ( + children, + { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }: { channel: Channel; message: Message; } +) => { const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0; - const hasEmbedsInSnapshots = messageSnapshots.some( - (snapshot: MessageSnapshot) => snapshot?.message.embeds.length - ); + const hasEmbedsInSnapshots = messageSnapshots.some(s => s.message.embeds.length); if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return; From 6a66b7f54f3ef6693941c024004a163b6639a44c Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:16:07 -0400 Subject: [PATCH 064/141] fix plugins broken by Discord update (#3583) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Co-authored-by: V <vendicated@riseup.net> --- src/plugins/_api/notices.ts | 4 ++-- src/plugins/reviewDB/index.tsx | 7 ------- src/plugins/showAllMessageButtons/index.ts | 5 +++-- src/plugins/showHiddenChannels/index.tsx | 12 +++--------- src/plugins/superReactionTweaks/index.ts | 4 ++-- src/plugins/vencordToolbox/index.tsx | 4 ++-- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts index f0445924..89aa1a46 100644 --- a/src/plugins/_api/notices.ts +++ b/src/plugins/_api/notices.ts @@ -33,8 +33,8 @@ export default definePlugin({ replace: "if(Vencord.Api.Notices.currentNotice)return false;$&" }, { - match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/, - replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&" + match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/, + replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` } ] } diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index d8374e6f..c5c102ae 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -83,13 +83,6 @@ export default definePlugin({ replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," } }, - { - find: ".MODAL,user:", - replacement: { - match: /children:\[(?=[^[]+?shouldShowTooltip:)/, - replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," - } - }, { find: ".SIDEBAR,shouldShowTooltip:", replacement: { diff --git a/src/plugins/showAllMessageButtons/index.ts b/src/plugins/showAllMessageButtons/index.ts index 1d689013..6ac17e5d 100644 --- a/src/plugins/showAllMessageButtons/index.ts +++ b/src/plugins/showAllMessageButtons/index.ts @@ -28,8 +28,9 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /isExpanded:\i&&(.+?),/, - replace: "isExpanded:$1," + // isExpanded = isShiftPressed && other conditions... + match: /(?<=(\i)=)\i(?=&&.+?isExpanded:\1)/, + replace: "true" } } ] diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 43cd677e..78c81d00 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -319,20 +319,14 @@ export default definePlugin({ replacement: [ { // Create a variable for the channel prop - match: /users:\i,maxUsers:\i.+?}=(\i).*?;/, - replace: (m, props) => `${m}let{shcChannel}=${props};` + match: /(function \i\(\i\)\{)([^}]+?hideOverflowCount)/, + replace: "$1let {shcChannel}=arguments[0];$2" }, { // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen - match: /\i>0(?=&&.{0,30}Math.min)/, + match: /\i>0(?=&&!\i&&!\i)/, replace: m => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)?true:${m})` }, - { - // Prevent Discord from overwriting the last children with the plus button - // if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen - match: /(?<=\i\.length-)1(?=\]=.{0,60}renderPopout)(?<=(\i)=\i\.length-\i.+?)/, - replace: (_, amount) => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?0:1)` - }, { // Show only the plus text without overflowed children amount // if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 188f868a..b8aed478 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -42,8 +42,8 @@ export default definePlugin({ { find: ",BURST_REACTION_EFFECT_PLAY", replacement: { - match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/, - replace: "$1!$self.shouldPlayBurstReaction($2)" + match: /(?<=(\i)=\i=>{.+?)(\i\(\i,\i\))>=\i(?=\).+BURST_REACTION_EFFECT_PLAY:\1)/, + replace: "!$self.shouldPlayBurstReaction($2)" } }, { diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 2f671a23..82cd0284 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -140,9 +140,9 @@ export default definePlugin({ patches: [ { - find: "toolbar:function", + find: ".controlButtonWrapper,", replacement: { - match: /(?<=toolbar:function.{0,100}\()\i.Fragment,/, + match: /(?<=function (\i).{0,100}\()\i.Fragment,(?=.+?toolbar:\1\(\))/, replace: "$self.ToolboxFragmentWrapper," } } From 74d78d89ed087cf25ad2c1e565f0736b92d89539 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:11:38 -0400 Subject: [PATCH 065/141] fix TypingTweaks (#3586) Co-authored-by: V <vendicated@riseup.net> --- .../src/stores/AuthenticationStore.d.ts | 11 +++ .../src/stores/RelationshipStore.d.ts | 5 ++ .../discord-types/src/stores/TypingStore.d.ts | 9 +++ packages/discord-types/src/stores/index.d.ts | 2 + src/plugins/typingIndicator/index.tsx | 7 +- src/plugins/typingTweaks/index.tsx | 67 +++++++++++++------ src/webpack/common/stores.ts | 5 ++ 7 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 packages/discord-types/src/stores/AuthenticationStore.d.ts create mode 100644 packages/discord-types/src/stores/TypingStore.d.ts diff --git a/packages/discord-types/src/stores/AuthenticationStore.d.ts b/packages/discord-types/src/stores/AuthenticationStore.d.ts new file mode 100644 index 00000000..3ad2354f --- /dev/null +++ b/packages/discord-types/src/stores/AuthenticationStore.d.ts @@ -0,0 +1,11 @@ +import { FluxStore } from ".."; + +export class AuthenticationStore extends FluxStore { + /** + * Gets the id of the current user + */ + getId(): string; + + // This Store has a lot more methods related to everything Auth, but they really should + // not be needed, so they are not typed +} diff --git a/packages/discord-types/src/stores/RelationshipStore.d.ts b/packages/discord-types/src/stores/RelationshipStore.d.ts index 5d1a08af..9aceac47 100644 --- a/packages/discord-types/src/stores/RelationshipStore.d.ts +++ b/packages/discord-types/src/stores/RelationshipStore.d.ts @@ -15,6 +15,11 @@ export class RelationshipStore extends FluxStore { isFriend(userId: string): boolean; isBlocked(userId: string): boolean; isIgnored(userId: string): boolean; + /** + * @see {@link isBlocked} + * @see {@link isIgnored} + */ + isBlockedOrIgnored(userId: string): boolean; getSince(userId: string): string; getMutableRelationships(): Map<string, number>; diff --git a/packages/discord-types/src/stores/TypingStore.d.ts b/packages/discord-types/src/stores/TypingStore.d.ts new file mode 100644 index 00000000..0ebe994f --- /dev/null +++ b/packages/discord-types/src/stores/TypingStore.d.ts @@ -0,0 +1,9 @@ +import { FluxStore } from ".."; + +export class TypingStore extends FluxStore { + /** + * returns a map of user ids to timeout ids + */ + getTypingUsers(channelId: string): Record<string, number>; + isTyping(channelId: string, userId: string): boolean; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index 23045832..16a329c5 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -1,4 +1,5 @@ // please keep in alphabetical order +export * from "./AuthenticationStore"; export * from "./ChannelStore"; export * from "./DraftStore"; export * from "./EmojiStore"; @@ -11,6 +12,7 @@ export * from "./RelationshipStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; export * from "./ThemeStore"; +export * from "./TypingStore"; export * from "./UserProfileStore"; export * from "./UserStore"; export * from "./WindowStore"; diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index 0a0cd53a..47a0fbeb 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -24,13 +24,12 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common"; +import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, TypingStore, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common"; import { buildSeveralUsers } from "../typingTweaks"; const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:"); -const TypingStore = findStoreLazy("TypingStore"); const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); const enum IndicatorMode { @@ -46,7 +45,7 @@ function getDisplayName(guildId: string, userId: string) { function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: string; }) { const typingUsers: Record<string, number> = useStateFromStores( [TypingStore], - () => ({ ...TypingStore.getTypingUsers(channelId) as Record<string, number> }), + () => ({ ...TypingStore.getTypingUsers(channelId) }), null, (old, current) => { const oldKeys = Object.keys(old); @@ -90,7 +89,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s } default: { tooltipText = Settings.plugins.TypingTweaks.enabled - ? buildSeveralUsers({ a: UserStore.getUser(a), b: UserStore.getUser(b), count: typingUsersArray.length - 2, guildId }) + ? buildSeveralUsers({ users: [a, b].map(UserStore.getUser), count: typingUsersArray.length - 2, guildId }) : getIntlMessage("SEVERAL_USERS_TYPING"); break; } diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index bcfea898..8369497c 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -20,9 +20,11 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; +import { isNonNullish } from "@utils/guards"; +import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { User } from "@vencord/discord-types"; -import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; +import { Channel, User } from "@vencord/discord-types"; +import { AuthenticationStore, Avatar, GuildMemberStore, React, RelationshipStore, TypingStore, UserStore, useStateFromStores } from "@webpack/common"; import { PropsWithChildren } from "react"; import managedStyle from "./style.css?managed"; @@ -45,24 +47,26 @@ const settings = definePluginSettings({ } }); -export const buildSeveralUsers = ErrorBoundary.wrap(({ a, b, count, guildId }: { a: User, b: User, count: number; guildId: string; }) => { +export const buildSeveralUsers = ErrorBoundary.wrap(function buildSeveralUsers({ users, count, guildId }: { users: User[], count: number; guildId: string; }) { return ( <> - <TypingUser user={a} guildId={guildId} /> - {", "} - <TypingUser user={b} guildId={guildId} /> - {", "} + {users.slice(0, count).map(user => ( + <React.Fragment key={user.id}> + <TypingUser user={user} guildId={guildId} /> + {", "} + </React.Fragment> + ))} and {count} others are typing... </> ); }, { noop: true }); -interface Props { +interface TypingUserProps { user: User; guildId: string; } -const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) { +const TypingUser = ErrorBoundary.wrap(function TypingUser({ user, guildId }: TypingUserProps) { return ( <strong className="vc-typing-user" @@ -91,7 +95,7 @@ const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) { export default definePlugin({ name: "TypingTweaks", description: "Show avatars and role colours in the typing indicator", - authors: [Devs.zt], + authors: [Devs.zt, Devs.sadan], settings, managedStyle, @@ -103,25 +107,50 @@ export default definePlugin({ replacement: [ { // Style the indicator and add function call to modify the children before rendering - match: /(?<=children:\[(\i)\.length>0.{0,300}?"aria-atomic":!0,children:)\i/, - replace: "$self.renderTypingUsers({ users: $1, guildId: arguments[0]?.channel?.guild_id, children: $& })" + match: /(?<="aria-atomic":!0,children:)\i/, + replace: "$self.renderTypingUsers({ users: arguments[0]?.typingUserObjects, guildId: arguments[0]?.channel?.guild_id, children: $& })" }, { - // Changes the indicator to keep the user object when creating the list of typing users - match: /\.map\((\i)=>\i\.\i\.getName\(\i(?:\.guild_id)?,\i\.id,\1\)\)/, - replace: "" + match: /(?<=function \i\(\i\)\{)(?=[^}]+?\{channel:\i,isThreadCreation:\i=!1\})/, + replace: "let typingUserObjects = $self.useTypingUsers(arguments[0]?.channel);" + }, + { + // Get the typing users as user objects instead of names + match: /typingUsers:(\i)\?\[\]:\i,/, + // check by typeof so if the variable is not defined due to other patch failing, it won't throw a ReferenceError + replace: "$&typingUserObjects: $1 || typeof typingUserObjects === 'undefined' ? [] : typingUserObjects," }, { // Adds the alternative formatting for several users typing - match: /(,{a:(\i),b:(\i),c:\i}\):\i\.length>3&&\(\i=)\i\.\i\.string\(\i\.\i#{intl::SEVERAL_USERS_TYPING}\)(?<=(\i)\.length.+?)/, - replace: (_, rest, a, b, users) => - `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2, guildId: arguments[0]?.channel?.guild_id })`, + // users.length > 3 && (component = intl(key)) + match: /(&&\(\i=)\i\.\i\.format\(\i\.\i#{intl::SEVERAL_USERS_TYPING_STRONG},\{\}\)/, + replace: "$1$self.buildSeveralUsers({ users: arguments[0]?.typingUserObjects, count: arguments[0]?.typingUserObjects?.length - 2, guildId: arguments[0]?.channel?.guild_id })", predicate: () => settings.store.alternativeFormatting } ] } ], + useTypingUsers(channel: Channel | undefined): User[] { + try { + if (!channel) { + throw new Error("No channel"); + } + + const typingUsers = useStateFromStores([TypingStore], () => TypingStore.getTypingUsers(channel.id)); + const myId = useStateFromStores([AuthenticationStore], () => AuthenticationStore.getId()); + + return Object.keys(typingUsers) + .filter(id => id && id !== myId && !RelationshipStore.isBlockedOrIgnored(id)) + .map(id => UserStore.getUser(id)) + .filter(isNonNullish); + } catch (e) { + new Logger("TypingTweaks").error("Failed to get typing users:", e); + return []; + } + }, + + buildSeveralUsers, renderTypingUsers: ErrorBoundary.wrap(({ guildId, users, children }: PropsWithChildren<{ guildId: string, users: User[]; }>) => { @@ -140,7 +169,7 @@ export default definePlugin({ return <TypingUser key={user.id} guildId={guildId} user={user} />; }); } catch (e) { - console.error(e); + new Logger("TypingTweaks").error("Failed to render typing users:", e); } return children; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index ca867c70..221218c6 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -40,10 +40,12 @@ export let GuildStore: t.GuildStore; export let GuildRoleStore: t.GuildRoleStore; export let GuildMemberStore: t.GuildMemberStore; export let UserStore: t.UserStore; +export let AuthenticationStore: t.AuthenticationStore; export let UserProfileStore: t.UserProfileStore; export let SelectedChannelStore: t.SelectedChannelStore; export let SelectedGuildStore: t.SelectedGuildStore; export let ChannelStore: t.ChannelStore; +export let TypingStore: t.TypingStore; export let RelationshipStore: t.RelationshipStore; export let EmojiStore: t.EmojiStore; @@ -51,11 +53,13 @@ export let ThemeStore: t.ThemeStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; + /** * @see jsdoc of {@link t.useStateFromStores} */ export const useStateFromStores: t.useStateFromStores = findByCodeLazy("useStateFromStores"); +waitForStore("AuthenticationStore", s => AuthenticationStore = s); waitForStore("DraftStore", s => DraftStore = s); waitForStore("UserStore", s => UserStore = s); waitForStore("UserProfileStore", m => UserProfileStore = m); @@ -73,6 +77,7 @@ waitForStore("GuildRoleStore", m => GuildRoleStore = m); waitForStore("MessageStore", m => MessageStore = m); waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); +waitForStore("TypingStore", m => TypingStore = m); waitForStore("ThemeStore", m => { ThemeStore = m; // Importing this directly can easily cause circular imports. For this reason, use a non import access here. From fe2ed0776f983ab79c6079c0a660d6091b57ba55 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 6 Aug 2025 21:46:02 -0300 Subject: [PATCH 066/141] ShowHiddenChannels: Fix showing allowed roles --- src/plugins/showHiddenChannels/index.tsx | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 78c81d00..b8075d8c 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -23,7 +23,6 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; -import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; import type { Channel, Role } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; @@ -302,15 +301,18 @@ export default definePlugin({ }, { // Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen) - match: /MANAGE_ROLES.{0,90}?return(?=\(.+?(\(0,\i\.jsxs\)\("div",{className:\i\.members.+?guildId:(\i)\.guild_id.+?roleColor.+?\]}\)))/, - replace: (m, component, channel) => { - // Export the channel for the users allowed component patch - component = component.replace(canonicalizeMatch(/(?<=users:\i)/), `,shcChannel:${channel}`); - // Always render the component for multiple allowed users - component = component.replace(canonicalizeMatch(/1!==\i\.length/), "true"); - - return `${m} $self.isHiddenChannel(${channel},true)?${component}:`; - } + match: /\.members.+?\]}\),(?<=channel:(\i).+?)/, + replace: (m, channel) => `${m}$self.isHiddenChannel(${channel},true)?null:` + }, + { + // Export the channel for the users allowed component patch + match: /maxUsers:\i,users:\i(?<=channel:(\i).+?)/, + replace: (m, channel) => `${m},shcChannel:${channel}` + }, + { + // Always render the component for multiple allowed users + match: /1!==\i\.length(?=\|\|)/, + replace: "true" } ] }, @@ -319,8 +321,8 @@ export default definePlugin({ replacement: [ { // Create a variable for the channel prop - match: /(function \i\(\i\)\{)([^}]+?hideOverflowCount)/, - replace: "$1let {shcChannel}=arguments[0];$2" + match: /let{users:\i,maxUsers:\i,/, + replace: "let{shcChannel}=arguments[0];$&" }, { // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen From 164fd43cc441413a6ecf479b510d837201898c1e Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 7 Aug 2025 02:28:34 +0200 Subject: [PATCH 067/141] Fix IgnoreActivities --- src/plugins/ignoreActivities/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index b0889186..94334f5a 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -256,8 +256,10 @@ export default definePlugin({ { find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}", replacement: { - match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/, - replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` + // let { ... nowPlaying: a = !1 ... + // let { overlay: b ... } = Props + match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)/, + replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` } }, // Activities from the apps launcher in the bottom right of the chat bar From 98efe13b97b43d1eb09c9d3e3c983d50dc410f0c Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 7 Aug 2025 04:24:34 +0200 Subject: [PATCH 068/141] ShowHiddenThings: fix crash when viewing Mod View --- src/plugins/showHiddenThings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index c39dccc4..6d2ca713 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -73,7 +73,7 @@ export default definePlugin({ predicate: () => settings.store.showModView, replacement: { match: /(?<=\.highestRole\),)role:\i(?<=\[\i\.roles,\i\.highestRoleId,(\i)\].+)/, - replace: "role:$self.getHighestRole(arguments[0],$2)", + replace: "role:$self.getHighestRole(arguments[0],$1)", } }, // allows you to open mod view on yourself From c7e799e9352637bad2086f64553e08b7ff6f460c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 7 Aug 2025 09:29:33 -0300 Subject: [PATCH 069/141] ShowHiddenChannels: Fix incorrect allowed users and roles component --- src/plugins/showHiddenChannels/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index b8075d8c..33f44657 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -301,8 +301,8 @@ export default definePlugin({ }, { // Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen) - match: /\.members.+?\]}\),(?<=channel:(\i).+?)/, - replace: (m, channel) => `${m}$self.isHiddenChannel(${channel},true)?null:` + match: /return\(0,\i\.jsxs?\)\(\i\.\i,{channelId:(\i)\.id(?=.+?(\(0,\i\.jsxs?\)\("div",{className:\i\.members.+?\]}\)),)/, + replace: (m, channel, allowedUsersAndRolesComponent) => `if($self.isHiddenChannel(${channel},true)){return${allowedUsersAndRolesComponent};}${m}` }, { // Export the channel for the users allowed component patch From 7e028267f1ea6ad64dc8e2d62c171c28330ecdb9 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:04:59 -0400 Subject: [PATCH 070/141] SpotifyShareCommands: correctly handle local tracks (#3592) --- src/plugins/spotifyShareCommands/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/spotifyShareCommands/index.ts b/src/plugins/spotifyShareCommands/index.ts index e11864ae..d00d4a2c 100644 --- a/src/plugins/spotifyShareCommands/index.ts +++ b/src/plugins/spotifyShareCommands/index.ts @@ -46,7 +46,7 @@ interface Artist { } interface Track { - id: string; + id: string | null; album: Album; artists: Artist[]; duration: number; @@ -71,6 +71,13 @@ function makeCommand(name: string, formatUrl: (track: Track) => string): Command }); } + // local tracks have an id of null + if (track.id == null) { + return sendBotMessage(channel.id, { + content: "Failed to find the track on spotify." + }); + } + const data = formatUrl(track); const message = findOption(options, "message"); From 4403aee3c1436b88d14b245e9d8e29d6b527eb84 Mon Sep 17 00:00:00 2001 From: byeoon <47872200+byeoon@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:24:13 -0400 Subject: [PATCH 071/141] New plugin CopyStickerLinks: adds Copy/Open Link option to stickers (#3191) Co-authored-by: V <vendicated@riseup.net> --- packages/discord-types/enums/index.ts | 1 + packages/discord-types/enums/messages.ts | 13 +++ .../src/common/messages/Message.d.ts | 3 +- .../src/common/messages/Sticker.d.ts | 35 +++++++ .../src/common/messages/index.d.ts | 1 + .../src/stores/StickersStore.d.ts | 15 +++ packages/discord-types/src/stores/index.d.ts | 1 + src/plugins/copyStickerLinks/README.MD | 5 + src/plugins/copyStickerLinks/index.tsx | 94 +++++++++++++++++++ src/plugins/expressionCloner/index.tsx | 16 +--- src/plugins/fakeNitro/index.tsx | 68 +++----------- src/utils/constants.ts | 2 +- src/webpack/common/stores.ts | 3 +- 13 files changed, 188 insertions(+), 69 deletions(-) create mode 100644 packages/discord-types/enums/messages.ts create mode 100644 packages/discord-types/src/common/messages/Sticker.d.ts create mode 100644 packages/discord-types/src/stores/StickersStore.d.ts create mode 100644 src/plugins/copyStickerLinks/README.MD create mode 100644 src/plugins/copyStickerLinks/index.tsx diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index 62074d46..e2227d09 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1 +1,2 @@ export * from "./commands"; +export * from "./messages"; diff --git a/packages/discord-types/enums/messages.ts b/packages/discord-types/enums/messages.ts new file mode 100644 index 00000000..9c0025b7 --- /dev/null +++ b/packages/discord-types/enums/messages.ts @@ -0,0 +1,13 @@ +export const enum StickerType { + /** an official sticker in a pack */ + STANDARD = 1, + /** a sticker uploaded to a guild for the guild's members */ + GUILD = 2 +} + +export const enum StickerFormatType { + PNG = 1, + APNG = 2, + LOTTIE = 3, + GIF = 4 +} diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts index 41de8816..e3586255 100644 --- a/packages/discord-types/src/common/messages/Message.d.ts +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -2,6 +2,7 @@ import { CommandOption } from './Commands'; import { User, UserJSON } from '../User'; import { Embed, EmbedJSON } from './Embed'; import { DiscordRecord } from "../Record"; +import { StickerFormatType } from "../../../enums"; /** * TODO: looks like discord has moved over to Date instead of Moment; @@ -92,7 +93,7 @@ export class Message extends DiscordRecord { reactions: MessageReaction[]; state: string; stickerItems: { - format_type: number; + format_type: StickerFormatType; id: string; name: string; }[]; diff --git a/packages/discord-types/src/common/messages/Sticker.d.ts b/packages/discord-types/src/common/messages/Sticker.d.ts new file mode 100644 index 00000000..744b0623 --- /dev/null +++ b/packages/discord-types/src/common/messages/Sticker.d.ts @@ -0,0 +1,35 @@ +import { StickerFormatType, StickerType } from "../../../enums"; + +interface BaseSticker { + asset: string; + available: boolean; + description: string; + format_type: StickerFormatType; + id: string; + name: string; + sort_value?: number; + /** a comma separated string */ + tags: string; +} + +export interface PackSticker extends BaseSticker { + pack_id: string; + type: StickerType.STANDARD; +} + +export interface GuildSticker extends BaseSticker { + guild_id: string; + type: StickerType.GUILD; +} + +export type Sticker = PackSticker | GuildSticker; + +export interface PremiumStickerPack { + banner_asset_id?: string; + cover_sticker_id?: string; + description: string; + id: string; + name: string; + sku_id: string; + stickers: PackSticker[]; +} diff --git a/packages/discord-types/src/common/messages/index.d.ts b/packages/discord-types/src/common/messages/index.d.ts index 245e971e..23987546 100644 --- a/packages/discord-types/src/common/messages/index.d.ts +++ b/packages/discord-types/src/common/messages/index.d.ts @@ -2,3 +2,4 @@ export * from "./Commands"; export * from "./Message"; export * from "./Embed"; export * from "./Emoji"; +export * from "./Sticker"; diff --git a/packages/discord-types/src/stores/StickersStore.d.ts b/packages/discord-types/src/stores/StickersStore.d.ts new file mode 100644 index 00000000..fdfb012c --- /dev/null +++ b/packages/discord-types/src/stores/StickersStore.d.ts @@ -0,0 +1,15 @@ +import { FluxStore, GuildSticker, PremiumStickerPack, Sticker } from ".."; + +export type StickerGuildMap = Map<string, GuildSticker[]>; + +export class StickersStore extends FluxStore { + getAllGuildStickers(): StickerGuildMap; + getRawStickersByGuild(): StickerGuildMap; + getPremiumPacks(): PremiumStickerPack[]; + + getStickerById(id: string): Sticker | undefined; + getStickerPack(id: string): PremiumStickerPack | undefined; + getStickersByGuildId(guildId: string): Sticker[] | undefined; + + isPremiumPack(id: string): boolean; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index 16a329c5..7a435ff5 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -11,6 +11,7 @@ export * from "./MessageStore"; export * from "./RelationshipStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; +export * from "./StickersStore"; export * from "./ThemeStore"; export * from "./TypingStore"; export * from "./UserProfileStore"; diff --git a/src/plugins/copyStickerLinks/README.MD b/src/plugins/copyStickerLinks/README.MD new file mode 100644 index 00000000..40e63232 --- /dev/null +++ b/src/plugins/copyStickerLinks/README.MD @@ -0,0 +1,5 @@ +# CopyStickerLinks + +Adds "Copy Link" and "Open Link" options to the context menu of stickers! + +![](https://github.com/user-attachments/assets/a0982d5c-ab83-458b-9ca3-834803e0782e) diff --git a/src/plugins/copyStickerLinks/index.tsx b/src/plugins/copyStickerLinks/index.tsx new file mode 100644 index 00000000..eaccc9b7 --- /dev/null +++ b/src/plugins/copyStickerLinks/index.tsx @@ -0,0 +1,94 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2025 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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { Devs } from "@utils/constants"; +import { copyWithToast } from "@utils/misc"; +import definePlugin from "@utils/types"; +import { Message, Sticker } from "@vencord/discord-types"; +import { Menu, React, StickersStore } from "@webpack/common"; +import ExpressionClonerPlugin from "plugins/expressionCloner"; + +const StickerExt = [, "png", "png", "json", "gif"] as const; + +type PartialSticker = Pick<Sticker, "id" | "format_type">; + +function getUrl(data: PartialSticker): string { + if (data.format_type === 4) + return `https:${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.gif?size=512&lossless=true`; + + return `https://${window.GLOBAL_ENV.CDN_HOST}/stickers/${data.id}.${StickerExt[data.format_type]}?size=512&lossless=true`; +} + +function buildMenuItem(sticker: PartialSticker, addBottomSeparator: boolean) { + return ( + <> + <Menu.MenuGroup> + <Menu.MenuItem + id="vc-copy-sticker-link" + key="vc-copy-sticker-link" + label="Copy Link" + action={() => copyWithToast(getUrl(sticker), "Link copied!")} + /> + + <Menu.MenuItem + id="vc-open-sticker-link" + key="vc-open-sticker-link" + label="Open Link" + action={() => VencordNative.native.openExternal(getUrl(sticker))} + /> + </Menu.MenuGroup> + {addBottomSeparator && <Menu.MenuSeparator />} + </> + ); +} + +const messageContextMenuPatch: NavContextMenuPatchCallback = ( + children, + { favoriteableId, favoriteableType, message }: { favoriteableId: string; favoriteableType: string; message: Message; } +) => { + if (!favoriteableId || favoriteableType !== "sticker") return; + + const sticker = message.stickerItems.find(s => s.id === favoriteableId); + if (!sticker?.format_type) return; + + const idx = children.findIndex(c => Array.isArray(c) && findGroupChildrenByChildId("vc-copy-sticker-url", c) != null); + + children.splice(idx, 0, buildMenuItem(sticker, idx !== -1)); +}; + +const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => { + const id = props?.target?.dataset?.id; + if (!id) return; + if (props.target.className?.includes("lottieCanvas")) return; + + const sticker = StickersStore.getStickerById(id); + if (sticker) { + children.push(buildMenuItem(sticker, Vencord.Plugins.isPluginEnabled(ExpressionClonerPlugin.name))); + } +}; + +export default definePlugin({ + name: "CopyStickerLinks", + description: "Adds the ability to copy & open Sticker links", + authors: [Devs.Ven, Devs.Byeoon], + contextMenus: { + "message": messageContextMenuPatch, + "expression-picker": expressionPickerPatch + } +}); diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index 60a95336..45022e60 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -25,25 +25,17 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; -import { Guild } from "@vencord/discord-types"; -import { findByCodeLazy, findStoreLazy } from "@webpack"; -import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Guild, GuildSticker } from "@vencord/discord-types"; +import { findByCodeLazy } from "@webpack"; +import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, StickersStore, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Promisable } from "type-fest"; -const StickersStore = findStoreLazy("StickersStore"); const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); const getGuildMaxEmojiSlots = findByCodeLazy(".additionalEmojiSlots") as (guild: Guild) => number; -interface Sticker { +interface Sticker extends GuildSticker { t: "Sticker"; - description: string; - format_type: number; - guild_id: string; - id: string; - name: string; - tags: string; - type: number; } interface Emoji { diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index f697c3fd..981a7da8 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -24,17 +24,12 @@ import { getCurrentGuild, getEmojiURL } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, Patch } from "@utils/types"; import type { Emoji, Message } from "@vencord/discord-types"; +import { StickerFormatType } from "@vencord/discord-types/enums"; import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; -import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; +import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, StickersStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import type { ReactElement, ReactNode } from "react"; -const StickerStore = findStoreLazy("StickersStore") as { - getPremiumPacks(): StickerPack[]; - getAllGuildStickers(): Map<string, Sticker[]>; - getStickerById(id: string): Sticker | undefined; -}; - const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); const BINARY_READ_OPTIONS = findByPropsLazy("readerFactory"); @@ -70,41 +65,6 @@ const enum EmojiIntentions { const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`; -const enum StickerType { - PNG = 1, - APNG = 2, - LOTTIE = 3, - // don't think you can even have gif stickers but the docs have it - GIF = 4 -} - -interface BaseSticker { - available: boolean; - description: string; - format_type: number; - id: string; - name: string; - tags: string; - type: number; -} -interface GuildSticker extends BaseSticker { - guild_id: string; -} -interface DiscordSticker extends BaseSticker { - pack_id: string; -} -type Sticker = GuildSticker | DiscordSticker; - -interface StickerPack { - id: string; - name: string; - sku_id: string; - description: string; - cover_sticker_id: string; - banner_asset_id: string; - stickers: Sticker[]; -} - const enum FakeNoticeType { Sticker, Emoji @@ -549,8 +509,8 @@ export default definePlugin({ const gifMatch = child.props.href.match(fakeNitroGifStickerRegex); if (gifMatch) { - // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(gifMatch[1])) return null; + // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker + if (StickersStore.getStickerById(gifMatch[1])) return null; } } @@ -638,7 +598,7 @@ export default definePlugin({ url = new URL(item); } catch { } - const stickerName = StickerStore.getStickerById(imgMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroSticker"; + const stickerName = StickersStore.getStickerById(imgMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroSticker"; stickers.push({ format_type: 1, id: imgMatch[1], @@ -651,9 +611,9 @@ export default definePlugin({ const gifMatch = item.match(fakeNitroGifStickerRegex); if (gifMatch) { - if (!StickerStore.getStickerById(gifMatch[1])) continue; + if (!StickersStore.getStickerById(gifMatch[1])) continue; - const stickerName = StickerStore.getStickerById(gifMatch[1])?.name ?? "FakeNitroSticker"; + const stickerName = StickersStore.getStickerById(gifMatch[1])?.name ?? "FakeNitroSticker"; stickers.push({ format_type: 2, id: gifMatch[1], @@ -689,8 +649,8 @@ export default definePlugin({ const gifMatch = url.match(fakeNitroGifStickerRegex); if (gifMatch) { - // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(gifMatch[1])) return true; + // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker + if (StickersStore.getStickerById(gifMatch[1])) return true; } } @@ -710,8 +670,8 @@ export default definePlugin({ const match = attachment.url.match(fakeNitroGifStickerRegex); if (match) { - // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(match[1])) return false; + // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker + if (StickersStore.getStickerById(match[1])) return false; } return true; @@ -866,7 +826,7 @@ export default definePlugin({ if (!s.enableStickerBypass) break stickerBypass; - const sticker = StickerStore.getStickerById(extra.stickers?.[0]!); + const sticker = StickersStore.getStickerById(extra.stickers?.[0]!); if (!sticker) break stickerBypass; @@ -883,11 +843,11 @@ export default definePlugin({ // but will give us a normal non animated png for no reason // TODO: Remove this workaround when it's not needed anymore let link = this.getStickerLink(sticker.id); - if (sticker.format_type === StickerType.GIF && link.includes(".png")) { + if (sticker.format_type === StickerFormatType.GIF && link.includes(".png")) { link = link.replace(".png", ".gif"); } - if (sticker.format_type === StickerType.APNG) { + if (sticker.format_type === StickerFormatType.APNG) { if (!hasAttachmentPerms(channelId)) { Alerts.show({ title: "Hold on!", diff --git a/src/utils/constants.ts b/src/utils/constants.ts index eb19bf86..7c7488b6 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -482,7 +482,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Sqaaakoi", id: 259558259491340288n }, - Byron: { + Byeoon: { name: "byeoon", id: 1167275288036655133n }, diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 221218c6..472abd4e 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -49,11 +49,11 @@ export let TypingStore: t.TypingStore; export let RelationshipStore: t.RelationshipStore; export let EmojiStore: t.EmojiStore; +export let StickersStore: t.StickersStore; export let ThemeStore: t.ThemeStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; - /** * @see jsdoc of {@link t.useStateFromStores} */ @@ -77,6 +77,7 @@ waitForStore("GuildRoleStore", m => GuildRoleStore = m); waitForStore("MessageStore", m => MessageStore = m); waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); +waitForStore("StickersStore", m => StickersStore = m); waitForStore("TypingStore", m => TypingStore = m); waitForStore("ThemeStore", m => { ThemeStore = m; From 0c89314d497bd49a230f72f21763e3c0a1fa7e82 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 7 Aug 2025 23:38:27 +0200 Subject: [PATCH 072/141] PermissionFreeWill: don't break permission toggles --- src/plugins/permissionFreeWill/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts index 767d4f98..781c64bc 100644 --- a/src/plugins/permissionFreeWill/index.ts +++ b/src/plugins/permissionFreeWill/index.ts @@ -46,8 +46,9 @@ export default definePlugin({ find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}", replacement: [ { + // replace export getters with functions that always resolve to true match: /{(?:\i:\(\)=>\i,?){2}}/, - replace: m => m.replaceAll(canonicalizeMatch(/\(\)=>\i/g), "()=>Promise.resolve(true)") + replace: m => m.replaceAll(canonicalizeMatch(/\(\)=>\i/g), "()=>()=>Promise.resolve(true)") } ], predicate: () => settings.store.onboarding From d9e2732a8defcda19d9142b28524bf3ec6dec3d8 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:35:29 -0300 Subject: [PATCH 073/141] Fix MessageEventsAPI broken patch --- src/plugins/_api/messageEvents.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts index e5192763..3030db03 100644 --- a/src/plugins/_api/messageEvents.ts +++ b/src/plugins/_api/messageEvents.ts @@ -35,10 +35,10 @@ export default definePlugin({ } }, { - find: ".handleSendMessage,onResize", + find: ".handleSendMessage,onResize:", replacement: { - // https://regex101.com/r/hBlXpl/1 - match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/, + // https://regex101.com/r/7iswuk/1 + match: /let (\i)=\i\.\i\.parse\((\i),.+?\.getSendMessageOptions\(\{.+?\}\);(?=.+?(\i)\.flags=)(?<=\)\(({.+?})\)\.then.+?)/, replace: (m, parsedMessage, channel, replyOptions, extra) => m + `if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` + "return{shouldClear:false,shouldRefocus:true};" From 27b2e97e3fe8bbac8dffbddcff62743f73457d9d Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 12 Aug 2025 02:55:01 +0200 Subject: [PATCH 074/141] ReviewDB: fix input --- src/plugins/reviewDB/components/ReviewsView.tsx | 6 +++--- src/plugins/reviewDB/style.css | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 7a7d8d02..42ab0938 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -29,8 +29,8 @@ import ReviewComponent from "./ReviewComponent"; const Transforms = findByPropsLazy("insertNodes", "textToText"); const Editor = findByPropsLazy("start", "end", "toSlateRange"); -const ChatInputTypes = findByPropsLazy("FORM"); -const InputComponent = findComponentByCodeLazy("disableThemedBackground", "CHANNEL_TEXT_AREA"); +const ChatInputTypes = findByPropsLazy("FORM", "USER_PROFILE"); +const InputComponent = findComponentByCodeLazy("editorClassName", "CHANNEL_TEXT_AREA"); const createChannelRecordFromServer = findByCodeLazy(".GUILD_TEXT])", "fromServer)"); interface UserProps { @@ -127,7 +127,7 @@ export function ReviewsInputComponent( ) { const { token } = Auth; const editorRef = useRef<any>(null); - const inputType = ChatInputTypes.FORM; + const inputType = ChatInputTypes.USER_PROFILE_REPLY; inputType.disableAutoFocus = true; const channel = createChannelRecordFromServer({ id: "0", type: 1 }); diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css index 47ccd091..bf2fed30 100644 --- a/src/plugins/reviewDB/style.css +++ b/src/plugins/reviewDB/style.css @@ -8,6 +8,7 @@ } .vc-rdb-input { + padding-left: 12px; margin-top: 6px; margin-bottom: 12px; resize: none; @@ -132,4 +133,4 @@ .vc-rdb-block-modal-unblock { cursor: pointer; -} +} \ No newline at end of file From 72329f901cae0e45feb24d9ff38eb2eb8d3756d0 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:28:19 -0400 Subject: [PATCH 075/141] AccountPanelServerProfile: fix right click menu (#3600) Co-authored-by: Vendicated <vendicated@riseup.net> --- .../accountPanelServerProfile/index.tsx | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index ce9f6b51..57144ec0 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -11,7 +11,7 @@ import { getCurrentChannel } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; -import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common"; +import { ContextMenuApi, Menu } from "@webpack/common"; interface UserProfileProps { popoutProps: Record<string, any>; @@ -22,7 +22,7 @@ interface UserProfileProps { const UserProfile = findComponentByCodeLazy(".POPOUT,user"); let openAlternatePopout = false; -let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null }; +let accountPanelRef: React.RefObject<HTMLDivElement | null> = { current: null }; const AccountPanelContextMenu = ErrorBoundary.wrap(() => { const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]); @@ -38,8 +38,7 @@ const AccountPanelContextMenu = ErrorBoundary.wrap(() => { disabled={getCurrentChannel()?.getGuildId() == null} action={e => { openAlternatePopout = true; - accountPanelRef.current?.props.onMouseDown(); - accountPanelRef.current?.props.onClick(e); + accountPanelRef.current?.click(); }} /> <Menu.MenuCheckboxItem @@ -71,10 +70,6 @@ export default definePlugin({ find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", group: true, replacement: [ - { - match: /let{ref:\i,speaking:\i/, - replace: "$self.useAccountPanelRef();$&" - }, { match: /(\.AVATAR,children:.+?renderPopout:\((\i),\i\)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})` @@ -84,8 +79,8 @@ export default definePlugin({ replace: "$&$self.onPopoutClose();" }, { - match: /(?<=#{intl::SET_STATUS}\),)/, - replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu," + match: /#{intl::SET_STATUS}\)(?<=innerRef:(\i),style:.+?)/, + replace: "$&,onContextMenu:($self.grabRef($1),$self.openAccountPanelContextMenu)" } ] } @@ -95,12 +90,9 @@ export default definePlugin({ return accountPanelRef; }, - useAccountPanelRef() { - useEffect(() => () => { - accountPanelRef.current = null; - }, []); - - return (accountPanelRef = useRef(null)); + grabRef(ref: React.RefObject<HTMLDivElement>) { + accountPanelRef = ref; + return ref; }, openAccountPanelContextMenu(event: React.UIEvent) { From aad88fe9cd5cafd34691e487f39acd4d215d74ed Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 13 Aug 2025 12:55:24 +0200 Subject: [PATCH 076/141] fix plugins sending messages --- src/api/MessageEvents.ts | 10 +++++----- src/utils/discord.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 8b1d9e78..6e752c90 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -62,7 +62,7 @@ export interface MessageReplyOptions { }; } -export interface MessageExtra { +export interface MessageOptions { stickers?: string[]; uploads?: Upload[]; replyOptions: MessageReplyOptions; @@ -72,17 +72,17 @@ export interface MessageExtra { openWarningPopout: (props: any) => any; } -export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>; +export type MessageSendListener = (channelId: string, messageObj: MessageObject, options: MessageOptions) => Promisable<void | { cancel: boolean; }>; export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>; const sendListeners = new Set<MessageSendListener>(); const editListeners = new Set<MessageEditListener>(); -export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) { - extra.replyOptions = replyOptions; +export async function _handlePreSend(channelId: string, messageObj: MessageObject, options: MessageOptions, replyOptions: MessageReplyOptions) { + options.replyOptions = replyOptions; for (const listener of sendListeners) { try { - const result = await listener(channelId, messageObj, extra); + const result = await listener(channelId, messageObj, options); if (result?.cancel) { return true; } diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index d7a38ebb..f236943b 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -110,7 +110,7 @@ export function insertTextIntoChatInputBox(text: string) { }); } -interface MessageExtra { +interface MessageOptions { messageReference: Message["messageReference"]; allowedMentions: { parse: string[]; @@ -122,8 +122,8 @@ interface MessageExtra { export function sendMessage( channelId: string, data: Partial<MessageObject>, - waitForChannelReady?: boolean, - extra?: Partial<MessageExtra> + waitForChannelReady = true, + options: Partial<MessageOptions> = {} ) { const messageData = { content: "", @@ -133,7 +133,7 @@ export function sendMessage( ...data }; - return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra); + return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, options); } /** From f356f647ff90d1abed69dc47c05e641a94dde7a5 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 13 Aug 2025 12:58:29 +0200 Subject: [PATCH 077/141] bump to v1.12.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5438d37f..498b8919 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.9", + "version": "1.12.10", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 204f916b2a1a29198331c1d862998710b7681412 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 14 Aug 2025 15:42:58 -0400 Subject: [PATCH 078/141] fix Settings UI and various plugins (#3608) Co-authored-by: Vendicated <vendicated@riseup.net> --- packages/discord-types/src/components.d.ts | 7 +----- src/plugins/betterSettings/index.tsx | 4 +-- src/plugins/consoleJanitor/index.tsx | 2 +- src/plugins/decor/settings.tsx | 2 +- .../decor/ui/modals/ChangeDecorationModal.tsx | 2 +- .../decor/ui/modals/CreateDecorationModal.tsx | 4 +-- src/plugins/fakeNitro/index.tsx | 2 +- src/plugins/ignoreActivities/index.tsx | 4 +-- src/plugins/showHiddenChannels/index.tsx | 2 +- src/webpack/common/FormText.tsx | 25 +++++++++++++++++++ src/webpack/common/components.ts | 2 +- src/webpack/common/utils.ts | 2 +- 12 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 src/webpack/common/FormText.tsx diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index 1df03735..f9e778d8 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -43,12 +43,7 @@ export type FormDivider = ComponentType<{ }>; -export type FormText = ComponentType<PropsWithChildren<{ - disabled?: boolean; - selectable?: boolean; - /** defaults to FormText.Types.DEFAULT */ - type?: string; -}> & TextProps> & { Types: FormTextTypes; }; +export type FormText = ComponentType<TextProps>; export type Tooltip = ComponentType<{ text: ReactNode | ComponentType; diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index cbf94c2b..8fecb262 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -123,8 +123,8 @@ export default definePlugin({ find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", replacement: [ { - match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, - replace: "$1$self.wrapMenu($2)" + match: /(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, + replace: "$self.wrapMenu($1)" }, { match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/, diff --git a/src/plugins/consoleJanitor/index.tsx b/src/plugins/consoleJanitor/index.tsx index 8a11cf01..4981ac2c 100644 --- a/src/plugins/consoleJanitor/index.tsx +++ b/src/plugins/consoleJanitor/index.tsx @@ -60,7 +60,7 @@ const AllowLevelSettings = ErrorBoundary.wrap(() => { return ( <Forms.FormSection> <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle> - <Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Always allow loggers of these types</Forms.FormText> + <Forms.FormText className={Margins.bottom8}>Always allow loggers of these types</Forms.FormText> <Flex flexDirection="row"> {Object.keys(settings.store.allowLevel).map(key => ( <AllowLevelSetting key={key} settingKey={key as keyof AllowLevels} /> diff --git a/src/plugins/decor/settings.tsx b/src/plugins/decor/settings.tsx index 8e82bbae..361ecf89 100644 --- a/src/plugins/decor/settings.tsx +++ b/src/plugins/decor/settings.tsx @@ -24,7 +24,7 @@ export const settings = definePluginSettings({ return <div> <DecorSection hideTitle hideDivider noMargin /> - <Forms.FormText type="description" className={classes(Margins.top8, Margins.bottom8)}> + <Forms.FormText className={classes(Margins.top8, Margins.bottom8)}> You can also access Decor decorations from the <Link href="/settings/profile-customization" onClick={e => { diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx index d9cfd50c..ed81eaf0 100644 --- a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx @@ -83,7 +83,7 @@ function SectionHeader({ section }: SectionHeaderProps) { />} </Flex> {hasSubtitle && - <Forms.FormText type="description" className={Margins.bottom8}> + <Forms.FormText className={Margins.bottom8}> {section.subtitle} </Forms.FormText> } diff --git a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx index 4afb7464..d772ac4a 100644 --- a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx @@ -97,7 +97,7 @@ function CreateDecorationModal(props: ModalProps) { filters={[{ name: "Decoration file", extensions: ["png", "apng"] }]} onFileSelect={setFile} /> - <Forms.FormText type="description" className={Margins.top8}> + <Forms.FormText className={Margins.top8}> File should be APNG or PNG. </Forms.FormText> </Forms.FormSection> @@ -107,7 +107,7 @@ function CreateDecorationModal(props: ModalProps) { value={name} onChange={setName} /> - <Forms.FormText type="description" className={Margins.top8}> + <Forms.FormText className={Margins.top8}> This name will be used when referring to this decoration. </Forms.FormText> </Forms.FormSection> diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 981a7da8..8dcb2300 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -800,7 +800,7 @@ export default definePlugin({ however you do not have permissions to embed links in the current channel. Are you sure you want to send this message? Your FakeNitro items will appear as a link only. </Forms.FormText> - <Forms.FormText type={Forms.FormText.Types.DESCRIPTION}> + <Forms.FormText> You can disable this notice in the plugin settings. </Forms.FormText> </div>, diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 94334f5a..88906c38 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -82,7 +82,7 @@ function recalculateActivities() { function ImportCustomRPCComponent() { return ( <Flex flexDirection="column"> - <Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText> + <Forms.FormText>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText> <div> <Button onClick={() => { @@ -133,7 +133,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) { return ( <Forms.FormSection> <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle> - <Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText> + <Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText> <TextInput type="text" value={idsList} diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 33f44657..1e5891ce 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -200,7 +200,7 @@ export default definePlugin({ }, { // Hide the new version of unreads box for hidden channels - find: '="ChannelListUnreadsStore",', + find: '"ChannelListUnreadsStore",', replacement: { match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/, replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` diff --git a/src/webpack/common/FormText.tsx b/src/webpack/common/FormText.tsx new file mode 100644 index 00000000..8c371ed4 --- /dev/null +++ b/src/webpack/common/FormText.tsx @@ -0,0 +1,25 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import * as t from "@vencord/discord-types"; + +import { Text } from "./components"; + +// TODO: replace with our own component soon +export const FormText: t.FormText = function FormText(props) { + const variant = props.variant || "text-sm/normal"; + return ( + <Text + variant={variant} + {...props} + > + {props.children} + </Text> + ); +} as any; + +// @ts-expect-error +FormText.Types = {}; diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index bf7c9dd1..d5e8dad1 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -20,11 +20,11 @@ import { LazyComponent } from "@utils/lazyReact"; import * as t from "@vencord/discord-types"; import { filters, mapMangledModuleLazy, waitFor } from "@webpack"; +import { FormText } from "./FormText"; import { waitForComponent } from "./internal"; const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"')); -const FormText = waitForComponent<t.FormText>("FormText", filters.componentByCode(".SELECTABLE),", ".DISABLED:")); const FormSection = waitForComponent<t.FormSection>("FormSection", filters.componentByCode(".titleId)")); const FormDivider = waitForComponent<t.FormDivider>("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/)); diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 1d653dea..862f3843 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -203,6 +203,6 @@ export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/ export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:", { calendarFormat: filters.byCode("sameElse"), dateFormat: filters.byCode('":'), - isSameDay: filters.byCode("Math.abs(+"), + isSameDay: filters.byCode(/Math\.abs\(\i-\i\)/), diffAsUnits: filters.byCode("days:0", "millisecondsInUnit") }); From 93294673de00bbca7610da258128629f6296ee1c Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 14 Aug 2025 21:48:28 +0200 Subject: [PATCH 079/141] bump to v1.12.11 --- package.json | 2 +- src/webpack/common/utils.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 498b8919..50219306 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.10", + "version": "1.12.11", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 862f3843..633eceea 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -203,6 +203,7 @@ export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/ export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:", { calendarFormat: filters.byCode("sameElse"), dateFormat: filters.byCode('":'), - isSameDay: filters.byCode(/Math\.abs\(\i-\i\)/), + // TODO: the +? are for compat with the old version - Remove them once no longer needed + isSameDay: filters.byCode(/Math\.abs\(\+?\i-\+?\i\)/), diffAsUnits: filters.byCode("days:0", "millisecondsInUnit") }); From 330c3cead76ca39787d0f199e3c566e90fb8f5a8 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 17 Aug 2025 17:33:23 -0300 Subject: [PATCH 080/141] Fix plugins broken by latest Discord update (#3609) --- src/plugins/_api/notices.ts | 4 ++-- src/plugins/copyStickerLinks/{README.MD => README.md} | 0 src/plugins/customIdle/index.ts | 2 +- src/plugins/greetStickerPicker/index.tsx | 2 +- src/plugins/showAllMessageButtons/index.ts | 6 +++--- src/plugins/showHiddenChannels/index.tsx | 2 +- src/plugins/superReactionTweaks/index.ts | 4 ++-- src/plugins/vencordToolbox/index.tsx | 2 +- src/plugins/webContextMenus.web/index.ts | 4 ---- 9 files changed, 11 insertions(+), 15 deletions(-) rename src/plugins/copyStickerLinks/{README.MD => README.md} (100%) diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts index 89aa1a46..2384a10e 100644 --- a/src/plugins/_api/notices.ts +++ b/src/plugins/_api/notices.ts @@ -33,8 +33,8 @@ export default definePlugin({ replace: "if(Vencord.Api.Notices.currentNotice)return false;$&" }, { - match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/, - replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` + match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/, + replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` } ] } diff --git a/src/plugins/copyStickerLinks/README.MD b/src/plugins/copyStickerLinks/README.md similarity index 100% rename from src/plugins/copyStickerLinks/README.MD rename to src/plugins/copyStickerLinks/README.md diff --git a/src/plugins/customIdle/index.ts b/src/plugins/customIdle/index.ts index 9a0a47a2..78f4f9d0 100644 --- a/src/plugins/customIdle/index.ts +++ b/src/plugins/customIdle/index.ts @@ -40,7 +40,7 @@ export default definePlugin({ replace: "$self.getIdleTimeout()||" }, { - match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/, + match: /Math\.min\((\i\*\i\.\i\.\i\.SECOND),\i\.\i\)/, replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum) }, { diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx index 4ea1f25d..0cc3e170 100644 --- a/src/plugins/greetStickerPicker/index.tsx +++ b/src/plugins/greetStickerPicker/index.tsx @@ -161,7 +161,7 @@ export default definePlugin({ { find: "#{intl::WELCOME_CTA_LABEL}", replacement: { - match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).+?)/, + match: /className:\i\.welcomeCTA,(?<={channel:\i,message:\i}=(\i).+?)/, replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1)," } } diff --git a/src/plugins/showAllMessageButtons/index.ts b/src/plugins/showAllMessageButtons/index.ts index 6ac17e5d..aa52d34b 100644 --- a/src/plugins/showAllMessageButtons/index.ts +++ b/src/plugins/showAllMessageButtons/index.ts @@ -28,9 +28,9 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - // isExpanded = isShiftPressed && other conditions... - match: /(?<=(\i)=)\i(?=&&.+?isExpanded:\1)/, - replace: "true" + // isExpanded: isShiftPressed && other conditions... + match: /isExpanded:\i&&(.+?),/, + replace: "isExpanded:$1," } } ] diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 1e5891ce..b4fcd713 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -306,7 +306,7 @@ export default definePlugin({ }, { // Export the channel for the users allowed component patch - match: /maxUsers:\i,users:\i(?<=channel:(\i).+?)/, + match: /maxUsers:\d+?,users:\i(?<=channel:(\i).+?)/, replace: (m, channel) => `${m},shcChannel:${channel}` }, { diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index b8aed478..22e7d6f3 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -42,8 +42,8 @@ export default definePlugin({ { find: ",BURST_REACTION_EFFECT_PLAY", replacement: { - match: /(?<=(\i)=\i=>{.+?)(\i\(\i,\i\))>=\i(?=\).+BURST_REACTION_EFFECT_PLAY:\1)/, - replace: "!$self.shouldPlayBurstReaction($2)" + match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=\d+?(?=\))/, + replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})` } }, { diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 82cd0284..af9fbc87 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -142,7 +142,7 @@ export default definePlugin({ { find: ".controlButtonWrapper,", replacement: { - match: /(?<=function (\i).{0,100}\()\i.Fragment,(?=.+?toolbar:\1\(\))/, + match: /(?<=toolbar:function\(.{0,100}\()\i.Fragment,/, replace: "$self.ToolboxFragmentWrapper," } } diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 666e51be..be85e33c 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -142,10 +142,6 @@ export default definePlugin({ match: /!\i\.isPlatformEmbedded/, replace: "false" }, - { - match: /return\s*?\[.{0,50}?(?=\?\(0,\i\.jsxs?.{0,100}?id:"copy-image")/, - replace: "return [true" - }, { match: /(?<=#{intl::COPY_IMAGE_MENU_ITEM}\),)action:/, replace: "action:()=>$self.copyImage(arguments[0]),oldAction:" From 0bb2ed6b727f7205fe6e5a4f788f6c7e3f83dc23 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 18 Aug 2025 18:58:40 +0200 Subject: [PATCH 081/141] bump to v1.12.12 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 50219306..09a82293 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.11", + "version": "1.12.12", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -105,4 +105,4 @@ "engines": { "node": ">=18" } -} +} \ No newline at end of file From 643656d798b4d328adaaf0f12dccc59a2c955990 Mon Sep 17 00:00:00 2001 From: fawn <fawn@rinici.de> Date: Tue, 19 Aug 2025 17:41:02 +0300 Subject: [PATCH 082/141] new plugin ImageFilename: displays image filename tooltips on hover (#3617) Co-authored-by: Vendicated <vendicated@riseup.net> --- src/plugins/imageFilename/README.md | 5 +++ src/plugins/imageFilename/index.ts | 51 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/plugins/imageFilename/README.md create mode 100644 src/plugins/imageFilename/index.ts diff --git a/src/plugins/imageFilename/README.md b/src/plugins/imageFilename/README.md new file mode 100644 index 00000000..7929a73a --- /dev/null +++ b/src/plugins/imageFilename/README.md @@ -0,0 +1,5 @@ +# ImageFilename + +Display the file name of images & GIFs as a tooltip when hovering over them + +![](https://github.com/user-attachments/assets/44583f17-506f-4913-b85c-513eee77b645) diff --git a/src/plugins/imageFilename/index.ts b/src/plugins/imageFilename/index.ts new file mode 100644 index 00000000..08a446f8 --- /dev/null +++ b/src/plugins/imageFilename/index.ts @@ -0,0 +1,51 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 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 ImageExtensionRe = /\.(png|jpg|jpeg|gif|webp|avif)$/i; +const GifHostRegex = /^(.+?\.)?(tenor|giphy|imgur)\.com$/i; + +const settings = definePluginSettings({ + showFullUrl: { + description: "Show the full URL of the image instead of just the file name. Always enabled for GIFs because they usually have no meaningful file name", + type: OptionType.BOOLEAN, + default: false, + }, +}); + +export default definePlugin({ + name: "ImageFilename", + authors: [Devs.Ven], + description: "Display the file name of images & GIFs as a tooltip when hovering over them", + settings, + + patches: [ + { + find: ".clickableWrapper", + replacement: { + match: /\.originalLink,href:(\i)/, + replace: "$&,title:$self.getTitle($1)" + } + }, + ], + + getTitle(src: string) { + try { + const url = new URL(src); + const isGif = GifHostRegex.test(url.hostname); + if (!isGif && !ImageExtensionRe.test(url.pathname)) return undefined; + + return isGif || settings.store.showFullUrl + ? src + : url.pathname.split("/").pop(); + } catch { + return undefined; + } + } +}); From 4a35cf1769f2f6bc178451b5641140ef06e478b5 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sat, 23 Aug 2025 01:57:27 +0200 Subject: [PATCH 083/141] Toolbox: fix & move to the titlebar --- src/plugins/vencordToolbox/index.tsx | 31 ++++++++++------------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index af9fbc87..1bc97cf5 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -94,7 +94,7 @@ function VencordPopoutIcon(isShown: boolean) { ); } -function VencordPopoutButton() { +function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) { const buttonRef = useRef(null); const [show, setShow] = useState(false); @@ -111,7 +111,7 @@ function VencordPopoutButton() { {(_, { isShown }) => ( <HeaderBarIcon ref={buttonRef} - className="vc-toolbox-btn" + className={`vc-toolbox-btn ${buttonClass}`} onClick={() => setShow(v => !v)} tooltip={isShown ? null : "Vencord Toolbox"} icon={() => VencordPopoutIcon(isShown)} @@ -122,33 +122,24 @@ function VencordPopoutButton() { ); } -function ToolboxFragmentWrapper({ children }: { children: ReactNode[]; }) { - children.splice( - children.length - 1, 0, - <ErrorBoundary noop> - <VencordPopoutButton /> - </ErrorBoundary> - ); - - return <>{children}</>; -} - export default definePlugin({ name: "VencordToolbox", - description: "Adds a button next to the inbox button in the channel header that houses Vencord quick actions", + description: "Adds a button to the titlebar that houses Vencord quick actions", authors: [Devs.Ven, Devs.AutumnVN], patches: [ { - find: ".controlButtonWrapper,", + find: '"M9 3v18"', replacement: { - match: /(?<=toolbar:function\(.{0,100}\()\i.Fragment,/, - replace: "$self.ToolboxFragmentWrapper," + match: /focusSectionProps:"HELP".{0,20},className:(\i\.button)\}\),/, + replace: "$& $self.renderVencordPopoutButton($1)," } } ], - ToolboxFragmentWrapper: ErrorBoundary.wrap(ToolboxFragmentWrapper, { - fallback: () => <p style={{ color: "red" }}>Failed to render :(</p> - }) + renderVencordPopoutButton: (buttonClass: string) => ( + <ErrorBoundary noop> + <VencordPopoutButton buttonClass={buttonClass} /> + </ErrorBoundary> + ) }); From abe910d80d2848127e5ee1857cfccedfbdbbbf07 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sat, 23 Aug 2025 02:02:02 +0200 Subject: [PATCH 084/141] fix not being able to dismiss Vencord Notices --- src/plugins/_api/notices.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts index 2384a10e..3b43c31a 100644 --- a/src/plugins/_api/notices.ts +++ b/src/plugins/_api/notices.ts @@ -32,9 +32,16 @@ export default definePlugin({ match: /(?<=!1;)\i=null;(?=.{0,80}getPremiumSubscription\(\))/g, replace: "if(Vencord.Api.Notices.currentNotice)return false;$&" }, + + // TODO: remove this compat eventually { match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/, - replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` + replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`, + noWarn: true, + }, + { + match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/, + replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}` } ] } From 76a60e07e9d299953461640b3409aff13045295d Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sat, 23 Aug 2025 02:16:32 +0200 Subject: [PATCH 085/141] fix SuperReactionTweaks --- src/plugins/superReactionTweaks/index.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 22e7d6f3..2edf99e8 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -42,8 +42,13 @@ export default definePlugin({ { find: ",BURST_REACTION_EFFECT_PLAY", replacement: { - match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=\d+?(?=\))/, - replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})` + /* + * var limit = 5 + * ... + * if (calculatePlayingCount(a,b) >= limit) return; + */ + match: /(?<=(\i)=5.+?)if\((.{0,20}?)>=\1\)return;/, + replace: "if(!$self.shouldPlayBurstReaction($2))return;" } }, { @@ -58,8 +63,7 @@ export default definePlugin({ shouldPlayBurstReaction(playingCount: number) { if (settings.store.unlimitedSuperReactionPlaying) return true; - if (settings.store.superReactionPlayingLimit === 0) return false; - if (playingCount <= settings.store.superReactionPlayingLimit) return true; + if (settings.store.superReactionPlayingLimit > playingCount) return true; return false; }, From 5dee7469864f042cd221deff5ab1495b092acd24 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 23 Aug 2025 11:30:58 -0300 Subject: [PATCH 086/141] Fix IgnoreActivities --- src/plugins/_api/notices.ts | 2 +- src/plugins/ignoreActivities/index.tsx | 22 ++++++++++++++---- src/plugins/superReactionTweaks/index.ts | 29 ++++++++++++++++-------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts index 3b43c31a..43ef2acc 100644 --- a/src/plugins/_api/notices.ts +++ b/src/plugins/_api/notices.ts @@ -33,7 +33,7 @@ export default definePlugin({ replace: "if(Vencord.Api.Notices.currentNotice)return false;$&" }, - // TODO: remove this compat eventually + // FIXME(Bundler minifier change related): Remove the non used compability once enough time has passed { match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/, replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`, diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 88906c38..014c1abd 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -56,15 +56,15 @@ function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string ); } -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); +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) { const s = settings.use(["ignoredActivities"]); const { ignoredActivities } = s; if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)"); - return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)"); + return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--interactive-normal)"); } function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) { @@ -253,15 +253,29 @@ export default definePlugin({ replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));` } }, + + // FIXME(Bundler minifier change related): Remove the non used compability once enough time has passed { find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}", replacement: { // let { ... nowPlaying: a = !1 ... // let { overlay: b ... } = Props match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)/, - replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` + replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`, + noWarn: true, } }, + { + find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}", + replacement: { + // let { ... nowPlaying: a = !1 ... + // let { overlay: b ... } = Props ... + // ToggleOverLayButton(), nowPlaying && ... RemoveGameButton() + match: /\.gameNameLastPlayed.+?,\i\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)(?=\1&&)/, + replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`, + } + }, + // Activities from the apps launcher in the bottom right of the chat bar { find: ".promotedLabelWrapperNonBanner,children", diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 2edf99e8..a8827903 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -41,21 +41,30 @@ export default definePlugin({ patches: [ { find: ",BURST_REACTION_EFFECT_PLAY", - replacement: { - /* - * var limit = 5 - * ... - * if (calculatePlayingCount(a,b) >= limit) return; - */ - match: /(?<=(\i)=5.+?)if\((.{0,20}?)>=\1\)return;/, - replace: "if(!$self.shouldPlayBurstReaction($2))return;" - } + replacement: [ + // FIXME(Bundler minifier change related): Remove the non used compability once enough time has passed + { + // if (inlinedCalculatePlayingCount(a,b) >= limit) return; + match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=5+?(?=\))/, + replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})`, + noWarn: true, + }, + { + /* + * var limit = 5 + * ... + * if (calculatePlayingCount(a,b) >= limit) return; + */ + match: /((\i)=5.+?)if\((.{0,20}?)>=\2\)return;/, + replace: (_, rest, playingCount) => `${rest}if(!$self.shouldPlayBurstReaction(${playingCount}))return;` + } + ] }, { find: ".EMOJI_PICKER_CONSTANTS_EMOJI_CONTAINER_PADDING_HORIZONTAL)", replacement: { match: /(openPopoutType:void 0(?=.+?isBurstReaction:(\i).+?(\i===\i\.\i.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, - replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})` + replace: (_, rest, _isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})` } } ], From 19c1eaed18cdc484ed0c910c8ce0743cef6d5531 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:43:15 -0400 Subject: [PATCH 087/141] fix failing to find the Select component (#3631) --- src/components/settings/tabs/plugins/index.tsx | 1 - src/webpack/common/components.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx index 6faa6368..4db9699a 100644 --- a/src/components/settings/tabs/plugins/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -262,7 +262,6 @@ function PluginSettings() { select={onStatusChange} isSelected={v => v === searchValue.status} closeOnSelect={true} - className={InputStyles.input} /> </div> </div> diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index d5e8dad1..9715619a 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -55,8 +55,8 @@ export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.comp export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:")); export const Text = waitForComponent<t.Text>("Text", filters.componentByCode('case"always-white"')); export const Heading = waitForComponent<t.Heading>("Heading", filters.componentByCode(">6?{", "variant:")); -export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"===')); -export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:")); +export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('="bottom",', ".select,", '"Escape"===')); +export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode(".setSelectionRange(", ".multi]:")); export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat(')); export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,")); export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1')); From c5888c25f70ad85793ba33bead19604d50469265 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Fri, 29 Aug 2025 02:50:32 +0200 Subject: [PATCH 088/141] bump to v1.12.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09a82293..3f0e3371 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.12", + "version": "1.12.13", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 67aff64fed0422092961f2255691504e92b680e8 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:01:27 -0400 Subject: [PATCH 089/141] MutualGroupDMs: Fix broken patch (#3634) --- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 8d81e43d..28889923 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -109,14 +109,14 @@ export default definePlugin({ }, // User Profile Modal v2 { - find: ".tabBarPanel,children:", + find: ".tabBarPanel,{[", replacement: [ { match: /items:(\i),.+?(?=return\(0,\i\.jsxs?\)\("div)/, replace: "$&$self.pushSection($1,arguments[0].user);" }, { - match: /\.tabBarPanel,children:(?=.+?section:(\i))/, + match: /\.tabBarPanel,.+?children:(?=.+?section:(\i))/, replace: "$&$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):" }, // Make the gap between each item smaller so our tab can fit. From aca30bcb9a37425c4cdecda6f0a5b94a57225112 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:02:45 -0400 Subject: [PATCH 090/141] ShowHiddenChannels: Fix broken patch (#3635) --- src/plugins/showHiddenChannels/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index b4fcd713..5f83a3f5 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -239,7 +239,7 @@ export default definePlugin({ replace: (m, channel) => `${m}if($self.isHiddenChannel(${channel}))break;` }, { - match: /(?<="renderHeaderBar",\i=>{.+?hideSearch:(\i)\.isDirectory\(\))/, + match: /(?<="renderHeaderBar",\(\)=>{.+?hideSearch:(\i)\.isDirectory\(\))/, replace: (_, channel) => `||$self.isHiddenChannel(${channel})` }, { From 8807564053c7b4cc05c763e2dc7171f5d61e39c7 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:23:23 -0300 Subject: [PATCH 091/141] ConsoleJanitor: Fix outdated settings margin --- .../settings/tabs/plugins/components/Common.tsx | 2 +- src/plugins/consoleJanitor/index.tsx | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx index 3d84859b..61c3da52 100644 --- a/src/components/settings/tabs/plugins/components/Common.tsx +++ b/src/components/settings/tabs/plugins/components/Common.tsx @@ -36,7 +36,7 @@ export function resolveError(isValidResult: boolean | string) { interface SettingsSectionProps extends PropsWithChildren { name: string; description: string; - error: string | null; + error?: string | null; inlineSetting?: boolean; } diff --git a/src/plugins/consoleJanitor/index.tsx b/src/plugins/consoleJanitor/index.tsx index 4981ac2c..fb4117c1 100644 --- a/src/plugins/consoleJanitor/index.tsx +++ b/src/plugins/consoleJanitor/index.tsx @@ -6,11 +6,10 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; -import { Flex } from "@components/Flex"; +import { SettingsSection } from "@components/settings/tabs/plugins/components/Common"; import { Devs } from "@utils/constants"; -import { Margins } from "@utils/margins"; import definePlugin, { defineDefault, OptionType, StartAt } from "@utils/types"; -import { Checkbox, Forms, Text } from "@webpack/common"; +import { Checkbox, Text } from "@webpack/common"; const Noop = () => { }; const NoopLogger = { @@ -58,15 +57,13 @@ function AllowLevelSetting({ settingKey }: AllowLevelSettingProps) { const AllowLevelSettings = ErrorBoundary.wrap(() => { return ( - <Forms.FormSection> - <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle> - <Forms.FormText className={Margins.bottom8}>Always allow loggers of these types</Forms.FormText> - <Flex flexDirection="row"> + <SettingsSection name="Filter List" description="Always allow loggers of these types"> + <div style={{ display: "flex", flexDirection: "row" }}> {Object.keys(settings.store.allowLevel).map(key => ( <AllowLevelSetting key={key} settingKey={key as keyof AllowLevels} /> ))} - </Flex> - </Forms.FormSection> + </div> + </SettingsSection> ); }); From 17b90beee1af377becbf92764cf954910037f140 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 31 Aug 2025 05:18:40 +0200 Subject: [PATCH 092/141] add context menu options to vencord badges --- src/api/Badges.ts | 39 +++++++++++++++------- src/plugins/_api/badges/index.tsx | 54 +++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/api/Badges.ts b/src/api/Badges.ts index f79ca099..1345b26d 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -34,7 +34,9 @@ export interface ProfileBadge { image?: string; link?: string; /** Action to perform when you click the badge */ - onClick?(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, props: BadgeUserArgs): void; + onClick?(event: React.MouseEvent, props: ProfileBadge & BadgeUserArgs): void; + /** Action to perform when you right click the badge */ + onContextMenu?(event: React.MouseEvent, props: BadgeUserArgs & BadgeUserArgs): void; /** Should the user display this badge? */ shouldShow?(userInfo: BadgeUserArgs): boolean; /** Optional props (e.g. style) for the badge, ignored for component badges */ @@ -76,21 +78,34 @@ export function removeProfileBadge(badge: ProfileBadge) { export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { - if (!badge.shouldShow || badge.shouldShow(args)) { - const b = badge.getBadges - ? badge.getBadges(args).map(b => { - b.component &&= ErrorBoundary.wrap(b.component, { noop: true }); - return b; - }) - : [{ ...badge, ...args }]; + if (badge.shouldShow && !badge.shouldShow(args)) { + continue; + } - badge.position === BadgePosition.START - ? badges.unshift(...b) - : badges.push(...b); + const b = badge.getBadges + ? badge.getBadges(args).map(badge => ({ + ...args, + ...badge, + component: badge.component && ErrorBoundary.wrap(badge.component, { noop: true }) + })) + : [{ ...args, ...badge }]; + + if (badge.position === BadgePosition.START) { + badges.unshift(...b); + } else { + badges.push(...b); } } + const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); - if (donorBadges) badges.unshift(...donorBadges); + if (donorBadges) { + badges.unshift( + ...donorBadges.map(badge => ({ + ...args, + ...badge, + })) + ); + } return badges; } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 433c3080..4118435a 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -27,11 +27,11 @@ import { openContributorModal } from "@components/settings/tabs"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { shouldShowContributorBadge } from "@utils/misc"; +import { copyWithToast, shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; import { User } from "@vencord/discord-types"; -import { Forms, Toasts, UserStore } from "@webpack/common"; +import { ContextMenuApi, Forms, Menu, Toasts, UserStore } from "@webpack/common"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; @@ -56,9 +56,35 @@ async function loadBadges(noCache = false) { let intervalId: any; +function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) { + console.log(badge); + return ( + <Menu.Menu + navId="vc-badge-context" + onClose={ContextMenuApi.closeContextMenu} + aria-label="Badge Options" + > + {badge.description && ( + <Menu.MenuItem + id="vc-badge-copy-name" + label="Copy Badge Name" + action={() => copyWithToast(badge.description!)} + /> + )} + {badge.image && ( + <Menu.MenuItem + id="vc-badge-copy-link" + label="Copy Badge Image Link" + action={() => copyWithToast(badge.image!)} + /> + )} + </Menu.Menu> + ); +} + export default definePlugin({ name: "BadgeAPI", - description: "API to add badges to users.", + description: "API to add badges to users", authors: [Devs.Megu, Devs.Ven, Devs.TheSun], required: true, patches: [ @@ -80,10 +106,10 @@ export default definePlugin({ match: /(?<="aria-label":(\i)\.description,.{0,200})children:/, replace: "children:$1.component?$self.renderBadgeComponent({...$1}) :" }, - // conditionally override their onClick with badge.onClick if it exists + // handle onClick and onContextMenu { match: /href:(\i)\.link/, - replace: "...($1.onClick&&{onClick:vcE=>$1.onClick(vcE,$1)}),$&" + replace: "...$self.getBadgeMouseEventHandlers($1),$&" } ] } @@ -137,6 +163,19 @@ export default definePlugin({ }, { noop: true }), + getBadgeMouseEventHandlers(badge: ProfileBadge & BadgeUserArgs) { + const handlers = {} as Record<string, (e: React.MouseEvent) => void>; + + if (!badge) return handlers; // sanity check + + const { onClick, onContextMenu } = badge; + + if (onClick) handlers.onClick = e => onClick(e, badge); + if (onContextMenu) handlers.onContextMenu = e => onContextMenu(e, badge); + + return handlers; + }, + getDonorBadges(userId: string) { return DonorBadges[userId]?.map(badge => ({ image: badge.badge, @@ -148,6 +187,9 @@ export default definePlugin({ transform: "scale(0.9)" // The image is a bit too big compared to default badges } }, + onContextMenu(event, badge) { + ContextMenuApi.openContextMenu(event, () => <BadgeContextMenu badge={badge} />); + }, onClick() { const modalKey = openModal(props => ( <ErrorBoundary noop onError={() => { @@ -203,6 +245,6 @@ export default definePlugin({ </ErrorBoundary> )); }, - })); + } satisfies ProfileBadge)); } }); From 8ebfd9a19073037ed8c714ddb429b6ced8ed09c0 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 1 Sep 2025 20:53:38 -0300 Subject: [PATCH 093/141] NoTypingAnimation: Fix not working due to broken patches --- src/plugins/noTypingAnimation/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/plugins/noTypingAnimation/index.ts b/src/plugins/noTypingAnimation/index.ts index d4aab900..74bcdd50 100644 --- a/src/plugins/noTypingAnimation/index.ts +++ b/src/plugins/noTypingAnimation/index.ts @@ -11,11 +11,13 @@ export default definePlugin({ name: "NoTypingAnimation", authors: [Devs.AutumnVN], description: "Disables the CPU-intensive typing dots animation", - patches: [{ - find: "dotCycle", - replacement: { - match: /document.hasFocus\(\)/, - replace: "false" + patches: [ + { + find: "dotCycle", + replacement: { + match: /focused:(\i)/g, + replace: (_, focused) => `_focused:${focused}=false` + } } - }] + ] }); From f0f75aa91877ce2b6dec426684ad96f36ebb862e Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Mon, 1 Sep 2025 19:59:15 -0400 Subject: [PATCH 094/141] AlwaysAnimate: Add nameplates support (#3641) --- src/plugins/alwaysAnimate/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index 4064de9f..1635781c 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -54,6 +54,14 @@ export default definePlugin({ match: /(\.headerContent.+?guildBanner:\i,animate:)\i/, replace: "$1!0" } + }, + { + // Nameplates + find: ".MINI_PREVIEW,[", + replacement: { + match: /animate:\i,loop:/, + replace: "animate:true,loop:true,_loop:" + } } ] }); From 9b0ae0fd9068e3e6763c1d1e258ef4c7580a48d5 Mon Sep 17 00:00:00 2001 From: jamesbt365 <jamesbt365@gmail.com> Date: Tue, 2 Sep 2025 03:20:56 +0100 Subject: [PATCH 095/141] Translate: support automod & forwarded messages (#3367) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/translate/index.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx index 363897d1..1bcc8b3f 100644 --- a/src/plugins/translate/index.tsx +++ b/src/plugins/translate/index.tsx @@ -21,6 +21,7 @@ import "./styles.css"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore, Menu } from "@webpack/common"; import { settings } from "./settings"; @@ -28,8 +29,9 @@ import { setShouldShowTranslateEnabledTooltip, TranslateChatBarIcon, TranslateIc import { handleTranslate, TranslationAccessory } from "./TranslationAccessory"; import { translate } from "./utils"; -const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => { - if (!message.content) return; +const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { + const content = getMessageContent(message); + if (!content) return; const group = findGroupChildrenByChildId("copy-text", children); if (!group) return; @@ -40,13 +42,23 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => label="Translate" icon={TranslateIcon} action={async () => { - const trans = await translate("received", message.content); + const trans = await translate("received", content); handleTranslate(message.id, trans); }} /> )); }; + +function getMessageContent(message: Message) { + // Message snapshots is an array, which allows for nested snapshots, which Discord does not do yet. + // no point collecting content or rewriting this to render in a certain way that makes sense + // for something currently impossible. + return message.content + || message.messageSnapshots?.[0]?.message.content + || message.embeds?.find(embed => embed.type === "auto_moderation_message")?.rawDescription || ""; +} + let tooltipTimeout: any; export default definePlugin({ @@ -64,8 +76,9 @@ export default definePlugin({ renderChatBarButton: TranslateChatBarIcon, - renderMessagePopoverButton(message) { - if (!message.content) return null; + renderMessagePopoverButton(message: Message) { + const content = getMessageContent(message); + if (!content) return null; return { label: "Translate", @@ -73,7 +86,7 @@ export default definePlugin({ message, channel: ChannelStore.getChannel(message.channel_id), onClick: async () => { - const trans = await translate("received", message.content); + const trans = await translate("received", content); handleTranslate(message.id, trans); } }; From 75a2506c51993c7e44be8d53cd0055264ae65eab Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:42:17 -0700 Subject: [PATCH 096/141] ViewRaw: Adjust icon size to match other icons (#3605) --- src/plugins/viewRaw/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index e6781784..1dcef31b 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -32,7 +32,7 @@ import { Button, ChannelStore, Forms, GuildRoleStore, Menu, Text } from "@webpac const CopyIcon = () => { - return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="18" height="18"> + return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="20" height="20"> <path d="M12.9297 3.25007C12.7343 3.05261 12.4154 3.05226 12.2196 3.24928L11.5746 3.89824C11.3811 4.09297 11.3808 4.40733 11.5739 4.60245L16.5685 9.64824C16.7614 9.84309 16.7614 10.1569 16.5685 10.3517L11.5739 15.3975C11.3808 15.5927 11.3811 15.907 11.5746 16.1017L12.2196 16.7507C12.4154 16.9477 12.7343 16.9474 12.9297 16.7499L19.2604 10.3517C19.4532 10.1568 19.4532 9.84314 19.2604 9.64832L12.9297 3.25007Z" /> <path d="M8.42616 4.60245C8.6193 4.40733 8.61898 4.09297 8.42545 3.89824L7.78047 3.24928C7.58466 3.05226 7.26578 3.05261 7.07041 3.25007L0.739669 9.64832C0.5469 9.84314 0.546901 10.1568 0.739669 10.3517L7.07041 16.7499C7.26578 16.9474 7.58465 16.9477 7.78047 16.7507L8.42545 16.1017C8.61898 15.907 8.6193 15.5927 8.42616 15.3975L3.43155 10.3517C3.23869 10.1569 3.23869 9.84309 3.43155 9.64824L8.42616 4.60245Z" /> </svg>; From 5c69d340d967084a567da6f3d6d276cbe715c9d7 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:44:25 -0400 Subject: [PATCH 097/141] Fix MutualGroupDMs & Decor broken patches (#3644) --- src/plugins/decor/ui/modals/CreateDecorationModal.tsx | 2 +- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx index d772ac4a..6feb3b4f 100644 --- a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx @@ -17,7 +17,7 @@ import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDeco import { cl, DecorationModalStyles, requireAvatarDecorationModal, requireCreateStickerModal } from "../"; import { AvatarDecorationModalPreview } from "../components"; -const FileUpload = findComponentByCodeLazy("fileUploadInput,"); +const FileUpload = findComponentByCodeLazy(".fileUpload),"); const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', { HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"), diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 28889923..6909fbd2 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -109,14 +109,14 @@ export default definePlugin({ }, // User Profile Modal v2 { - find: ".tabBarPanel,{[", + find: ".WIDGETS?", replacement: [ { match: /items:(\i),.+?(?=return\(0,\i\.jsxs?\)\("div)/, replace: "$&$self.pushSection($1,arguments[0].user);" }, { - match: /\.tabBarPanel,.+?children:(?=.+?section:(\i))/, + match: /\.tabBarPanel,.*?children:(?=.+?section:(\i))/, replace: "$&$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):" }, // Make the gap between each item smaller so our tab can fit. From 4ff3614dc0a96ebfd825dc5fd3ef9e77de7d1da1 Mon Sep 17 00:00:00 2001 From: ayuxia <vi@placeq.com> Date: Tue, 2 Sep 2025 22:19:12 -0300 Subject: [PATCH 098/141] ShowMeYourName: support friend nicknames (#3639) Co-authored-by: V <vendicated@riseup.net> --- packages/discord-types/enums/channel.ts | 15 +++++++ packages/discord-types/enums/index.ts | 1 + src/plugins/noReplyMention/index.tsx | 2 +- src/plugins/onePingPerDM/index.ts | 6 +-- src/plugins/relationshipNotifier/functions.ts | 3 +- src/plugins/relationshipNotifier/types.ts | 4 -- src/plugins/relationshipNotifier/utils.ts | 3 +- src/plugins/showMeYourName/index.tsx | 45 +++++++++++++++---- src/utils/constants.ts | 6 +-- 9 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 packages/discord-types/enums/channel.ts diff --git a/packages/discord-types/enums/channel.ts b/packages/discord-types/enums/channel.ts new file mode 100644 index 00000000..7aae546b --- /dev/null +++ b/packages/discord-types/enums/channel.ts @@ -0,0 +1,15 @@ +export const enum ChannelType { + GUILD_TEXT = 0, + DM = 1, + GUILD_VOICE = 2, + GROUP_DM = 3, + GUILD_CATEGORY = 4, + GUILD_ANNOUNCEMENT = 5, + ANNOUNCEMENT_THREAD = 10, + PUBLIC_THREAD = 11, + PRIVATE_THREAD = 12, + GUILD_STAGE_VOICE = 13, + GUILD_DIRECTORY = 14, + GUILD_FORUM = 15, + GUILD_MEDIA = 16 +} \ No newline at end of file diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index e2227d09..0ab49887 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1,2 +1,3 @@ export * from "./commands"; export * from "./messages"; +export * from "./channel"; \ No newline at end of file diff --git a/src/plugins/noReplyMention/index.tsx b/src/plugins/noReplyMention/index.tsx index b4de573f..4af78ee2 100644 --- a/src/plugins/noReplyMention/index.tsx +++ b/src/plugins/noReplyMention/index.tsx @@ -53,7 +53,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "NoReplyMention", description: "Disables reply pings by default", - authors: [Devs.DustyAngel47, Devs.axyie, Devs.pylix, Devs.outfoxxed], + authors: [Devs.DustyAngel47, Devs.rae, Devs.pylix, Devs.outfoxxed], settings, shouldMention(message: Message, isHoldingShift: boolean) { diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts index 0ef81781..b6192fb6 100644 --- a/src/plugins/onePingPerDM/index.ts +++ b/src/plugins/onePingPerDM/index.ts @@ -8,13 +8,9 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { MessageJSON } from "@vencord/discord-types"; +import { ChannelType } from "@vencord/discord-types/enums"; import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common"; -const enum ChannelType { - DM = 1, - GROUP_DM = 3 -} - const settings = definePluginSettings({ channelToAffect: { type: OptionType.SELECT, diff --git a/src/plugins/relationshipNotifier/functions.ts b/src/plugins/relationshipNotifier/functions.ts index 5bff474b..19f36086 100644 --- a/src/plugins/relationshipNotifier/functions.ts +++ b/src/plugins/relationshipNotifier/functions.ts @@ -17,10 +17,11 @@ */ import { getUniqueUsername, openUserProfile } from "@utils/discord"; +import { ChannelType } from "@vencord/discord-types/enums"; import { UserUtils } from "@webpack/common"; import settings from "./settings"; -import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types"; +import { ChannelDelete, GuildDelete, RelationshipRemove, RelationshipType } from "./types"; import { deleteGroup, deleteGuild, getGroup, getGuild, GuildAvailabilityStore, notify } from "./utils"; let manuallyRemovedFriend: string | undefined; diff --git a/src/plugins/relationshipNotifier/types.ts b/src/plugins/relationshipNotifier/types.ts index f2e482d6..5d3d12d4 100644 --- a/src/plugins/relationshipNotifier/types.ts +++ b/src/plugins/relationshipNotifier/types.ts @@ -52,10 +52,6 @@ export interface SimpleGuild { iconURL?: string; } -export const enum ChannelType { - GROUP_DM = 3, -} - export const enum RelationshipType { FRIEND = 1, BLOCKED = 2, diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts index aaef783b..78371f13 100644 --- a/src/plugins/relationshipNotifier/utils.ts +++ b/src/plugins/relationshipNotifier/utils.ts @@ -20,11 +20,12 @@ import { DataStore, Notices } from "@api/index"; import { showNotification } from "@api/Notifications"; import { getUniqueUsername, openUserProfile } from "@utils/discord"; import { FluxStore } from "@vencord/discord-types"; +import { ChannelType } from "@vencord/discord-types/enums"; import { findStoreLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common"; import settings from "./settings"; -import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types"; +import { RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types"; export const GuildAvailabilityStore = findStoreLazy("GuildAvailabilityStore") as FluxStore & { totalGuilds: number; diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 018fa305..623c70ca 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -10,10 +10,12 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Message, User } from "@vencord/discord-types"; +import { Channel, Message, User } from "@vencord/discord-types"; +import { RelationshipStore } from "@webpack/common"; interface UsernameProps { - author: { nick: string; }; + author: { nick: string; authorId: string; }; + channel: Channel; message: Message; withMentionPrefix?: boolean; isRepliedMessage: boolean; @@ -30,6 +32,15 @@ const settings = definePluginSettings({ { label: "Username only", value: "user" }, ], }, + friendNicknames: { + type: OptionType.SELECT, + description: "How to prioritise friend nicknames over server nicknames", + options: [ + { label: "Show friend nicknames only in direct messages", value: "dms", default: true }, + { label: "Prefer friend nicknames over server nicknames", value: "always" }, + { label: "Prefer server nicknames over friend nicknames", value: "fallback" } + ] + }, displayNames: { type: OptionType.BOOLEAN, description: "Use display names in place of usernames", @@ -45,7 +56,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ShowMeYourName", description: "Display usernames next to nicks, or no nicks at all", - authors: [Devs.Rini, Devs.TheKodeToad], + authors: [Devs.Rini, Devs.TheKodeToad, Devs.rae], patches: [ { find: '="SYSTEM_TAG"', @@ -58,23 +69,39 @@ export default definePlugin({ ], settings, - renderUsername: ErrorBoundary.wrap(({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => { + renderUsername: ErrorBoundary.wrap(({ author, channel, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => { try { + const { mode, friendNicknames, displayNames, inReplies } = settings.store; + const user = userOverride ?? message.author; let { username } = user; - if (settings.store.displayNames) + + if (displayNames) username = user.globalName || username; - const { nick } = author; + let { nick } = author; + + const friendNickname = RelationshipStore.getNickname(author.authorId); + + if (friendNickname) { + const shouldUseFriendNickname = + friendNicknames === "always" || + (friendNicknames === "dms" && channel.isPrivate()) || + (friendNicknames === "fallback" && !nick); + + if (shouldUseFriendNickname) + nick = friendNickname; + } + const prefix = withMentionPrefix ? "@" : ""; - if (isRepliedMessage && !settings.store.inReplies || username.toLowerCase() === nick.toLowerCase()) + if (isRepliedMessage && !inReplies || username.toLowerCase() === nick.toLowerCase()) return <>{prefix}{nick}</>; - if (settings.store.mode === "user-nick") + if (mode === "user-nick") return <>{prefix}{username} <span className="vc-smyn-suffix">{nick}</span></>; - if (settings.store.mode === "nick-user") + if (mode === "nick-user") return <>{prefix}{nick} <span className="vc-smyn-suffix">{username}</span></>; return <>{prefix}{username}</>; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7c7488b6..705b1451 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -197,9 +197,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "sunnie", id: 406028027768733696n }, - axyie: { - name: "'ax", - id: 929877747151548487n, + rae: { + name: "rae", + id: 1398136199503282277n }, pointy: { name: "pointy", From 8789973bf5ee233a68318e80e9223c853ddc86b3 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:41:46 -0400 Subject: [PATCH 099/141] WhoReacted: remove ugly more users tooltip (#3640) Co-authored-by: Vendicated <vendicated@riseup.net> --- src/plugins/whoReacted/index.tsx | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index af6d8fdb..2faedfa2 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -24,7 +24,7 @@ import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; -import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common"; +import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common"; const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); let Scroll: any = null; @@ -73,24 +73,6 @@ function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) { return cache.users; } -function makeRenderMoreUsers(users: User[]) { - return function renderMoreUsers(_label: string, _count: number) { - return ( - <Tooltip text={users.slice(4).map(u => u.username).join(", ")} > - {({ onMouseEnter, onMouseLeave }) => ( - <div - className={AvatarStyles.moreUsers} - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - > - +{users.length - 4} - </div> - )} - </Tooltip > - ); - }; -} - function handleClickAvatar(event: React.UIEvent<HTMLElement, Event>) { event.stopPropagation(); } @@ -163,7 +145,7 @@ export default definePlugin({ <div style={{ marginLeft: "0.5em", transform: "scale(0.9)" }} > - <div onClick={handleClickAvatar} onKeyPress={handleClickAvatar}> + <div onClick={handleClickAvatar} onKeyDown={handleClickAvatar}> <UserSummaryItem users={users} guildId={ChannelStore.getChannel(message.channel_id)?.guild_id} @@ -171,7 +153,6 @@ export default definePlugin({ max={5} showDefaultAvatarsForNullUsers showUserPopout - renderMoreUsers={makeRenderMoreUsers(users)} /> </div> </div> From 65c85a522202bdd090f9fcf7bd7702b2c5eba165 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 3 Sep 2025 04:26:33 +0200 Subject: [PATCH 100/141] CallTimer: fix overflow when using aligned chat input Co-Authored-By: sadan4 <117494111+sadan4@users.noreply.github.com> Co-Authored-By: God --- src/plugins/callTimer/alignedChatInputFix.css | 4 ++++ src/plugins/callTimer/index.tsx | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 src/plugins/callTimer/alignedChatInputFix.css diff --git a/src/plugins/callTimer/alignedChatInputFix.css b/src/plugins/callTimer/alignedChatInputFix.css new file mode 100644 index 00000000..9bb4a039 --- /dev/null +++ b/src/plugins/callTimer/alignedChatInputFix.css @@ -0,0 +1,4 @@ +.align-chat-input [class*="panels"] [class*="inner_"], +.align-chat-input [class*="rtcConnectionStatus_"] { + height: fit-content; +} \ No newline at end of file diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index dcab0551..ae98a787 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -23,6 +23,8 @@ import { useTimer } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { React } from "@webpack/common"; +import alignedChatInputFix from "./alignedChatInputFix.css"; + function formatDuration(ms: number) { // here be dragons (moment fucking sucks) const human = Settings.plugins.CallTimer.format === "human"; @@ -50,6 +52,7 @@ export default definePlugin({ name: "CallTimer", description: "Adds a timer to vcs", authors: [Devs.Ven], + managedStyle: alignedChatInputFix, startTime: 0, interval: void 0 as NodeJS.Timeout | undefined, From 9700ec9cd289e67a5af5398a2ceaf74737e1b5ef Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 3 Sep 2025 04:37:38 +0200 Subject: [PATCH 101/141] fix minor bugs --- src/plugins/_api/badges/fixDiscordBadgePadding.css | 8 ++++---- src/plugins/_api/badges/index.tsx | 1 - src/plugins/callTimer/index.tsx | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plugins/_api/badges/fixDiscordBadgePadding.css b/src/plugins/_api/badges/fixDiscordBadgePadding.css index eb1d60d5..7e7702a3 100644 --- a/src/plugins/_api/badges/fixDiscordBadgePadding.css +++ b/src/plugins/_api/badges/fixDiscordBadgePadding.css @@ -1,5 +1,5 @@ /* the profile popout badge container(s) */ -[class*="biteSize_"] [class*="tags_"] [class*="container_"] { - /* Discord has padding set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */ - padding: 0 1px; -} +[class*="profile_"] [class*="tags_"] [class*="container_"] { + /* Discord has gap set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */ + gap: 1px; +} \ No newline at end of file diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 4118435a..8742a4bd 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -57,7 +57,6 @@ async function loadBadges(noCache = false) { let intervalId: any; function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) { - console.log(badge); return ( <Menu.Menu navId="vc-badge-context" diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index ae98a787..76b3efd6 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -23,7 +23,7 @@ import { useTimer } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { React } from "@webpack/common"; -import alignedChatInputFix from "./alignedChatInputFix.css"; +import alignedChatInputFix from "./alignedChatInputFix.css?managed"; function formatDuration(ms: number) { // here be dragons (moment fucking sucks) From b7f19bbe3735590dcd4e52e1a491d2cbf18fe88b Mon Sep 17 00:00:00 2001 From: union <43791401+Unionizing@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:41:06 -0400 Subject: [PATCH 102/141] NoReplyMention: add role whitelist / blacklist (#2794) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/noReplyMention/index.tsx | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/plugins/noReplyMention/index.tsx b/src/plugins/noReplyMention/index.tsx index 4af78ee2..28bfc256 100644 --- a/src/plugins/noReplyMention/index.tsx +++ b/src/plugins/noReplyMention/index.tsx @@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import type { Message } from "@vencord/discord-types"; +import { ChannelStore, GuildMemberStore } from "@webpack/common"; const settings = definePluginSettings({ userList: { @@ -28,16 +29,22 @@ const settings = definePluginSettings({ type: OptionType.STRING, default: "1234567890123445,1234567890123445", }, + roleList: { + description: + "List of roles to allow or exempt pings for (separated by commas or spaces)", + type: OptionType.STRING, + default: "1234567890123445,1234567890123445", + }, shouldPingListed: { description: "Behaviour", type: OptionType.SELECT, options: [ { - label: "Do not ping the listed users", + label: "Do not ping the listed users / roles", value: false, }, { - label: "Only ping the listed users", + label: "Only ping the listed users / roles", value: true, default: true, }, @@ -57,7 +64,14 @@ export default definePlugin({ settings, shouldMention(message: Message, isHoldingShift: boolean) { - const isListed = settings.store.userList.includes(message.author.id); + let isListed = settings.store.userList.includes(message.author.id); + + const channel = ChannelStore.getChannel(message.channel_id); + if (channel?.guild_id && !isListed) { + const roles = GuildMemberStore.getMember(channel.guild_id, message.author.id)?.roles; + isListed = !!roles && roles.some(role => settings.store.roleList.includes(role)); + } + const isExempt = settings.store.shouldPingListed ? isListed : !isListed; return settings.store.inverseShiftReply ? isHoldingShift !== isExempt : !isHoldingShift && isExempt; }, From 77b016de366c827786bdcd45819552696c562875 Mon Sep 17 00:00:00 2001 From: union <43791401+Unionizing@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:46:02 -0400 Subject: [PATCH 103/141] ReverseImageSearch: add Bing (#2793) --- src/plugins/reverseImageSearch/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/reverseImageSearch/index.tsx b/src/plugins/reverseImageSearch/index.tsx index a2e7b7e6..3c1a5402 100644 --- a/src/plugins/reverseImageSearch/index.tsx +++ b/src/plugins/reverseImageSearch/index.tsx @@ -28,6 +28,7 @@ const Engines = { Yandex: "https://yandex.com/images/search?rpt=imageview&url=", SauceNAO: "https://saucenao.com/search.php?url=", IQDB: "https://iqdb.org/?url=", + Bing: "https://www.bing.com/images/search?view=detailv2&iss=sbi&q=imgurl:", TinEye: "https://www.tineye.com/search?url=", ImgOps: "https://imgops.com/start?url=" } as const; From 1d00ba4161fd1c285d1efe25f489c8bf68c77359 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:59:30 -0400 Subject: [PATCH 104/141] fix patches for Experiments and Vencord Toolbox (#3647) --- src/plugins/experiments/index.tsx | 2 +- src/plugins/vencordToolbox/index.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index e916e158..9b6a3377 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -80,7 +80,7 @@ export default definePlugin({ }, // Change top right chat toolbar button from the help one to the dev one { - find: '"M9 3v18"', + find: '?"BACK_FORWARD_NAVIGATION":', replacement: { match: /hasBugReporterAccess:(\i)/, replace: "_hasBugReporterAccess:$1=true" diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 1bc97cf5..5a7b8cad 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -129,9 +129,10 @@ export default definePlugin({ patches: [ { - find: '"M9 3v18"', + find: '?"BACK_FORWARD_NAVIGATION":', replacement: { - match: /focusSectionProps:"HELP".{0,20},className:(\i\.button)\}\),/, + // TODO: (?:\.button) is for stable compat and should be removed soon:tm: + match: /focusSectionProps:"HELP".{0,20},className:(\i(?:\.button)?)\}\),/, replace: "$& $self.renderVencordPopoutButton($1)," } } From b6e96a4d3b631c5a2d236ae28179ee0879ae00fe Mon Sep 17 00:00:00 2001 From: Gleb P <63789651+ultard@users.noreply.github.com> Date: Fri, 5 Sep 2025 04:37:13 +0300 Subject: [PATCH 105/141] MemberCount: also show members in voice (#2937) Co-authored-by: Vendicated <vendicated@riseup.net> --- .../src/stores/VoiceStateStore.d.ts | 42 +++++++++++++++++++ packages/discord-types/src/stores/index.d.ts | 1 + src/plugins/memberCount/MemberCount.tsx | 42 ++++++++++++++++--- src/plugins/memberCount/VoiceIcon.tsx | 14 +++++++ src/plugins/memberCount/index.tsx | 17 +++++--- src/plugins/memberCount/style.css | 21 +++++++++- src/plugins/userVoiceShow/components.tsx | 9 ++-- src/plugins/vcNarrator/index.tsx | 15 +++---- src/utils/constants.ts | 4 ++ src/webpack/common/stores.ts | 2 + 10 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 packages/discord-types/src/stores/VoiceStateStore.d.ts create mode 100644 src/plugins/memberCount/VoiceIcon.tsx diff --git a/packages/discord-types/src/stores/VoiceStateStore.d.ts b/packages/discord-types/src/stores/VoiceStateStore.d.ts new file mode 100644 index 00000000..84540d99 --- /dev/null +++ b/packages/discord-types/src/stores/VoiceStateStore.d.ts @@ -0,0 +1,42 @@ +import { DiscordRecord } from "../common"; +import { FluxStore } from "./FluxStore"; + +export type UserVoiceStateRecords = Record<string, VoiceState>; +export type VoiceStates = Record<string, UserVoiceStateRecords>; + +export interface VoiceState extends DiscordRecord { + userId: string; + channelId: string | null | undefined; + sessionId: string | null | undefined; + mute: boolean; + deaf: boolean; + selfMute: boolean; + selfDeaf: boolean; + selfVideo: boolean; + selfStream: boolean | undefined; + suppress: boolean; + requestToSpeakTimestamp: string | null | undefined; + discoverable: boolean; + + isVoiceMuted(): boolean; + isVoiceDeafened(): boolean; +} + +export class VoiceStateStore extends FluxStore { + getAllVoiceStates(): VoiceStates; + + getVoiceStates(guildId?: string | null): UserVoiceStateRecords; + getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; + getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords; + + getVoiceState(guildId: string | null, userId: string): VoiceState | undefined; + getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined; + getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined; + getVoiceStateForUser(userId: string): VoiceState | undefined; + + getCurrentClientVoiceChannelId(guildId: string | null): string | undefined; + isCurrentClientInVoiceChannel(): boolean; + + isInChannel(channelId: string, userId?: string): boolean; + hasVideo(channelId: string): boolean; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index 7a435ff5..5906262a 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -16,6 +16,7 @@ export * from "./ThemeStore"; export * from "./TypingStore"; export * from "./UserProfileStore"; export * from "./UserStore"; +export * from "./VoiceStateStore"; export * from "./WindowStore"; /** diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx index 0a3f5e62..02033886 100644 --- a/src/plugins/memberCount/MemberCount.tsx +++ b/src/plugins/memberCount/MemberCount.tsx @@ -6,16 +6,37 @@ import { getCurrentChannel } from "@utils/discord"; import { isObjectEmpty } from "@utils/misc"; -import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common"; +import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, Tooltip, useEffect, useStateFromStores, VoiceStateStore } from "@webpack/common"; -import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, ThreadMemberListStore } from "."; +import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, settings, ThreadMemberListStore } from "."; import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; +import { VoiceIcon } from "./VoiceIcon"; export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { - const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); + const { voiceActivity } = settings.use(["voiceActivity"]); + const includeVoice = voiceActivity && !isTooltip; + const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id; + const voiceActivityCount = useStateFromStores( + [VoiceStateStore], + () => { + if (!includeVoice) return 0; + + const voiceStates = VoiceStateStore.getVoiceStates(guildId); + if (!voiceStates) return 0; + + return Object.values(voiceStates) + .filter(({ channelId }) => { + if (!channelId) return false; + const channel = ChannelStore.getChannel(channelId); + return channel && PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel); + }) + .length; + } + ); + const totalCount = useStateFromStores( [GuildMemberCountStore], () => GuildMemberCountStore.getMemberCount(guildId) @@ -51,13 +72,14 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t if (totalCount == null) return null; + const formattedVoiceCount = numberFormat(voiceActivityCount ?? 0); const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?"; return ( <div className={cl("widget", { tooltip: isTooltip, "member-list": !isTooltip })}> <Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom"> {props => ( - <div {...props}> + <div {...props} className={cl("container")}> <span className={cl("online-dot")} /> <span className={cl("online")}>{formattedOnlineCount}</span> </div> @@ -65,12 +87,22 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t </Tooltip> <Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom"> {props => ( - <div {...props}> + <div {...props} className={cl("container")}> <span className={cl("total-dot")} /> <span className={cl("total")}>{numberFormat(totalCount)}</span> </div> )} </Tooltip> + {includeVoice && voiceActivityCount > 0 && + <Tooltip text={`${formattedVoiceCount} members in voice`} position="bottom"> + {props => ( + <div {...props} className={cl("container")}> + <VoiceIcon className={cl("voice-icon")} /> + <span className={cl("voice")}>{formattedVoiceCount}</span> + </div> + )} + </Tooltip> + } </div> ); } diff --git a/src/plugins/memberCount/VoiceIcon.tsx b/src/plugins/memberCount/VoiceIcon.tsx new file mode 100644 index 00000000..e76f7151 --- /dev/null +++ b/src/plugins/memberCount/VoiceIcon.tsx @@ -0,0 +1,14 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export function VoiceIcon({ className }: { className?: string; }) { + return ( + <svg viewBox="0 0 32 32" fill="currentColor" className={className}> + <path d="M15.6668 3C14.2523 3 12.8958 3.5619 11.8956 4.5621C10.8954 5.56229 10.3335 6.91884 10.3335 8.33333V13.6666C10.3335 15.0811 10.8954 16.4378 11.8956 17.438C12.8958 18.4381 14.2523 19 15.6668 19C17.0813 19 18.4378 18.4381 19.438 17.438C20.4382 16.4378 21.0001 15.0811 21.0001 13.6666V8.33333C21.0001 6.91884 20.4382 5.56229 19.438 4.5621C18.4378 3.5619 17.0813 3 15.6668 3Z" /> + <path d="M7.66667 13.6666C7.66667 13.313 7.52619 12.9739 7.27614 12.7238C7.02609 12.4738 6.68695 12.3333 6.33333 12.3333C5.97971 12.3333 5.64057 12.4738 5.39052 12.7238C5.14047 12.9739 5 13.313 5 13.6666C4.99911 16.2653 5.94692 18.7749 7.66545 20.7243C9.38399 22.6736 11.7551 23.9285 14.3334 24.2533V27H11.6667C11.3131 27 10.9739 27.1404 10.7239 27.3905C10.4738 27.6405 10.3334 27.9797 10.3334 28.3333C10.3334 28.6869 10.4738 29.0261 10.7239 29.2761C10.9739 29.5262 11.3131 29.6666 11.6667 29.6666H19.6667C20.0203 29.6666 20.3595 29.5262 20.6095 29.2761C20.8596 29.0261 21 28.6869 21 28.3333C21 27.9797 20.8596 27.6405 20.6095 27.3905C20.3595 27.1404 20.0203 27 19.6667 27H17V24.2533C19.5783 23.9285 21.9494 22.6736 23.6679 20.7243C25.3864 18.7749 26.3343 16.2653 26.3334 13.6666C26.3334 13.313 26.1929 12.9739 25.9428 12.7238C25.6928 12.4738 25.3536 12.3333 25 12.3333C24.6464 12.3333 24.3073 12.4738 24.0572 12.7238C23.8072 12.9739 23.6667 13.313 23.6667 13.6666C23.6667 15.7884 22.8238 17.8232 21.3235 19.3235C19.8233 20.8238 17.7884 21.6666 15.6667 21.6666C13.545 21.6666 11.5101 20.8238 10.0098 19.3235C8.50952 17.8232 7.66667 15.7884 7.66667 13.6666Z" /> + </svg> + ); +} diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 7fa6fbcd..bb48eedb 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -37,18 +37,23 @@ export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as F }; -const settings = definePluginSettings({ +export const settings = definePluginSettings({ toolTip: { type: OptionType.BOOLEAN, - description: "If the member count should be displayed on the server tooltip", + description: "Show member count on the server tooltip", default: true, restartNeeded: true }, memberList: { type: OptionType.BOOLEAN, - description: "If the member count should be displayed on the member list", + description: "Show member count in the member list", default: true, restartNeeded: true + }, + voiceActivity: { + type: OptionType.BOOLEAN, + description: "Show voice activity with member count in the member list", + default: true } }); @@ -58,8 +63,8 @@ export const cl = classNameFactory("vc-membercount-"); export default definePlugin({ name: "MemberCount", - description: "Shows the amount of online & total members in the server member list and tooltip", - authors: [Devs.Ven, Devs.Commandtechno], + description: "Shows the number of online members, total members, and users in voice channels on the server — in the member list and tooltip.", + authors: [Devs.Ven, Devs.Commandtechno, Devs.Apexo], settings, patches: [ @@ -82,6 +87,6 @@ export default definePlugin({ predicate: () => settings.store.toolTip } ], - render: ErrorBoundary.wrap(MemberCount, { noop: true }), + render: ErrorBoundary.wrap(() => <MemberCount />, { noop: true }), renderTooltip: ErrorBoundary.wrap(guild => <MemberCount isTooltip tooltipGuildId={guild.id} />, { noop: true }) }); diff --git a/src/plugins/memberCount/style.css b/src/plugins/memberCount/style.css index f43bff83..2b0e4400 100644 --- a/src/plugins/memberCount/style.css +++ b/src/plugins/memberCount/style.css @@ -1,9 +1,11 @@ .vc-membercount-widget { + gap: 0.85em; display: flex; align-content: center; --color-online: var(--green-360); --color-total: var(--primary-400); + --color-voice: var(--primary-400); } .vc-membercount-tooltip { @@ -13,10 +15,17 @@ .vc-membercount-member-list { justify-content: center; + flex-wrap: wrap; margin-top: 1em; padding-inline: 1em; } +.vc-membercount-container { + display: flex; + align-items: center; + gap: 0.5em; +} + .vc-membercount-online { color: var(--color-online); } @@ -25,13 +34,16 @@ color: var(--color-total); } +.vc-membercount-voice { + color: var(--color-voice); +} + .vc-membercount-online-dot { background-color: var(--color-online); display: inline-block; width: 12px; height: 12px; border-radius: 50%; - margin-right: 0.5em; } .vc-membercount-total-dot { @@ -40,5 +52,10 @@ height: 6px; border-radius: 50%; border: 3px solid var(--color-total); - margin: 0 0.5em 0 1em; } + +.vc-membercount-voice-icon { + color: var(--color-voice); + width: 15px; + height: 15px; +} \ No newline at end of file diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index 9d1c7211..b7907978 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -8,8 +8,8 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { Channel } from "@vencord/discord-types"; -import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack"; -import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common"; +import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack"; +import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; const cl = classNameFactory("vc-uvs-"); @@ -18,7 +18,6 @@ const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", { useChannelName: filters.byCode("()=>null==") }); const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({"); -const VoiceStateStore = findStoreLazy("VoiceStateStore"); const Avatar = findComponentByCodeLazy(".status)/2):0"); const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL"); @@ -84,7 +83,7 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) { const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id)); const users = useMemo( - () => Object.values<any>(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null), + () => Object.values(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null), [voiceStates] ); @@ -139,7 +138,7 @@ export interface VoiceChannelIndicatorProps { const clickTimers = {} as Record<string, any>; export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { - const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); + const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId); const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); if (channel == null) return null; diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index 8931efe3..5b7b3f82 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -22,13 +22,12 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { wordsToTitle } from "@utils/text"; import definePlugin, { ReporterTestable } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; -import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common"; +import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore, VoiceStateStore } from "@webpack/common"; import { ReactElement } from "react"; import { getCurrentVoice, settings } from "./settings"; -interface VoiceState { +interface VoiceStateChangeEvent { userId: string; channelId?: string; oldChannelId?: string; @@ -38,8 +37,6 @@ interface VoiceState { selfMute: boolean; } -const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentClientVoiceChannelId"); - // Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying // Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would // not say the second mute, which would lead you to believe they're unmuted @@ -88,7 +85,7 @@ let StatusMap = {} as Record<string, { // for some ungodly reason let myLastChannelId: string | undefined; -function getTypeAndChannelId({ channelId, oldChannelId }: VoiceState, isMe: boolean) { +function getTypeAndChannelId({ channelId, oldChannelId }: VoiceStateChangeEvent, isMe: boolean) { if (isMe && channelId !== myLastChannelId) { oldChannelId = myLastChannelId; myLastChannelId = channelId; @@ -163,7 +160,7 @@ export default definePlugin({ settings, flux: { - VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { + VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceStateChangeEvent[]; }) { const myGuildId = SelectedGuildStore.getGuildId(); const myChanId = SelectedChannelStore.getVoiceChannelId(); const myId = UserStore.getCurrentUser().id; @@ -195,7 +192,7 @@ export default definePlugin({ AUDIO_TOGGLE_SELF_MUTE() { const chanId = SelectedChannelStore.getVoiceChannelId()!; - const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState; + const s = VoiceStateStore.getVoiceStateForChannel(chanId); if (!s) return; const event = s.mute || s.selfMute ? "unmute" : "mute"; @@ -204,7 +201,7 @@ export default definePlugin({ AUDIO_TOGGLE_SELF_DEAF() { const chanId = SelectedChannelStore.getVoiceChannelId()!; - const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState; + const s = VoiceStateStore.getVoiceStateForChannel(chanId); if (!s) return; const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen"; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 705b1451..684f25bb 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -48,6 +48,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "V", id: 343383572805058560n }, + Apexo: { + name: "Apexo", + id: 228548952687902720n + }, Arjix: { name: "ArjixWasTaken", id: 674710789138939916n, diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 472abd4e..b1877b08 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -47,6 +47,7 @@ export let SelectedGuildStore: t.SelectedGuildStore; export let ChannelStore: t.ChannelStore; export let TypingStore: t.TypingStore; export let RelationshipStore: t.RelationshipStore; +export let VoiceStateStore: t.VoiceStateStore; export let EmojiStore: t.EmojiStore; export let StickersStore: t.StickersStore; @@ -79,6 +80,7 @@ waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); waitForStore("StickersStore", m => StickersStore = m); waitForStore("TypingStore", m => TypingStore = m); +waitForStore("VoiceStateStore", m => VoiceStateStore = m); waitForStore("ThemeStore", m => { ThemeStore = m; // Importing this directly can easily cause circular imports. For this reason, use a non import access here. From e857f6806fa181596c160ce867b6b90e8a3895eb Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Fri, 5 Sep 2025 03:55:38 +0200 Subject: [PATCH 106/141] ShowMeYourName: respect streamer mode --- .../discord-types/src/stores/StreamerModeStore.d.ts | 11 +++++++++++ packages/discord-types/src/stores/index.d.ts | 1 + src/plugins/showMeYourName/index.tsx | 6 ++++-- src/webpack/common/stores.ts | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 packages/discord-types/src/stores/StreamerModeStore.d.ts diff --git a/packages/discord-types/src/stores/StreamerModeStore.d.ts b/packages/discord-types/src/stores/StreamerModeStore.d.ts new file mode 100644 index 00000000..efa6cfc9 --- /dev/null +++ b/packages/discord-types/src/stores/StreamerModeStore.d.ts @@ -0,0 +1,11 @@ +import { FluxStore } from "@vencord/discord-types"; + +export class StreamerModeStore extends FluxStore { + get autoToggle(): boolean; + get disableNotifications(): boolean; + get disableSounds(): boolean; + get enableContentProtection(): boolean; + get enabled(): boolean; + get hideInstantInvites(): boolean; + get hidePersonalInformation(): boolean; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts index 5906262a..a843445b 100644 --- a/packages/discord-types/src/stores/index.d.ts +++ b/packages/discord-types/src/stores/index.d.ts @@ -12,6 +12,7 @@ export * from "./RelationshipStore"; export * from "./SelectedChannelStore"; export * from "./SelectedGuildStore"; export * from "./StickersStore"; +export * from "./StreamerModeStore"; export * from "./ThemeStore"; export * from "./TypingStore"; export * from "./UserProfileStore"; diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 623c70ca..24a1121d 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -11,7 +11,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { Channel, Message, User } from "@vencord/discord-types"; -import { RelationshipStore } from "@webpack/common"; +import { RelationshipStore, StreamerModeStore } from "@webpack/common"; interface UsernameProps { author: { nick: string; authorId: string; }; @@ -74,7 +74,9 @@ export default definePlugin({ const { mode, friendNicknames, displayNames, inReplies } = settings.store; const user = userOverride ?? message.author; - let { username } = user; + let username = StreamerModeStore.enabled + ? user.username[0] + "…" + : user.username; if (displayNames) username = user.globalName || username; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index b1877b08..1e3e5797 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -54,6 +54,7 @@ export let StickersStore: t.StickersStore; export let ThemeStore: t.ThemeStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; +export let StreamerModeStore: t.StreamerModeStore; /** * @see jsdoc of {@link t.useStateFromStores} @@ -81,6 +82,7 @@ waitForStore("EmojiStore", m => EmojiStore = m); waitForStore("StickersStore", m => StickersStore = m); waitForStore("TypingStore", m => TypingStore = m); waitForStore("VoiceStateStore", m => VoiceStateStore = m); +waitForStore("StreamerModeStore", m => StreamerModeStore = m); waitForStore("ThemeStore", m => { ThemeStore = m; // Importing this directly can easily cause circular imports. For this reason, use a non import access here. From 26074b7f185013745da0656adb500b3fbe95fa4f Mon Sep 17 00:00:00 2001 From: u32 <u32@u32.uk> Date: Fri, 5 Sep 2025 03:04:08 +0100 Subject: [PATCH 107/141] MessageLatency: fix bot check (#3523) --- src/plugins/messageLatency/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index cd331b9d..b81c55cf 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -98,7 +98,7 @@ export default definePlugin({ if (!isNonNullish(nonce)) return null; // Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake - if (message.bot) return null; + if (message.author.bot) return null; let isDiscordKotlin = false; let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds From 1cfc3fb8f808c5770c0175a2471c7eb5525800fd Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Sat, 6 Sep 2025 12:27:29 -0400 Subject: [PATCH 108/141] fix OnePingPerDM (#3648) also removes a now obsolete patch from FakeNitro --- src/plugins/fakeNitro/index.tsx | 3 +-- src/plugins/onePingPerDM/index.ts | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 8dcb2300..2988f8fb 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -159,7 +159,6 @@ function makeBypassPatches(): Omit<Patch, "plugin"> { { func: "canUseHighVideoUploadQuality", predicate: () => settings.store.enableStreamQualityBypass }, { func: "canStreamQuality", predicate: () => settings.store.enableStreamQualityBypass }, { func: "canUseClientThemes" }, - { func: "canUseCustomNotificationSounds" }, { func: "canUsePremiumAppIcons" } ]; @@ -176,7 +175,7 @@ function makeBypassPatches(): Omit<Patch, "plugin"> { export default definePlugin({ name: "FakeNitro", authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN], - description: "Allows you to stream in nitro quality, send fake emojis/stickers, use client themes and custom Discord notifications.", + description: "Allows you to stream in nitro quality, send fake emojis/stickers, and use client themes.", dependencies: ["MessageEventsAPI"], settings, diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts index b6192fb6..fae787a8 100644 --- a/src/plugins/onePingPerDM/index.ts +++ b/src/plugins/onePingPerDM/index.ts @@ -38,17 +38,21 @@ export default definePlugin({ 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], settings, - patches: [{ - find: ".getDesktopType()===", - replacement: [{ - match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/, - replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else " - }, + patches: [ { - match: /sound:(\i\?\i:void 0,soundpack:\i,volume:\i,onClick)/, - replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1" - }] - }], + find: ".getDesktopType()===", + replacement: [ + { + match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/, + replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else " + }, + { + match: /sound:(\i\?\i:void 0,volume:\i,onClick)/, + replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1" + } + ] + } + ], isPrivateChannelRead(message: MessageJSON) { const channelType = ChannelStore.getChannel(message.channel_id)?.type; if ( From efecbae75b17f8b1ccb72da8e6c67642f2bb87af Mon Sep 17 00:00:00 2001 From: nin0 <personal@nin0.dev> Date: Sun, 7 Sep 2025 20:14:38 -0400 Subject: [PATCH 109/141] LastFMRPC: add setting to show artist/song name in member list (#3629) --- src/plugins/lastfmRichPresence/index.tsx | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx index 77fa2784..abf42d5f 100644 --- a/src/plugins/lastfmRichPresence/index.tsx +++ b/src/plugins/lastfmRichPresence/index.tsx @@ -47,6 +47,7 @@ interface Activity { buttons?: Array<string>; name: string; application_id: string; + status_display_type?: number; metadata?: { button_urls?: Array<string>; }; @@ -134,6 +135,25 @@ const settings = definePluginSettings({ type: OptionType.STRING, default: "some music", }, + statusDisplayType: { + description: "Show the track / artist name in the member list", + type: OptionType.SELECT, + options: [ + { + label: "Don't show (shows generic listening message)", + value: "off", + default: true + }, + { + label: "Show artist name", + value: "artist" + }, + { + label: "Show track name", + value: "track" + } + ] + }, nameFormat: { description: "Show name of song and artist in status name", type: OptionType.SELECT, @@ -346,6 +366,11 @@ export default definePlugin({ details: trackData.name, state: trackData.artist, + status_display_type: { + "off": 0, + "artist": 1, + "track": 2 + }[settings.store.statusDisplayType], assets, buttons: buttons.length ? buttons.map(v => v.label) : undefined, From b225f2ec6cbb29f565bc3379254cd846cb259e2e Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 8 Sep 2025 02:51:04 +0200 Subject: [PATCH 110/141] SpotifyControls: add copy song/artist/album name options --- .../spotifyControls/PlayerComponent.tsx | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 32c78c36..dabd31dc 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -21,7 +21,7 @@ import "./spotifyStyles.css"; import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; -import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; +import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { debounce } from "@shared/debounce"; import { openImageModal } from "@utils/discord"; import { classes, copyWithToast } from "@utils/misc"; @@ -76,27 +76,28 @@ function Button(props: React.ButtonHTMLAttributes<HTMLButtonElement>) { ); } -function CopyContextMenu({ name, path }: { name: string; path: string; }) { - const copyId = `spotify-copy-${name}`; - const openId = `spotify-open-${name}`; - +function CopyContextMenu({ name, type, path }: { type: string; name: string; path: string; }) { return ( <Menu.Menu - navId={`spotify-${name}-menu`} - onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label={`Spotify ${name} Menu`} + navId="vc-spotify-menu" + onClose={ContextMenuApi.closeContextMenu} + aria-label={`Spotify ${type} Menu`} > <Menu.MenuItem - key={copyId} - id={copyId} - label={`Copy ${name} Link`} + id="vc-spotify-copy-name" + label={`Copy ${type} Name`} + action={() => copyWithToast(name)} + icon={CopyIcon} + /> + <Menu.MenuItem + id="vc-spotify-copy-link" + label={`Copy ${type} Link`} action={() => copyWithToast("https://open.spotify.com" + path)} icon={LinkIcon} /> <Menu.MenuItem - key={openId} - id={openId} - label={`Open ${name} in Spotify`} + id="vc-spotify-open" + label={`Open ${type} in Spotify`} action={() => SpotifyStore.openExternal(path)} icon={OpenExternalIcon} /> @@ -104,11 +105,6 @@ function CopyContextMenu({ name, path }: { name: string; path: string; }) { ); } -function makeContextMenu(name: string, path: string) { - return (e: React.MouseEvent<HTMLElement, MouseEvent>) => - ContextMenuApi.openContextMenu(e, () => <CopyContextMenu name={name} path={path} />); -} - function Controls() { const [isPlaying, shuffle, repeat] = useStateFromStores( [SpotifyStore], @@ -259,13 +255,14 @@ function AlbumContextMenu({ track }: { track: Track; }) { ); } -function makeLinkProps(name: string, condition: unknown, path: string) { +function makeLinkProps(type: "Song" | "Artist" | "Album", condition: unknown, name: string, path: string) { if (!condition) return {}; return { role: "link", onClick: () => SpotifyStore.openExternal(path), - onContextMenu: makeContextMenu(name, path) + onContextMenu: e => + ContextMenuApi.openContextMenu(e, () => <CopyContextMenu type={type} name={name} path={path} />) } satisfies React.HTMLAttributes<HTMLElement>; } @@ -306,7 +303,7 @@ function Info({ track }: { track: Track; }) { id={cl("song-title")} className={cl("ellipoverflow")} title={track.name} - {...makeLinkProps("Song", track.id, `/track/${track.id}`)} + {...makeLinkProps("Song", track.id, track.name, `/track/${track.id}`)} > {track.name} </Forms.FormText> @@ -319,7 +316,7 @@ function Info({ track }: { track: Track; }) { className={cl("artist")} style={{ fontSize: "inherit" }} title={a.name} - {...makeLinkProps("Artist", a.id, `/artist/${a.id}`)} + {...makeLinkProps("Artist", a.id, a.name, `/artist/${a.id}`)} > {a.name} </span> @@ -336,7 +333,7 @@ function Info({ track }: { track: Track; }) { className={cl("album")} style={{ fontSize: "inherit" }} title={track.album.name} - {...makeLinkProps("Album", track.album.id, `/album/${track.album.id}`)} + {...makeLinkProps("Album", track.album.id, track.album.name, `/album/${track.album.id}`)} > {track.album.name} </span> From a4e1d026ea3d630ec990882fcc225f4e7f28ee03 Mon Sep 17 00:00:00 2001 From: ww <153429803+q1werasd1@users.noreply.github.com> Date: Mon, 8 Sep 2025 04:30:08 +0300 Subject: [PATCH 111/141] VolumeBooster: make multiplier option more flexible (#3656) --- src/plugins/volumeBooster/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts index 664a59dd..81dcf663 100644 --- a/src/plugins/volumeBooster/index.ts +++ b/src/plugins/volumeBooster/index.ts @@ -24,7 +24,7 @@ const settings = definePluginSettings({ multiplier: { description: "Volume Multiplier", type: OptionType.SLIDER, - markers: makeRange(1, 5, 1), + markers: makeRange(1, 5, 0.5), default: 2, stickToMarkers: true, } From 84957b0e887fe1fa68bc6bf3325b44d1ba07a3f1 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:49:24 +0000 Subject: [PATCH 112/141] improve `wordsFromCamel` correctness (#3621) Co-authored-by: V <vendicated@riseup.net> --- src/utils/text.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/text.ts b/src/utils/text.ts index 2e85af4e..a48c140d 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -21,7 +21,8 @@ import { moment } from "@webpack/common"; // Utils for readable text transformations eg: `toTitle(fromKebab())` // Case style to words -export const wordsFromCamel = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase()); +export const wordsFromCamel = (text: string) => + text.split(/(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).map(w => /^[A-Z]{2,}$/.test(w) ? w : w.toLowerCase()); export const wordsFromSnake = (text: string) => text.toLowerCase().split("_"); export const wordsFromKebab = (text: string) => text.toLowerCase().split("-"); export const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase()); From c38aac23fd1e1f4acf171479fe4beb17d8fe4ae9 Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Tue, 9 Sep 2025 01:48:53 +0200 Subject: [PATCH 113/141] improve various types (#3663) Co-authored-by: V <vendicated@riseup.net> Co-authored-by: John Davis <70701251+gobuster@users.noreply.github.com> Co-authored-by: sadan4 <117494111+sadan4@users.noreply.github.com> --- packages/discord-types/enums/index.ts | 3 +- packages/discord-types/enums/misc.ts | 4 + packages/discord-types/src/index.d.ts | 1 + .../src/modules/CloudUpload.d.ts | 74 +++++++++++++++++++ packages/discord-types/src/modules/index.d.ts | 1 + src/api/MessageEvents.ts | 28 +------ src/api/{Notices.ts => Notices.tsx} | 11 ++- src/plugins/anonymiseFileNames/index.tsx | 6 +- src/plugins/previewMessage/index.tsx | 6 +- src/plugins/voiceMessages/index.tsx | 8 +- src/utils/discord.tsx | 16 +++- src/utils/react.tsx | 10 ++- 12 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 packages/discord-types/enums/misc.ts create mode 100644 packages/discord-types/src/modules/CloudUpload.d.ts create mode 100644 packages/discord-types/src/modules/index.d.ts rename src/api/{Notices.ts => Notices.tsx} (73%) diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index 0ab49887..3d80895c 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1,3 +1,4 @@ +export * from "./channel"; export * from "./commands"; export * from "./messages"; -export * from "./channel"; \ No newline at end of file +export * from "./misc"; diff --git a/packages/discord-types/enums/misc.ts b/packages/discord-types/enums/misc.ts new file mode 100644 index 00000000..d1419ba3 --- /dev/null +++ b/packages/discord-types/enums/misc.ts @@ -0,0 +1,4 @@ +export const enum CloudUploadPlatform { + REACT_NATIVE = 0, + WEB = 1, +} diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index 6d9356b4..fdbd65f1 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -4,6 +4,7 @@ export * from "./components"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; +export * from "./modules"; export * from "./stores"; export * from "./utils"; export * as Webpack from "../webpack"; diff --git a/packages/discord-types/src/modules/CloudUpload.d.ts b/packages/discord-types/src/modules/CloudUpload.d.ts new file mode 100644 index 00000000..02192ed5 --- /dev/null +++ b/packages/discord-types/src/modules/CloudUpload.d.ts @@ -0,0 +1,74 @@ +import EventEmitter from "events"; +import { CloudUploadPlatform } from "../../enums"; + +interface BaseUploadItem { + platform: CloudUploadPlatform; + id?: string; + origin?: string; + isThumbnail?: boolean; + clip?: unknown; +} + +export interface ReactNativeUploadItem extends BaseUploadItem { + platform: CloudUploadPlatform.REACT_NATIVE; + uri: string; + filename?: string; + mimeType?: string; + durationSecs?: number; + waveform?: string; + isRemix?: boolean; +} + +export interface WebUploadItem extends BaseUploadItem { + platform: CloudUploadPlatform.WEB; + file: File; +} + +export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem; + +export class CloudUpload extends EventEmitter { + constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number); + + channelId: string; + classification: string; + clip: unknown; + contentHash: unknown; + currentSize: number; + description: string | null; + durationSecs: number | undefined; + etag: string | undefined; + error: unknown; + filename: string; + id: string; + isImage: boolean; + isRemix: boolean | undefined; + isThumbnail: boolean; + isVideo: boolean; + item: { + file: File; + platform: CloudUploadPlatform; + origin: string; + }; + loaded: number; + mimeType: string; + origin: string; + postCompressionSize: number | undefined; + preCompressionSize: number; + responseUrl: string; + sensitive: boolean; + showLargeMessageDialog: boolean; + spoiler: boolean; + startTime: number; + status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT"; + uniqueId: string; + uploadedFilename: string; + waveform: string | undefined; + + // there are many more methods than just these but I didn't find them particularly useful + upload(): Promise<void>; + cancel(): void; + delete(): Promise<void>; + getSize(): number; + maybeConvertToWebP(): Promise<void>; + removeFromMsgDraft(): void; +} diff --git a/packages/discord-types/src/modules/index.d.ts b/packages/discord-types/src/modules/index.d.ts new file mode 100644 index 00000000..fe0c1e24 --- /dev/null +++ b/packages/discord-types/src/modules/index.d.ts @@ -0,0 +1 @@ +export * from "./CloudUpload"; diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 6e752c90..54d40000 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -17,7 +17,7 @@ */ import { Logger } from "@utils/Logger"; -import type { Channel, CustomEmoji, Message } from "@vencord/discord-types"; +import type { Channel, CloudUpload, CustomEmoji, Message } from "@vencord/discord-types"; import { MessageStore } from "@webpack/common"; import type { Promisable } from "type-fest"; @@ -30,30 +30,6 @@ export interface MessageObject { tts: boolean; } -export interface Upload { - classification: string; - currentSize: number; - description: string | null; - filename: string; - id: string; - isImage: boolean; - isVideo: boolean; - item: { - file: File; - platform: number; - }; - loaded: number; - mimeType: string; - preCompressionSize: number; - responseUrl: string; - sensitive: boolean; - showLargeMessageDialog: boolean; - spoiler: boolean; - status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED"; - uniqueId: string; - uploadedFilename: string; -} - export interface MessageReplyOptions { messageReference: Message["messageReference"]; allowedMentions?: { @@ -64,7 +40,7 @@ export interface MessageReplyOptions { export interface MessageOptions { stickers?: string[]; - uploads?: Upload[]; + uploads?: CloudUpload[]; replyOptions: MessageReplyOptions; content: string; channel: Channel; diff --git a/src/api/Notices.ts b/src/api/Notices.tsx similarity index 73% rename from src/api/Notices.ts rename to src/api/Notices.tsx index 6d20087a..b4c44b88 100644 --- a/src/api/Notices.ts +++ b/src/api/Notices.tsx @@ -16,7 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import ErrorBoundary from "@components/ErrorBoundary"; +import { isPrimitiveReactNode } from "@utils/react"; import { waitFor } from "@webpack"; +import { ReactNode } from "react"; let NoticesModule: any; waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m); @@ -36,7 +39,11 @@ export function nextNotice() { } } -export function showNotice(message: string, buttonText: string, onOkClick: () => void) { - noticesQueue.push(["GENERIC", message, buttonText, onOkClick]); +export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) { + const notice = isPrimitiveReactNode(message) + ? message + : <ErrorBoundary fallback={() => "Error Showing Notice"}>{message}</ErrorBoundary>; + + noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]); if (!currentNotice) nextNotice(); } diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 8237be8b..62cd7796 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -16,11 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Upload } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { CloudUpload } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { useState } from "@webpack/common"; @@ -89,7 +89,7 @@ export default definePlugin({ }, ], - AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: Upload; }) => { + AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: CloudUpload; }) => { const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault); function onToggleAnonymise() { @@ -110,7 +110,7 @@ export default definePlugin({ ); }, { noop: true }), - anonymise(upload: Upload) { + anonymise(upload: CloudUpload) { if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) { return; } diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index 1f64c714..6bc9d25f 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -20,7 +20,7 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; -import { MessageAttachment } from "@vencord/discord-types"; +import { CloudUpload, MessageAttachment } from "@vencord/discord-types"; import { findByPropsLazy } from "@webpack"; import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; @@ -45,7 +45,7 @@ const getImageBox = (url: string): Promise<{ width: number, height: number; } | const getAttachments = async (channelId: string) => await Promise.all( UploadStore.getUploads(channelId, DraftType.ChannelMessage) - .map(async (upload: any) => { + .map(async (upload: CloudUpload) => { const { isImage, filename, spoiler, item: { file } } = upload; const url = URL.createObjectURL(file); const attachment: MessageAttachment = { @@ -53,7 +53,7 @@ const getAttachments = async (channelId: string) => filename: spoiler ? "SPOILER_" + filename : filename, // weird eh? if i give it the normal content type the preview doenst work content_type: undefined, - size: await upload.getSize(), + size: upload.getSize(), spoiler, // discord adds query params to the url, so we need to add a hash to prevent that url: url + "#", diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 74f39ea3..ddb395e4 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -27,6 +27,8 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { chooseFile } from "@utils/web"; +import { CloudUpload as TCloudUpload } from "@vencord/discord-types"; +import { CloudUploadPlatform } from "@vencord/discord-types/enums"; import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; @@ -37,7 +39,7 @@ import { cl } from "./utils"; import { VoicePreview } from "./VoicePreview"; import { VoiceRecorderWeb } from "./WebRecorder"; -const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished); +const CloudUpload: typeof TCloudUpload = findLazy(m => m.prototype?.trackUploadFinished); const PendingReplyStore = findStoreLazy("PendingReplyStore"); const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel"); @@ -92,8 +94,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) { const upload = new CloudUpload({ file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }), isThumbnail: false, - platform: 1, - }, channelId, false, 0); + platform: CloudUploadPlatform.WEB, + }, channelId); upload.on("complete", () => { RestAPI.post({ diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index f236943b..83713625 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,7 +17,7 @@ */ import { MessageObject } from "@api/MessageEvents"; -import { Channel, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; +import { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types"; import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Except } from "type-fest"; @@ -117,6 +117,20 @@ interface MessageOptions { replied_user: boolean; }; stickerIds: string[]; + attachmentsToUpload: CloudUpload[]; + poll: { + allow_multiselect: boolean; + answers: Array<{ + poll_media: { + text: string; + attachment_ids?: unknown; + emoji?: { name: string; id?: string; }; + }; + }>; + duration: number; + layout_type: number; + question: { text: string; }; + }; } export function sendMessage( diff --git a/src/utils/react.tsx b/src/utils/react.tsx index 146725c5..8963bdf5 100644 --- a/src/utils/react.tsx +++ b/src/utils/react.tsx @@ -17,7 +17,7 @@ */ import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; -import { ActionDispatch } from "react"; +import { ActionDispatch, ReactNode } from "react"; import { checkIntersecting } from "./misc"; @@ -25,6 +25,14 @@ export * from "./lazyReact"; export const NoopComponent = () => null; +/** + * Check if a React node is a primitive (string, number, bigint, boolean, undefined) + */ +export function isPrimitiveReactNode(node: ReactNode): boolean { + const t = typeof node; + return t === "string" || t === "number" || t === "bigint" || t === "boolean" || t === "undefined"; +} + /** * Check if an element is on screen * @param intersectOnly If `true`, will only update the state when the element comes into view From 0f29eab3ea668b4d1dca39bee827b3e5e938d97d Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:03:12 -0400 Subject: [PATCH 114/141] MemberCount: use circle svg instead of css hacks (#3653) fixes some deformations --- src/plugins/memberCount/CircleIcon.tsx | 17 +++++++++++++++++ src/plugins/memberCount/MemberCount.tsx | 5 +++-- src/plugins/memberCount/index.tsx | 1 - src/plugins/memberCount/style.css | 23 +++++++++++------------ 4 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 src/plugins/memberCount/CircleIcon.tsx diff --git a/src/plugins/memberCount/CircleIcon.tsx b/src/plugins/memberCount/CircleIcon.tsx new file mode 100644 index 00000000..bf6604a4 --- /dev/null +++ b/src/plugins/memberCount/CircleIcon.tsx @@ -0,0 +1,17 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export function CircleIcon({ className }: { className?: string; }) { + return ( + <svg viewBox="0 0 24 24" className={className}> + <circle + cx="12" + cy="12" + r="8" + /> + </svg> + ); +} diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx index 02033886..6887af51 100644 --- a/src/plugins/memberCount/MemberCount.tsx +++ b/src/plugins/memberCount/MemberCount.tsx @@ -9,6 +9,7 @@ import { isObjectEmpty } from "@utils/misc"; import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, Tooltip, useEffect, useStateFromStores, VoiceStateStore } from "@webpack/common"; import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, settings, ThreadMemberListStore } from "."; +import { CircleIcon } from "./CircleIcon"; import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; import { VoiceIcon } from "./VoiceIcon"; @@ -80,7 +81,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t <Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom"> {props => ( <div {...props} className={cl("container")}> - <span className={cl("online-dot")} /> + <CircleIcon className={cl("online-count")} /> <span className={cl("online")}>{formattedOnlineCount}</span> </div> )} @@ -88,7 +89,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t <Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom"> {props => ( <div {...props} className={cl("container")}> - <span className={cl("total-dot")} /> + <CircleIcon className={cl("total-count")} /> <span className={cl("total")}>{numberFormat(totalCount)}</span> </div> )} diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index bb48eedb..e2d78e2b 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -36,7 +36,6 @@ export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as F getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; }; - export const settings = definePluginSettings({ toolTip: { type: OptionType.BOOLEAN, diff --git a/src/plugins/memberCount/style.css b/src/plugins/memberCount/style.css index 2b0e4400..009e7d10 100644 --- a/src/plugins/memberCount/style.css +++ b/src/plugins/memberCount/style.css @@ -18,6 +18,7 @@ flex-wrap: wrap; margin-top: 1em; padding-inline: 1em; + line-height: 1.2em; } .vc-membercount-container { @@ -38,20 +39,18 @@ color: var(--color-voice); } -.vc-membercount-online-dot { - background-color: var(--color-online); - display: inline-block; - width: 12px; - height: 12px; - border-radius: 50%; +.vc-membercount-online-count { + fill: var(--status-online); + width: 18px; + height: 18px; } -.vc-membercount-total-dot { - display: inline-block; - width: 6px; - height: 6px; - border-radius: 50%; - border: 3px solid var(--color-total); +.vc-membercount-total-count { + fill: none; + stroke: var(--status-offline); + stroke-width: 4px; + width: 15px; + height: 15px; } .vc-membercount-voice-icon { From 4c7acbbbc79a69416288ce0aef84af9ca5c4d444 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 5 Sep 2025 14:57:43 -0400 Subject: [PATCH 115/141] fix NoPendingCount --- src/plugins/noPendingCount/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts index 914deaa0..2d860dcf 100644 --- a/src/plugins/noPendingCount/index.ts +++ b/src/plugins/noPendingCount/index.ts @@ -87,7 +87,7 @@ export default definePlugin({ replacement: { // The two groups inside the first group grab the minified names of the variables, // they are then referenced later to find unviewedTrialCount + unviewedDiscountCount. - match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300}\i=)\1\+\2/, + match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300})\1\+\2/, replace: "0" } } From dc72ee3809d764982059e2fa60948aeb49bd3aa6 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 9 Sep 2025 02:23:57 +0200 Subject: [PATCH 116/141] GameActivityToggle: fix overflow --- src/plugins/gameActivityToggle/style.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/gameActivityToggle/style.css b/src/plugins/gameActivityToggle/style.css index e13e2425..040212ce 100644 --- a/src/plugins/gameActivityToggle/style.css +++ b/src/plugins/gameActivityToggle/style.css @@ -1,3 +1,4 @@ -[class^="panels"] [class^="avatarWrapper"] { - min-width: 88px; -} +/* make gap smaller since we're adding an extra button */ +[class^="panels"] [class^="buttons"] { + gap: 4px; +} \ No newline at end of file From 51c23ff7968fa9d15863a8fbf806d7ffdbfe5300 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:51:03 -0400 Subject: [PATCH 117/141] FakeNitro: Add custom client theme color picker (#3534) --- src/plugins/fakeNitro/index.tsx | 35 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 2988f8fb..370d6857 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -174,8 +174,8 @@ function makeBypassPatches(): Omit<Patch, "plugin"> { export default definePlugin({ name: "FakeNitro", - authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN], - description: "Allows you to stream in nitro quality, send fake emojis/stickers, and use client themes.", + authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN, Devs.sadan], + description: "Allows you to send fake emojis/stickers, use nitro themes, and stream in nitro quality", dependencies: ["MessageEventsAPI"], settings, @@ -273,6 +273,14 @@ export default definePlugin({ replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});` } }, + // Allow users to use custom client themes + { + find: "customUserThemeSettings:{", + replacement: { + match: /(?<=\i=)\(0,\i\.\i\)\(\i\.\i\.TIER_2\)(?=,|;)/g, + replace: "true" + } + }, { find: '["strong","em","u","text","inlineCode","s","spoiler"]', replacement: [ @@ -386,24 +394,15 @@ export default definePlugin({ if (premiumType !== 2) { proto.appearance ??= AppearanceSettingsActionCreators.create(); - if (UserSettingsProtoStore.settings.appearance?.theme != null) { - const appearanceSettingsDummy = AppearanceSettingsActionCreators.create({ - theme: UserSettingsProtoStore.settings.appearance.theme - }); + const protoStoreAppearenceSettings = UserSettingsProtoStore.settings.appearance; - proto.appearance.theme = appearanceSettingsDummy.theme; - } + const appearanceSettingsOverwrite = AppearanceSettingsActionCreators.create({ + ...proto.appearance, + theme: protoStoreAppearenceSettings?.theme, + clientThemeSettings: protoStoreAppearenceSettings?.clientThemeSettings + }); - if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) { - const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({ - backgroundGradientPresetId: { - value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value - } - }); - - proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy; - proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId; - } + proto.appearance = appearanceSettingsOverwrite; } } catch (err) { new Logger("FakeNitro").error(err); From 4e8d22b4d56e3646c67d30b692b3f5c1407b5919 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:01:16 -0300 Subject: [PATCH 118/141] NoPendingCount: Improve slow patch --- src/plugins/noPendingCount/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts index 2d860dcf..1e963d9f 100644 --- a/src/plugins/noPendingCount/index.ts +++ b/src/plugins/noPendingCount/index.ts @@ -87,8 +87,8 @@ export default definePlugin({ replacement: { // The two groups inside the first group grab the minified names of the variables, // they are then referenced later to find unviewedTrialCount + unviewedDiscountCount. - match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300})\1\+\2/, - replace: "0" + match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?\i)=\1\+\2/, + replace: (_, rest) => `${rest}=0` } } ], From 50eb62045a329a8e1208ebbe26bb4ff232538f4d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:18:39 -0300 Subject: [PATCH 119/141] Fix FavoriteGifSearch & broken Menu component find --- src/plugins/favGifSearch/index.tsx | 8 +------- src/webpack/common/menu.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index d88ced34..7ab0145e 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -20,13 +20,11 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; import { useCallback, useEffect, useRef, useState } from "@webpack/common"; interface SearchBarComponentProps { - ref?: React.MutableRefObject<any>; + ref?: React.RefObject<any>; autoFocus: boolean; - className: string; size: string; onChange: (query: string) => void; onClear: () => void; @@ -59,9 +57,6 @@ interface Instance { forceUpdate: () => void; } - -const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow"); - export const settings = definePluginSettings({ searchOption: { type: OptionType.SELECT, @@ -181,7 +176,6 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc <SearchBarComponent ref={ref} autoFocus={true} - className={containerClasses.searchBar} size="md" onChange={onChange} onClear={() => { diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index e4c5d0a3..7e5447ce 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -42,7 +42,7 @@ waitFor(m => m.name === "MenuCheckboxItem", (_, id) => { waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m); waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m); -waitFor(filters.componentByCode('role:"searchbox', "top:2", "query:"), m => Menu.MenuSearchControl = m); +waitFor(filters.componentByCode(".SEARCH)", ".focus()", "query:"), m => Menu.MenuSearchControl = m); export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', { closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"), From 98058f0caec5409be6b2f7529f67d20ea2e0801b Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:27:41 -0300 Subject: [PATCH 120/141] Fix mistakes --- src/plugins/favGifSearch/index.tsx | 2 +- src/plugins/noPendingCount/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index 7ab0145e..2e0ea651 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -131,7 +131,7 @@ export default definePlugin({ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; SearchBarComponent: TSearchBarComponent; }) { const [query, setQuery] = useState(""); - const ref = useRef<{ containerRef?: React.MutableRefObject<HTMLDivElement>; } | null>(null); + const ref = useRef<{ containerRef?: React.RefObject<HTMLDivElement>; } | null>(null); const onChange = useCallback((searchQuery: string) => { setQuery(searchQuery); diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts index 1e963d9f..b1a7ffad 100644 --- a/src/plugins/noPendingCount/index.ts +++ b/src/plugins/noPendingCount/index.ts @@ -87,8 +87,8 @@ export default definePlugin({ replacement: { // The two groups inside the first group grab the minified names of the variables, // they are then referenced later to find unviewedTrialCount + unviewedDiscountCount. - match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?\i)=\1\+\2/, - replace: (_, rest) => `${rest}=0` + match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?)\2\+\3/, + replace: (_, rest) => `${rest}0` } } ], From 8eabb1112577eef13369c3bf8d78f60937653958 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:35:01 -0400 Subject: [PATCH 121/141] ClearURLs: use rules from ClearURLs browser extension (#3657) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/clearURLs/README.md | 11 ++ src/plugins/clearURLs/defaultRules.ts | 159 -------------------------- src/plugins/clearURLs/index.ts | 147 ++++++++++++------------ src/utils/constants.ts | 4 + 4 files changed, 90 insertions(+), 231 deletions(-) create mode 100644 src/plugins/clearURLs/README.md delete mode 100644 src/plugins/clearURLs/defaultRules.ts diff --git a/src/plugins/clearURLs/README.md b/src/plugins/clearURLs/README.md new file mode 100644 index 00000000..43a453d1 --- /dev/null +++ b/src/plugins/clearURLs/README.md @@ -0,0 +1,11 @@ +# ClearURLs + +Automatically removes tracking elements from URLs you send. + +Uses data from the [ClearURLs browser extension](https://clearurls.xyz/). + +## Example + +**Before:** `https://www.amazon.com/dp/exampleProduct/ref=sxin_0_pb?__mk_de_DE=ÅMÅŽÕÑ&keywords=tea&pd_rd_i=exampleProduct&pd_rd_r=8d39e4cd-1e4f-43db-b6e7-72e969a84aa5&pd_rd_w=1pcKM&pd_rd_wg=hYrNl&pf_rd_p=50bbfd25-5ef7-41a2-68d6-74d854b30e30&pf_rd_r=0GMWD0YYKA7XFGX55ADP&qid=1517757263&rnid=2914120011` + +**After:** `https://www.amazon.com/dp/exampleProduct/` diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts deleted file mode 100644 index e4e7d380..00000000 --- a/src/plugins/clearURLs/defaultRules.ts +++ /dev/null @@ -1,159 +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/>. -*/ - -export const defaultRules = [ - "action_object_map", - "action_type_map", - "action_ref_map", - "spm@*.aliexpress.com", - "scm@*.aliexpress.com", - "aff_platform", - "aff_trace_key", - "algo_expid@*.aliexpress.*", - "algo_pvid@*.aliexpress.*", - "btsid", - "ws_ab_test", - "pd_rd_*@amazon.*", - "_encoding@amazon.*", - "psc@amazon.*", - "tag@amazon.*", - "ref_@amazon.*", - "pf_rd_*@amazon.*", - "pf@amazon.*", - "crid@amazon.*", - "keywords@amazon.*", - "sprefix@amazon.*", - "sr@amazon.*", - "ie@amazon.*", - "node@amazon.*", - "qid@amazon.*", - "callback@bilibili.com", - "cvid@bing.com", - "form@bing.com", - "sk@bing.com", - "sp@bing.com", - "sc@bing.com", - "qs@bing.com", - "pq@bing.com", - "sc_cid", - "mkt_tok", - "trk", - "trkCampaign", - "ga_*", - "gclid", - "gclsrc", - "hmb_campaign", - "hmb_medium", - "hmb_source", - "spReportId", - "spJobID", - "spUserID", - "spMailingID", - "itm_*", - "s_cid", - "elqTrackId", - "elqTrack", - "assetType", - "assetId", - "recipientId", - "campaignId", - "siteId", - "mc_cid", - "mc_eid", - "pk_*", - "sc_campaign", - "sc_channel", - "sc_content", - "sc_medium", - "sc_outcome", - "sc_geo", - "sc_country", - "nr_email_referer", - "vero_conv", - "vero_id", - "yclid", - "_openstat", - "mbid", - "cmpid", - "cid", - "c_id", - "campaign_id", - "Campaign", - "hash@ebay.*", - "fb_action_ids", - "fb_action_types", - "fb_ref", - "fb_source", - "fbclid", - "refsrc@facebook.com", - "hrc@facebook.com", - "gs_l", - "gs_lcp@google.*", - "ved@google.*", - "ei@google.*", - "sei@google.*", - "gws_rd@google.*", - "gs_gbg@google.*", - "gs_mss@google.*", - "gs_rn@google.*", - "_hsenc", - "_hsmi", - "__hssc", - "__hstc", - "hsCtaTracking", - "source@sourceforge.net", - "position@sourceforge.net", - "t@*.twitter.com", - "s@*.twitter.com", - "ref_*@*.twitter.com", - "t@*.x.com", - "s@*.x.com", - "ref_*@*.x.com", - "t@*.fixupx.com", - "s@*.fixupx.com", - "ref_*@*.fixupx.com", - "t@*.fxtwitter.com", - "s@*.fxtwitter.com", - "ref_*@*.fxtwitter.com", - "t@*.twittpr.com", - "s@*.twittpr.com", - "ref_*@*.twittpr.com", - "t@*.fixvx.com", - "s@*.fixvx.com", - "ref_*@*.fixvx.com", - "tt_medium", - "tt_content", - "lr@yandex.*", - "redircnt@yandex.*", - "feature@*.youtube.com", - "kw@*.youtube.com", - "si@*.youtube.com", - "pp@*.youtube.com", - "si@*.youtu.be", - "wt_zmc", - "utm_source", - "utm_content", - "utm_medium", - "utm_campaign", - "utm_term", - "si@open.spotify.com", - "igshid", - "igsh", - "share_id@reddit.com", - "si@soundcloud.com", -]; diff --git a/src/plugins/clearURLs/index.ts b/src/plugins/clearURLs/index.ts index f00c751d..36270607 100644 --- a/src/plugins/clearURLs/index.ts +++ b/src/plugins/clearURLs/index.ts @@ -22,77 +22,74 @@ import { import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { defaultRules } from "./defaultRules"; +const CLEAR_URLS_JSON_URL = "https://raw.githubusercontent.com/ClearURLs/Rules/master/data.min.json"; -// From lodash -const reRegExpChar = /[\\^$.*+?()[\]{}|]/g; -const reHasRegExpChar = RegExp(reRegExpChar.source); +interface Provider { + urlPattern: string; + completeProvider: boolean; + rules?: string[]; + rawRules?: string[]; + referralMarketing?: string[]; + exceptions?: string[]; + redirections?: string[]; + forceRedirection?: boolean; +} + +interface ClearUrlsData { + providers: Record<string, Provider>; +} + +interface RuleSet { + name: string; + urlPattern: RegExp; + rules?: RegExp[]; + rawRules?: RegExp[]; + exceptions?: RegExp[]; +} export default definePlugin({ name: "ClearURLs", - description: "Removes tracking garbage from URLs", - authors: [Devs.adryd], + description: "Automatically removes tracking elements from URLs you send", + authors: [Devs.adryd, Devs.thororen], - start() { - this.createRules(); + rules: [] as RuleSet[], + + async start() { + await this.createRules(); + }, + + stop() { + this.rules = []; }, onBeforeMessageSend(_, msg) { - return this.onSend(msg); + return this.cleanMessage(msg); }, onBeforeMessageEdit(_cid, _mid, msg) { - return this.onSend(msg); + return this.cleanMessage(msg); }, - escapeRegExp(str: string) { - return (str && reHasRegExpChar.test(str)) - ? str.replace(reRegExpChar, "\\$&") - : (str || ""); - }, + async createRules() { + const res = await fetch(CLEAR_URLS_JSON_URL) + .then(res => res.json()) as ClearUrlsData; - createRules() { - // Can be extended upon once user configs are available - // Eg. (useDefaultRules: boolean, customRules: Array[string]) - const rules = defaultRules; + this.rules = []; - this.universalRules = new Set(); - this.rulesByHost = new Map(); - this.hostRules = new Map(); + for (const [name, provider] of Object.entries(res.providers)) { + const urlPattern = new RegExp(provider.urlPattern, "i"); - for (const rule of rules) { - const splitRule = rule.split("@"); - const paramRule = new RegExp( - "^" + - this.escapeRegExp(splitRule[0]).replace(/\\\*/, ".+?") + - "$" - ); + const rules = provider.rules?.map(rule => new RegExp(rule, "i")); + const rawRules = provider.rawRules?.map(rule => new RegExp(rule, "i")); + const exceptions = provider.exceptions?.map(ex => new RegExp(ex, "i")); - if (!splitRule[1]) { - this.universalRules.add(paramRule); - continue; - } - const hostRule = new RegExp( - "^(www\\.)?" + - this.escapeRegExp(splitRule[1]) - .replace(/\\\./, "\\.") - .replace(/^\\\*\\\./, "(.+?\\.)?") - .replace(/\\\*/, ".+?") + - "$" - ); - const hostRuleIndex = hostRule.toString(); - - this.hostRules.set(hostRuleIndex, hostRule); - if (this.rulesByHost.get(hostRuleIndex) == null) { - this.rulesByHost.set(hostRuleIndex, new Set()); - } - this.rulesByHost.get(hostRuleIndex).add(paramRule); - } - }, - - removeParam(rule: string | RegExp, param: string, parent: URLSearchParams) { - if (param === rule || rule instanceof RegExp && rule.test(param)) { - parent.delete(param); + this.rules.push({ + name, + urlPattern, + rules, + rawRules, + exceptions, + }); } }, @@ -106,34 +103,40 @@ export default definePlugin({ } // Cheap way to check if there are any search params - if (url.searchParams.entries().next().done) { - // If there are none, we don't need to modify anything - return match; - } + if (url.searchParams.entries().next().done) return match; - // Check all universal rules - this.universalRules.forEach(rule => { - url.searchParams.forEach((_value, param, parent) => { - this.removeParam(rule, param, parent); - }); - }); + // Check rules for each provider that matches + this.rules.forEach(({ urlPattern, exceptions, rawRules, rules }) => { + if (!urlPattern.test(url.href) || exceptions?.some(ex => ex.test(url.href))) return; - // Check rules for each hosts that match - this.hostRules.forEach((regex, hostRuleName) => { - if (!regex.test(url.hostname)) return; - this.rulesByHost.get(hostRuleName).forEach(rule => { - url.searchParams.forEach((_value, param, parent) => { - this.removeParam(rule, param, parent); + const toDelete: string[] = []; + + if (rules) { + // Add matched params to delete list + url.searchParams.forEach((_, param) => { + if (rules.some(rule => rule.test(param))) { + toDelete.push(param); + } }); + } + + // Delete matched params from list + toDelete.forEach(param => url.searchParams.delete(param)); + + // Match and remove any raw rules + let cleanedUrl = url.href; + rawRules?.forEach(rawRule => { + cleanedUrl = cleanedUrl.replace(rawRule, ""); }); + url = new URL(cleanedUrl); }); return url.toString(); }, - onSend(msg: MessageObject) { + cleanMessage(msg: MessageObject) { // Only run on messages that contain URLs - if (msg.content.match(/http(s)?:\/\//)) { + if (/http(s)?:\/\//.test(msg.content)) { msg.content = msg.content.replace( /(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g, match => this.replacer(match) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 684f25bb..92de3d91 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -602,6 +602,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Cootshk", id: 921605971577548820n }, + thororen: { + name: "thororen", + id: 848339671629299742n + }, } satisfies Record<string, Dev>); // iife so #__PURE__ works correctly From 479d01a1b9417eb4201f8490bfe7cb0eab4bc51a Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 9 Sep 2025 03:56:53 +0200 Subject: [PATCH 122/141] fix FavGifSearch regression --- src/plugins/favGifSearch/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index 2e0ea651..28823a68 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -30,6 +30,7 @@ interface SearchBarComponentProps { onClear: () => void; query: string; placeholder: string; + className?: string; } type TSearchBarComponent = @@ -147,7 +148,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc // scroll back to top ref.current?.containerRef?.current - .closest("#gif-picker-tab-panel") + ?.closest("#gif-picker-tab-panel") ?.querySelector("[class|=\"content\"]") ?.firstElementChild?.scrollTo(0, 0); @@ -177,6 +178,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc ref={ref} autoFocus={true} size="md" + className="" onChange={onChange} onClear={() => { setQuery(""); From 9c0af5adee7d7e64226e5e51c1b723bae6189b69 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:28:37 -0400 Subject: [PATCH 123/141] Fix broken MessagePopoverAPI not working (#3661) --- src/plugins/_api/messagePopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index a49658fb..af861673 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + match: /(?<=\]\}\)),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},` } From fbc2dbe78189dcfe9dc907058770e951730995bd Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:36:34 -0400 Subject: [PATCH 124/141] FakeNitro: fix nitro themes not working for some users (#3666) --- src/plugins/fakeNitro/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 370d6857..bb9a6daa 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -276,6 +276,8 @@ export default definePlugin({ // Allow users to use custom client themes { find: "customUserThemeSettings:{", + // Discord has two separate modules for treatments 1 and 2 + all: true, replacement: { match: /(?<=\i=)\(0,\i\.\i\)\(\i\.\i\.TIER_2\)(?=,|;)/g, replace: "true" From 56d25b03f918b952172986676ecd4ec6f368359b Mon Sep 17 00:00:00 2001 From: Gabriel <38334104+gabrielmar@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:35:01 -0300 Subject: [PATCH 125/141] UserVoiceShow: Improve tooltip & add icons for muted/deafened (#3630) Co-authored-by: V <vendicated@riseup.net> Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/userVoiceShow/components.tsx | 166 ++++++++++++----------- src/plugins/userVoiceShow/style.css | 4 +- 2 files changed, 87 insertions(+), 83 deletions(-) diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index b7907978..13dfed60 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -8,8 +8,9 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { Channel } from "@vencord/discord-types"; -import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack"; -import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; +import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; +import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; +import { PropsWithChildren } from "react"; const cl = classNameFactory("vc-uvs-"); @@ -17,60 +18,71 @@ const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChan const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", { useChannelName: filters.byCode("()=>null==") }); -const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({"); - -const Avatar = findComponentByCodeLazy(".status)/2):0"); -const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL"); const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); -interface IconProps extends React.ComponentPropsWithoutRef<"div"> { +type IconProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & { size?: number; iconClassName?: string; -} +}; -function SpeakerIcon(props: IconProps) { - props.size ??= 16; +function Icon(props: PropsWithChildren<IconProps>) { + const { + size = 16, + className, + iconClassName, + ...restProps + } = props; return ( <div - {...props} - role={props.onClick != null ? "button" : undefined} - className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined, props.className)} + {...restProps} + className={classes(cl("speaker"), className)} > <svg - className={props.iconClassName} - width={props.size} - height={props.size} + className={iconClassName} + width={size} + height={size} viewBox="0 0 24 24" fill="currentColor" > - <path d="M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.92l4.28 4.68a1 1 0 0 0 .74.32H11a1 1 0 0 0 1-1V3ZM15.1 20.75c-.58.14-1.1-.33-1.1-.92v-.03c0-.5.37-.92.85-1.05a7 7 0 0 0 0-13.5A1.11 1.11 0 0 1 14 4.2v-.03c0-.6.52-1.06 1.1-.92a9 9 0 0 1 0 17.5Z" /> - <path d="M15.16 16.51c-.57.28-1.16-.2-1.16-.83v-.14c0-.43.28-.8.63-1.02a3 3 0 0 0 0-5.04c-.35-.23-.63-.6-.63-1.02v-.14c0-.63.59-1.1 1.16-.83a5 5 0 0 1 0 9.02Z" /> + {props.children} </svg> </div> ); } -function LockedSpeakerIcon(props: IconProps) { - props.size ??= 16; - +function SpeakerIcon(props: IconProps) { return ( - <div - {...props} - role={props.onClick != null ? "button" : undefined} - className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined, props.className)} - > - <svg - width={props.size} - height={props.size} - viewBox="0 0 24 24" - fill="currentColor" - > - <path fillRule="evenodd" clipRule="evenodd" d="M16 4h.5v-.5a2.5 2.5 0 0 1 5 0V4h.5a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1Zm4-.5V4h-2v-.5a1 1 0 1 1 2 0Z" /> - <path d="M11 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1h-.06a1 1 0 0 1-.74-.32L5.92 17H3a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h2.92l4.28-4.68a1 1 0 0 1 .74-.32H11ZM20.5 12c-.28 0-.5.22-.52.5a7 7 0 0 1-5.13 6.25c-.48.13-.85.55-.85 1.05v.03c0 .6.52 1.06 1.1.92a9 9 0 0 0 6.89-8.25.48.48 0 0 0-.49-.5h-1ZM16.5 12c-.28 0-.5.23-.54.5a3 3 0 0 1-1.33 2.02c-.35.23-.63.6-.63 1.02v.14c0 .63.59 1.1 1.16.83a5 5 0 0 0 2.82-4.01c.02-.28-.2-.5-.48-.5h-1Z" /> - </svg> - </div> + <Icon {...props}> + <path d="M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.92l4.28 4.68a1 1 0 0 0 .74.32H11a1 1 0 0 0 1-1V3ZM15.1 20.75c-.58.14-1.1-.33-1.1-.92v-.03c0-.5.37-.92.85-1.05a7 7 0 0 0 0-13.5A1.11 1.11 0 0 1 14 4.2v-.03c0-.6.52-1.06 1.1-.92a9 9 0 0 1 0 17.5Z" /> + <path d="M15.16 16.51c-.57.28-1.16-.2-1.16-.83v-.14c0-.43.28-.8.63-1.02a3 3 0 0 0 0-5.04c-.35-.23-.63-.6-.63-1.02v-.14c0-.63.59-1.1 1.16-.83a5 5 0 0 1 0 9.02Z" /> + </Icon> + ); +} + +function LockedSpeakerIcon(props: IconProps) { + return ( + <Icon {...props}> + <path fillRule="evenodd" clipRule="evenodd" d="M16 4h.5v-.5a2.5 2.5 0 0 1 5 0V4h.5a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1Zm4-.5V4h-2v-.5a1 1 0 1 1 2 0Z" /> + <path d="M11 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1h-.06a1 1 0 0 1-.74-.32L5.92 17H3a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h2.92l4.28-4.68a1 1 0 0 1 .74-.32H11ZM20.5 12c-.28 0-.5.22-.52.5a7 7 0 0 1-5.13 6.25c-.48.13-.85.55-.85 1.05v.03c0 .6.52 1.06 1.1.92a9 9 0 0 0 6.89-8.25.48.48 0 0 0-.49-.5h-1ZM16.5 12c-.28 0-.5.23-.54.5a3 3 0 0 1-1.33 2.02c-.35.23-.63.6-.63 1.02v.14c0 .63.59 1.1 1.16.83a5 5 0 0 0 2.82-4.01c.02-.28-.2-.5-.48-.5h-1Z" /> + </Icon> + ); +} + +function MutedIcon(props: IconProps) { + return ( + <Icon {...props}> + <path d="m2.7 22.7 20-20a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4ZM10.8 17.32c-.21.21-.1.58.2.62V20H9a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2h-2v-2.06A8 8 0 0 0 20 10a1 1 0 0 0-2 0c0 1.45-.52 2.79-1.38 3.83l-.02.02A5.99 5.99 0 0 1 12.32 16a.52.52 0 0 0-.34.15l-1.18 1.18ZM15.36 4.52c.15-.15.19-.38.08-.56A4 4 0 0 0 8 6v4c0 .3.03.58.1.86.07.34.49.43.74.18l6.52-6.52ZM5.06 13.98c.16.28.53.31.75.09l.75-.75c.16-.16.19-.4.08-.61A5.97 5.97 0 0 1 6 10a1 1 0 0 0-2 0c0 1.45.39 2.81 1.06 3.98Z" /> + </Icon> + ); +} + +function DeafIcon(props: IconProps) { + return ( + <Icon {...props}> + <path d="M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4l20-20ZM17.06 2.94a.48.48 0 0 0-.11-.77A11 11 0 0 0 2.18 16.94c.14.3.53.35.76.12l3.2-3.2c.25-.25.15-.68-.2-.76a5 5 0 0 0-1.02-.1H3.05a9 9 0 0 1 12.66-9.2c.2.09.44.05.59-.1l.76-.76ZM20.2 8.28a.52.52 0 0 1 .1-.58l.76-.76a.48.48 0 0 1 .77.11 11 11 0 0 1-4.5 14.57c-1.27.71-2.73.23-3.55-.74a3.1 3.1 0 0 1-.17-3.78l1.38-1.97a5 5 0 0 1 4.1-2.13h1.86a9.1 9.1 0 0 0-.75-4.72ZM10.1 17.9c.25-.25.65-.18.74.14a3.1 3.1 0 0 1-.62 2.84 2.85 2.85 0 0 1-3.55.74.16.16 0 0 1-.04-.25l3.48-3.48Z" /> + </Icon> ); } @@ -87,36 +99,13 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) { [voiceStates] ); - const guild = channel.getGuildId() == null ? undefined : GuildStore.getGuild(channel.getGuildId()); - const guildIcon = guild?.icon == null ? undefined : IconUtils.getGuildIconURL({ - id: guild.id, - icon: guild.icon, - size: 30 - }); - - const channelIcon = match(channel.type) - .with(P.union(1, 3), () => { - return channel.recipients.length >= 2 && channel.icon == null - ? <GroupDMAvatars recipients={channel.recipients} size="SIZE_32" /> - : <Avatar src={getDMChannelIcon(channel)} size="SIZE_32" />; - }) - .otherwise(() => null); - const channelName = useChannelName(channel); - + const Icon = isLocked ? LockedSpeakerIcon : SpeakerIcon; return ( <> - {guild != null && ( - <div className={cl("name")}> - {guildIcon != null && <img className={cl("guild-icon")} src={guildIcon} alt="" />} - <Text variant="text-sm/bold">{guild.name}</Text> - </div> - )} - <div className={cl("name")}> - {channelIcon} - <Text variant="text-sm/semibold">{channelName}</Text> - </div> + <Text variant="text-sm/bold">In Voice Chat</Text> + <Text variant="text-sm/bold">{Parser.parse(`<#${channel.id}>`)}</Text> <div className={cl("vc-members")}> - {isLocked ? <LockedSpeakerIcon size={18} /> : <SpeakerIcon size={18} />} + <Icon size={18} /> <UserSummaryItem users={users} renderIcon={false} @@ -135,11 +124,19 @@ export interface VoiceChannelIndicatorProps { shouldHighlight?: boolean; } -const clickTimers = {} as Record<string, any>; +const clickTimers = new Map<string, any>(); export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId); + const { isMuted, isDeaf } = useStateFromStores([VoiceStateStore], () => { + const voiceState = VoiceStateStore.getVoiceStateForUser(userId); + return { + isMuted: voiceState?.mute || voiceState?.selfMute || false, + isDeaf: voiceState?.deaf || voiceState?.selfDeaf || false + }; + }); + const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); if (channel == null) return null; @@ -154,8 +151,8 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, is if (channel == null || channelId == null) return; - clearTimeout(clickTimers[channelId]); - delete clickTimers[channelId]; + clearTimeout(clickTimers.get(channelId)); + clickTimers.delete(channelId); if (e.detail > 1) { if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) { @@ -165,32 +162,39 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, is selectVoiceChannel(channelId); } else { - clickTimers[channelId] = setTimeout(() => { + const timeoutId = setTimeout(() => { ChannelRouter.transitionToChannel(channelId); - delete clickTimers[channelId]; + clickTimers.delete(channelId); }, 250); + clickTimers.set(channelId, timeoutId); } } + const IconComponent = + isLocked + ? LockedSpeakerIcon + : isDeaf + ? DeafIcon + : isMuted + ? MutedIcon + : SpeakerIcon; + return ( <Tooltip text={<VoiceChannelTooltip channel={channel} isLocked={isLocked} />} tooltipClassName={cl("tooltip-container")} tooltipContentClassName={cl("tooltip-content")} > - {props => { - const iconProps: IconProps = { - ...props, - className: classes(isActionButton && ActionButtonClasses.actionButton, isActionButton && shouldHighlight && ActionButtonClasses.highlight), - iconClassName: classes(isProfile && cl("profile-speaker")), - size: isActionButton ? 20 : 16, - onClick - }; - - return isLocked ? - <LockedSpeakerIcon {...iconProps} /> - : <SpeakerIcon {...iconProps} />; - }} + {props => ( + <IconComponent + {...props} + role="button" + onClick={onClick} + className={classes(cl("clickable"), isActionButton && ActionButtonClasses.actionButton, isActionButton && shouldHighlight && ActionButtonClasses.highlight)} + iconClassName={classes(cl(isProfile && "profile-speaker"))} + size={isActionButton ? 20 : 16} + /> + )} </Tooltip> ); }, { noop: true }); diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css index 32b42208..322b8ed3 100644 --- a/src/plugins/userVoiceShow/style.css +++ b/src/plugins/userVoiceShow/style.css @@ -19,7 +19,7 @@ } .vc-uvs-tooltip-container { - max-width: 300px; + max-width: 50vw; } .vc-uvs-tooltip-content { @@ -42,4 +42,4 @@ .vc-uvs-vc-members { display: flex; gap: 6px; -} +} \ No newline at end of file From 3005906a280c2bfae8fb86d5b14266946f92eb42 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sat, 20 Sep 2025 20:37:01 +0200 Subject: [PATCH 126/141] CallTimer: fix horizontal text cutoff --- src/plugins/callTimer/alignedChatInputFix.css | 6 +++--- src/plugins/callTimer/index.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/callTimer/alignedChatInputFix.css b/src/plugins/callTimer/alignedChatInputFix.css index 9bb4a039..33c14fbf 100644 --- a/src/plugins/callTimer/alignedChatInputFix.css +++ b/src/plugins/callTimer/alignedChatInputFix.css @@ -1,4 +1,4 @@ -.align-chat-input [class*="panels"] [class*="inner_"], -.align-chat-input [class*="rtcConnectionStatus_"] { - height: fit-content; +[class*="panels"] [class*="inner_"], +[class*="rtcConnectionStatus_"] { + height: fit-content !important; } \ No newline at end of file diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index 76b3efd6..6503568e 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -95,6 +95,6 @@ export default definePlugin({ deps: [channelId] }); - return <p style={{ margin: 0 }}>Connected for <span style={{ fontFamily: "var(--font-code)" }}>{formatDuration(time)}</span></p>; + return <p style={{ margin: 0, fontFamily: "var(--font-code)" }}>{formatDuration(time)}</p>; } }); From 8c2dc84f3b3c18e20fc5c8c94ea1b837a1a3b093 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:38:57 -0400 Subject: [PATCH 127/141] Settings: fix debug info layout (#3673) --- src/plugins/_core/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 4a0f297e..4d12f37a 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -39,7 +39,7 @@ export default definePlugin({ find: ".versionHash", replacement: [ { - match: /\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, + match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, replace: (m, component, props) => { props = props.replace(/children:\[.+\]/, ""); return `${m},$self.makeInfoElements(${component}, ${props})`; From 80872f4ab9a0184c05d1b34dfec4a052c1c0709b Mon Sep 17 00:00:00 2001 From: quarty <142720982+qwertyquarty@users.noreply.github.com> Date: Mon, 22 Sep 2025 04:44:13 +0200 Subject: [PATCH 128/141] MessageLatency: add option to ignore own messages (#3677) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/messageLatency/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index b81c55cf..437ce644 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -11,7 +11,7 @@ import { isNonNullish } from "@utils/guards"; import definePlugin, { OptionType } from "@utils/types"; import { Message } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; -import { SnowflakeUtils, Tooltip } from "@webpack/common"; +import { AuthenticationStore, SnowflakeUtils, Tooltip } from "@webpack/common"; type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted"); type Fill = [FillValue, FillValue, FillValue]; @@ -48,6 +48,11 @@ export default definePlugin({ type: OptionType.BOOLEAN, description: "Show milliseconds", default: false + }, + ignoreSelf: { + type: OptionType.BOOLEAN, + description: "Don't add indicator to your own messages", + default: false } }), @@ -91,7 +96,7 @@ export default definePlugin({ }, latencyTooltipData(message: Message) { - const { latency, detectDiscordKotlin, showMillis } = this.settings.store; + const { latency, detectDiscordKotlin, showMillis, ignoreSelf } = this.settings.store; const { id, nonce } = message; // Message wasn't received through gateway @@ -100,6 +105,8 @@ export default definePlugin({ // Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake if (message.author.bot) return null; + if (ignoreSelf && message.author.id === AuthenticationStore.getId()) return null; + let isDiscordKotlin = false; let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds if (!showMillis) { From edd68fe08e5f929e95e6635ec65ec9fdc54089f7 Mon Sep 17 00:00:00 2001 From: nin0 <personal@nin0.dev> Date: Sun, 21 Sep 2025 23:08:08 -0400 Subject: [PATCH 129/141] AppleMusicRichPresence: add status display type (#3669) Co-authored-by: V <vendicated@riseup.net> --- packages/discord-types/enums/activity.ts | 30 +++++++++ packages/discord-types/enums/index.ts | 1 + .../discord-types/src/common/Activity.d.ts | 36 ++++++++++ packages/discord-types/src/common/index.d.ts | 1 + src/plugins/appleMusic.desktop/index.tsx | 67 ++++++++----------- src/plugins/customRPC/index.tsx | 38 +---------- src/plugins/lastfmRichPresence/index.tsx | 51 ++------------ 7 files changed, 104 insertions(+), 120 deletions(-) create mode 100644 packages/discord-types/enums/activity.ts create mode 100644 packages/discord-types/src/common/Activity.d.ts diff --git a/packages/discord-types/enums/activity.ts b/packages/discord-types/enums/activity.ts new file mode 100644 index 00000000..513a65de --- /dev/null +++ b/packages/discord-types/enums/activity.ts @@ -0,0 +1,30 @@ +export const enum ActivityType { + PLAYING = 0, + STREAMING = 1, + LISTENING = 2, + WATCHING = 3, + CUSTOM_STATUS = 4, + COMPETING = 5, + HANG_STATUS = 6 +} + +export const enum ActivityFlags { + INSTANCE = 1 << 0, + JOIN = 1 << 1, + /** @deprecated */ + SPECTATE = 1 << 2, + /** @deprecated */ + JOIN_REQUEST = 1 << 3, + SYNC = 1 << 4, + PLAY = 1 << 5, + PARTY_PRIVACY_FRIENDS = 1 << 6, + PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7, + EMBEDDED = 1 << 8, + CONTEXTLESS = 1 << 9 +} + +export const enum ActivityStatusDisplayType { + NAME = 0, + STATE = 1, + DETAILS = 2 +} diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts index 3d80895c..a25ff3b5 100644 --- a/packages/discord-types/enums/index.ts +++ b/packages/discord-types/enums/index.ts @@ -1,3 +1,4 @@ +export * from "./activity"; export * from "./channel"; export * from "./commands"; export * from "./messages"; diff --git a/packages/discord-types/src/common/Activity.d.ts b/packages/discord-types/src/common/Activity.d.ts new file mode 100644 index 00000000..d513780a --- /dev/null +++ b/packages/discord-types/src/common/Activity.d.ts @@ -0,0 +1,36 @@ +import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums"; + +export interface ActivityAssets { + large_image?: string; + large_text?: string; + small_image?: string; + small_text?: string; +} + +export interface ActivityButton { + label: string; + url: string; +} + +export interface Activity { + name: string; + application_id: string; + type: ActivityType; + state?: string; + state_url?: string; + details?: string; + details_url?: string; + url?: string; + flags: ActivityFlags; + status_display_type?: ActivityStatusDisplayType; + timestamps?: { + start?: number; + end?: number; + }; + assets?: ActivityAssets; + buttons?: string[]; + metadata?: { + button_urls?: Array<string>; + }; +} + diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts index 5e50e96e..65ad8856 100644 --- a/packages/discord-types/src/common/index.d.ts +++ b/packages/discord-types/src/common/index.d.ts @@ -1,3 +1,4 @@ +export * from "./Activity"; export * from "./Application"; export * from "./Channel"; export * from "./Guild"; diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx index 57e5ae19..f6fc9da9 100644 --- a/src/plugins/appleMusic.desktop/index.tsx +++ b/src/plugins/appleMusic.desktop/index.tsx @@ -7,49 +7,12 @@ import { definePluginSettings } from "@api/Settings"; import { Devs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types"; +import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>; -interface ActivityAssets { - large_image?: string; - large_text?: string; - small_image?: string; - small_text?: string; -} - -interface ActivityButton { - label: string; - url: string; -} - -interface Activity { - state?: string; - details?: string; - timestamps?: { - start?: number; - end?: number; - }; - assets?: ActivityAssets; - buttons?: Array<string>; - name: string; - application_id: string; - metadata?: { - button_urls?: Array<string>; - }; - type: number; - flags: number; -} - -const enum ActivityType { - PLAYING = 0, - LISTENING = 2, -} - -const enum ActivityFlag { - INSTANCE = 1 << 0, -} - export interface TrackData { name: string; album?: string; @@ -90,6 +53,25 @@ const settings = definePluginSettings({ { label: "Listening", value: ActivityType.LISTENING } ], }, + statusDisplayType: { + description: "Show the track / artist name in the member list", + type: OptionType.SELECT, + options: [ + { + label: "Don't show (shows generic listening message)", + value: "off", + default: true + }, + { + label: "Show artist name", + value: "artist" + }, + { + label: "Show track name", + value: "track" + } + ] + }, refreshInterval: { type: OptionType.SLIDER, description: "The interval between activity refreshes (seconds)", @@ -258,7 +240,12 @@ export default definePlugin({ metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined, type: settings.store.activityType, - flags: ActivityFlag.INSTANCE, + status_display_type: { + "off": ActivityStatusDisplayType.NAME, + "artist": ActivityStatusDisplayType.STATE, + "track": ActivityStatusDisplayType.DETAILS + }[settings.store.statusDisplayType], + flags: ActivityFlags.INSTANCE, }; } }); diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index d35dad13..9a5a1cd9 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -27,6 +27,8 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; +import { Activity } from "@vencord/discord-types"; +import { ActivityType } from "@vencord/discord-types/enums"; import { findByCodeLazy, findComponentByCodeLazy } from "@webpack"; import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common"; @@ -39,41 +41,7 @@ async function getApplicationAsset(key: string): Promise<string> { return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0]; } -interface ActivityAssets { - large_image?: string; - large_text?: string; - small_image?: string; - small_text?: string; -} - -interface Activity { - state?: string; - details?: string; - timestamps?: { - start?: number; - end?: number; - }; - assets?: ActivityAssets; - buttons?: Array<string>; - name: string; - application_id: string; - metadata?: { - button_urls?: Array<string>; - }; - type: ActivityType; - url?: string; - flags: number; -} - -const enum ActivityType { - PLAYING = 0, - STREAMING = 1, - LISTENING = 2, - WATCHING = 3, - COMPETING = 5 -} - -const enum TimestampMode { +export const enum TimestampMode { NONE, NOW, TIME, diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx index abf42d5f..3c36e9dc 100644 --- a/src/plugins/lastfmRichPresence/index.tsx +++ b/src/plugins/lastfmRichPresence/index.tsx @@ -21,40 +21,11 @@ import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; +import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types"; +import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums"; import { findByPropsLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; -interface ActivityAssets { - large_image?: string; - large_text?: string; - small_image?: string; - small_text?: string; -} - - -interface ActivityButton { - label: string; - url: string; -} - -interface Activity { - state: string; - details?: string; - timestamps?: { - start?: number; - }; - assets?: ActivityAssets; - buttons?: Array<string>; - name: string; - application_id: string; - status_display_type?: number; - metadata?: { - button_urls?: Array<string>; - }; - type: number; - flags: number; -} - interface TrackData { name: string; album: string; @@ -63,16 +34,6 @@ interface TrackData { imageUrl?: string; } -// only relevant enum values -const enum ActivityType { - PLAYING = 0, - LISTENING = 2, -} - -const enum ActivityFlag { - INSTANCE = 1 << 0, -} - const enum NameFormat { StatusName = "status-name", ArtistFirst = "artist-first", @@ -367,9 +328,9 @@ export default definePlugin({ details: trackData.name, state: trackData.artist, status_display_type: { - "off": 0, - "artist": 1, - "track": 2 + "off": ActivityStatusDisplayType.NAME, + "artist": ActivityStatusDisplayType.STATE, + "track": ActivityStatusDisplayType.DETAILS }[settings.store.statusDisplayType], assets, @@ -379,7 +340,7 @@ export default definePlugin({ }, type: settings.store.useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING, - flags: ActivityFlag.INSTANCE, + flags: ActivityFlags.INSTANCE, }; } }); From 2e9d67f0b4dbc59088b22e317c634d96c92a5985 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Sun, 21 Sep 2025 23:15:14 -0400 Subject: [PATCH 130/141] add Vencord badges to user settings section (#3667) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/_api/badges/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 8742a4bd..8c900038 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -111,6 +111,13 @@ export default definePlugin({ replace: "...$self.getBadgeMouseEventHandlers($1),$&" } ] + }, + { + find: "profileCardUsernameRow,children:", + replacement: { + match: /badges:(\i)(?<=displayProfile:(\i).+?)/, + replace: "badges:[...$self.getBadges($2),...$1]" + } } ], From 228b85a0c8287e72f88b89a821919c51cb982468 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 22 Sep 2025 05:18:10 +0200 Subject: [PATCH 131/141] bump to v1.13.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3f0e3371..4f05e969 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.13", + "version": "1.13.0", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -105,4 +105,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} From 746c8240206107dfc40f0995d17cd24730d40b0d Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 23 Sep 2025 22:24:58 +0200 Subject: [PATCH 132/141] Fix Debug Logging toggle not working Closes #3268 --- src/plugins/_core/noTrack.ts | 9 +++++++++ src/utils/types.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index ad1f255b..eada58cb 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -71,6 +71,15 @@ export default definePlugin({ } ], + // The TRACK event takes an optional `resolve` property that is called when the tracking event was submitted to the server. + // A few spots in Discord await this callback before continuing (most notably the Voice Debug Logging toggle). + // Since we NOOP the AnalyticsActionHandlers module, there is no handler for the TRACK event, so we have to handle it ourselves + flux: { + TRACK(event) { + event?.resolve?.(); + } + }, + startAt: StartAt.Init, start() { // Sentry is initialized in its own WebpackInstance. diff --git a/src/utils/types.ts b/src/utils/types.ts index 2f1d6396..2293b2ff 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -26,6 +26,7 @@ import { MessageClickListener, MessageEditListener, MessageSendListener } from " import { MessagePopoverButtonFactory } from "@api/MessagePopover"; import { Command, FluxEvents } from "@vencord/discord-types"; import { ReactNode } from "react"; +import { LiteralUnion } from "type-fest"; // exists to export default definePlugin({...}) export default function definePlugin<P extends PluginDef>(p: P & Record<PropertyKey, any>) { @@ -151,7 +152,7 @@ export interface PluginDef { * Allows you to subscribe to Flux events */ flux?: { - [E in FluxEvents]?: (event: any) => void | Promise<void>; + [E in LiteralUnion<FluxEvents, string>]?: (event: any) => void | Promise<void>; }; /** * Allows you to manipulate context menus From cb845b522458532479c077ed82279c27e025601e Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 23 Sep 2025 23:58:38 +0200 Subject: [PATCH 133/141] PlatformIndicators: update indicators in real time if status changes Closes #3516 --- packages/discord-types/src/utils.d.ts | 8 +++-- src/plugins/index.ts | 4 +-- src/plugins/platformIndicators/index.tsx | 41 +++++++----------------- src/utils/types.ts | 6 ++-- 4 files changed, 21 insertions(+), 38 deletions(-) diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 77b6f88b..14b8fa50 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -6,13 +6,15 @@ import type { FluxEvents } from "./fluxEvents"; export { FluxEvents }; +type FluxEventsAutoComplete = LiteralUnion<FluxEvents, string>; + export interface FluxDispatcher { _actionHandlers: any; _subscriptions: any; - dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise<void>; + dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise<void>; isDispatching(): boolean; - subscribe(event: FluxEvents, callback: (data: any) => void): void; - unsubscribe(event: FluxEvents, callback: (data: any) => void): void; + subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; + unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; wait(callback: () => void): void; } diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 8288935b..5bb60a13 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -222,7 +222,7 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux for (const [event, handler] of Object.entries(p.flux)) { const wrappedHandler = p.flux[event] = function () { try { - const res = handler.apply(p, arguments as any); + const res = handler!.apply(p, arguments as any); return res instanceof Promise ? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e)) : res; @@ -242,7 +242,7 @@ export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Fl logger.debug("Unsubscribing from flux events of plugin", p.name); for (const [event, handler] of Object.entries(p.flux)) { - fluxDispatcher.unsubscribe(event as FluxEvents, handler); + fluxDispatcher.unsubscribe(event as FluxEvents, handler!); } } } diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index f804e987..add93064 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -22,12 +22,11 @@ import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProf import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; import { Settings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { User } from "@vencord/discord-types"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack"; -import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; +import { AuthenticationStore, PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; export interface Session { sessionId: string; @@ -85,7 +84,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: }; function ensureOwnStatus(user: User) { - if (user.id === UserStore.getCurrentUser().id) { + if (user.id === AuthenticationStore.getId()) { const sessions = SessionsStore.getSessions(); if (typeof sessions !== "object") return null; const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => { @@ -104,7 +103,7 @@ function ensureOwnStatus(user: User) { }, {}); const { clientStatuses } = PresenceStore.getState(); - clientStatuses[UserStore.getCurrentUser().id] = ownStatus; + clientStatuses[AuthenticationStore.getId()] = ownStatus; } } @@ -115,7 +114,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { ensureOwnStatus(user); - const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>; + const status = PresenceStore.getClientStatus(user.id) as Record<Platform, string>; if (!status) return []; return Object.entries(status).map(([platform, status]) => ({ @@ -134,11 +133,9 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { } const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => { - if (!user || user.bot) return null; - ensureOwnStatus(user); - const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>; + const status = useStateFromStores([PresenceStore], () => PresenceStore.getClientStatus(user.id) as Record<Platform, string>); if (!status) return null; const icons = Object.entries(status).map(([platform, status]) => ( @@ -170,10 +167,8 @@ const badge: ProfileBadge = { const indicatorLocations = { list: { description: "In the member list", - onEnable: () => addMemberListDecorator("platform-indicator", props => - <ErrorBoundary noop> - <PlatformIndicator user={props.user} small={true} /> - </ErrorBoundary> + onEnable: () => addMemberListDecorator("platform-indicator", ({ user }) => + user && !user.bot ? <PlatformIndicator user={user} small={true} /> : null ), onDisable: () => removeMemberListDecorator("platform-indicator") }, @@ -184,11 +179,10 @@ const indicatorLocations = { }, messages: { description: "Inside messages", - onEnable: () => addMessageDecoration("platform-indicator", props => - <ErrorBoundary noop> - <PlatformIndicator user={props.message?.author} /> - </ErrorBoundary> - ), + onEnable: () => addMessageDecoration("platform-indicator", props => { + const user = props.message?.author; + return user && !user.bot ? <PlatformIndicator user={props.message?.author} /> : null; + }), onDisable: () => removeMessageDecoration("platform-indicator") } }; @@ -201,19 +195,6 @@ export default definePlugin({ start() { const settings = Settings.plugins.PlatformIndicators; - const { displayMode } = settings; - - // transfer settings from the old ones, which had a select menu instead of booleans - if (displayMode) { - if (displayMode !== "both") settings[displayMode] = true; - else { - settings.list = true; - settings.badges = true; - } - settings.messages = true; - delete settings.displayMode; - } - Object.entries(indicatorLocations).forEach(([key, value]) => { if (settings[key]) value.onEnable(); }); diff --git a/src/utils/types.ts b/src/utils/types.ts index 2293b2ff..2d21eaff 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -151,9 +151,9 @@ export interface PluginDef { /** * Allows you to subscribe to Flux events */ - flux?: { - [E in LiteralUnion<FluxEvents, string>]?: (event: any) => void | Promise<void>; - }; + flux?: Partial<{ + [E in LiteralUnion<FluxEvents, string>]: (event: any) => void | Promise<void>; + }>; /** * Allows you to manipulate context menus */ From 7c839be64fbef65b4825ec1d17acd0264aadcd64 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:49:50 -0400 Subject: [PATCH 134/141] BetterFolders: close folder if the last server is removed (#3658) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/betterFolders/FolderSideBar.tsx | 23 +++++++++++++++++---- src/plugins/betterFolders/index.tsx | 5 ++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 40329122..0e643958 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -21,24 +21,39 @@ import { findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Animations, useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; -import { ExpandedGuildFolderStore, settings } from "."; +import { ExpandedGuildFolderStore, settings, SortedGuildStore } from "."; const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const GuildsBar = findComponentByCodeLazy('("guildsnav")'); +function getExpandedFolderIds() { + const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders(); + const folders = SortedGuildStore.getGuildFolders(); + + const expandedFolderIds = new Set<string>(); + + for (const folder of folders) { + if (expandedFolders.has(folder.folderId) && folder.guildIds?.length) { + expandedFolderIds.add(folder.folderId); + } + } + + return expandedFolderIds; +} + export default ErrorBoundary.wrap(guildsBarProps => { - const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); + const expandedFolderIds = useStateFromStores([ExpandedGuildFolderStore, SortedGuildStore], () => getExpandedFolderIds()); const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext()); const Sidebar = ( <GuildsBar {...guildsBarProps} isBetterFolders={true} - betterFoldersExpandedIds={expandedFolders} + betterFoldersExpandedIds={expandedFolderIds} /> ); - const visible = !!expandedFolders.size; + const visible = !!expandedFolderIds.size; const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join("")); // We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it. diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 62567f51..5a1754d3 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -22,7 +22,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { FluxDispatcher } from "@webpack/common"; import { ReactNode } from "react"; @@ -35,8 +35,7 @@ enum FolderIconDisplay { } export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); -const SortedGuildStore = findStoreLazy("SortedGuildStore"); -const GuildsTree = findLazy(m => m.prototype?.moveNextTo); +export const SortedGuildStore = findStoreLazy("SortedGuildStore"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); let lastGuildId = null as string | null; From c5a1bbd7db2472cfb7cfc62a0cec59f3135e479e Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 25 Sep 2025 15:19:34 +0200 Subject: [PATCH 135/141] GameActivityToggle: fix background colour when using nameplate --- src/plugins/gameActivityToggle/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 97885177..8959acc2 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -60,7 +60,7 @@ function makeIcon(showCurrentGame?: boolean) { }; } -function GameActivityToggleButton() { +function GameActivityToggleButton(props: { nameplate?: any; }) { const showCurrentGame = ShowCurrentGame.useSetting(); return ( @@ -70,6 +70,7 @@ function GameActivityToggleButton() { role="switch" aria-checked={!showCurrentGame} redGlow={!showCurrentGame} + plated={props?.nameplate != null} onClick={() => ShowCurrentGame.updateSetting(old => !old)} /> ); @@ -97,7 +98,7 @@ export default definePlugin({ find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", replacement: { match: /className:\i\.buttons,.{0,50}children:\[/, - replace: "$&$self.GameActivityToggleButton()," + replace: "$&$self.GameActivityToggleButton(arguments[0])," } } ], From 79c5cf530403e214ea2cf4f3c3c500d98ad0424c Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Sat, 27 Sep 2025 15:59:18 -0400 Subject: [PATCH 136/141] fix plugins for latest Discord update (#3681) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/_core/settings.tsx | 5 +++-- src/plugins/implicitRelationships/index.ts | 2 +- src/webpack/common/components.ts | 8 +------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 4d12f37a..a632e9fa 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -67,8 +67,9 @@ export default definePlugin({ { find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", replacement: { - match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/, - replace: "$2.open($1);return;" + // Skip the check Discord performs to make sure the section being selected in the user settings context menu is valid + match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object.values\(\i\.\i\).+?(\(0,\i\.openUserSettings\))\()/, + replace: (_, settingsPanel, section, openUserSettings) => `${openUserSettings}(${settingsPanel},{section:${section}});return;` } } ], diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index d57370a0..a49635e1 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -48,7 +48,7 @@ export default definePlugin({ }, // Sections header { - find: "#{intl::FRIENDS_SECTION_ONLINE}", + find: "#{intl::FRIENDS_SECTION_ONLINE}),className:", replacement: { match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/, replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}` diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 9715619a..b975f303 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -87,13 +87,7 @@ export const ListScrollerNone = LazyComponent(() => createListScroller(listScrol export const ListScrollerThin = LazyComponent(() => createListScroller(listScrollerClasses.thin, listScrollerClasses.fade, "", ResizeObserver)); export const ListScrollerAuto = LazyComponent(() => createListScroller(listScrollerClasses.auto, listScrollerClasses.fade, "", ResizeObserver)); -const { FocusLock_ } = mapMangledModuleLazy('document.getElementById("app-mount"))', { - FocusLock_: filters.componentByCode(".containerRef") -}) as { - FocusLock_: t.FocusLock; -}; - -export const FocusLock = LazyComponent(() => FocusLock_); +export const FocusLock = waitForComponent<t.FocusLock>("FocusLock", filters.componentByCode(".containerRef,{keyboardModeEnabled:")); export let useToken: t.useToken; waitFor(m => { From 631c763fc44b94bbce78eea15f115b2290f3d62b Mon Sep 17 00:00:00 2001 From: TheRealClarity <68876810+TheRealClarity@users.noreply.github.com> Date: Sun, 28 Sep 2025 21:46:14 +0100 Subject: [PATCH 137/141] YoutubeAdblock: fix blocking in watch together activity (#3690) --- src/plugins/youtubeAdblock.desktop/native.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/youtubeAdblock.desktop/native.ts b/src/plugins/youtubeAdblock.desktop/native.ts index ae05d646..02112dac 100644 --- a/src/plugins/youtubeAdblock.desktop/native.ts +++ b/src/plugins/youtubeAdblock.desktop/native.ts @@ -13,8 +13,10 @@ app.on("browser-window-created", (_, win) => { frame?.once("dom-ready", () => { if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return; - if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) { + if (frame.url.includes("youtube.com/embed/")) { frame.executeJavaScript(adguard); + } else if (frame.parent?.url.includes("youtube.com/embed/")) { + frame.parent.executeJavaScript(adguard); } }); }); From f040133412f1edc7ccfa3e4c3cc8309b46ff9aed Mon Sep 17 00:00:00 2001 From: V <vendicated@riseup.net> Date: Sun, 28 Sep 2025 23:18:23 +0200 Subject: [PATCH 138/141] QuickReply/MessageClickActions: ignore non-replyable & ephemeral messages (#3692) Co-authored-by: YashRaj <91825864+YashRajCodes@users.noreply.github.com> --- packages/discord-types/CONTRIBUTING.md | 1 + packages/discord-types/enums/messages.ts | 583 ++++++++++++++++++ .../src/common/messages/Message.d.ts | 19 +- src/plugins/messageClickActions/index.ts | 8 +- src/plugins/quickReply/index.ts | 4 +- src/webpack/common/utils.ts | 2 + 6 files changed, 607 insertions(+), 10 deletions(-) create mode 100644 packages/discord-types/CONTRIBUTING.md diff --git a/packages/discord-types/CONTRIBUTING.md b/packages/discord-types/CONTRIBUTING.md new file mode 100644 index 00000000..3bef4092 --- /dev/null +++ b/packages/discord-types/CONTRIBUTING.md @@ -0,0 +1 @@ +Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces diff --git a/packages/discord-types/enums/messages.ts b/packages/discord-types/enums/messages.ts index 9c0025b7..8aa6fbb3 100644 --- a/packages/discord-types/enums/messages.ts +++ b/packages/discord-types/enums/messages.ts @@ -11,3 +11,586 @@ export const enum StickerFormatType { LOTTIE = 3, GIF = 4 } + +export const enum MessageType { + /** + * A default message (see below) + * + * Value: 0 + * Name: DEFAULT + * Rendered Content: "{content}" + * Deletable: true + */ + DEFAULT = 0, + /** + * A message sent when a user is added to a group DM or thread + * + * Value: 1 + * Name: RECIPIENT_ADD + * Rendered Content: "{author} added {mentions [0] } to the {group/thread}." + * Deletable: false + */ + RECIPIENT_ADD = 1, + /** + * A message sent when a user is removed from a group DM or thread + * + * Value: 2 + * Name: RECIPIENT_REMOVE + * Rendered Content: "{author} removed {mentions [0] } from the {group/thread}." + * Deletable: false + */ + RECIPIENT_REMOVE = 2, + /** + * A message sent when a user creates a call in a private channel + * + * Value: 3 + * Name: CALL + * Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}." + * Deletable: false + */ + CALL = 3, + /** + * A message sent when a group DM or thread's name is changed + * + * Value: 4 + * Name: CHANNEL_NAME_CHANGE + * Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} " + * Deletable: false + */ + CHANNEL_NAME_CHANGE = 4, + /** + * A message sent when a group DM's icon is changed + * + * Value: 5 + * Name: CHANNEL_ICON_CHANGE + * Rendered Content: "{author} changed the channel icon." + * Deletable: false + */ + CHANNEL_ICON_CHANGE = 5, + /** + * A message sent when a message is pinned in a channel + * + * Value: 6 + * Name: CHANNEL_PINNED_MESSAGE + * Rendered Content: "{author} pinned a message to this channel." + * Deletable: true + */ + CHANNEL_PINNED_MESSAGE = 6, + /** + * A message sent when a user joins a guild + * + * Value: 7 + * Name: USER_JOIN + * Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13 + * Deletable: true + */ + USER_JOIN = 7, + /** + * A message sent when a user subscribes to (boosts) a guild + * + * Value: 8 + * Name: PREMIUM_GUILD_SUBSCRIPTION + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}!" + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION = 8, + /** + * A message sent when a user subscribes to (boosts) a guild to tier 1 + * + * Value: 9 + * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1 + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! " + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9, + /** + * A message sent when a user subscribes to (boosts) a guild to tier 2 + * + * Value: 10 + * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2 + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! " + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10, + /** + * A message sent when a user subscribes to (boosts) a guild to tier 3 + * + * Value: 11 + * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3 + * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! " + * Deletable: true + */ + PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11, + /** + * A message sent when a news channel is followed + * + * Value: 12 + * Name: CHANNEL_FOLLOW_ADD + * Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here." + * Deletable: true + */ + CHANNEL_FOLLOW_ADD = 12, + /** + * A message sent when a guild is disqualified from discovery + * + * Value: 14 + * Name: GUILD_DISCOVERY_DISQUALIFIED + * Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details." + * Deletable: true + */ + GUILD_DISCOVERY_DISQUALIFIED = 14, + /** + * A message sent when a guild requalifies for discovery + * + * Value: 15 + * Name: GUILD_DISCOVERY_REQUALIFIED + * Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!" + * Deletable: true + */ + GUILD_DISCOVERY_REQUALIFIED = 15, + /** + * A message sent when a guild has failed discovery requirements for a week + * + * Value: 16 + * Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING + * Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery." + * Deletable: true + */ + GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, + /** + * A message sent when a guild has failed discovery requirements for 3 weeks + * + * Value: 17 + * Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING + * Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery." + * Deletable: true + */ + GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, + /** + * A message sent when a thread is created + * + * Value: 18 + * Name: THREAD_CREATED + * Rendered Content: "{author} started a thread: {content} . See all threads." + * Deletable: true + */ + THREAD_CREATED = 18, + /** + * A message sent when a user replies to a message + * + * Value: 19 + * Name: REPLY + * Rendered Content: "{content}" + * Deletable: true + */ + REPLY = 19, + /** + * A message sent when a user uses a slash command + * + * Value: 20 + * Name: CHAT_INPUT_COMMAND + * Rendered Content: "{content}" + * Deletable: true + */ + CHAT_INPUT_COMMAND = 20, + /** + * A message sent when a thread starter message is added to a thread + * + * Value: 21 + * Name: THREAD_STARTER_MESSAGE + * Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread" + * Deletable: false + */ + THREAD_STARTER_MESSAGE = 21, + /** + * A message sent to remind users to invite friends to a guild + * + * Value: 22 + * Name: GUILD_INVITE_REMINDER + * Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!" + * Deletable: true + */ + GUILD_INVITE_REMINDER = 22, + /** + * A message sent when a user uses a context menu command + * + * Value: 23 + * Name: CONTEXT_MENU_COMMAND + * Rendered Content: "{content}" + * Deletable: true + */ + CONTEXT_MENU_COMMAND = 23, + /** + * A message sent when auto moderation takes an action + * + * Value: 24 + * Name: AUTO_MODERATION_ACTION + * Rendered Content: Special embed rendered from embeds[0] + * Deletable: true 1 + */ + AUTO_MODERATION_ACTION = 24, + /** + * A message sent when a user purchases or renews a role subscription + * + * Value: 25 + * Name: ROLE_SUBSCRIPTION_PURCHASE + * Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!" + * Deletable: true + */ + ROLE_SUBSCRIPTION_PURCHASE = 25, + /** + * A message sent when a user is upsold to a premium interaction + * + * Value: 26 + * Name: INTERACTION_PREMIUM_UPSELL + * Rendered Content: "{content}" + * Deletable: true + */ + INTERACTION_PREMIUM_UPSELL = 26, + /** + * A message sent when a stage channel starts + * + * Value: 27 + * Name: STAGE_START + * Rendered Content: "{author} started {content} " + * Deletable: true + */ + STAGE_START = 27, + /** + * A message sent when a stage channel ends + * + * Value: 28 + * Name: STAGE_END + * Rendered Content: "{author} ended {content} " + * Deletable: true + */ + STAGE_END = 28, + /** + * A message sent when a user starts speaking in a stage channel + * + * Value: 29 + * Name: STAGE_SPEAKER + * Rendered Content: "{author} is now a speaker." + * Deletable: true + */ + STAGE_SPEAKER = 29, + /** + * A message sent when a user raises their hand in a stage channel + * + * Value: 30 + * Name: STAGE_RAISE_HAND + * Rendered Content: "{author} requested to speak." + * Deletable: true + */ + STAGE_RAISE_HAND = 30, + /** + * A message sent when a stage channel's topic is changed + * + * Value: 31 + * Name: STAGE_TOPIC + * Rendered Content: "{author} changed the Stage topic: {content} " + * Deletable: true + */ + STAGE_TOPIC = 31, + /** + * A message sent when a user purchases an application premium subscription + * + * Value: 32 + * Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION + * Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!" + * Deletable: true + */ + GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32, + /** + * A message sent when a user gifts a premium (Nitro) referral + * + * Value: 35 + * Name: PREMIUM_REFERRAL + * Rendered Content: "{content}" + * Deletable: true + */ + PREMIUM_REFERRAL = 35, + /** + * A message sent when a user enabled lockdown for the guild + * + * Value: 36 + * Name: GUILD_INCIDENT_ALERT_MODE_ENABLED + * Rendered Content: "{author} enabled security actions until {content}." + * Deletable: true + */ + GUILD_INCIDENT_ALERT_MODE_ENABLED = 36, + /** + * A message sent when a user disables lockdown for the guild + * + * Value: 37 + * Name: GUILD_INCIDENT_ALERT_MODE_DISABLED + * Rendered Content: "{author} disabled security actions." + * Deletable: true + */ + GUILD_INCIDENT_ALERT_MODE_DISABLED = 37, + /** + * A message sent when a user reports a raid for the guild + * + * Value: 38 + * Name: GUILD_INCIDENT_REPORT_RAID + * Rendered Content: "{author} reported a raid in {guild}." + * Deletable: true + */ + GUILD_INCIDENT_REPORT_RAID = 38, + /** + * A message sent when a user reports a false alarm for the guild + * + * Value: 39 + * Name: GUILD_INCIDENT_REPORT_FALSE_ALARM + * Rendered Content: "{author} reported a false alarm in {guild}." + * Deletable: true + */ + GUILD_INCIDENT_REPORT_FALSE_ALARM = 39, + /** + * A message sent when no one sends a message in the current channel for 1 hour + * + * Value: 40 + * Name: GUILD_DEADCHAT_REVIVE_PROMPT + * Rendered Content: "{content}" + * Deletable: true + */ + GUILD_DEADCHAT_REVIVE_PROMPT = 40, + /** + * A message sent when a user buys another user a gift + * + * Value: 41 + * Name: CUSTOM_GIFT + * Rendered Content: Special embed rendered from embeds[0].url and gift_info + * Deletable: true + */ + CUSTOM_GIFT = 41, + /** + * Value: 42 + * Name: GUILD_GAMING_STATS_PROMPT + * Rendered Content: "{content}" + * Deletable: true + */ + GUILD_GAMING_STATS_PROMPT = 42, + /** + * A message sent when a user purchases a guild product + * + * Value: 44 + * Name: PURCHASE_NOTIFICATION + * Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!" + * Deletable: true + */ + PURCHASE_NOTIFICATION = 44, + /** + * A message sent when a poll is finalized + * + * Value: 46 + * Name: POLL_RESULT + * Rendered Content: Special embed rendered from embeds[0] + * Deletable: true + */ + POLL_RESULT = 46, + /** + * A message sent by the Discord Updates account when a new changelog is posted + * + * Value: 47 + * Name: CHANGELOG + * Rendered Content: "{content}" + * Deletable: true + */ + CHANGELOG = 47, + /** + * A message sent when a Nitro promotion is triggered + * + * Value: 48 + * Name: NITRO_NOTIFICATION + * Rendered Content: Special embed rendered from content + * Deletable: true + */ + NITRO_NOTIFICATION = 48, + /** + * A message sent when a voice channel is linked to a lobby + * + * Value: 49 + * Name: CHANNEL_LINKED_TO_LOBBY + * Rendered Content: "{content}" + * Deletable: true + */ + CHANNEL_LINKED_TO_LOBBY = 49, + /** + * A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary + * + * Value: 50 + * Name: GIFTING_PROMPT + * Rendered Content: Special embed + * Deletable: true + */ + GIFTING_PROMPT = 50, + /** + * A local-only message sent when a user receives an in-game message NUX + * + * Value: 51 + * Name: IN_GAME_MESSAGE_NUX + * Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More " + * Deletable: true + */ + IN_GAME_MESSAGE_NUX = 51, + /** + * A message sent when a user accepts a guild join request + * + * Value: 52 + * Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2 + * Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!" + * Deletable: true + */ + GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52, + /** + * A message sent when a user rejects a guild join request + * + * Value: 53 + * Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2 + * Rendered Content: "{join_request.user}'s application to {content} was rejected." + * Deletable: true + */ + GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53, + /** + * A message sent when a user withdraws a guild join request + * + * Value: 54 + * Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2 + * Rendered Content: "{join_request.user}'s application to {content} has been withdrawn." + * Deletable: true + */ + GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54, + /** + * A message sent when a user upgrades to HD streaming + * + * Value: 55 + * Name: HD_STREAMING_UPGRADED + * Rendered Content: "{author} activated HD Splash Potion " + * Deletable: true + */ + HD_STREAMING_UPGRADED = 55, + /** + * A message sent when a user resolves a moderation report by deleting the offending message + * + * Value: 58 + * Name: REPORT_TO_MOD_DELETED_MESSAGE + * Rendered Content: "{author} deleted the message" + * Deletable: true + */ + REPORT_TO_MOD_DELETED_MESSAGE = 58, + /** + * A message sent when a user resolves a moderation report by timing out the offending user + * + * Value: 59 + * Name: REPORT_TO_MOD_TIMEOUT_USER + * Rendered Content: "{author} timed out {mentions [0] }" + * Deletable: true + */ + REPORT_TO_MOD_TIMEOUT_USER = 59, + /** + * A message sent when a user resolves a moderation report by kicking the offending user + * + * Value: 60 + * Name: REPORT_TO_MOD_KICK_USER + * Rendered Content: "{author} kicked {mentions [0] }" + * Deletable: true + */ + REPORT_TO_MOD_KICK_USER = 60, + /** + * A message sent when a user resolves a moderation report by banning the offending user + * + * Value: 61 + * Name: REPORT_TO_MOD_BAN_USER + * Rendered Content: "{author} banned {mentions [0] }" + * Deletable: true + */ + REPORT_TO_MOD_BAN_USER = 61, + /** + * A message sent when a user resolves a moderation report + * + * Value: 62 + * Name: REPORT_TO_MOD_CLOSED_REPORT + * Rendered Content: "{author} resolved this flag" + * Deletable: true + */ + REPORT_TO_MOD_CLOSED_REPORT = 62, + /** + * A message sent when a user adds a new emoji to a guild + * + * Value: 63 + * Name: EMOJI_ADDED + * Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: " + * Deletable: true + */ + EMOJI_ADDED = 63, +} + +export const enum MessageFlags { + /** + * Message has been published to subscribed channels (via Channel Following) + * + * Value: 1 << 0 + */ + CROSSPOSTED = 1 << 0, + /** + * Message originated from a message in another channel (via Channel Following) + */ + IS_CROSSPOST = 1 << 1, + /** + * Embeds will not be included when serializing this message + */ + SUPPRESS_EMBEDS = 1 << 2, + /** + * Source message for this crosspost has been deleted (via Channel Following) + */ + SOURCE_MESSAGE_DELETED = 1 << 3, + /** + * Message came from the urgent message system + */ + URGENT = 1 << 4, + /** + * Message has an associated thread, with the same ID as the message + */ + HAS_THREAD = 1 << 5, + /** + * Message is only visible to the user who invoked the interaction + */ + EPHEMERAL = 1 << 6, + /** + * Message is an interaction response and the bot is "thinking" + */ + LOADING = 1 << 7, + /** + * Some roles were not mentioned and added to the thread + */ + FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8, + /** + * Message is hidden from the guild's feed + */ + GUILD_FEED_HIDDEN = 1 << 9, + /** + * Message contains a link that impersonates Discord + */ + SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10, + /** + * Message will not trigger push and desktop notifications + */ + SUPPRESS_NOTIFICATIONS = 1 << 12, + /** + * Message's audio attachment is rendered as a voice message + */ + IS_VOICE_MESSAGE = 1 << 13, + /** + * Message has a forwarded message snapshot attached + */ + HAS_SNAPSHOT = 1 << 14, + /** + * Message contains components from version 2 of the UI kit + */ + IS_COMPONENTS_V2 = 1 << 15, + /** + * Message was triggered by the social layer integration + */ + SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16, +} diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts index e3586255..c2af2496 100644 --- a/packages/discord-types/src/common/messages/Message.d.ts +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -2,9 +2,9 @@ import { CommandOption } from './Commands'; import { User, UserJSON } from '../User'; import { Embed, EmbedJSON } from './Embed'; import { DiscordRecord } from "../Record"; -import { StickerFormatType } from "../../../enums"; +import { MessageFlags, MessageType, StickerFormatType } from "../../../enums"; -/** +/* * TODO: looks like discord has moved over to Date instead of Moment; */ export class Message extends DiscordRecord { @@ -35,7 +35,7 @@ export class Message extends DiscordRecord { customRenderedContent: unknown; editedTimestamp: Date; embeds: Embed[]; - flags: number; + flags: MessageFlags; giftCodes: string[]; id: string; interaction: { @@ -100,7 +100,7 @@ export class Message extends DiscordRecord { stickers: unknown[]; timestamp: moment.Moment; tts: boolean; - type: number; + type: MessageType; webhookId: string | undefined; /** @@ -121,10 +121,13 @@ export class Message extends DiscordRecord { removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; getChannelId(): string; - hasFlag(flag: number): boolean; + hasFlag(flag: MessageFlags): boolean; isCommandType(): boolean; isEdited(): boolean; isSystemDM(): boolean; + + /** Vencord added */ + deleted?: boolean; } /** A smaller Message object found in FluxDispatcher and elsewhere. */ @@ -193,3 +196,9 @@ export interface MessageReaction { emoji: ReactionEmoji; me: boolean; } + +// Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|") +export type MessageTypeSets = Record< + "UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE", + Set<MessageType> +>; diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 723ece12..148998f8 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -19,8 +19,9 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { MessageFlags } from "@vencord/discord-types/enums"; import { findByPropsLazy } from "@webpack"; -import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; +import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; import NoReplyMentionPlugin from "plugins/noReplyMention"; const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); @@ -73,7 +74,7 @@ export default definePlugin({ WindowStore.removeChangeListener(focusChanged); }, - onMessageClick(msg: any, channel, event) { + onMessageClick(msg, channel, event) { const isMe = msg.author.id === UserStore.getCurrentUser().id; if (!isDeletePressed) { if (event.detail < 2) return; @@ -89,8 +90,7 @@ export default definePlugin({ } else { if (!settings.store.enableDoubleClickToReply) return; - const EPHEMERAL = 64; - if (msg.hasFlag(EPHEMERAL)) return; + if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return; const isShiftPress = event.shiftKey && !settings.store.requireModifier; const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name) diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index 215268c6..06cd7688 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -20,7 +20,8 @@ import { definePluginSettings } from "@api/Settings"; import { Devs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { Message } from "@vencord/discord-types"; -import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; +import { MessageFlags } from "@vencord/discord-types/enums"; +import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, MessageTypeSets, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; import NoReplyMentionPlugin from "plugins/noReplyMention"; @@ -134,6 +135,7 @@ function getNextMessage(isUp: boolean, isReply: boolean) { if (m.deleted) return false; if (!isReply && m.author.id !== meId) return false; // editing only own messages if (hasNoBlockedMessages && NoBlockedMessagesPlugin.shouldIgnoreMessage(m)) return false; + if (!MessageTypeSets.REPLYABLE.has(m.type) || m.hasFlag(MessageFlags.EPHEMERAL)) return false; return true; }); diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 633eceea..cae1ecd5 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -207,3 +207,5 @@ export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:" isSameDay: filters.byCode(/Math\.abs\(\+?\i-\+?\i\)/), diffAsUnits: filters.byCode("days:0", "millisecondsInUnit") }); + +export const MessageTypeSets: t.MessageTypeSets = findByPropsLazy("REPLYABLE", "FORWARDABLE"); From 8943c90cb0b8be7c6c268ec4fb6931d30ad1dc59 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Sun, 28 Sep 2025 17:34:23 -0400 Subject: [PATCH 139/141] arRPC: increase connection timeout to 5s (#3680) Co-authored-by: V <vendicated@riseup.net> --- src/plugins/arRPC.web/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 426b9a9b..538bfe31 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -79,7 +79,7 @@ export default definePlugin({ ws.onmessage = this.handleEvent; - const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s + const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 5000)); // check if open after 5s if (!connectionSuccessful) { showNotice("Failed to connect to arRPC, is it running?", "Retry", () => { // show notice about failure to connect, with retry/ignore popNotice(); From 2a33d719eecb067c2d08cb6fd17168e5c1fa7654 Mon Sep 17 00:00:00 2001 From: Eve <git@nomadants.net> Date: Mon, 29 Sep 2025 16:53:07 -0400 Subject: [PATCH 140/141] Bump Vencord version. --- src/main/csp/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts index c4192f4b..a46da7ee 100644 --- a/src/main/csp/index.ts +++ b/src/main/csp/index.ts @@ -63,6 +63,9 @@ export const CspPolicies: PolicyMap = { "dearrow-thumb.ajay.app": ImageSrc, // Dearrow Thumbnail CDN "usrbg.is-hardly.online": ImageSrc, // USRBG API "icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search) + + // forked addition + "*.dorkbutt.lol": ImageAndCssSrc, }; const findHeader = (headers: PolicyMap, headerName: Lowercase<string>) => { From b79b102833ca7c41ec6a37ae2b1d7b7e119d4d71 Mon Sep 17 00:00:00 2001 From: Eve <git@nomadants.net> Date: Mon, 29 Sep 2025 20:44:49 -0400 Subject: [PATCH 141/141] add the plugin --- src/plugins/usrbg-fork/index.ts | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/plugins/usrbg-fork/index.ts diff --git a/src/plugins/usrbg-fork/index.ts b/src/plugins/usrbg-fork/index.ts new file mode 100644 index 00000000..d77e8780 --- /dev/null +++ b/src/plugins/usrbg-fork/index.ts @@ -0,0 +1,120 @@ +/* + * 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 { Link } from "@components/Link"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const API_URL = "https://usrbg.dorkbutt.lol/users/users.json"; + +interface UsrbgApiReturn { + endpoint: string; + bucket: string; + prefix: string; + users: Record<string, string>; +} + +const settings = definePluginSettings({ + nitroFirst: { + description: "Banner to use if both Nitro and USRBG banners are present", + type: OptionType.SELECT, + options: [ + { label: "Nitro banner", value: true, default: true }, + { label: "USRBG banner", value: false }, + ] + }, + voiceBackground: { + description: "Use USRBG banners as voice chat backgrounds", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "FORKED - USRBG", + description: "Displays user banners from dorkbutt's USRBG server, allowing anyone to get a banner without Nitro", + authors: [Devs.dorkbutt, Devs.AutumnVN, Devs.katlyn, Devs.pylix, Devs.TheKodeToad], + settings, + patches: [ + { + find: '.banner)==null?"COMPLETE"', + replacement: { + match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, + replace: "$self.patchBannerUrl(arguments[0])||$&" + + } + }, + { + find: "\"data-selenium-video-tile\":", + predicate: () => settings.store.voiceBackground, + replacement: [ + { + match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, + replace: "$1.style=$self.getVoiceBackgroundStyles($1);" + } + ] + } + ], + + data: null as UsrbgApiReturn | null, + + settingsAboutComponent: () => { + return ( + "Message @dorkbutt about getting your personal banner added!" + ); + }, + + getVoiceBackgroundStyles({ className, participantUserId }: any) { + if (className.includes("tile_")) { + if (this.userHasBackground(participantUserId)) { + return { + backgroundImage: `url(${this.getImageUrl(participantUserId)})`, + backgroundSize: "cover", + backgroundPosition: "center", + backgroundRepeat: "no-repeat" + }; + } + } + }, + + patchBannerUrl({ displayProfile }: any) { + if (displayProfile?.banner && settings.store.nitroFirst) return; + if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId); + }, + + userHasBackground(userId: string) { + return !!this.data?.users[userId]; + }, + + getImageUrl(userId: string): string | null { + if (!this.userHasBackground(userId)) return null; + + // We can assert that data exists because userHasBackground returned true + const { endpoint, bucket, prefix, users: { [userId]: etag } } = this.data!; + return `${endpoint}/${bucket}/${prefix}${userId}?${etag}`; + }, + + async start() { + const res = await fetch(API_URL); + if (res.ok) { + this.data = await res.json(); + } + } +});