diff --git a/.eslintrc.json b/.eslintrc.json index 2ee24e8b3..918edca0c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,7 @@ { "root": true, "parser": "@typescript-eslint/parser", - "ignorePatterns": ["dist", "browser"], + "ignorePatterns": ["dist", "browser", "packages/vencord-types"], "plugins": [ "@typescript-eslint", "simple-header", diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml index e8ca246de..2439d86a7 100644 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ b/.github/ISSUE_TEMPLATE/blank.yml @@ -12,7 +12,8 @@ body: DO NOT USE THIS FORM, unless - you are a vencord contributor - you were given explicit permission to use this form by a moderator in our support server - - you are filing a security related report + + DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new) - type: textarea id: content diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7a2f24e8..ba22b1230 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Build web - run: pnpm buildWeb --standalone + run: pnpm buildWebStandalone - name: Build run: pnpm build --standalone diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8407e08e2..190e3069c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Build web - run: pnpm buildWeb --standalone + run: pnpm buildWebStandalone - name: Publish extension run: | diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml index d3a882fa3..a669c1a27 100644 --- a/.github/workflows/reportBrokenPlugins.yml +++ b/.github/workflows/reportBrokenPlugins.yml @@ -37,8 +37,8 @@ jobs: with: chrome-version: stable - - name: Build web - run: pnpm buildWeb --standalone --dev + - name: Build Vencord Reporter Version + run: pnpm buildReporter - name: Create Report timeout-minutes: 10 diff --git a/.vscode/settings.json b/.vscode/settings.json index fa543b38c..8be0795f9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,8 @@ "typescript.preferences.quoteStyle": "double", "javascript.preferences.quoteStyle": "double", + "eslint.experimental.useFlatConfig": false, + "gitlens.remotes": [ { "domain": "codeberg.org", diff --git a/README.md b/README.md index a43c9f834..61575d4bb 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ The cutest Discord client mod | ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) | -|:--:| -| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) | +| :--------------------------------------------------------------------------------------------------: | +| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) | ## Features @@ -32,9 +32,9 @@ https://discord.gg/D9uwnFnqmd ## Sponsors -| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | -|:--:| -| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) | +| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | +| :------------------------------------------------------------------------------------------: | +| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) | | *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* | diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index 77c72369c..79f0f2cd3 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -19,8 +19,8 @@ /// /// -import monacoHtmlLocal from "~fileContent/monacoWin.html"; -import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html"; +import monacoHtmlLocal from "file://monacoWin.html?minify"; +import monacoHtmlCdn from "file://../src/main/monacoWin.html?minify"; import * as DataStore from "../src/api/DataStore"; import { debounce } from "../src/utils"; import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; diff --git a/package.json b/package.json index 04b811e9c..e80c3970a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.8.6", + "version": "1.9.0", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -18,8 +18,13 @@ }, "scripts": { "build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs", + "buildStandalone": "pnpm build --standalone", "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", - "watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch", + "buildWebStandalone": "pnpm buildWeb --standalone", + "buildReporter": "pnpm buildWebStandalone --reporter --skip-extension", + "buildReporterDesktop": "pnpm build --reporter", + "watch": "pnpm build --watch", + "watchWeb": "pnpm buildWeb --watch", "generatePluginJson": "tsx scripts/generatePluginList.ts", "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types", "inject": "node scripts/runInstaller.mjs", @@ -27,7 +32,7 @@ "lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint:fix": "pnpm lint --fix", - "test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson", + "test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testTsc": "tsc --noEmit" }, @@ -61,6 +66,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unused-imports": "^2.0.0", "highlight.js": "10.6.0", + "html-minifier-terser": "^7.2.0", "moment": "^2.29.4", "puppeteer-core": "^19.11.1", "standalone-electron-types": "^1.0.0", @@ -101,6 +107,6 @@ }, "engines": { "node": ">=18", - "pnpm": ">=8" + "pnpm": ">=9" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 065d5d4e7..b03585799 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: highlight.js: specifier: 10.6.0 version: 10.6.0 + html-minifier-terser: + specifier: ^7.2.0 + version: 7.2.0 moment: specifier: ^2.29.4 version: 2.29.4 @@ -393,6 +396,27 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -688,6 +712,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} engines: {node: '>=8'} @@ -716,6 +743,10 @@ packages: resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} engines: {node: '>=0.10.0'} + clean-css@5.3.3: + resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} + engines: {node: '>= 10.0'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -740,6 +771,13 @@ packages: colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + component-emitter@1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} @@ -871,12 +909,19 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1402,6 +1447,11 @@ packages: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} + html-minifier-terser@7.2.0: + resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} @@ -1673,6 +1723,9 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1770,6 +1823,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -1855,6 +1911,9 @@ packages: pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1863,6 +1922,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + pascalcase@0.1.1: resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} engines: {node: '>=0.10.0'} @@ -1985,6 +2047,10 @@ packages: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2228,6 +2294,11 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + terser@5.31.0: + resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==} + engines: {node: '>=10'} + hasBin: true + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2263,6 +2334,9 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tsutils@3.21.0: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -2599,6 +2673,28 @@ snapshots: '@humanwhocodes/object-schema@1.2.1': {} + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2958,6 +3054,11 @@ snapshots: callsites@3.1.0: {} + camel-case@4.1.2: + dependencies: + pascal-case: 3.1.2 + tslib: 2.6.2 + camelcase-keys@6.2.2: dependencies: camelcase: 5.3.1 @@ -2991,6 +3092,10 @@ snapshots: isobject: 3.0.1 static-extend: 0.1.2 + clean-css@5.3.3: + dependencies: + source-map: 0.6.1 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -3016,6 +3121,10 @@ snapshots: colord@2.9.3: {} + commander@10.0.1: {} + + commander@2.20.3: {} + component-emitter@1.3.0: {} concat-map@0.0.1: {} @@ -3139,12 +3248,19 @@ snapshots: dependencies: esutils: 2.0.3 + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.6.2 + emoji-regex@8.0.0: {} end-of-stream@1.4.4: dependencies: once: 1.4.0 + entities@4.5.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -3733,6 +3849,16 @@ snapshots: dependencies: lru-cache: 6.0.0 + html-minifier-terser@7.2.0: + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.3 + commander: 10.0.1 + entities: 4.5.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.31.0 + html-tags@3.3.1: {} https-proxy-agent@5.0.1: @@ -3974,6 +4100,10 @@ snapshots: lodash.truncate@4.4.2: {} + lower-case@2.0.2: + dependencies: + tslib: 2.6.2 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -4071,6 +4201,11 @@ snapshots: natural-compare@1.4.0: {} + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.6.2 + node-fetch@2.6.7: dependencies: whatwg-url: 5.0.0 @@ -4168,6 +4303,11 @@ snapshots: pako@1.0.11: {} + param-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.6.2 + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4179,6 +4319,11 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + pascal-case@3.1.2: + dependencies: + no-case: 3.0.4 + tslib: 2.6.2 + pascalcase@0.1.1: {} path-exists@4.0.0: {} @@ -4296,6 +4441,8 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 + relateurl@0.2.7: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -4605,6 +4752,13 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + terser@5.31.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.10.0 + commander: 2.20.3 + source-map-support: 0.5.21 + text-table@0.2.0: {} through@2.3.8: {} @@ -4646,6 +4800,8 @@ snapshots: tslib@1.14.1: {} + tslib@2.6.2: {} + tsutils@3.21.0(typescript@5.4.5): dependencies: tslib: 1.14.1 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 0c2a930a0..fcf56f66c 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,19 +21,21 @@ import esbuild from "esbuild"; import { readdir } from "fs/promises"; import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isDev, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs"; const defines = { - IS_STANDALONE: isStandalone, - IS_DEV: JSON.stringify(isDev), - IS_UPDATER_DISABLED: updaterDisabled, + IS_STANDALONE, + IS_DEV, + IS_REPORTER, + IS_UPDATER_DISABLED, IS_WEB: false, IS_EXTENSION: false, VERSION: JSON.stringify(VERSION), - BUILD_TIMESTAMP, + BUILD_TIMESTAMP }; -if (defines.IS_STANDALONE === "false") - // If this is a local build (not standalone), optimise + +if (defines.IS_STANDALONE === false) + // If this is a local build (not standalone), optimize // for the specific platform we're on defines["process.platform"] = JSON.stringify(process.platform); @@ -46,7 +48,7 @@ const nodeCommonOpts = { platform: "node", target: ["esnext"], external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external], - define: defines, + define: defines }; const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; @@ -73,13 +75,13 @@ const globNativesPlugin = { let i = 0; for (const dir of pluginDirs) { const dirPath = join("src", dir); - if (!await existsAsync(dirPath)) continue; + if (!await exists(dirPath)) continue; const plugins = await readdir(dirPath); for (const p of plugins) { const nativePath = join(dirPath, p, "native.ts"); const indexNativePath = join(dirPath, p, "native/index.ts"); - if (!(await existsAsync(nativePath)) && !(await existsAsync(indexNativePath))) + if (!(await exists(nativePath)) && !(await exists(indexNativePath))) continue; const nameParts = p.split("."); diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index b4c726064..bc15ccced 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises import { join } from "path"; import Zip from "zip-local"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, isDev, VERSION } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs"; /** * @type {esbuild.BuildOptions} @@ -33,22 +33,23 @@ const commonOptions = { entryPoints: ["browser/Vencord.ts"], globalName: "Vencord", format: "iife", - external: ["plugins", "git-hash", "/assets/*"], + external: ["~plugins", "~git-hash", "/assets/*"], plugins: [ globPlugins("web"), ...commonOpts.plugins, ], target: ["esnext"], define: { - IS_WEB: "true", - IS_EXTENSION: "false", - IS_STANDALONE: "true", - IS_DEV: JSON.stringify(isDev), - IS_DISCORD_DESKTOP: "false", - IS_VESKTOP: "false", - IS_UPDATER_DISABLED: "true", + IS_WEB: true, + IS_EXTENSION: false, + IS_STANDALONE: true, + IS_DEV, + IS_REPORTER, + IS_DISCORD_DESKTOP: false, + IS_VESKTOP: false, + IS_UPDATER_DISABLED: true, VERSION: JSON.stringify(VERSION), - BUILD_TIMESTAMP, + BUILD_TIMESTAMP } }; @@ -87,16 +88,16 @@ await Promise.all( esbuild.build({ ...commonOptions, outfile: "dist/browser.js", - footer: { js: "//# sourceURL=VencordWeb" }, + footer: { js: "//# sourceURL=VencordWeb" } }), esbuild.build({ ...commonOptions, outfile: "dist/extension.js", define: { ...commonOptions?.define, - IS_EXTENSION: "true", + IS_EXTENSION: true, }, - footer: { js: "//# sourceURL=VencordWeb" }, + footer: { js: "//# sourceURL=VencordWeb" } }), esbuild.build({ ...commonOptions, @@ -112,7 +113,7 @@ await Promise.all( footer: { // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});" - }, + } }) ] ); @@ -165,7 +166,7 @@ async function buildExtension(target, files) { f.startsWith("manifest") ? "manifest.json" : f, content ]; - }))), + }))) }; await rm(target, { recursive: true, force: true }); @@ -192,14 +193,19 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content return appendFile("dist/Vencord.user.js", cssRuntime); }); -await Promise.all([ - appendCssRuntime, - buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), - buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), -]); +if (!process.argv.includes("--skip-extension")) { + await Promise.all([ + appendCssRuntime, + buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), + buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), + ]); -Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip"); -console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); + Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip"); + console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); -Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); -console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); + Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); + console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); + +} else { + await appendCssRuntime; +} diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index e2c901226..cdbb26eec 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -20,8 +20,10 @@ import "../suppressExperimentalWarnings.js"; import "../checkNodeVersion.js"; import { exec, execSync } from "child_process"; +import esbuild from "esbuild"; import { constants as FsConstants, readFileSync } from "fs"; import { access, readdir, readFile } from "fs/promises"; +import { minify as minifyHtml } from "html-minifier-terser"; import { join, relative } from "path"; import { promisify } from "util"; @@ -33,24 +35,26 @@ const PackageJSON = JSON.parse(readFileSync("package.json")); export const VERSION = PackageJSON.version; // https://reproducible-builds.org/docs/source-date-epoch/ export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now(); + export const watch = process.argv.includes("--watch"); -export const isDev = watch || process.argv.includes("--dev"); -export const isStandalone = JSON.stringify(process.argv.includes("--standalone")); -export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater")); +export const IS_DEV = watch || process.argv.includes("--dev"); +export const IS_REPORTER = process.argv.includes("--reporter"); +export const IS_STANDALONE = process.argv.includes("--standalone"); + +export const IS_UPDATER_DISABLED = process.argv.includes("--disable-updater"); export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); + export const banner = { js: ` // Vencord ${gitHash} -// Standalone: ${isStandalone} -// Platform: ${isStandalone === "false" ? process.platform : "Universal"} -// Updater disabled: ${updaterDisabled} +// Standalone: ${IS_STANDALONE} +// Platform: ${IS_STANDALONE === false ? process.platform : "Universal"} +// Updater Disabled: ${IS_UPDATER_DISABLED} `.trim() }; -const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs")); - -export function existsAsync(path) { - return access(path, FsConstants.F_OK) +export async function exists(path) { + return await access(path, FsConstants.F_OK) .then(() => true) .catch(() => false); } @@ -64,7 +68,7 @@ export const makeAllPackagesExternalPlugin = { setup(build) { const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../" build.onResolve({ filter }, args => ({ path: args.path, external: true })); - }, + } }; /** @@ -87,14 +91,14 @@ export const globPlugins = kind => ({ let plugins = "\n"; let i = 0; for (const dir of pluginDirs) { - if (!await existsAsync(`./src/${dir}`)) continue; + if (!await exists(`./src/${dir}`)) continue; const files = await readdir(`./src/${dir}`); for (const file of files) { if (file.startsWith("_") || file.startsWith(".")) continue; if (file === "index.ts") continue; const target = getPluginTarget(file); - if (target) { + if (target && !IS_REPORTER) { if (target === "dev" && !watch) continue; if (target === "web" && kind === "discordDesktop") continue; if (target === "desktop" && kind === "web") continue; @@ -161,21 +165,60 @@ export const gitRemotePlugin = { /** * @type {import("esbuild").Plugin} */ -export const fileIncludePlugin = { - name: "file-include-plugin", +export const fileUrlPlugin = { + name: "file-uri-plugin", setup: build => { - const filter = /^~fileContent\/.+$/; + const filter = /^file:\/\/.+$/; build.onResolve({ filter }, args => ({ - namespace: "include-file", + namespace: "file-uri", path: args.path, pluginData: { - path: join(args.resolveDir, args.path.slice("include-file/".length)) + uri: args.path, + path: join(args.resolveDir, args.path.slice("file://".length).split("?")[0]) } })); - build.onLoad({ filter, namespace: "include-file" }, async ({ pluginData: { path } }) => { - const [name, format] = path.split(";"); + build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => { + const { searchParams } = new URL(uri); + const base64 = searchParams.has("base64"); + const minify = IS_STANDALONE === true && searchParams.has("minify"); + const noTrim = searchParams.get("trim") === "false"; + + const encoding = base64 ? "base64" : "utf-8"; + + let content; + if (!minify) { + content = await readFile(path, encoding); + if (!noTrim) content = content.trimEnd(); + } else { + if (path.endsWith(".html")) { + content = await minifyHtml(await readFile(path, "utf-8"), { + collapseWhitespace: true, + removeComments: true, + minifyCSS: true, + minifyJS: true, + removeEmptyAttributes: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + useShortDoctype: true + }); + } else if (/[mc]?[jt]sx?$/.test(path)) { + const res = await esbuild.build({ + entryPoints: [path], + write: false, + minify: true + }); + content = res.outputFiles[0].text; + } else { + throw new Error(`Don't know how to minify file type: ${path}`); + } + + if (base64) + content = Buffer.from(content).toString("base64"); + } + return { - contents: `export default ${JSON.stringify(await readFile(name, format ?? "utf-8"))}` + contents: `export default ${JSON.stringify(content)}` }; }); } @@ -217,7 +260,7 @@ export const commonOpts = { sourcemap: watch ? "inline" : "", legalComments: "linked", banner, - plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin], + plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin], external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"], inject: ["./scripts/build/inject/react.mjs"], jsxFactory: "VencordCreateElement", diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 8bb87d812..d8cbb44a0 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +/* eslint-disable no-fallthrough */ + // eslint-disable-next-line spaced-comment /// // eslint-disable-next-line spaced-comment @@ -40,10 +42,12 @@ const browser = await pup.launch({ const page = await browser.newPage(); await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"); +await page.setBypassCSP(true); -function maybeGetError(handle: JSHandle) { - return (handle as JSHandle)?.getProperty("message") - .then(m => m.jsonValue()); +async function maybeGetError(handle: JSHandle): Promise { + return await (handle as JSHandle)?.getProperty("message") + .then(m => m?.jsonValue()) + .catch(() => undefined); } const report = { @@ -59,6 +63,7 @@ const report = { error: string; }[], otherErrors: [] as string[], + ignoredErrors: [] as string[], badWebpackFinds: [] as string[] }; @@ -71,9 +76,11 @@ const IGNORED_DISCORD_ERRORS = [ "Attempting to set fast connect zstd when unsupported" ] as Array; -function toCodeBlock(s: string) { +function toCodeBlock(s: string, indentation = 0, isDiscord = false) { s = s.replace(/```/g, "`\u200B`\u200B`"); - return "```" + s + " ```"; + + const indentationStr = Array(!isDiscord ? indentation : 0).fill(" ").join(""); + return `\`\`\`\n${s.split("\n").map(s => indentationStr + s).join("\n")}\n${indentationStr}\`\`\``; } async function printReport() { @@ -87,44 +94,35 @@ async function printReport() { report.badPatches.forEach(p => { console.log(`- ${p.plugin} (${p.type})`); console.log(` - ID: \`${p.id}\``); - console.log(` - Match: ${toCodeBlock(p.match)}`); - if (p.error) console.log(` - Error: ${toCodeBlock(p.error)}`); + console.log(` - Match: ${toCodeBlock(p.match, " - Match: ".length)}`); + if (p.error) console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`); }); console.log(); console.log("## Bad Webpack Finds"); - report.badWebpackFinds.forEach(p => console.log("- " + p)); + report.badWebpackFinds.forEach(p => console.log("- " + toCodeBlock(p, "- ".length))); console.log(); console.log("## Bad Starts"); report.badStarts.forEach(p => { console.log(`- ${p.plugin}`); - console.log(` - Error: ${toCodeBlock(p.error)}`); + console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`); }); console.log(); - const ignoredErrors = [] as string[]; - report.otherErrors = report.otherErrors.filter(e => { - if (IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))) { - ignoredErrors.push(e); - return false; - } - return true; - }); - console.log("## Discord Errors"); report.otherErrors.forEach(e => { - console.log(`- ${toCodeBlock(e)}`); + console.log(`- ${toCodeBlock(e, "- ".length)}`); }); console.log(); console.log("## Ignored Discord Errors"); - ignoredErrors.forEach(e => { - console.log(`- ${toCodeBlock(e)}`); + report.ignoredErrors.forEach(e => { + console.log(`- ${toCodeBlock(e, "- ".length)}`); }); console.log(); @@ -146,16 +144,16 @@ async function printReport() { const lines = [ `**__${p.plugin} (${p.type}):__**`, `ID: \`${p.id}\``, - `Match: ${toCodeBlock(p.match)}` + `Match: ${toCodeBlock(p.match, "Match: ".length, true)}` ]; - if (p.error) lines.push(`Error: ${toCodeBlock(p.error)}`); + if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`); return lines.join("\n"); }).join("\n\n") || "None", color: report.badPatches.length ? 0xff0000 : 0x00ff00 }, { title: "Bad Webpack Finds", - description: report.badWebpackFinds.map(toCodeBlock).join("\n") || "None", + description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None", color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 }, { @@ -163,7 +161,7 @@ async function printReport() { description: report.badStarts.map(p => { const lines = [ `**__${p.plugin}:__**`, - toCodeBlock(p.error) + toCodeBlock(p.error, 0, true) ]; return lines.join("\n"); } @@ -172,7 +170,7 @@ async function printReport() { }, { title: "Discord Errors", - description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None", + description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None", color: report.otherErrors.length ? 0xff0000 : 0x00ff00 } ] @@ -188,61 +186,6 @@ page.on("console", async e => { const level = e.type(); const rawArgs = e.args(); - const firstArg = await rawArgs[0]?.jsonValue(); - if (firstArg === "[PUPPETEER_TEST_DONE_SIGNAL]") { - await browser.close(); - await printReport(); - process.exit(); - } - - const isVencord = firstArg === "[Vencord]"; - const isDebug = firstArg === "[PUP_DEBUG]"; - const isWebpackFindFail = firstArg === "[PUP_WEBPACK_FIND_FAIL]"; - - if (isWebpackFindFail) { - process.exitCode = 1; - report.badWebpackFinds.push(await rawArgs[1].jsonValue() as string); - } - - if (isVencord) { - const args = await Promise.all(e.args().map(a => a.jsonValue())); - - const [, tag, message] = args as Array; - const cause = await maybeGetError(e.args()[3]); - - switch (tag) { - case "WebpackInterceptor:": - const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; - if (!patchFailMatch) break; - - process.exitCode = 1; - - const [, plugin, type, id, regex] = patchFailMatch; - report.badPatches.push({ - plugin, - type, - id, - match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"), - error: cause - }); - - break; - case "PluginManager:": - const failedToStartMatch = message.match(/Failed to start (.+)/); - if (!failedToStartMatch) break; - - process.exitCode = 1; - - const [, name] = failedToStartMatch; - report.badStarts.push({ - plugin: name, - error: cause - }); - - break; - } - } - async function getText() { try { return await Promise.all( @@ -255,299 +198,121 @@ page.on("console", async e => { } } - if (isDebug) { - const text = await getText(); + const firstArg = await rawArgs[0]?.jsonValue(); - console.error(text); - if (text.includes("A fatal error occurred:")) { - process.exit(1); + const isVencord = firstArg === "[Vencord]"; + const isDebug = firstArg === "[PUP_DEBUG]"; + + outer: + if (isVencord) { + try { + var args = await Promise.all(e.args().map(a => a.jsonValue())); + } catch { + break outer; } + + const [, tag, message, otherMessage] = args as Array; + + switch (tag) { + case "WebpackInterceptor:": + const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; + if (!patchFailMatch) break; + + console.error(await getText()); + process.exitCode = 1; + + const [, plugin, type, id, regex] = patchFailMatch; + report.badPatches.push({ + plugin, + type, + id, + match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"), + error: await maybeGetError(e.args()[3]) + }); + + break; + case "PluginManager:": + const failedToStartMatch = message.match(/Failed to start (.+)/); + if (!failedToStartMatch) break; + + console.error(await getText()); + process.exitCode = 1; + + const [, name] = failedToStartMatch; + report.badStarts.push({ + plugin: name, + error: await maybeGetError(e.args()[3]) ?? "Unknown error" + }); + + break; + case "LazyChunkLoader:": + console.error(await getText()); + + switch (message) { + case "A fatal error occurred:": + process.exit(1); + } + + break; + case "Reporter:": + console.error(await getText()); + + switch (message) { + case "A fatal error occurred:": + process.exit(1); + case "Webpack Find Fail:": + process.exitCode = 1; + report.badWebpackFinds.push(otherMessage); + break; + case "Finished test": + await browser.close(); + await printReport(); + process.exit(); + } + } + } + + if (isDebug) { + console.error(await getText()); } else if (level === "error") { const text = await getText(); if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { - console.error("[Unexpected Error]", text); - report.otherErrors.push(text); + if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) { + report.ignoredErrors.push(text); + } else { + console.error("[Unexpected Error]", text); + report.otherErrors.push(text); + } } } }); -page.on("error", e => console.error("[Error]", e)); -page.on("pageerror", e => console.error("[Page Error]", e)); - -await page.setBypassCSP(true); - -async function runtime(token: string) { - console.log("[PUP_DEBUG]", "Starting test..."); - - try { - // Spoof languages to not be suspicious - Object.defineProperty(navigator, "languages", { - get: function () { - return ["en-US", "en"]; - }, - }); - - // Monkey patch Logger to not log with custom css - // @ts-ignore - const originalLog = Vencord.Util.Logger.prototype._log; - // @ts-ignore - Vencord.Util.Logger.prototype._log = function (level, levelColor, args) { - if (level === "warn" || level === "error") - return console[level]("[Vencord]", this.name + ":", ...args); - - return originalLog.call(this, level, levelColor, args); - }; - - // Force enable all plugins and patches - Vencord.Plugins.patches.length = 0; - Object.values(Vencord.Plugins.plugins).forEach(p => { - // Needs native server to run - if (p.name === "WebRichPresence (arRPC)") return; - - Vencord.Settings.plugins[p.name].enabled = true; - p.patches?.forEach(patch => { - patch.plugin = p.name; - delete patch.predicate; - delete patch.group; - - Vencord.Util.canonicalizeFind(patch); - if (!Array.isArray(patch.replacement)) { - patch.replacement = [patch.replacement]; - } - - patch.replacement.forEach(r => { - delete r.predicate; - }); - - Vencord.Plugins.patches.push(patch); - }); - }); - - let wreq: typeof Vencord.Webpack.wreq; - - const { canonicalizeMatch, Logger } = Vencord.Util; - - const validChunks = new Set(); - const invalidChunks = new Set(); - const deferredRequires = new Set(); - - let chunksSearchingResolve: (value: void | PromiseLike) => void; - const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r); - - // True if resolved, false otherwise - const chunksSearchPromises = [] as Array<() => boolean>; - - const LazyChunkRegex = canonicalizeMatch(/(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\)))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g); - - async function searchAndLoadLazyChunks(factoryCode: string) { - const lazyChunks = factoryCode.matchAll(LazyChunkRegex); - const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>(); - - // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before - // the chunk containing the component - const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT"); - - await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIdsArray, rawChunkIdsSingle, entryPoint]) => { - const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle; - const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Vencord.Webpack.ChunkIdsRegex)).map(m => m[1]) : []; - - if (chunkIds.length === 0) { - return; - } - - let invalidChunkGroup = false; - - for (const id of chunkIds) { - if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue; - - const isWasm = await fetch(wreq.p + wreq.u(id)) - .then(r => r.text()) - .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); - - if (isWasm) { - invalidChunks.add(id); - invalidChunkGroup = true; - continue; - } - - validChunks.add(id); - } - - if (!invalidChunkGroup) { - validChunkGroups.add([chunkIds, entryPoint]); - } - })); - - // Loads all found valid chunk groups - await Promise.all( - Array.from(validChunkGroups) - .map(([chunkIds]) => - Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { }))) - ) - ); - - // Requires the entry points for all valid chunk groups - for (const [, entryPoint] of validChunkGroups) { - try { - if (shouldForceDefer) { - deferredRequires.add(entryPoint); - continue; - } - - if (wreq.m[entryPoint]) wreq(entryPoint as any); - } catch (err) { - console.error(err); - } - } - - // setImmediate to only check if all chunks were loaded after this function resolves - // We check if all chunks were loaded every time a factory is loaded - // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved - // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them - setTimeout(() => { - let allResolved = true; - - for (let i = 0; i < chunksSearchPromises.length; i++) { - const isResolved = chunksSearchPromises[i](); - - if (isResolved) { - // Remove finished promises to avoid having to iterate through a huge array everytime - chunksSearchPromises.splice(i--, 1); - } else { - allResolved = false; - } - } - - if (allResolved) chunksSearchingResolve(); - }, 0); - } - - Vencord.Webpack.waitFor( - "loginToken", - m => { - console.log("[PUP_DEBUG]", "Logging in with token..."); - m.loginToken(token); - } - ); - - Vencord.Webpack.beforeInitListeners.add(async webpackRequire => { - console.log("[PUP_DEBUG]", "Loading all chunks..."); - - wreq = webpackRequire; - - Vencord.Webpack.factoryListeners.add(factory => { - let isResolved = false; - searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true); - - chunksSearchPromises.push(() => isResolved); - }); - - // setImmediate to only search the initial factories after Discord initialized the app - // our beforeInitListeners are called before Discord initializes the app - setTimeout(() => { - for (const factoryId in wreq.m) { - let isResolved = false; - searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true); - - chunksSearchPromises.push(() => isResolved); - } - }, 0); - }); - - await chunksSearchingDone; - - // Require deferred entry points - for (const deferredRequire of deferredRequires) { - wreq!(deferredRequire as any); - } - - // All chunks Discord has mapped to asset files, even if they are not used anymore - const allChunks = [] as string[]; - - // Matches "id" or id: - for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) { - const id = currentMatch[1] ?? currentMatch[2]; - if (id == null) continue; - - allChunks.push(id); - } - - if (allChunks.length === 0) throw new Error("Failed to get all chunks"); - - // Chunks that are not loaded (not used) by Discord code anymore - const chunksLeft = allChunks.filter(id => { - return !(validChunks.has(id) || invalidChunks.has(id)); - }); - - await Promise.all(chunksLeft.map(async id => { - const isWasm = await fetch(wreq.p + wreq.u(id)) - .then(r => r.text()) - .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push")); - - // Loads and requires a chunk - if (!isWasm) { - await wreq.e(id as any); - if (wreq.m[id]) wreq(id as any); - } - })); - - console.log("[PUP_DEBUG]", "Finished loading all chunks!"); - - for (const patch of Vencord.Plugins.patches) { - if (!patch.all) { - new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); - } - } - - for (const [searchType, args] of Vencord.Webpack.lazyWebpackSearchHistory) { - let method = searchType; - - if (searchType === "findComponent") method = "find"; - if (searchType === "findExportedComponent") method = "findByProps"; - if (searchType === "waitFor" || searchType === "waitForComponent") { - if (typeof args[0] === "string") method = "findByProps"; - else method = "find"; - } - if (searchType === "waitForStore") method = "findStore"; - - try { - let result: any; - - if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { - const [factory] = args; - result = factory(); - } else if (method === "extractAndLoadChunks") { - const [code, matcher] = args; - - const module = Vencord.Webpack.findModuleFactory(...code); - if (module) result = module.toString().match(canonicalizeMatch(matcher)); - } else { - // @ts-ignore - result = Vencord.Webpack[method](...args); - } - - if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro"; - } catch (e) { - let logMessage = searchType; - if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; - else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; - else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; - - console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage); - } - } - - setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000); - } catch (e) { - console.log("[PUP_DEBUG]", "A fatal error occurred:", e); +page.on("error", e => console.error("[Error]", e.message)); +page.on("pageerror", e => { + if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) { + console.error("[Page Error]", e.message); + report.otherErrors.push(e.message); + } else { + report.ignoredErrors.push(e.message); } +}); + +async function reporterRuntime(token: string) { + Vencord.Webpack.waitFor( + "loginToken", + m => { + console.log("[PUP_DEBUG]", "Logging in with token..."); + m.loginToken(token); + } + ); } await page.evaluateOnNewDocument(` - ${readFileSync("./dist/browser.js", "utf-8")} - - ;(${runtime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)}); + if (location.host.endsWith("discord.com")) { + ${readFileSync("./dist/browser.js", "utf-8")}; + (${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)}); + } `); await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login"); diff --git a/src/Vencord.ts b/src/Vencord.ts index 72541148e..c4c6d4705 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -42,6 +42,10 @@ import { checkForUpdates, update, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; import { SettingsRouter } from "./webpack/common"; +if (IS_REPORTER) { + require("./debug/runReporter"); +} + async function syncSettings() { // pre-check for local shared settings if ( diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 061bdeb8a..24c68c4ed 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -17,7 +17,6 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { User } from "discord-types/general"; import { ComponentType, HTMLProps } from "react"; import Plugins from "~plugins"; @@ -79,14 +78,14 @@ export function _getBadges(args: BadgeUserArgs) { : badges.push({ ...badge, ...args }); } } - const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id); + const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); if (donorBadges) badges.unshift(...donorBadges); return badges; } export interface BadgeUserArgs { - user: User; + userId: string; guildId: string; } diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index 2f7039137..4ae022c59 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -17,14 +17,14 @@ */ import { mergeDefaults } from "@utils/mergeDefaults"; -import { findByPropsLazy } from "@webpack"; +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 MessageCreator = findByPropsLazy("createBotMessage"); +const createBotMessage = findByCodeLazy('username:"Clyde"'); export function generateId() { return `-${SnowflakeUtils.fromTimestamp(Date.now())}`; @@ -37,7 +37,7 @@ export function generateId() { * @returns {Message} */ export function sendBotMessage(channelId: string, message: PartialDeep): Message { - const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] }); + const botMessage = createBotMessage({ channelId, content: "", embeds: [] }); MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage)); diff --git a/src/api/DataStore/index.ts b/src/api/DataStore/index.ts index 97f43edd6..47ae39dbd 100644 --- a/src/api/DataStore/index.ts +++ b/src/api/DataStore/index.ts @@ -49,7 +49,7 @@ let defaultGetStoreFunc: UseStore | undefined; function defaultGetStore() { if (!defaultGetStoreFunc) { - defaultGetStoreFunc = createStore("VencordData", "VencordStore"); + defaultGetStoreFunc = createStore(!IS_REPORTER ? "VencordData" : "VencordDataReporter", "VencordStore"); } return defaultGetStoreFunc; } diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts new file mode 100644 index 000000000..284a20886 --- /dev/null +++ b/src/api/MessageUpdater.ts @@ -0,0 +1,29 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { MessageCache, MessageStore } from "@webpack/common"; +import { FluxStore } from "@webpack/types"; +import { Message } from "discord-types/general"; + +/** + * Update and re-render a message + * @param channelId The channel id of the message + * @param messageId The message id + * @param fields The fields of the message to change. Leave empty if you just want to re-render + */ +export function updateMessage(channelId: string, messageId: string, fields?: Partial>) { + const channelMessageCache = MessageCache.getOrCreate(channelId); + if (!channelMessageCache.has(messageId)) return; + + // To cause a message to re-render, we basically need to create a new instance of the message and obtain a new reference + // If we have fields to modify we can use the merge method of the class, otherwise we just create a new instance with the old fields + const newChannelMessageCache = channelMessageCache.update(messageId, (oldMessage: any) => { + return fields ? oldMessage.merge(fields) : new oldMessage.constructor(oldMessage); + }); + + MessageCache.commit(newChannelMessageCache); + (MessageStore as unknown as FluxStore).emitChange(); +} diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx index caa4b64ef..d07143c45 100644 --- a/src/api/Notifications/NotificationComponent.tsx +++ b/src/api/Notifications/NotificationComponent.tsx @@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({ {timeout !== 0 && !permanent && (
)} diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 490e6ef7f..70ba0bd4a 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -106,7 +106,7 @@ const DefaultSettings: Settings = { } }; -const settings = VencordNative.settings.get(); +const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings; mergeDefaults(settings, DefaultSettings); const saveSettingsOnFrequentAction = debounce(async () => { @@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, { if (path === "plugins" && key in plugins) return target[key] = { - enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false + enabled: IS_REPORTER ?? plugins[key].required ?? plugins[key].enabledByDefault ?? false }; // Since the property is not set, check if this is a plugin's setting and if so, try to resolve @@ -156,12 +156,14 @@ export const SettingsStore = new SettingsStoreClass(settings, { } }); -SettingsStore.addGlobalChangeListener((_, path) => { - SettingsStore.plain.cloud.settingsSyncVersion = Date.now(); - localStorage.Vencord_settingsDirty = true; - saveSettingsOnFrequentAction(); - VencordNative.settings.set(SettingsStore.plain, path); -}); +if (!IS_REPORTER) { + SettingsStore.addGlobalChangeListener((_, path) => { + SettingsStore.plain.cloud.settingsSyncVersion = Date.now(); + localStorage.Vencord_settingsDirty = true; + saveSettingsOnFrequentAction(); + VencordNative.settings.set(SettingsStore.plain, path); + }); +} /** * Same as {@link Settings} but unproxied. You should treat this as readonly, diff --git a/src/api/SettingsStores.ts b/src/api/SettingsStores.ts new file mode 100644 index 000000000..18139e4e6 --- /dev/null +++ b/src/api/SettingsStores.ts @@ -0,0 +1,69 @@ +/* + * 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 { proxyLazy } from "@utils/lazy"; +import { Logger } from "@utils/Logger"; +import { findModuleId, proxyLazyWebpack, wreq } from "@webpack"; + +import { Settings } from "./Settings"; + +interface Setting { + /** + * Get the setting value + */ + getSetting(): T; + /** + * Update the setting value + * @param value The new value + */ + updateSetting(value: T | ((old: T) => T)): Promise; + /** + * React hook for automatically updating components when the setting is updated + */ + useSetting(): T; + settingsStoreApiGroup: string; + settingsStoreApiName: string; +} + +export const SettingsStores: Array> | undefined = proxyLazyWebpack(() => { + const modId = findModuleId('"textAndImages","renderSpoilers"') as any; + if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module."); + + const mod = wreq(modId); + if (mod == null) return; + + return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any; +}); + +/** + * Get the store for a setting + * @param group The setting group + * @param name The name of the setting + */ +export function getSettingStore(group: string, name: string): Setting | undefined { + if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency."); + + return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name); +} + +/** + * getSettingStore but lazy + */ +export function getSettingStoreLazy(group: string, name: string) { + return proxyLazy(() => getSettingStore(group, name)); +} diff --git a/src/api/index.ts b/src/api/index.ts index 5dca63105..737e06d60 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -26,10 +26,12 @@ import * as $MessageAccessories from "./MessageAccessories"; import * as $MessageDecorations from "./MessageDecorations"; import * as $MessageEventsAPI from "./MessageEvents"; import * as $MessagePopover from "./MessagePopover"; +import * as $MessageUpdater from "./MessageUpdater"; import * as $Notices from "./Notices"; import * as $Notifications from "./Notifications"; import * as $ServerList from "./ServerList"; import * as $Settings from "./Settings"; +import * as $SettingsStores from "./SettingsStores"; import * as $Styles from "./Styles"; /** @@ -110,3 +112,10 @@ export const ContextMenu = $ContextMenu; * An API allowing you to add buttons to the chat input */ export const ChatButtons = $ChatButtons; + +/** + * An API allowing you to update and re-render messages + */ +export const MessageUpdater = $MessageUpdater; + +export const SettingsStores = $SettingsStores; diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index e6b2cf1fb..978d2e85a 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -69,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) { Restart now to apply new plugins and their settings - @@ -261,8 +261,9 @@ export default function PluginSettings() { plugins = []; requiredPlugins = []; + const showApi = searchValue.value === "API"; for (const p of sortedPlugins) { - if (!p.options && p.name.endsWith("API") && searchValue.value !== "API") + if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi)) continue; if (!pluginFilter(p)) continue; diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index 66b2a2158..d3d182e58 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -78,6 +78,7 @@ .vc-plugins-restart-card button { margin-top: 0.5em; + background: var(--info-warning-foreground) !important; } .vc-plugins-info-button svg:not(:hover, :focus) { diff --git a/src/debug/Tracer.ts b/src/debug/Tracer.ts index 4337e0019..7d80f425c 100644 --- a/src/debug/Tracer.ts +++ b/src/debug/Tracer.ts @@ -18,14 +18,14 @@ import { Logger } from "@utils/Logger"; -if (IS_DEV) { +if (IS_DEV || IS_REPORTER) { var traces = {} as Record; var logger = new Logger("Tracer", "#FFD166"); } const noop = function () { }; -export const beginTrace = !IS_DEV ? noop : +export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop : function beginTrace(name: string, ...args: any[]) { if (name in traces) throw new Error(`Trace ${name} already exists!`); @@ -33,7 +33,7 @@ export const beginTrace = !IS_DEV ? noop : traces[name] = [performance.now(), args]; }; -export const finishTrace = !IS_DEV ? noop : function finishTrace(name: string) { +export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) { const end = performance.now(); const [start, args] = traces[name]; @@ -48,7 +48,7 @@ type TraceNameMapper = (...args: Parameters) => string; const noopTracer = (name: string, f: F, mapper?: TraceNameMapper) => f; -export const traceFunction = !IS_DEV +export const traceFunction = !(IS_DEV || IS_REPORTER) ? noopTracer : function traceFunction(name: string, f: F, mapper?: TraceNameMapper): F { return function (this: any, ...args: Parameters) { diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts new file mode 100644 index 000000000..64c3e0ead --- /dev/null +++ b/src/debug/loadLazyChunks.ts @@ -0,0 +1,169 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Logger } from "@utils/Logger"; +import { canonicalizeMatch } from "@utils/patches"; +import * as Webpack from "@webpack"; +import { wreq } from "@webpack"; + +const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); + +export async function loadLazyChunks() { + try { + LazyChunkLoaderLogger.log("Loading all chunks..."); + + const validChunks = new Set(); + const invalidChunks = new Set(); + const deferredRequires = new Set(); + + let chunksSearchingResolve: (value: void | PromiseLike) => void; + const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r); + + // True if resolved, false otherwise + const chunksSearchPromises = [] as Array<() => boolean>; + + const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g); + + async function searchAndLoadLazyChunks(factoryCode: string) { + const lazyChunks = factoryCode.matchAll(LazyChunkRegex); + const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>(); + + // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before + // the chunk containing the component + const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT"); + + await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { + const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : []; + + if (chunkIds.length === 0) { + return; + } + + let invalidChunkGroup = false; + + for (const id of chunkIds) { + if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue; + + const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => t.includes("importScripts(")); + + if (isWorkerAsset) { + invalidChunks.add(id); + invalidChunkGroup = true; + continue; + } + + validChunks.add(id); + } + + if (!invalidChunkGroup) { + validChunkGroups.add([chunkIds, entryPoint]); + } + })); + + // Loads all found valid chunk groups + await Promise.all( + Array.from(validChunkGroups) + .map(([chunkIds]) => + Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { }))) + ) + ); + + // Requires the entry points for all valid chunk groups + for (const [, entryPoint] of validChunkGroups) { + try { + if (shouldForceDefer) { + deferredRequires.add(entryPoint); + continue; + } + + if (wreq.m[entryPoint]) wreq(entryPoint as any); + } catch (err) { + console.error(err); + } + } + + // setImmediate to only check if all chunks were loaded after this function resolves + // We check if all chunks were loaded every time a factory is loaded + // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved + // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them + setTimeout(() => { + let allResolved = true; + + for (let i = 0; i < chunksSearchPromises.length; i++) { + const isResolved = chunksSearchPromises[i](); + + if (isResolved) { + // Remove finished promises to avoid having to iterate through a huge array everytime + chunksSearchPromises.splice(i--, 1); + } else { + allResolved = false; + } + } + + if (allResolved) chunksSearchingResolve(); + }, 0); + } + + Webpack.factoryListeners.add(factory => { + let isResolved = false; + searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true); + + chunksSearchPromises.push(() => isResolved); + }); + + for (const factoryId in wreq.m) { + let isResolved = false; + searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true); + + chunksSearchPromises.push(() => isResolved); + } + + await chunksSearchingDone; + + // Require deferred entry points + for (const deferredRequire of deferredRequires) { + wreq!(deferredRequire as any); + } + + // All chunks Discord has mapped to asset files, even if they are not used anymore + const allChunks = [] as string[]; + + // Matches "id" or id: + for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) { + const id = currentMatch[1] ?? currentMatch[2]; + if (id == null) continue; + + allChunks.push(id); + } + + if (allChunks.length === 0) throw new Error("Failed to get all chunks"); + + // Chunks that are not loaded (not used) by Discord code anymore + const chunksLeft = allChunks.filter(id => { + return !(validChunks.has(id) || invalidChunks.has(id)); + }); + + await Promise.all(chunksLeft.map(async id => { + const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) + .then(r => r.text()) + .then(t => t.includes("importScripts(")); + + // Loads and requires a chunk + if (!isWorkerAsset) { + await wreq.e(id as any); + // Technically, the id of the chunk does not match the entry point + // But, still try it because we have no way to get the actual entry point + if (wreq.m[id]) wreq(id as any); + } + })); + + LazyChunkLoaderLogger.log("Finished loading all chunks!"); + } catch (e) { + LazyChunkLoaderLogger.log("A fatal error occurred:", e); + } +} diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts new file mode 100644 index 000000000..6c7a2a03f --- /dev/null +++ b/src/debug/runReporter.ts @@ -0,0 +1,75 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Logger } from "@utils/Logger"; +import * as Webpack from "@webpack"; +import { patches } from "plugins"; + +import { loadLazyChunks } from "./loadLazyChunks"; + +const ReporterLogger = new Logger("Reporter"); + +async function runReporter() { + try { + ReporterLogger.log("Starting test..."); + + let loadLazyChunksResolve: (value: void | PromiseLike) => void; + const loadLazyChunksDone = new Promise(r => loadLazyChunksResolve = r); + + Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve))); + await loadLazyChunksDone; + + for (const patch of patches) { + if (!patch.all) { + new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); + } + } + + for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) { + let method = searchType; + + if (searchType === "findComponent") method = "find"; + if (searchType === "findExportedComponent") method = "findByProps"; + if (searchType === "waitFor" || searchType === "waitForComponent") { + if (typeof args[0] === "string") method = "findByProps"; + else method = "find"; + } + if (searchType === "waitForStore") method = "findStore"; + + try { + let result: any; + + if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { + const [factory] = args; + result = factory(); + } else if (method === "extractAndLoadChunks") { + const [code, matcher] = args; + + result = await Webpack.extractAndLoadChunks(code, matcher); + if (result === false) result = null; + } else { + // @ts-ignore + result = Webpack[method](...args); + } + + if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw "a rock at ben shapiro"; + } catch (e) { + let logMessage = searchType; + if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; + else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; + else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; + + ReporterLogger.log("Webpack Find Fail:", logMessage); + } + } + + ReporterLogger.log("Finished test"); + } catch (e) { + ReporterLogger.log("A fatal error occurred:", e); + } +} + +runReporter(); diff --git a/src/globals.d.ts b/src/globals.d.ts index 94b5f15e8..e20ca4b71 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -34,9 +34,10 @@ declare global { */ export var IS_WEB: boolean; export var IS_EXTENSION: boolean; - export var IS_DEV: boolean; export var IS_STANDALONE: boolean; export var IS_UPDATER_DISABLED: boolean; + export var IS_DEV: boolean; + export var IS_REPORTER: boolean; export var IS_DISCORD_DESKTOP: boolean; export var IS_VESKTOP: boolean; export var VERSION: string; diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 9c9741db5..62785867e 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -23,12 +23,11 @@ import "./settings"; import { debounce } from "@shared/debounce"; import { IpcEvents } from "@shared/IpcEvents"; import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron"; +import monacoHtml from "file://monacoWin.html?minify&base64"; import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs"; import { open, readdir, readFile } from "fs/promises"; import { join, normalize } from "path"; -import monacoHtml from "~fileContent/monacoWin.html;base64"; - import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes"; import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, THEMES_DIR } from "./utils/constants"; import { makeLinksOpenExternally } from "./utils/externalLinks"; diff --git a/src/main/patcher.ts b/src/main/patcher.ts index f0e3c3ab4..e5b87290d 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -131,17 +131,28 @@ if (!IS_VANILLA) { process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord"); - // Monkey patch commandLine to disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790 + // Monkey patch commandLine to: + // - disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790 + // - disable UseEcoQoSForBackgroundProcess: Work around Discord unloading when in background const originalAppend = app.commandLine.appendSwitch; app.commandLine.appendSwitch = function (...args) { - if (args[0] === "disable-features" && !args[1]?.includes("WidgetLayering")) { - args[1] += ",WidgetLayering"; + if (args[0] === "disable-features") { + const disabledFeatures = new Set((args[1] ?? "").split(",")); + disabledFeatures.add("WidgetLayering"); + disabledFeatures.add("UseEcoQoSForBackgroundProcess"); + args[1] += [...disabledFeatures].join(","); } return originalAppend.apply(this, args); }; + // disable renderer backgrounding to prevent the app from unloading when in the background + // https://github.com/electron/electron/issues/2822 + // https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling // Work around discord unloading when in background + // Discord also recently started adding these flags but only on windows for some reason dunno why, it happens on Linux too app.commandLine.appendSwitch("disable-renderer-backgrounding"); + app.commandLine.appendSwitch("disable-background-timer-throttling"); + app.commandLine.appendSwitch("disable-backgrounding-occluded-windows"); } else { console.log("[Vencord] Running in vanilla mode. Not loading Vencord"); } diff --git a/src/main/updater/index.ts b/src/main/updater/index.ts index 32d5cd663..539b02a48 100644 --- a/src/main/updater/index.ts +++ b/src/main/updater/index.ts @@ -17,4 +17,4 @@ */ if (!IS_UPDATER_DISABLED) - import(IS_STANDALONE ? "./http" : "./git"); + require(IS_STANDALONE ? "./http" : "./git"); diff --git a/src/modules.d.ts b/src/modules.d.ts index 489799256..83a512b00 100644 --- a/src/modules.d.ts +++ b/src/modules.d.ts @@ -38,7 +38,7 @@ declare module "~git-remote" { export default remote; } -declare module "~fileContent/*" { +declare module "file://*" { const content: string; export default content; } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index bbccf0a11..cb153c6a9 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -18,18 +18,20 @@ import "./fixBadgeOverflow.css"; -import { BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; +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 { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { isPluginDev } from "@utils/misc"; import { closeModal, Modals, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; -import { Forms, Toasts } from "@webpack/common"; +import { Forms, Toasts, UserStore } from "@webpack/common"; +import { User } from "discord-types/general"; const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png"; @@ -37,8 +39,8 @@ const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", image: CONTRIBUTOR_BADGE, position: BadgePosition.START, - shouldShow: ({ user }) => isPluginDev(user.id), - onClick: (_, { user }) => openContributorModal(user) + shouldShow: ({ userId }) => isPluginDev(userId), + onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId)) }; let DonorBadges = {} as Record>>; @@ -66,7 +68,7 @@ export default definePlugin({ replacement: [ { match: /&&(\i)\.push\(\{id:"premium".+?\}\);/, - replace: "$&$1.unshift(...Vencord.Api.Badges._getBadges(arguments[0]));", + replace: "$&$1.unshift(...$self.getBadges(arguments[0]));", }, { // alt: "", aria-hidden: false, src: originalSrc @@ -82,7 +84,36 @@ export default definePlugin({ // conditionally override their onClick with badge.onClick if it exists { match: /href:(\i)\.link/, - replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, arguments[0]) }),$&" + replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&" + } + ] + }, + + /* new profiles */ + { + find: ".PANEL]:14", + replacement: { + match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/, + replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&" + } + }, + { + find: ".description,delay:", + replacement: [ + { + // alt: "", aria-hidden: false, src: originalSrc + match: /alt:" ","aria-hidden":!0,src:(?=.{0,20}(\i)\.icon)/, + // ...badge.props, ..., src: badge.image ?? ... + replace: "...$1.props,$& $1.image??" + }, + { + match: /(?<=text:(\i)\.description,.{0,50})children:/, + replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :" + }, + // conditionally override their onClick with badge.onClick if it exists + { + match: /href:(\i)\.link/, + replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&" } ] } @@ -104,6 +135,17 @@ export default definePlugin({ await loadBadges(); }, + getBadges(props: { userId: string; user?: User; guildId: string; }) { + try { + props.userId ??= props.user?.id!; + + return _getBadges(props); + } catch (e) { + new Logger("BadgeAPI#hasBadges").error(e); + return []; + } + }, + renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => { const Component = badge.component!; return ; diff --git a/src/plugins/_api/chatButtons.ts b/src/plugins/_api/chatButtons.ts index 1ec2fa25e..578861e2e 100644 --- a/src/plugins/_api/chatButtons.ts +++ b/src/plugins/_api/chatButtons.ts @@ -15,8 +15,8 @@ export default definePlugin({ patches: [{ find: '"sticker")', replacement: { - match: /!\i\.isMobile(?=.+?(\i)\.push\(.{0,50}"gift")/, - replace: "$& &&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)" + match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/, + replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&" } }] }); diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts index 48ae062c7..0347d5445 100644 --- a/src/plugins/_api/messageEvents.ts +++ b/src/plugins/_api/messageEvents.ts @@ -35,7 +35,7 @@ export default definePlugin({ } }, { - find: ".handleSendMessage", + find: ".handleSendMessage,onResize", replacement: { // props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); // Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid) diff --git a/src/plugins/_api/messageUpdater.ts b/src/plugins/_api/messageUpdater.ts new file mode 100644 index 000000000..8f6cca26a --- /dev/null +++ b/src/plugins/_api/messageUpdater.ts @@ -0,0 +1,37 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "MessageUpdaterAPI", + description: "API for updating and re-rendering messages.", + authors: [Devs.Nuckyz], + + patches: [ + { + // Message accessories have a custom logic to decide if they should render again, so we need to make it not ignore changed message reference + find: "}renderEmbeds(", + replacement: { + match: /(?<=this.props,\i,\[)"message",/, + replace: "" + } + } + ] +}); diff --git a/src/plugins/_api/settingsStores.ts b/src/plugins/_api/settingsStores.ts new file mode 100644 index 000000000..a888532ee --- /dev/null +++ b/src/plugins/_api/settingsStores.ts @@ -0,0 +1,43 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "SettingsStoreAPI", + description: "Patches Discord's SettingsStores to expose their group and name", + authors: [Devs.Nuckyz], + + patches: [ + { + find: ",updateSetting:", + replacement: [ + { + match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:/, + replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&" + }, + // some wrapper. just make it copy the group and name + { + match: /updateSetting:.{0,20}shouldSync/, + replace: "settingsStoreApiGroup:arguments[0].settingsStoreApiGroup,settingsStoreApiName:arguments[0].settingsStoreApiName,$&" + } + ] + } + ] +}); diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index fd221d27e..50cd6f04b 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -60,6 +60,7 @@ export default definePlugin({ // FIXME: remove once change merged to stable { find: "Messages.ACTIVITY_SETTINGS", + noWarn: true, replacement: { get match() { switch (Settings.plugins.Settings.settingsLocation) { @@ -83,18 +84,11 @@ export default definePlugin({ replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` } }, - { - find: "useDefaultUserSettingsSections:function", - replacement: { - match: /(?<=useDefaultUserSettingsSections:function\(\){return )(\i)\}/, - replace: "$self.wrapSettingsHook($1)}" - } - }, { find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", replacement: { - match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/, - replace: "$2.default.open($1);return;" + match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/, + replace: "$2.open($1);return;" } } ], diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index c7377833a..d59d82afc 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -65,7 +65,7 @@ export default definePlugin({ commands: [{ name: "vencord-debug", description: "Send Vencord Debug info", - predicate: ctx => AllowedChannelIds.includes(ctx.channel.id), + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), async execute() { const { RELEASE_CHANNEL } = window.GLOBAL_ENV; diff --git a/src/plugins/alwaysTrust/index.ts b/src/plugins/alwaysTrust/index.ts index b195e8ebf..7484a619c 100644 --- a/src/plugins/alwaysTrust/index.ts +++ b/src/plugins/alwaysTrust/index.ts @@ -49,7 +49,7 @@ export default definePlugin({ predicate: () => settings.store.domain }, { - find: "isSuspiciousDownload:", + find: "bitbucket.org", replacement: { match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/, replace: "$&return null;" diff --git a/src/plugins/appleMusic.desktop/README.md b/src/plugins/appleMusic.desktop/README.md new file mode 100644 index 000000000..52ab93bfd --- /dev/null +++ b/src/plugins/appleMusic.desktop/README.md @@ -0,0 +1,9 @@ +# AppleMusicRichPresence + +This plugin enables Discord rich presence for your Apple Music! (This only works on macOS with the Music app.) + +![Screenshot of the activity in Discord](https://github.com/Vendicated/Vencord/assets/70191398/1f811090-ab5f-4060-a9ee-d0ac44a1d3c0) + +## Configuration + +For the customizable activity format strings, you can use several special strings to include track data in activities! `{name}` is replaced with the track name; `{artist}` is replaced with the artist(s)' name(s); and `{album}` is replaced with the album name. diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx new file mode 100644 index 000000000..0d81204e9 --- /dev/null +++ b/src/plugins/appleMusic.desktop/index.tsx @@ -0,0 +1,262 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; + +const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative; + +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; + name: string; + application_id: string; + metadata?: { + button_urls?: Array; + }; + type: number; + flags: number; +} + +const enum ActivityType { + PLAYING = 0, + LISTENING = 2, +} + +const enum ActivityFlag { + INSTANCE = 1 << 0, +} + +export interface TrackData { + name: string; + album: string; + artist: string; + + appleMusicLink?: string; + songLink?: string; + + albumArtwork?: string; + artistArtwork?: string; + + playerPosition: number; + duration: number; +} + +const enum AssetImageType { + Album = "Album", + Artist = "Artist", + Disabled = "Disabled" +} + +const applicationId = "1239490006054207550"; + +function setActivity(activity: Activity | null) { + FluxDispatcher.dispatch({ + type: "LOCAL_ACTIVITY_UPDATE", + activity, + socketId: "AppleMusic", + }); +} + +const settings = definePluginSettings({ + activityType: { + type: OptionType.SELECT, + description: "Which type of activity", + options: [ + { label: "Playing", value: ActivityType.PLAYING, default: true }, + { label: "Listening", value: ActivityType.LISTENING } + ], + }, + refreshInterval: { + type: OptionType.SLIDER, + description: "The interval between activity refreshes (seconds)", + markers: [1, 2, 2.5, 3, 5, 10, 15], + default: 5, + restartNeeded: true, + }, + enableTimestamps: { + type: OptionType.BOOLEAN, + description: "Whether or not to enable timestamps", + default: true, + }, + enableButtons: { + type: OptionType.BOOLEAN, + description: "Whether or not to enable buttons", + default: true, + }, + nameString: { + type: OptionType.STRING, + description: "Activity name format string", + default: "Apple Music" + }, + detailsString: { + type: OptionType.STRING, + description: "Activity details format string", + default: "{name}" + }, + stateString: { + type: OptionType.STRING, + description: "Activity state format string", + default: "{artist}" + }, + largeImageType: { + type: OptionType.SELECT, + description: "Activity assets large image type", + options: [ + { label: "Album artwork", value: AssetImageType.Album, default: true }, + { label: "Artist artwork", value: AssetImageType.Artist }, + { label: "Disabled", value: AssetImageType.Disabled } + ], + }, + largeTextString: { + type: OptionType.STRING, + description: "Activity assets large text format string", + default: "{album}" + }, + smallImageType: { + type: OptionType.SELECT, + description: "Activity assets small image type", + options: [ + { label: "Album artwork", value: AssetImageType.Album }, + { label: "Artist artwork", value: AssetImageType.Artist, default: true }, + { label: "Disabled", value: AssetImageType.Disabled } + ], + }, + smallTextString: { + type: OptionType.STRING, + description: "Activity assets small text format string", + default: "{artist}" + }, +}); + +function customFormat(formatStr: string, data: TrackData) { + return formatStr + .replaceAll("{name}", data.name) + .replaceAll("{album}", data.album) + .replaceAll("{artist}", data.artist); +} + +function getImageAsset(type: AssetImageType, data: TrackData) { + const source = type === AssetImageType.Album + ? data.albumArtwork + : data.artistArtwork; + + if (!source) return undefined; + + return ApplicationAssetUtils.fetchAssetIds(applicationId, [source]).then(ids => ids[0]); +} + +export default definePlugin({ + name: "AppleMusicRichPresence", + description: "Discord rich presence for your Apple Music!", + authors: [Devs.RyanCaoDev], + hidden: !navigator.platform.startsWith("Mac"), + reporterTestable: ReporterTestable.None, + + settingsAboutComponent() { + return <> + + For the customizable activity format strings, you can use several special strings to include track data in activities!{" "} + {"{name}"} is replaced with the track name; {"{artist}"} is replaced with the artist(s)' name(s); and {"{album}"} is replaced with the album name. + + ; + }, + + settings, + + start() { + this.updatePresence(); + this.updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000); + }, + + stop() { + clearInterval(this.updateInterval); + FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); + }, + + updatePresence() { + this.getActivity().then(activity => { setActivity(activity); }); + }, + + async getActivity(): Promise { + const trackData = await Native.fetchTrackData(); + if (!trackData) return null; + + const [largeImageAsset, smallImageAsset] = await Promise.all([ + getImageAsset(settings.store.largeImageType, trackData), + getImageAsset(settings.store.smallImageType, trackData) + ]); + + const assets: ActivityAssets = {}; + + if (settings.store.largeImageType !== AssetImageType.Disabled) { + assets.large_image = largeImageAsset; + assets.large_text = customFormat(settings.store.largeTextString, trackData); + } + + if (settings.store.smallImageType !== AssetImageType.Disabled) { + assets.small_image = smallImageAsset; + assets.small_text = customFormat(settings.store.smallTextString, trackData); + } + + const buttons: ActivityButton[] = []; + + if (settings.store.enableButtons) { + if (trackData.appleMusicLink) + buttons.push({ + label: "Listen on Apple Music", + url: trackData.appleMusicLink, + }); + + if (trackData.songLink) + buttons.push({ + label: "View on SongLink", + url: trackData.songLink, + }); + } + + return { + application_id: applicationId, + + name: customFormat(settings.store.nameString, trackData), + details: customFormat(settings.store.detailsString, trackData), + state: customFormat(settings.store.stateString, trackData), + + timestamps: (settings.store.enableTimestamps ? { + start: Date.now() - (trackData.playerPosition * 1000), + end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000), + } : undefined), + + assets, + + buttons: buttons.length ? buttons.map(v => v.label) : undefined, + metadata: { button_urls: buttons.map(v => v.url) || undefined, }, + + type: settings.store.activityType, + flags: ActivityFlag.INSTANCE, + }; + } +}); diff --git a/src/plugins/appleMusic.desktop/native.ts b/src/plugins/appleMusic.desktop/native.ts new file mode 100644 index 000000000..2eb2a0757 --- /dev/null +++ b/src/plugins/appleMusic.desktop/native.ts @@ -0,0 +1,120 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { execFile } from "child_process"; +import { promisify } from "util"; + +import type { TrackData } from "."; + +const exec = promisify(execFile); + +// function exec(file: string, args: string[] = []) { +// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => { +// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] }); + +// let stdout: string | null = null; +// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; }); +// let stderr: string | null = null; +// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; }); + +// process.on("exit", code => { resolve({ code, stdout, stderr }); }); +// process.on("error", err => reject(err)); +// }); +// } + +async function applescript(cmds: string[]) { + const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat()); + return stdout; +} + +function makeSearchUrl(type: string, query: string) { + const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json"); + url.searchParams.set("types", type); + url.searchParams.set("limit", "1"); + url.searchParams.set("term", query); + return url; +} + +const requestOptions: RequestInit = { + headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" }, +}; + +interface RemoteData { + appleMusicLink?: string, + songLink?: string, + albumArtwork?: string, + artistArtwork?: string; +} + +let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; + +async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) { + if (id === cachedRemoteData?.id) { + if ("data" in cachedRemoteData) return cachedRemoteData.data; + if ("failures" in cachedRemoteData && cachedRemoteData.failures >= 5) return null; + } + + try { + const [songData, artistData] = await Promise.all([ + fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()), + fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json()) + ]); + + const appleMusicLink = songData?.songs?.data[0]?.attributes.url; + const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined; + + const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512"); + const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512"); + + cachedRemoteData = { + id, + data: { appleMusicLink, songLink, albumArtwork, artistArtwork } + }; + return cachedRemoteData.data; + } catch (e) { + console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e); + cachedRemoteData = { + id, + failures: (id === cachedRemoteData?.id && "failures" in cachedRemoteData ? cachedRemoteData.failures : 0) + 1 + }; + return null; + } +} + +export async function fetchTrackData(): Promise { + try { + await exec("pgrep", ["^Music$"]); + } catch (error) { + return null; + } + + const playerState = await applescript(['tell application "Music"', "get player state", "end tell"]) + .then(out => out.trim()); + if (playerState !== "playing") return null; + + const playerPosition = await applescript(['tell application "Music"', "get player position", "end tell"]) + .then(text => Number.parseFloat(text.trim())); + + const stdout = await applescript([ + 'set output to ""', + 'tell application "Music"', + "set t_id to database id of current track", + "set t_name to name of current track", + "set t_album to album of current track", + "set t_artist to artist of current track", + "set t_duration to duration of current track", + 'set output to "" & t_id & "\\n" & t_name & "\\n" & t_album & "\\n" & t_artist & "\\n" & t_duration', + "end tell", + "return output" + ]); + + const [id, name, album, artist, durationStr] = stdout.split("\n").filter(k => !!k); + const duration = Number.parseFloat(durationStr); + + const remoteData = await fetchRemoteData({ id, name, artist, album }); + + return { name, album, artist, playerPosition, duration, ...remoteData }; +} diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 423dce9b5..df307e756 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -19,11 +19,11 @@ import { popNotice, showNotice } from "@api/Notices"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import definePlugin, { ReporterTestable } from "@utils/types"; +import { findByCodeLazy } from "@webpack"; import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common"; -const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL"); +const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID"); async function lookupAsset(applicationId: string, key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; @@ -32,7 +32,7 @@ async function lookupAsset(applicationId: string, key: string): Promise const apps: any = {}; async function lookupApp(applicationId: string): Promise { const socket: any = {}; - await RpcUtils.fetchApplicationsRPC(socket, applicationId); + await fetchApplicationsRPC(socket, applicationId); return socket.application; } @@ -41,6 +41,7 @@ export default definePlugin({ name: "WebRichPresence (arRPC)", description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)", authors: [Devs.Ducko], + reporterTestable: ReporterTestable.None, settingsAboutComponent: () => ( <> diff --git a/src/plugins/banger/index.ts b/src/plugins/banger/index.ts index dd9b4c82f..7e0d2df73 100644 --- a/src/plugins/banger/index.ts +++ b/src/plugins/banger/index.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "BAN_CONFIRM_TITLE.", replacement: { - match: /src:\i\("\d+"\)/g, + match: /src:\i\("?\d+"?\)/g, replace: "src: Vencord.Settings.plugins.BANger.source" } } diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 38e1b8412..d0e8cf34c 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -19,7 +19,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { FluxDispatcher, i18n, useMemo } from "@webpack/common"; import FolderSideBar from "./FolderSideBar"; @@ -30,7 +30,7 @@ enum FolderIconDisplay { MoreThanOneFolderExpanded } -const { GuildsTree } = findByPropsLazy("GuildsTree"); +const GuildsTree = findLazy(m => m.prototype?.moveNextTo); const SortedGuildStore = findStoreLazy("SortedGuildStore"); export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); @@ -117,7 +117,7 @@ export default definePlugin({ }, // If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders { - match: /\[(\i)\]=(\(0,\i\.useStateFromStoresArray\).{0,40}getGuildsTree\(\).+?}\))(?=,)/, + match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/, replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)` }, // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children @@ -139,13 +139,13 @@ export default definePlugin({ }, { // This is the parent folder component - find: ".MAX_GUILD_FOLDER_NAME_LENGTH,", + find: ".toggleGuildFolderExpand(", predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always, replacement: [ { // Modify the expanded state to instead return the list of expanded folders - match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/, - replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`, + match: /(\],\(\)=>)(\i\.\i)\.isFolderExpanded\(\i\)\)/, + replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders())`, }, { // Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds @@ -196,7 +196,7 @@ export default definePlugin({ ] }, { - find: "APPLICATION_LIBRARY,render", + find: "APPLICATION_LIBRARY,render:", predicate: () => settings.store.sidebar, replacement: { // Render the Better Folders sidebar diff --git a/src/plugins/betterGifAltText/index.ts b/src/plugins/betterGifAltText/index.ts index f0090343e..55fa22525 100644 --- a/src/plugins/betterGifAltText/index.ts +++ b/src/plugins/betterGifAltText/index.ts @@ -36,7 +36,7 @@ export default definePlugin({ { find: ".Messages.GIF,", replacement: { - match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/, + match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/, replace: // rename prop so we can always use default value "alt_$$:$1=$self.altify($3)||$2", diff --git a/src/plugins/betterGifPicker/index.ts b/src/plugins/betterGifPicker/index.ts index f1608f28c..9d7d8db41 100644 --- a/src/plugins/betterGifPicker/index.ts +++ b/src/plugins/betterGifPicker/index.ts @@ -13,7 +13,7 @@ export default definePlugin({ authors: [Devs.Samwich], patches: [ { - find: ".GIFPickerResultTypes.SEARCH", + find: '"state",{resultType:', replacement: [{ match: /(?<="state",{resultType:)null/, replace: '"Favorites"' diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index ecb1ed400..d69e188c0 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -5,15 +5,18 @@ */ import { definePluginSettings } from "@api/Settings"; +import { getSettingStoreLazy } from "@api/SettingsStores"; import { ImageIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { getCurrentGuild, openImageModal } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common"; +import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common"; const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild"); +const DeveloperMode = getSettingStoreLazy("appearance", "developerMode")!; + function PencilIcon() { return ( Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, noWarn: true, replacement: { @@ -57,6 +58,7 @@ export default definePlugin({ }, { find: ".roleVerifiedIcon", + all: true, predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, noWarn: true, replacement: { diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index 9c93289c8..598e01042 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -77,15 +77,6 @@ export default definePlugin({ replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&" } ] - }, - { - // Add the ability to change BlobMask's lower badge height - // (it allows changing width so we can mirror that logic) - find: "this.getBadgePositionInterpolation(", - replacement: { - match: /(\i\.animated\.rect,{id:\i,x:48-\(\i\+8\)\+4,y:)28(,width:\i\+8,height:)24,/, - replace: (_, leftPart, rightPart) => `${leftPart} 48 - ((this.props.lowerBadgeHeight ?? 16) + 8) + 4 ${rightPart} (this.props.lowerBadgeHeight ?? 16) + 8,` - } } ], @@ -153,14 +144,16 @@ export default definePlugin({
} - lowerBadgeWidth={20} - lowerBadgeHeight={20} + lowerBadgeSize={{ + width: 20, + height: 20 + }} >
- +
); diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index e0267e4b0..6a3ded3c1 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -83,19 +83,19 @@ export default definePlugin({ find: "this.renderArtisanalHack()", replacement: [ { // Fade in on layer - match: /(?<=\((\i),"contextType",\i\.AccessibilityPreferencesContext\);)/, + match: /(?<=\((\i),"contextType",\i\.\i\);)/, replace: "$1=$self.Layer;", predicate: () => settings.store.disableFade }, { // Lazy-load contents - match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g, + match: /createPromise:\(\)=>([^:}]*?),webpackId:"?\d+"?,name:(?!="CollectiblesShop")"[^"]+"/g, replace: "$&,_:$1", predicate: () => settings.store.eagerLoad } ] }, { // For some reason standardSidebarView also has a small fade-in - find: "DefaultCustomContentScroller:function()", + find: 'minimal:"contentColumnMinimal"', replacement: [ { match: /\(0,\i\.useTransition\)\((\i)/, @@ -111,7 +111,7 @@ export default definePlugin({ { // Load menu TOC eagerly find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format", replacement: { - match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/, + match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/, replace: "$&(async ()=>$2)()," }, predicate: () => settings.store.eagerLoad @@ -119,8 +119,8 @@ export default definePlugin({ { // Settings cog context menu find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", replacement: { - match: /\(0,\i.useDefaultUserSettingsSections\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/, - replace: "$self.wrapMenu($&)" + match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, + replace: "$1$self.wrapMenu($2)" } } ], diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 4e07daf42..b36a2cb8d 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -11,7 +11,7 @@ import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Button, Forms, useStateFromStores } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); @@ -30,7 +30,7 @@ function onPickColor(color: number) { updateColorVars(hexColor); } -const { saveClientTheme } = findByPropsLazy("saveClientTheme"); +const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE",settings:{useSystemTheme:"system"==='); function setTheme(theme: string) { saveClientTheme({ theme }); diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index e25e7cb30..2fdf87356 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -17,119 +17,209 @@ */ import { Devs } from "@utils/constants"; +import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; +import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; import { relaunch } from "@utils/native"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; -import definePlugin from "@utils/types"; +import definePlugin, { PluginNative, StartAt } from "@utils/types"; import * as Webpack from "@webpack"; -import { extract, filters, findAll, search } from "@webpack"; -import { React, ReactDOM } from "@webpack/common"; +import { extract, filters, findAll, findModuleId, search } from "@webpack"; +import * as Common from "@webpack/common"; +import { loadLazyChunks } from "debug/loadLazyChunks"; import type { ComponentType } from "react"; -const WEB_ONLY = (f: string) => () => { +const DESKTOP_ONLY = (f: string) => () => { throw new Error(`'${f}' is Discord Desktop only.`); }; +const define: typeof Object.defineProperty = + (obj, prop, desc) => { + if (Object.hasOwn(desc, "value")) + desc.writable = true; + + return Object.defineProperty(obj, prop, { + configurable: true, + enumerable: true, + ...desc + }); + }; + +function makeShortcuts() { + function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) { + const cache = new Map(); + + return function (...filterProps: unknown[]) { + const cacheKey = String(filterProps); + if (cache.has(cacheKey)) return cache.get(cacheKey); + + const matches = findAll(filterFactory(...filterProps)); + + const result = (() => { + switch (matches.length) { + case 0: return null; + case 1: return matches[0]; + default: + const uniqueMatches = [...new Set(matches)]; + if (uniqueMatches.length > 1) + console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches); + + return matches[0]; + } + })(); + if (result && cacheKey) cache.set(cacheKey, result); + return result; + }; + } + + let fakeRenderWin: WeakRef | undefined; + const find = newFindWrapper(f => f); + const findByProps = newFindWrapper(filters.byProps); + + return { + ...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])), + wp: Webpack, + wpc: { getter: () => Webpack.cache }, + wreq: { getter: () => Webpack.wreq }, + wpsearch: search, + wpex: extract, + wpexs: (code: string) => extract(findModuleId(code)!), + loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); }, + find, + findAll: findAll, + findByProps, + findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)), + findByCode: newFindWrapper(filters.byCode), + findAllByCode: (code: string) => findAll(filters.byCode(code)), + findComponentByCode: newFindWrapper(filters.componentByCode), + findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)), + findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]], + findStore: newFindWrapper(filters.byStoreName), + PluginsApi: { getter: () => Vencord.Plugins }, + plugins: { getter: () => Vencord.Plugins.plugins }, + Settings: { getter: () => Vencord.Settings }, + Api: { getter: () => Vencord.Api }, + Util: { getter: () => Vencord.Util }, + reload: () => location.reload(), + restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch, + canonicalizeMatch, + canonicalizeReplace, + canonicalizeReplacement, + fakeRender: (component: ComponentType, props: any) => { + const prevWin = fakeRenderWin?.deref(); + const win = prevWin?.closed === false + ? prevWin + : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!; + fakeRenderWin = new WeakRef(win); + win.focus(); + + const doc = win.document; + doc.body.style.margin = "1em"; + + if (!win.prepared) { + win.prepared = true; + + [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => { + const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement; + + if (s.parentElement?.tagName === "HEAD") + doc.head.append(n); + else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-")) + doc.documentElement.append(n); + else + doc.body.append(n); + }); + } + + Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); + }, + + preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true, + + channel: { getter: () => getCurrentChannel(), preload: false }, + channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false }, + guild: { getter: () => getCurrentGuild(), preload: false }, + guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false }, + me: { getter: () => Common.UserStore.getCurrentUser(), preload: false }, + meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, + messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }, + + Stores: { + getter: () => Object.fromEntries( + Common.Flux.Store.getAll() + .map(store => [store.getName(), store] as const) + .filter(([name]) => name.length > 1) + ) + } + }; +} + +function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) { + const currentVal = val.getter(); + if (!currentVal || val.preload === false) return currentVal; + + const value = currentVal[SYM_LAZY_GET] + ? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED] + : currentVal; + + if (value) define(window.shortcutList, key, { value }); + + return value; +} + export default definePlugin({ name: "ConsoleShortcuts", description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.", authors: [Devs.Ven], - getShortcuts() { - function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) { - const cache = new Map(); + startAt: StartAt.Init, + start() { + const shortcuts = makeShortcuts(); + window.shortcutList = {}; - return function (...filterProps: unknown[]) { - const cacheKey = String(filterProps); - if (cache.has(cacheKey)) return cache.get(cacheKey); + for (const [key, val] of Object.entries(shortcuts)) { + if ("getter" in val) { + define(window.shortcutList, key, { + get: () => loadAndCacheShortcut(key, val, true) + }); - const matches = findAll(filterFactory(...filterProps)); - - const result = (() => { - switch (matches.length) { - case 0: return null; - case 1: return matches[0]; - default: - const uniqueMatches = [...new Set(matches)]; - if (uniqueMatches.length > 1) - console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches); - - return matches[0]; - } - })(); - if (result && cacheKey) cache.set(cacheKey, result); - return result; - }; + define(window, key, { + get: () => window.shortcutList[key] + }); + } else { + window.shortcutList[key] = val; + window[key] = val; + } } - let fakeRenderWin: WeakRef | undefined; - const find = newFindWrapper(f => f); - const findByProps = newFindWrapper(filters.byProps); - return { - ...Vencord.Webpack.Common, - wp: Vencord.Webpack, - wpc: Webpack.wreq.c, - wreq: Webpack.wreq, - wpsearch: search, - wpex: extract, - wpexs: (code: string) => extract(Webpack.findModuleId(code)!), - find, - findAll, - findByProps, - findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)), - findByCode: newFindWrapper(filters.byCode), - findAllByCode: (code: string) => findAll(filters.byCode(code)), - findComponentByCode: newFindWrapper(filters.componentByCode), - findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)), - findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]], - findStore: newFindWrapper(filters.byStoreName), - PluginsApi: Vencord.Plugins, - plugins: Vencord.Plugins.plugins, - Settings: Vencord.Settings, - Api: Vencord.Api, - reload: () => location.reload(), - restart: IS_WEB ? WEB_ONLY("restart") : relaunch, - canonicalizeMatch, - canonicalizeReplace, - canonicalizeReplacement, - fakeRender: (component: ComponentType, props: any) => { - const prevWin = fakeRenderWin?.deref(); - const win = prevWin?.closed === false ? prevWin : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!; - fakeRenderWin = new WeakRef(win); - win.focus(); + // unproxy loaded modules + Webpack.onceReady.then(() => { + setTimeout(() => this.eagerLoad(false), 1000); - const doc = win.document; - doc.body.style.margin = "1em"; - - if (!win.prepared) { - win.prepared = true; - - [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => { - const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement; - - if (s.parentElement?.tagName === "HEAD") - doc.head.append(n); - else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-")) - doc.documentElement.append(n); - else - doc.body.append(n); - }); - } - - ReactDOM.render(React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); + if (!IS_WEB) { + const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative; + Native.initDevtoolsOpenEagerLoad(); } - }; + }); }, - start() { - const shortcuts = this.getShortcuts(); - window.shortcutList = shortcuts; - for (const [key, val] of Object.entries(shortcuts)) - window[key] = val; + async eagerLoad(forceLoad: boolean) { + await Webpack.onceReady; + + const shortcuts = makeShortcuts(); + + for (const [key, val] of Object.entries(shortcuts)) { + if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue; + + try { + loadAndCacheShortcut(key, val, forceLoad); + } catch { } // swallow not found errors in DEV + } }, stop() { delete window.shortcutList; - for (const key in this.getShortcuts()) + for (const key in makeShortcuts()) { delete window[key]; + } } }); diff --git a/src/plugins/consoleShortcuts/native.ts b/src/plugins/consoleShortcuts/native.ts new file mode 100644 index 000000000..763b239a4 --- /dev/null +++ b/src/plugins/consoleShortcuts/native.ts @@ -0,0 +1,16 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { IpcMainInvokeEvent } from "electron"; + +export function initDevtoolsOpenEagerLoad(e: IpcMainInvokeEvent) { + const handleDevtoolsOpened = () => e.sender.executeJavaScript("Vencord.Plugins.plugins.ConsoleShortcuts.eagerLoad(true)"); + + if (e.sender.isDevToolsOpened()) + handleDevtoolsOpened(); + else + e.sender.once("devtools-opened", () => handleDevtoolsOpened()); +} diff --git a/src/plugins/copyEmojiMarkdown/README.md b/src/plugins/copyEmojiMarkdown/README.md new file mode 100644 index 000000000..9e62e6635 --- /dev/null +++ b/src/plugins/copyEmojiMarkdown/README.md @@ -0,0 +1,5 @@ +# CopyEmojiMarkdown + +Allows you to copy emojis as formatted string. Custom emojis will be copied as `<:trolley:1024751352028602449>`, default emojis as `🛒` + +![](https://github.com/Vendicated/Vencord/assets/45497981/417f345a-7031-4fe7-8e42-e238870cd547) diff --git a/src/plugins/copyEmojiMarkdown/index.tsx b/src/plugins/copyEmojiMarkdown/index.tsx new file mode 100644 index 000000000..a9c018a91 --- /dev/null +++ b/src/plugins/copyEmojiMarkdown/index.tsx @@ -0,0 +1,75 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import { copyWithToast } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Menu } from "@webpack/common"; + +const { convertNameToSurrogate } = findByPropsLazy("convertNameToSurrogate"); + +interface Emoji { + type: string; + id: string; + name: string; +} + +interface Target { + dataset: Emoji; + firstChild: HTMLImageElement; +} + +function getEmojiMarkdown(target: Target, copyUnicode: boolean): string { + const { id: emojiId, name: emojiName } = target.dataset; + + if (!emojiId) { + return copyUnicode + ? convertNameToSurrogate(emojiName) + : `:${emojiName}:`; + } + + const extension = target?.firstChild.src.match( + /https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(\w+)/ + )?.[1]; + + return `<${extension === "gif" ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`; +} + +const settings = definePluginSettings({ + copyUnicode: { + type: OptionType.BOOLEAN, + description: "Copy the raw unicode character instead of :name: for default emojis (👽)", + default: true, + }, +}); + +export default definePlugin({ + name: "CopyEmojiMarkdown", + description: "Allows you to copy emojis as formatted string (<:blobcatcozy:1026533070955872337>)", + authors: [Devs.HappyEnderman, Devs.Vishnya], + settings, + + contextMenus: { + "expression-picker"(children, { target }: { target: Target }) { + if (target.dataset.type !== "emoji") return; + + children.push( + { + copyWithToast( + getEmojiMarkdown(target, settings.store.copyUnicode), + "Success! Copied emoji markdown." + ); + }} + /> + ); + }, + }, +}); diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index 3297ca300..ab881e60c 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -24,20 +24,19 @@ import { closeAllModals } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; -import { DraftType, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; +import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; const CrashHandlerLogger = new Logger("CrashHandler"); -const { ModalStack, DraftManager, closeExpressionPicker } = proxyLazyWebpack(() => { - const [ModalStack, DraftManager, ExpressionManager] = findBulk( +const { ModalStack, DraftManager } = proxyLazyWebpack(() => { + const [ModalStack, DraftManager] = findBulk( filters.byProps("pushLazy", "popAll"), filters.byProps("clearDraft", "saveDraft"), - filters.byProps("closeExpressionPicker", "openExpressionPicker"),); + ); return { ModalStack, - DraftManager, - closeExpressionPicker: ExpressionManager?.closeExpressionPicker, + DraftManager }; }); @@ -144,7 +143,7 @@ export default definePlugin({ CrashHandlerLogger.debug("Failed to clear drafts.", err); } try { - closeExpressionPicker(); + ExpressionPickerStore.closeExpressionPicker(); } catch (err) { CrashHandlerLogger.debug("Failed to close expression picker.", err); diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index 4b9dd8e06..ee218060a 100644 --- a/src/plugins/ctrlEnterSend/index.ts +++ b/src/plugins/ctrlEnterSend/index.ts @@ -18,7 +18,7 @@ export default definePlugin({ type: OptionType.SELECT, options: [ { - label: "Ctrl+Enter (Enter or Shift+Enter for new line)", + label: "Ctrl+Enter (Enter or Shift+Enter for new line) (cmd+enter on macOS)", value: "ctrl+enter" }, { @@ -40,9 +40,9 @@ export default definePlugin({ }), patches: [ { - find: "KeyboardKeys.ENTER&&(!", + find: ".ENTER&&(!", replacement: { - match: /(?<=(\i)\.which===\i\.KeyboardKeys.ENTER&&).{0,100}(\(0,\i\.hasOpenPlainTextCodeBlock\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, + match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, replace: "$self.shouldSubmit($1, $2)" } } @@ -54,7 +54,7 @@ export default definePlugin({ result = event.shiftKey; break; case "ctrl+enter": - result = event.ctrlKey; + result = navigator.platform.includes("Mac") ? event.metaKey : event.ctrlKey; break; case "enter": result = !event.shiftKey && !event.ctrlKey; diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index f1b2fbf53..7e4e9a938 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -17,6 +17,7 @@ */ import { definePluginSettings, Settings } from "@api/Settings"; +import { getSettingStoreLazy } from "@api/SettingsStores"; import { ErrorCard } from "@components/ErrorCard"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; @@ -26,12 +27,15 @@ import { classes } from "@utils/misc"; import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common"; +import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color"); const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); const ActivityClassName = findByPropsLazy("activity", "buttonColor"); +const ShowCurrentGame = getSettingStoreLazy("status", "showCurrentGame")!; + + async function getApplicationAsset(key: string): Promise { if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, ""); return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0]; @@ -178,7 +182,7 @@ const settings = definePluginSettings({ }, startTime: { type: OptionType.NUMBER, - description: "Start timestamp in milisecond (only for custom timestamp mode)", + description: "Start timestamp in milliseconds (only for custom timestamp mode)", onChange: onChange, disabled: isTimestampDisabled, isValid: (value: number) => { @@ -188,7 +192,7 @@ const settings = definePluginSettings({ }, endTime: { type: OptionType.NUMBER, - description: "End timestamp in milisecond (only for custom timestamp mode)", + description: "End timestamp in milliseconds (only for custom timestamp mode)", onChange: onChange, disabled: isTimestampDisabled, isValid: (value: number) => { @@ -390,13 +394,14 @@ export default definePlugin({ name: "CustomRPC", description: "Allows you to set a custom rich presence.", authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], + dependencies: ["SettingsStoreAPI"], start: setRpc, stop: () => setRpc(true), settings, settingsAboutComponent: () => { const activity = useAwaiter(createActivity); - const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting(); + const gameActivityEnabled = ShowCurrentGame.useSetting(); const { profileThemeStyle } = useProfileThemeStyle({}); return ( @@ -412,7 +417,7 @@ export default definePlugin({ diff --git a/src/plugins/customidle/index.ts b/src/plugins/customidle/index.ts index a59bbcb01..ea56da10e 100644 --- a/src/plugins/customidle/index.ts +++ b/src/plugins/customidle/index.ts @@ -33,26 +33,23 @@ export default definePlugin({ authors: [Devs.newwares], settings, patches: [ - { - find: "IDLE_DURATION:function(){return", - replacement: { - match: /(IDLE_DURATION:function\(\){return )\i/, - replace: "$1$self.getIdleTimeout()" - } - }, { find: 'type:"IDLE",idle:', replacement: [ { - match: /Math\.min\((\i\.AfkTimeout\.getSetting\(\)\*\i\.default\.Millis\.SECOND),\i\.IDLE_DURATION\)/, + match: /(?<=Date\.now\(\)-\i>)\i\.\i/, + replace: "$self.getIdleTimeout()" + }, + { + match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/, replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum) }, { - match: /\i\.default\.dispatch\({type:"IDLE",idle:!1}\)/, + match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/, replace: "$self.handleOnline()" }, { - match: /(setInterval\(\i,\.25\*)\i\.IDLE_DURATION/, + match: /(setInterval\(\i,\.25\*)\i\.\i/, replace: "$1$self.getIntervalDelay()" // For web installs } ] diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index 888e2bb45..5fb438256 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -69,7 +69,7 @@ async function embedDidMount(this: Component) { if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { embed.dearrow.oldTitle = embed.rawTitle; - embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1"); + embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2"); } if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) { @@ -182,8 +182,8 @@ export default definePlugin({ // add dearrow button { - match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/, - replace: "children:[$self.renderButton(this),", + match: /children:\[(?=null!=\i\?(\i)\.renderSuppressButton)/, + replace: "children:[$self.renderButton($1),", predicate: () => !settings.store.hideButton } ] diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 5b2b6d0f0..fe1f6dcbe 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -9,7 +9,6 @@ import "./ui/styles.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; import { UserStore } from "@webpack/common"; import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants"; @@ -20,7 +19,6 @@ import { settings } from "./settings"; import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components"; import DecorSection from "./ui/components/DecorSection"; -const { isAnimatedAvatarDecoration } = findByPropsLazy("isAnimatedAvatarDecoration"); export interface AvatarDecoration { asset: string; skuId: string; @@ -61,7 +59,7 @@ export default definePlugin({ }, // Remove NEW label from decor avatar decorations { - match: /(?<=\.Section\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/, + match: /(?<=\.\i\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/, replace: "||$1.skuId===$self.SKU_ID" } ] @@ -93,7 +91,7 @@ export default definePlugin({ replacement: [ // Use Decor avatar decoration hook { - match: /(?<=getAvatarDecorationURL\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/, + match: /(?<=\i\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/, replace: "$self.useUserDecorAvatarDecoration($1)??$&" } ] @@ -133,7 +131,7 @@ export default definePlugin({ if (avatarDecoration?.skuId === SKU_ID) { const parts = avatarDecoration.asset.split("_"); // Remove a_ prefix if it's animated and animation is disabled - if (isAnimatedAvatarDecoration(avatarDecoration.asset) && !canAnimate) parts.shift(); + if (avatarDecoration.asset.startsWith("a_") && !canAnimate) parts.shift(); return `${CDN_URL}/${parts.join("_")}.png`; } else if (avatarDecoration?.skuId === RAW_SKU_ID) { return avatarDecoration.asset; diff --git a/src/plugins/decor/ui/index.ts b/src/plugins/decor/ui/index.ts index 0ead602e2..c7846364c 100644 --- a/src/plugins/decor/ui/index.ts +++ b/src/plugins/decor/ui/index.ts @@ -10,5 +10,5 @@ import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack"; export const cl = classNameFactory("vc-decor-"); export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton"); -export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]); +export const requireAvatarDecorationModal = extractAndLoadChunksLazy([".COLLECTIBLES_SHOP_FULLSCREEN&&"]); export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]); diff --git a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx index 0dcf855ef..f5596f391 100644 --- a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx +++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx @@ -9,7 +9,7 @@ import { Link } from "@components/Link"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { filters, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack"; import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common"; import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants"; @@ -19,7 +19,10 @@ import { AvatarDecorationModalPreview } from "../components"; const FileUpload = findComponentByCodeLazy("fileUploadInput,"); -const { default: HelpMessage, HelpMessageTypes } = findByPropsLazy("HelpMessageTypes"); +const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', { + HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"), + HelpMessage: filters.byCode(".iconDiv") +}); function useObjectURL(object: Blob | MediaSource | null) { const [url, setUrl] = useState(null); diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index 25fd563e4..a495907b2 100644 --- a/src/plugins/devCompanion.dev/index.tsx +++ b/src/plugins/devCompanion.dev/index.tsx @@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; import { filters, findAll, search } from "@webpack"; const PORT = 8485; @@ -243,6 +243,7 @@ export default definePlugin({ name: "DevCompanion", description: "Dev Companion Plugin", authors: [Devs.Ven], + reporterTestable: ReporterTestable.None, settings, toolboxActions: { diff --git a/src/plugins/disableCallIdle/index.ts b/src/plugins/disableCallIdle/index.ts index d26f72813..c36fce6ca 100644 --- a/src/plugins/disableCallIdle/index.ts +++ b/src/plugins/disableCallIdle/index.ts @@ -29,7 +29,7 @@ export default definePlugin({ { find: ".Messages.BOT_CALL_IDLE_DISCONNECT", replacement: { - match: /,?(?=\i\(this,"idleTimeout",new \i\.Timeout\))/, + match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/, replace: ";return;" } }, diff --git a/src/plugins/dontRoundMyTimestamps/index.ts b/src/plugins/dontRoundMyTimestamps/index.ts new file mode 100644 index 000000000..4c432c73f --- /dev/null +++ b/src/plugins/dontRoundMyTimestamps/index.ts @@ -0,0 +1,35 @@ +/* + * 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 { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { moment } from "@webpack/common"; + +export default definePlugin({ + name: "DontRoundMyTimestamps", + authors: [Devs.Lexi], + description: "Always rounds relative timestamps down, so 7.6y becomes 7y instead of 8y", + + start() { + moment.relativeTimeRounding(Math.floor); + }, + + stop() { + moment.relativeTimeRounding(Math.round); + } +}); diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index b456c351e..6dd3eb300 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +import { findByCodeLazy, findStoreLazy } from "@webpack"; import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Promisable } from "type-fest"; const StickersStore = findStoreLazy("StickersStore"); -const EmojiManager = findByPropsLazy("fetchEmoji", "uploadEmoji", "deleteEmoji"); +const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); interface Sticker { t: "Sticker"; @@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) { reader.readAsDataURL(data); }); - return EmojiManager.uploadEmoji({ + return uploadEmoji({ guildId, name: emoji.name.split("~")[0], image: dataUrl diff --git a/src/plugins/experiments/hideBugReport.css b/src/plugins/experiments/hideBugReport.css new file mode 100644 index 000000000..ff78555d7 --- /dev/null +++ b/src/plugins/experiments/hideBugReport.css @@ -0,0 +1,3 @@ +#staff-help-popout-staff-help-bug-reporter { + display: none; +} diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 50b9521f9..4cf8439bc 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -17,22 +17,23 @@ */ 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 { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Forms, React, UserStore } from "@webpack/common"; -import { User } from "discord-types/general"; +import { Forms, React } from "@webpack/common"; -const KbdStyles = findByPropsLazy("key", "removeBuildOverride"); +import hideBugReport from "./hideBugReport.css?managed"; + +const KbdStyles = findByPropsLazy("key", "combo"); const settings = definePluginSettings({ - enableIsStaff: { - description: "Enable isStaff", + toolbarDevMenu: { type: OptionType.BOOLEAN, + description: "Change the Help (?) toolbar button (top right in chat) to Discord's developer menu", default: false, restartNeeded: true } @@ -40,7 +41,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "Experiments", - description: "Enable Access to Experiments in Discord!", + description: "Enable Access to Experiments & other dev-only features in Discord!", authors: [ Devs.Megu, Devs.Ven, @@ -48,6 +49,7 @@ export default definePlugin({ Devs.BanTheNons, Devs.Nuckyz ], + settings, patches: [ @@ -65,37 +67,35 @@ export default definePlugin({ replace: "$1=!0;" } }, - { - find: '"isStaff",', - predicate: () => settings.store.enableIsStaff, - replacement: [ - { - match: /(?<=>)(\i)\.hasFlag\((\i\.\i)\.STAFF\)(?=})/, - replace: (_, user, flags) => `$self.isStaff(${user},${flags})` - }, - { - match: /hasFreePremium\(\){return this.isStaff\(\)\s*?\|\|/, - replace: "hasFreePremium(){return ", - } - ] - }, { find: 'H1,title:"Experiments"', replacement: { match: 'title:"Experiments",children:[', replace: "$&$self.WarningCard()," } + }, + // change top right chat toolbar button from the help one to the dev one + { + find: "toolbar:function", + replacement: { + match: /\i\.isStaff\(\)/, + replace: "true" + }, + predicate: () => settings.store.toolbarDevMenu + }, + + // makes the Favourites Server experiment allow favouriting DMs and threads + { + find: "useCanFavoriteChannel", + replacement: { + match: /!\(\i\.isDM\(\)\|\|\i\.isThread\(\)\)/, + replace: "true", + } } ], - isStaff(user: User, flags: any) { - try { - return UserStore.getCurrentUser()?.id === user.id || user.hasFlag(flags.STAFF); - } catch (err) { - new Logger("Experiments").error(err); - return user.hasFlag(flags.STAFF); - } - }, + start: () => enableStyle(hideBugReport), + stop: () => disableStyle(hideBugReport), settingsAboutComponent: () => { const isMacOS = navigator.platform.includes("Mac"); @@ -105,14 +105,12 @@ export default definePlugin({ More Information - You can enable client DevTools{" "} - {modKey} +{" "} - {altKey} +{" "} - O{" "} - after enabling isStaff below - - - and then toggling Enable DevTools in the Developer Options tab in settings. + You can open Discord's DevTools via {" "} +
+ {modKey} +{" "} + {altKey} +{" "} + O{" "} +
); @@ -128,6 +126,12 @@ export default definePlugin({ Only use experiments if you know what you're doing. Vencord is not responsible for any damage caused by enabling experiments. + + If you don't know what an experiment does, ignore it. Do not ask us what experiments do either, we probably don't know. + + + + No, you cannot use server-side features like checking the "Send to Client" box. ), { noop: true }) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 4ab0e18ee..26e78ea26 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -37,8 +37,8 @@ const StickerStore = findStoreLazy("StickersStore") as { }; const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); -const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS"); -const RoleSubscriptionEmojiUtils = findByPropsLazy("isUnusableRoleSubscriptionEmoji"); + +const BINARY_READ_OPTIONS = findByPropsLazy("readerFactory"); function searchProtoClassField(localName: string, protoClass: any) { const field = protoClass?.fields?.find((field: any) => field.localName === localName); @@ -234,15 +234,16 @@ export default definePlugin({ } ] }, + // FIXME // Allows the usage of subscription-locked emojis - { - find: "isUnusableRoleSubscriptionEmoji:function", + /* { + find: ".getUserIsAdmin(", replacement: { - match: /isUnusableRoleSubscriptionEmoji:function/, + match: /(?=.+?\.getUserIsAdmin\((?<=function (\i)\(\i,\i\){.+?))(\i):function\(\){return \1}/, // Replace the original export with a func that always returns false and alias the original - replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function" + replace: "$2:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function(){return $1}" } - }, + }, */ // Allow stickers to be sent everywhere { find: "canUseCustomStickersEverywhere:function", @@ -333,12 +334,12 @@ export default definePlugin({ ] }, { - find: "renderEmbeds(", + find: "}renderEmbeds(", replacement: [ { // Call our function to decide whether the embed should be ignored or not predicate: () => settings.store.transformEmojis || settings.store.transformStickers, - match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/, + match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\(\((\i),\i\)?=>{)/, replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;` }, { @@ -361,7 +362,7 @@ export default definePlugin({ replacement: [ { // Export the renderable sticker to be used in the fake nitro sticker notice - match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/, + match: /let{renderableSticker:(\i).{0,270}sticker:\i,channel:\i,/, replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},` }, { @@ -399,7 +400,7 @@ export default definePlugin({ }, // Separate patch for allowing using custom app icons { - find: ".FreemiumAppIconIds.DEFAULT&&(", + find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/, replacement: { match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/, replace: "true" @@ -472,12 +473,12 @@ export default definePlugin({ const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0; if (premiumType === 2 || backgroundGradientPresetId == null) return original(); - if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !ProtoUtils) return; + if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !BINARY_READ_OPTIONS) return; const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance; const newAppearanceProto = currentAppearanceSettings != null - ? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS) + ? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), BINARY_READ_OPTIONS) : AppearanceSettingsActionCreators.create(); newAppearanceProto.theme = theme; @@ -813,11 +814,12 @@ export default definePlugin({ }, canUseEmote(e: Emoji, channelId: string) { - if (e.type === "UNICODE") return true; + if (e.type === 0) return true; if (e.available === false) return false; - const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji; - if (isUnusableRoleSubEmoji(e, this.guildId)) return false; + // FIXME + /* const isUnusableRoleSubEmoji = isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji; + if (isUnusableRoleSubEmoji(e, this.guildId)) return false; */ if (this.canUseEmotes) return e.guildId === this.guildId || hasExternalEmojiPerms(channelId); diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 7a6bda9a5..31fc71a9e 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -111,7 +111,7 @@ interface ProfileModalProps { const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ProfileModal = findComponentByCodeLazy('"ProfileCustomizationPreview"'); -const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("(.+?)"\).then\(\i\.bind\(\i,"(.+?)"\)\)/); +const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); export default definePlugin({ name: "FakeProfileThemes", diff --git a/src/plugins/favEmojiFirst/index.ts b/src/plugins/favEmojiFirst/index.ts index afc72a1d2..d1a5458d3 100644 --- a/src/plugins/favEmojiFirst/index.ts +++ b/src/plugins/favEmojiFirst/index.ts @@ -50,7 +50,7 @@ export default definePlugin({ }, { - find: "MAX_AUTOCOMPLETE_RESULTS+", + find: "numLockedEmojiResults:", replacement: [ // set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10 // and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later diff --git a/src/plugins/fixCodeblockGap/index.ts b/src/plugins/fixCodeblockGap/index.ts index 133409959..721175fe7 100644 --- a/src/plugins/fixCodeblockGap/index.ts +++ b/src/plugins/fixCodeblockGap/index.ts @@ -25,7 +25,7 @@ export default definePlugin({ authors: [Devs.Grzesiek11], patches: [ { - find: ".default.Messages.DELETED_ROLE_PLACEHOLDER", + find: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`, replacement: { match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`, replace: "$&\\n?", diff --git a/src/plugins/forceOwnerCrown/index.ts b/src/plugins/forceOwnerCrown/index.ts index 15b1f6f56..771583fe7 100644 --- a/src/plugins/forceOwnerCrown/index.ts +++ b/src/plugins/forceOwnerCrown/index.ts @@ -27,7 +27,7 @@ export default definePlugin({ authors: [Devs.D3SOX, Devs.Nickyux], patches: [ { - find: "AVATAR_DECORATION_PADDING:", + find: ".PREMIUM_GUILD_SUBSCRIPTION_TOOLTIP", replacement: { match: /,isOwner:(\i),/, replace: ",_isOwner:$1=$self.isGuildOwner(e)," diff --git a/src/plugins/friendInvites/index.ts b/src/plugins/friendInvites/index.ts index 47e312c31..20c615d6f 100644 --- a/src/plugins/friendInvites/index.ts +++ b/src/plugins/friendInvites/index.ts @@ -16,14 +16,12 @@ * along with this program. If not, see . */ -import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; +import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Constants, RestAPI, UserStore } from "@webpack/common"; const FriendInvites = findByPropsLazy("createFriendInvite"); -const { uuid4 } = findByPropsLazy("uuid4"); export default definePlugin({ name: "FriendInvites", @@ -35,47 +33,9 @@ export default definePlugin({ name: "create friend invite", description: "Generates a friend invite link.", inputType: ApplicationCommandInputType.BOT, - options: [{ - name: "Uses", - description: "How many uses?", - choices: [ - { label: "1", name: "1", value: "1" }, - { label: "5", name: "5", value: "5" } - ], - required: false, - type: ApplicationCommandOptionType.INTEGER - }], execute: async (args, ctx) => { - const uses = findOption(args, "Uses", 5); - - if (uses === 1 && !UserStore.getCurrentUser().phone) - return sendBotMessage(ctx.channel.id, { - content: "You need to have a phone number connected to your account to create a friend invite with 1 use!" - }); - - let invite: any; - if (uses === 1) { - const random = uuid4(); - const { body: { invite_suggestions } } = await RestAPI.post({ - url: Constants.Endpoints.FRIEND_FINDER, - body: { - modified_contacts: { - [random]: [1, "", ""] - }, - phone_contact_methods_count: 1 - } - }); - invite = await FriendInvites.createFriendInvite({ - code: invite_suggestions[0][3], - recipient_phone_number_or_email: random, - contact_visibility: 1, - filter_visibilities: [], - filtered_invite_suggestions_index: 1 - }); - } else { - invite = await FriendInvites.createFriendInvite(); - } + const invite = await FriendInvites.createFriendInvite(); sendBotMessage(ctx.channel.id, { content: ` diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 8ad9dc7f4..b290a4450 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -4,21 +4,23 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { Heading, React, RelationshipStore, Text } from "@webpack/common"; const container = findByPropsLazy("memberSinceWrapper"); -const { getCreatedAtDate } = findByPropsLazy("getCreatedAtDate"); -const clydeMoreInfo = findByPropsLazy("clydeMoreInfo"); +const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const locale = findByPropsLazy("getLocale"); const lastSection = findByPropsLazy("lastSection"); +const cl = classNameFactory("vc-friendssince-"); + export default definePlugin({ name: "FriendsSince", description: "Shows when you became friends with someone in the user popout", @@ -26,17 +28,17 @@ export default definePlugin({ patches: [ // User popup { - find: ".AnalyticsSections.USER_PROFILE}", + find: ".USER_PROFILE}};return", replacement: { - match: /\i.default,\{userId:(\i.id).{0,30}}\)/, + match: /,{userId:(\i.id).{0,30}}\)/, replace: "$&,$self.friendsSince({ userId: $1 })" } }, // User DMs "User Profile" popup in the right { - find: ".UserPopoutUpsellSource.PROFILE_PANEL,", + find: ".PROFILE_PANEL,", replacement: { - match: /\i.default,\{userId:(\i)}\)/, + match: /,{userId:([^,]+?)}\)/, replace: "$&,$self.friendsSince({ userId: $1 })" } }, @@ -52,6 +54,8 @@ export default definePlugin({ getFriendSince(userId: string) { try { + if (!RelationshipStore.isFriend(userId)) return null; + return RelationshipStore.getSince(userId); } catch (err) { new Logger("FriendsSince").error(err); @@ -60,12 +64,14 @@ export default definePlugin({ }, friendsSince: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => { + if (!RelationshipStore.isFriend(userId)) return null; + const friendsSince = RelationshipStore.getSince(userId); if (!friendsSince) return null; return (
- + Friends Since @@ -82,7 +88,7 @@ export default definePlugin({ )} - + {getCreatedAtDate(friendsSince, locale.getLocale())}
diff --git a/src/plugins/friendsSince/styles.css b/src/plugins/friendsSince/styles.css new file mode 100644 index 000000000..9f73db0ba --- /dev/null +++ b/src/plugins/friendsSince/styles.css @@ -0,0 +1,12 @@ +/* copy pasted from discord */ + +.vc-friendssince-title { + display: flex; + font-weight: 700; + margin-bottom: 6px +} + +.vc-friendssince-body { + font-size: 14px; + line-height: 18px +} diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 51feb9165..4e2a390d6 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -17,17 +17,19 @@ */ import { definePluginSettings } from "@api/Settings"; +import { getSettingStoreLazy } from "@api/SettingsStores"; import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findComponentByCodeLazy } from "@webpack"; -import { StatusSettingsStores } from "@webpack/common"; import style from "./style.css?managed"; const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); +const ShowCurrentGame = getSettingStoreLazy("status", "showCurrentGame")!; + function makeIcon(showCurrentGame?: boolean) { const { oldIcon } = settings.use(["oldIcon"]); @@ -60,7 +62,7 @@ function makeIcon(showCurrentGame?: boolean) { } function GameActivityToggleButton() { - const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting(); + const showCurrentGame = ShowCurrentGame.useSetting(); return ( - )} - - ); -} - -function useForceServerHome() { - const { forceServerHome } = settings.use(["forceServerHome"]); - const [shouldViewServerHome, setShouldViewServerHome] = useState(currentShouldViewServerHome); - - useEffect(() => { - shouldViewServerHomeStates.add(setShouldViewServerHome); - - return () => { - shouldViewServerHomeStates.delete(setShouldViewServerHome); - }; - }, []); - - return shouldViewServerHome || forceServerHome; -} - -function useDisableViewServerHome() { - useEffect(() => () => { - currentShouldViewServerHome = false; - for (const setState of shouldViewServerHomeStates) { - setState(false); - } - }, []); -} - -const settings = definePluginSettings({ - forceServerHome: { - type: OptionType.BOOLEAN, - description: "Force the Server Guide to be the Server Home tab when it is enabled.", - default: false - } -}); - -export default definePlugin({ - name: "ResurrectHome", - description: "Re-enables the Server Home tab when there isn't a Server Guide. Also has an option to force the Server Home over the Server Guide, which is accessible through right-clicking a server.", - authors: [Devs.Dolfies, Devs.Nuckyz], - settings, - - patches: [ - // Force home deprecation override - { - find: "GuildFeatures.GUILD_HOME_DEPRECATION_OVERRIDE", - all: true, - replacement: [ - { - match: /\i\.hasFeature\(\i\.GuildFeatures\.GUILD_HOME_DEPRECATION_OVERRIDE\)/g, - replace: "true" - } - ], - }, - // Disable feedback prompts - { - find: "GuildHomeFeedbackExperiment.definition.id", - replacement: [ - { - match: /return{showFeedback:.+?,setOnDismissedFeedback:(\i)}/, - replace: "return{showFeedback:false,setOnDismissedFeedback:$1}" - } - ] - }, - // This feature was never finished, so the patch is disabled - - // Enable guild feed render mode selector - // { - // find: "2022-01_home_feed_toggle", - // replacement: [ - // { - // match: /showSelector:!1/, - // replace: "showSelector:true" - // } - // ] - // }, - - // Fix focusMessage clearing previously cached messages and causing a loop when fetching messages around home messages - { - find: '"MessageActionCreators"', - replacement: { - match: /focusMessage\(\i\){.+?(?=focus:{messageId:(\i)})/, - replace: "$&after:$1," - } - }, - // Force Server Home instead of Server Guide - { - find: "61eef9_2", - replacement: { - match: /getMutableGuildChannelsForGuild\(\i\);return\(0,\i\.useStateFromStores\).+?\]\)(?=}function)/, - replace: m => `${m}&&!$self.useForceServerHome()` - } - }, - // Add View Server Home Button to Server Guide - { - find: "487e85_1", - replacement: { - match: /(?<=text:(\i)\?\i\.\i\.Messages\.SERVER_GUIDE:\i\.\i\.Messages\.GUILD_HOME,)/, - replace: "trailing:$self.ViewServerHomeButton({serverGuide:$1})," - } - }, - // Disable view Server Home override when the Server Home is unmouted - { - find: "69386d_5", - replacement: { - match: /location:"69386d_5".+?;/, - replace: "$&$self.useDisableViewServerHome();" - } - } - ], - - ViewServerHomeButton: ErrorBoundary.wrap(({ serverGuide }: { serverGuide?: boolean; }) => { - if (serverGuide !== true) return null; - - return ; - }), - - useForceServerHome, - useDisableViewServerHome, - - contextMenus: { - "guild-context"(children, props) { - const { forceServerHome } = settings.use(["forceServerHome"]); - - if (!props?.guild) return; - - const group = findGroupChildrenByChildId("hide-muted-channels", children); - - group?.unshift( - settings.store.forceServerHome = !forceServerHome} - /> - ); - } - } -}); diff --git a/src/plugins/revealAllSpoilers/index.ts b/src/plugins/revealAllSpoilers/index.ts index e728181aa..98e8423cf 100644 --- a/src/plugins/revealAllSpoilers/index.ts +++ b/src/plugins/revealAllSpoilers/index.ts @@ -21,7 +21,7 @@ import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; const SpoilerClasses = findByPropsLazy("spoilerContent"); -const MessagesClasses = findByPropsLazy("messagesWrapper", "messages"); +const MessagesClasses = findByPropsLazy("messagesWrapper"); export default definePlugin({ name: "RevealAllSpoilers", diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx index 20b298ccb..31eab29f5 100644 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ b/src/plugins/reviewDB/components/ReviewComponent.tsx @@ -45,7 +45,7 @@ export default LazyComponent(() => { p("container", "isHeader"), p("avatar", "zalgo"), p("button", "wrapper", "selected"), - p("botTag", "botTagRegular") + p("botTagRegular") ); const dateFormat = new Intl.DateTimeFormat(); @@ -142,7 +142,7 @@ export default LazyComponent(() => { {review.type === ReviewType.System && ( System diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index a705bc80a..79e5e8ccc 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -17,7 +17,7 @@ */ import { useAwaiter, useForceUpdater } from "@utils/react"; -import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Auth, authorize } from "../auth"; @@ -27,12 +27,11 @@ import { settings } from "../settings"; import { cl, showToast } from "../utils"; import ReviewComponent from "./ReviewComponent"; - -const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms"); -const { ChatInputTypes } = findByPropsLazy("ChatInputTypes"); - -const InputComponent = findComponentByCodeLazy("default.CHANNEL_TEXT_AREA", "input"); -const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer"); +const Transforms = findByPropsLazy("insertNodes", "textToText"); +const Editor = findByPropsLazy("start", "end", "toSlateRange"); +const ChatInputTypes = findByPropsLazy("FORM"); +const InputComponent = findComponentByCodeLazy("disableThemedBackground", "CHANNEL_TEXT_AREA"); +const createChannelRecordFromServer = findByCodeLazy(".GUILD_TEXT])", "fromServer)"); interface UserProps { discordId: string; diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 56b224da8..37177caad 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -40,9 +40,16 @@ const settings = definePluginSettings({ default: true, description: "Show role colors in the voice chat user list", restartNeeded: true + }, + reactorsList: { + type: OptionType.BOOLEAN, + default: true, + description: "Show role colors in the reactors list", + restartNeeded: true } }); + export default definePlugin({ name: "RoleColorEverywhere", authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN], @@ -64,7 +71,7 @@ export default definePlugin({ find: ".userTooltip,children", replacement: [ { - match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.default,{(?=children)/, + match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.\i,{(?=children)/, replace: "$&color:$self.getUserColor($1,{guildId:$2})," } ], @@ -99,6 +106,14 @@ export default definePlugin({ } ], predicate: () => settings.store.voiceUsers, + }, + { + find: ".reactorDefault", + replacement: { + match: /,onContextMenu:e=>.{0,15}\((\i),(\i),(\i)\).{0,250}tag:"strong"/, + replace: "$&,style:{color:$self.getColor($2?.id,$1)}" + }, + predicate: () => settings.store.reactorsList, } ], settings, diff --git a/src/plugins/searchReply/index.tsx b/src/plugins/searchReply/index.tsx index 35b197874..298e74218 100644 --- a/src/plugins/searchReply/index.tsx +++ b/src/plugins/searchReply/index.tsx @@ -20,12 +20,12 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { ReplyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByCodeLazy } from "@webpack"; import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common"; import { Message } from "discord-types/general"; -const messageUtils = findByPropsLazy("replyToMessage"); +const replyToMessage = findByCodeLazy(".TEXTAREA_FOCUS)", "showMentionToggle:"); const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { // make sure the message is in the selected channel @@ -43,7 +43,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag id="reply" label={i18n.Messages.MESSAGE_ACTION_REPLY} icon={ReplyIcon} - action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} + action={(e: React.MouseEvent) => replyToMessage(channel, message, e)} /> )); return; @@ -57,7 +57,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag id="reply" label={i18n.Messages.MESSAGE_ACTION_REPLY} icon={ReplyIcon} - action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} + action={(e: React.MouseEvent) => replyToMessage(channel, message, e)} /> )); return; diff --git a/src/plugins/secretRingTone/index.ts b/src/plugins/secretRingTone/index.ts index 9c3956a80..be804efc4 100644 --- a/src/plugins/secretRingTone/index.ts +++ b/src/plugins/secretRingTone/index.ts @@ -16,9 +16,8 @@ export default definePlugin({ { find: '"call_ringing_beat"', replacement: { - // FIXME Remove === alternative when it hits stable - match: /500(!==|===)\i\(\)\.random\(1,1e3\)/, - replace: (_, predicate) => predicate === "!==" ? "false" : "true", + match: /500!==\i\(\)\.random\(1,1e3\)/, + replace: "false", } }, ], diff --git a/src/plugins/seeSummaries/README.md b/src/plugins/seeSummaries/README.md new file mode 100644 index 000000000..1c8b6c793 --- /dev/null +++ b/src/plugins/seeSummaries/README.md @@ -0,0 +1,9 @@ +# Summaries + +Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations. + +Read more about summaries in the [official Discord help article](https://support.discord.com/hc/en-us/articles/12926016807575-In-Channel-Conversation-Summaries)! + +Note that this plugin can't fetch old summaries, it can only display ones created while your Discord is running with the plugin enabled. + +![](https://github.com/Vendicated/Vencord/assets/45497981/bd931b0c-2e85-4c10-9f7c-8ba01eb55745) diff --git a/src/plugins/seeSummaries/index.tsx b/src/plugins/seeSummaries/index.tsx new file mode 100644 index 000000000..4ce8c4af7 --- /dev/null +++ b/src/plugins/seeSummaries/index.tsx @@ -0,0 +1,115 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { ChannelStore, GuildStore } from "@webpack/common"; + +const SummaryStore = findByPropsLazy("allSummaries", "findSummary"); +const createSummaryFromServer = findByCodeLazy(".people)),startId:"); + +const settings = definePluginSettings({ + summaryExpiryThresholdDays: { + type: OptionType.SLIDER, + description: "The time in days before a summary is removed. Note that only up to 50 summaries are kept per channel", + markers: [1, 3, 5, 7, 10, 15, 20, 25, 30], + stickToMarkers: false, + default: 3, + } +}); + +interface Summary { + count: number; + end_id: string; + id: string; + message_ids: string[]; + people: string[]; + source: number; + start_id: string; + summ_short: string; + topic: string; + type: number; + unsafe: boolean; +} + +interface ChannelSummaries { + type: string; + channel_id: string; + guild_id: string; + summaries: Summary[]; + + // custom property + time?: number; +} + +export default definePlugin({ + name: "Summaries", + description: "Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations", + authors: [Devs.mantikafasi], + settings, + patches: [ + { + find: "SUMMARIZEABLE.has", + replacement: { + match: /\i\.hasFeature\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g, + replace: "true" + } + }, + { + find: "RECEIVE_CHANNEL_SUMMARY(", + replacement: { + match: /shouldFetch\((\i),\i\){/, + replace: "$& if(!$self.shouldFetch($1)) return false;" + } + } + ], + flux: { + CONVERSATION_SUMMARY_UPDATE(data) { + const incomingSummaries: ChannelSummaries[] = data.summaries.map((summary: any) => ({ ...createSummaryFromServer(summary), time: Date.now() })); + + // idk if this is good for performance but it doesnt seem to be a problem in my experience + DataStore.update("summaries-data", summaries => { + summaries ??= {}; + summaries[data.channel_id] ? summaries[data.channel_id].unshift(...incomingSummaries) : (summaries[data.channel_id] = incomingSummaries); + if (summaries[data.channel_id].length > 50) + summaries[data.channel_id] = summaries[data.channel_id].slice(0, 50); + + return summaries; + }); + } + }, + + async start() { + await DataStore.update("summaries-data", summaries => { + summaries ??= {}; + for (const key of Object.keys(summaries)) { + for (let i = summaries[key].length - 1; i >= 0; i--) { + if (summaries[key][i].time < Date.now() - 1000 * 60 * 60 * 24 * settings.store.summaryExpiryThresholdDays) { + summaries[key].splice(i, 1); + } + } + + if (summaries[key].length === 0) { + delete summaries[key]; + } + } + + Object.assign(SummaryStore.allSummaries(), summaries); + return summaries; + }); + }, + + shouldFetch(channelId: string) { + 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"); + } +}); diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx similarity index 96% rename from src/plugins/serverProfile/GuildProfileModal.tsx rename to src/plugins/serverInfo/GuildInfoModal.tsx index 8e6f60518..fb8df2ce1 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -11,19 +11,19 @@ import { openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, 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 = findExportedComponentLazy("FriendRow"); +const FriendRow = findComponentByCodeLazy(".listName,discriminatorClass"); const cl = classNameFactory("vc-gp-"); -export function openGuildProfileModal(guild: Guild) { +export function openGuildInfoModal(guild: Guild) { openModal(props => - + ); } @@ -53,7 +53,7 @@ function renderTimestamp(timestamp: number) { ); } -function GuildProfileModal({ guild }: GuildProps) { +function GuildInfoModal({ guild }: GuildProps) { const [friendCount, setFriendCount] = useState(); const [blockedCount, setBlockedCount] = useState(); diff --git a/src/plugins/serverInfo/README.md b/src/plugins/serverInfo/README.md new file mode 100644 index 000000000..98c9013e0 --- /dev/null +++ b/src/plugins/serverInfo/README.md @@ -0,0 +1,7 @@ +# ServerInfo + +Allows you to view info about servers and see friends and blocked users + +![](https://github.com/Vendicated/Vencord/assets/45497981/a49783b5-e8fc-41d8-968f-58600e9f6580) +![](https://github.com/Vendicated/Vencord/assets/45497981/5efc158a-e671-4196-a15a-77edf79a2630) +![Available as "Server Profile" option in the server context menu](https://github.com/Vendicated/Vencord/assets/45497981/f43be943-6dc4-4232-9709-fbeb382d8e54) diff --git a/src/plugins/serverProfile/index.tsx b/src/plugins/serverInfo/index.tsx similarity index 65% rename from src/plugins/serverProfile/index.tsx rename to src/plugins/serverInfo/index.tsx index 9d495c9d3..be3172f01 100644 --- a/src/plugins/serverProfile/index.tsx +++ b/src/plugins/serverInfo/index.tsx @@ -5,30 +5,32 @@ */ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Menu } from "@webpack/common"; import { Guild } from "discord-types/general"; -import { openGuildProfileModal } from "./GuildProfileModal"; +import { openGuildInfoModal } from "./GuildInfoModal"; const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => { const group = findGroupChildrenByChildId("privacy", children); group?.push( openGuildProfileModal(guild)} + action={() => openGuildInfoModal(guild)} /> ); }; +migratePluginSettings("ServerInfo", "ServerProfile"); // what was I thinking with this name lmao export default definePlugin({ - name: "ServerProfile", - description: "Allows you to view info about a server by right clicking it in the server list", + name: "ServerInfo", + description: "Allows you to view info about a server", authors: [Devs.Ven, Devs.Nuckyz], - tags: ["guild", "info"], + tags: ["guild", "info", "ServerProfile"], contextMenus: { "guild-context": Patch, "guild-header-popout": Patch diff --git a/src/plugins/serverProfile/styles.css b/src/plugins/serverInfo/styles.css similarity index 100% rename from src/plugins/serverProfile/styles.css rename to src/plugins/serverInfo/styles.css diff --git a/src/plugins/serverProfile/README.md b/src/plugins/serverProfile/README.md deleted file mode 100644 index 9da70e74e..000000000 --- a/src/plugins/serverProfile/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ServerProfile - -Allows you to view info about servers and see friends and blocked users - -![image](https://github.com/Vendicated/Vencord/assets/45497981/a49783b5-e8fc-41d8-968f-58600e9f6580) -![image](https://github.com/Vendicated/Vencord/assets/45497981/5efc158a-e671-4196-a15a-77edf79a2630) -![image](https://github.com/Vendicated/Vencord/assets/45497981/f43be943-6dc4-4232-9709-fbeb382d8e54) diff --git a/src/plugins/shikiCodeblocks.desktop/index.ts b/src/plugins/shikiCodeblocks.desktop/index.ts index ef1b5d3df..e6676a866 100644 --- a/src/plugins/shikiCodeblocks.desktop/index.ts +++ b/src/plugins/shikiCodeblocks.desktop/index.ts @@ -20,9 +20,8 @@ import "./shiki.css"; import { enableStyle } from "@api/Styles"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -import previewExampleText from "~fileContent/previewExample.tsx"; +import definePlugin, { ReporterTestable } from "@utils/types"; +import previewExampleText from "file://previewExample.tsx"; import { shiki } from "./api/shiki"; import { createHighlighter } from "./components/Highlighter"; @@ -35,6 +34,9 @@ export default definePlugin({ name: "ShikiCodeblocks", description: "Brings vscode-style codeblocks into Discord, powered by Shiki", authors: [Devs.Vap], + reporterTestable: ReporterTestable.Patches, + settings, + patches: [ { find: "codeBlock:{react(", @@ -67,7 +69,6 @@ export default definePlugin({ isPreview: true, tempSettings, }), - settings, // exports shiki, diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index d70c09315..5ef09066c 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -33,7 +33,8 @@ import { VerifiedIcon } from "./VerifiedIcon"; const Section = findComponentByCodeLazy(".lastSection", "children:"); const ThemeStore = findStoreLazy("ThemeStore"); -const platformHooks: { useLegacyPlatformType(platform: string): string; } = findByPropsLazy("useLegacyPlatformType"); + +const useLegacyPlatformType: (platform: string) => string = findByCodeLazy(".TWITTER_LEGACY:"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:"); @@ -74,15 +75,28 @@ interface ConnectionPlatform { icon: { lightSVG: string, darkSVG: string; }; } -const profilePopoutComponent = ErrorBoundary.wrap((props: { user: User, displayProfile; }) => - +const profilePopoutComponent = ErrorBoundary.wrap( + (props: { user: User; displayProfile?: any; simplified?: boolean; }) => ( + + ), + { noop: true } ); -const profilePanelComponent = ErrorBoundary.wrap(({ id }: { id: string; }) => - +const profilePanelComponent = ErrorBoundary.wrap( + (props: { id: string; simplified?: boolean; }) => ( + + ), + { noop: true } ); -function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { +function ConnectionsComponent({ id, theme, simplified }: { id: string, theme: string, simplified?: boolean; }) { const profile = UserProfileStore.getUserProfile(id); if (!profile) return null; @@ -91,6 +105,19 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { if (!connections?.length) return null; + const connectionsContainer = ( + + {connections.map(connection => )} + + ); + + if (simplified) + return connectionsContainer; + return (
Connections - - {connections.map(connection => )} - + {connectionsContainer}
); } function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) { - const platform = platforms.get(platformHooks.useLegacyPlatformType(connection.type)); + const platform = platforms.get(useLegacyPlatformType(connection.type)); const url = platform.getPlatformUserUrl?.(connection); const img = ( @@ -132,7 +153,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect - {connection.name} + {connection.name} {connection.verified && }
@@ -182,12 +203,19 @@ export default definePlugin({ } }, { - find: "\"Profile Panel: user cannot be undefined\"", + find: ".PROFILE_PANEL,", replacement: { // createElement(Divider, {}), createElement(NoteComponent) match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/, replace: "$self.profilePanelComponent({ id: $1.recipients[0] }),$&" } + }, + { + find: ".BITE_SIZE,onOpenProfile", + replacement: { + match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/, + replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })" + } } ], settings, diff --git a/src/plugins/showConnections/styles.css b/src/plugins/showConnections/styles.css index 383593c11..cead5201c 100644 --- a/src/plugins/showConnections/styles.css +++ b/src/plugins/showConnections/styles.css @@ -9,3 +9,11 @@ gap: 0.25em; align-items: center; } + +.vc-sc-connection-name { + word-break: break-all; +} + +.vc-sc-tooltip svg { + min-width: 16px; +} diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index a8f5735e7..bdb9fee8c 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -78,7 +78,7 @@ const enum ChannelFlags { } -const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); +const ChatScrollClasses = findByPropsLazy("auto", "managedReactiveScroller"); const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent"); const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"); const TagComponent = findComponentLazy(m => { diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index c120d72d8..b06686736 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -74,7 +74,7 @@ export default definePlugin({ replacement: [ // Remove the special logic for channels we don't have access to { - match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:\i}}/, + match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:\[\]}}/, replace: "" }, // Do not check for unreads when selecting the render level if the channel is hidden @@ -116,14 +116,14 @@ export default definePlugin({ }, // Prevent Discord from trying to connect to hidden stage channels { - find: ".MAX_STAGE_VOICE_USER_LIMIT})", + find: ".AUDIENCE),{isSubscriptionGated", replacement: { match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})` } }, { - find: "ChannelItemEditButton:function(){", + find: 'tutorialId:"instant-invite"', replacement: [ // Render null instead of the buttons if the channel is hidden ...[ @@ -195,7 +195,7 @@ export default definePlugin({ // Hide the new version of unreads box for hidden channels find: '="ChannelListUnreadsStore",', replacement: { - match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i)\))/g, // Global because Discord has multiple methods like that in the same module + match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/, replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` } }, @@ -203,15 +203,15 @@ export default definePlugin({ // Make the old version of unreads box not visible for hidden channels find: "renderBottomUnread(){", replacement: { - match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i\.record)\))/, + match: /(?<=!0\))(?=&&\(0,\i\.\i\)\((\i\.record)\))/, replace: "&&!$self.isHiddenChannel($1)" } }, { // Make the state of the old version of unreads box not include hidden channels - find: ".useFlattenedChannelIdListWithThreads)", + find: "ignoreRecents:!0", replacement: { - match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i)\))/, + match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/, replace: "&&!$self.isHiddenChannel($1)" } }, @@ -257,7 +257,7 @@ export default definePlugin({ { find: '"alt+shift+down"', replacement: { - match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?getHasImportantUnread\)\(\i\))/, + match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?>0\)&&\(0,\i\.\i\)\(\i\))/, replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` } }, @@ -289,7 +289,7 @@ export default definePlugin({ }, { // If the @everyone role has the required permissions, make the array only contain it - match: /computePermissionsForRoles.+?.value\(\)(?<=channel:(\i).+?)/, + match: /forceRoles:.+?.value\(\)(?<=channel:(\i).+?)/, replace: (m, channel) => `${m}.reduce(...$self.makeAllowedRolesReduce(${channel}.guild_id))` }, { @@ -422,7 +422,7 @@ export default definePlugin({ }, { // Avoid filtering out hidden channels from the channel list - match: /(?<=queryChannels\(\i\){.+?isGuildChannelType\)\((\i)\.type\))(?=&&!\i\.\i\.can\()/, + match: /(?<=queryChannels\(\i\){.+?\)\((\i)\.type\))(?=&&!\i\.\i\.can\()/, replace: "&&!$self.isHiddenChannel($1)" } ] diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index db4fe5aa6..58269b360 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -64,18 +64,18 @@ export default definePlugin({ }, }, { - find: "useShouldShowInvitesDisabledNotif:", + find: "2022-07_invites_disabled", predicate: () => settings.store.showInvitesPaused, replacement: { - match: /\i\.\i\.can\(\i\.Permissions.MANAGE_GUILD,\i\)/, + match: /\i\.\i\.can\(\i\.\i.MANAGE_GUILD,\i\)/, replace: "true", }, }, { - find: "canAccessGuildMemberModViewWithExperiment:", + find: /context:\i,checkElevated:!1\}\),\i\.\i.{0,200}autoTrackExposure/, predicate: () => settings.store.showModView, replacement: { - match: /return \i\.hasAny\(\i\.computePermissions\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.MemberSafetyPagePermissions\)/, + match: /return \i\.\i\(\i\.\i\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.\i\)/, replace: "return true", } }, @@ -87,28 +87,31 @@ export default definePlugin({ replace: "{}" } }, + // remove the 200 server minimum { - find: "MINIMUM_MEMBER_COUNT:", + find: '">200"', predicate: () => settings.store.disableDiscoveryFilters, replacement: { - match: /MINIMUM_MEMBER_COUNT:function\(\)\{return \i}/, - replace: "MINIMUM_MEMBER_COUNT:() => \">0\"" + match: '">200"', + replace: '">0"' } }, + // empty word filter (why would anyone search "horny" in fucking server discovery... please... why are we patching this again??) { - find: "DiscoveryBannedSearchWords.includes", + find: '"horny","fart"', predicate: () => settings.store.disableDisallowedDiscoveryFilters, replacement: { - match: /(?<=function\(\){)(?=.{0,130}DiscoveryBannedSearchWords\.includes)/, - replace: "return false;" + match: /=\["egirl",.+?\]/, + replace: "=[]" } }, + // patch request that queries if term is allowed { - find: "Endpoints.GUILD_DISCOVERY_VALID_TERM", + find: ".GUILD_DISCOVERY_VALID_TERM", predicate: () => settings.store.disableDisallowedDiscoveryFilters, all: true, replacement: { - match: /\i\.HTTP\.get\(\{url:\i\.Endpoints\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g, + match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g, replace: "Promise.resolve({ body: { valid: true } });" } } diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 7ba245da5..833904f62 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -48,7 +48,7 @@ export default definePlugin({ authors: [Devs.Rini, Devs.TheKodeToad], patches: [ { - find: ".useCanSeeRemixBadge)", + find: '?"@":"")', replacement: { match: /(?<=onContextMenu:\i,children:).*?\)}/, replace: "$self.renderUsername(arguments[0])}" diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts index b3cd0b282..7fdd6bba1 100644 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ b/src/plugins/spotifyControls/SpotifyStore.ts @@ -17,7 +17,7 @@ */ import { Settings } from "@api/Settings"; -import { findByProps, proxyLazyWebpack } from "@webpack"; +import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack"; import { Flux, FluxDispatcher } from "@webpack/common"; export interface Track { @@ -70,7 +70,7 @@ export const SpotifyStore = proxyLazyWebpack(() => { const { Store } = Flux; const SpotifySocket = findByProps("getActiveSocketAndDevice"); - const SpotifyUtils = findByProps("SpotifyAPI"); + const SpotifyAPI = findByPropsLazy("vcSpotifyMarker"); const API_BASE = "https://api.spotify.com/v1/me/player"; @@ -168,7 +168,7 @@ export const SpotifyStore = proxyLazyWebpack(() => { (data.query ??= {}).device_id = this.device.id; const { socket } = SpotifySocket.getActiveSocketAndDevice(); - return SpotifyUtils.SpotifyAPI[method](socket.accountId, socket.accessToken, { + return SpotifyAPI[method](socket.accountId, socket.accessToken, { url: API_BASE + route, ...data }); diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index 06595892f..033d49357 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -61,7 +61,7 @@ export default definePlugin({ replacement: [{ // Adds POST and a Marker to the SpotifyAPI (so we can easily find it) match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/, - replace: "post:$1.bind(null,$2.post),$&" + replace: "post:$1.bind(null,$2.post),vcSpotifyMarker:1,$&" }, { // Spotify Connect API returns status 202 instead of 204 when skipping tracks. @@ -77,6 +77,13 @@ export default definePlugin({ match: /repeat:"off"!==(.{1,3}),/, replace: "actual_repeat:$1,$&" } + }, + { + find: "artists.filter", + replacement: { + match: /(?<=artists.filter\(\i=>).{0,10}\i\.id\)&&/, + replace: "" + } } ], diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 7878ba630..1a5e3a985 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -47,9 +47,9 @@ export default definePlugin({ } }, { - find: ".trackEmojiSearchEmpty,200", + find: ".EMOJI_PICKER_CONSTANTS_EMOJI_CONTAINER_PADDING_HORIZONTAL)", replacement: { - match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, + match: /(openPopoutType:void 0(?=.+?isBurstReaction:(\i).+?(\i===\i\.\i.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})` } } diff --git a/src/plugins/usrbg/index.tsx b/src/plugins/usrbg/index.tsx index b92839a9a..fbc75f52c 100644 --- a/src/plugins/usrbg/index.tsx +++ b/src/plugins/usrbg/index.tsx @@ -27,10 +27,10 @@ import style from "./index.css?managed"; const API_URL = "https://usrbg.is-hardly.online/users"; interface UsrbgApiReturn { - endpoint: string - bucket: string - prefix: string - users: Record + endpoint: string; + bucket: string; + prefix: string; + users: Record; } const settings = definePluginSettings({ @@ -58,20 +58,18 @@ export default definePlugin({ patches: [ { find: ".NITRO_BANNER,", - replacement: [ - { - match: /(\i)\.premiumType/, - replace: "$self.premiumHook($1)||$&" - }, - { - match: /(?<=function \i\((\i)\)\{)(?=var.{30,50},bannerSrc:)/, - replace: "$1.bannerSrc=$self.useBannerHook($1);" - }, - { - match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/, - replace: "&&$self.shouldShowBadge(arguments[0])$&" - } - ] + replacement: { + match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/, + replace: "&&$self.shouldShowBadge(arguments[0])$&" + } + }, + { + find: ".banner)==null", + replacement: { + match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, + replace: "$self.patchBannerUrl(arguments[0])||$&" + + } }, { find: "\"data-selenium-video-tile\":", @@ -79,7 +77,7 @@ export default definePlugin({ replacement: [ { match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/, - replace: "$1.style=$self.voiceBackgroundHook($1);" + replace: "$1.style=$self.getVoiceBackgroundStyles($1);" } ] } @@ -93,7 +91,7 @@ export default definePlugin({ ); }, - voiceBackgroundHook({ className, participantUserId }: any) { + getVoiceBackgroundStyles({ className, participantUserId }: any) { if (className.includes("tile_")) { if (this.userHasBackground(participantUserId)) { return { @@ -106,13 +104,9 @@ export default definePlugin({ } }, - useBannerHook({ displayProfile, user }: any) { + patchBannerUrl({ displayProfile }: any) { if (displayProfile?.banner && settings.store.nitroFirst) return; - if (this.userHasBackground(user.id)) return this.getImageUrl(user.id); - }, - - premiumHook({ userId }: any) { - if (this.userHasBackground(userId)) return 2; + if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId); }, shouldShowBadge({ displayProfile, user }: any) { @@ -123,7 +117,7 @@ export default definePlugin({ return !!this.data?.users[userId]; }, - getImageUrl(userId: string): string|null { + getImageUrl(userId: string): string | null { if (!this.userHasBackground(userId)) return null; // We can assert that data exists because userHasBackground returned true diff --git a/src/plugins/validReply/index.ts b/src/plugins/validReply/index.ts index 21a1bdd1f..b65496f44 100644 --- a/src/plugins/validReply/index.ts +++ b/src/plugins/validReply/index.ts @@ -6,7 +6,7 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +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"; @@ -29,7 +29,7 @@ interface Reply { const fetching = new Map(); let ReplyStore: any; -const { createMessageRecord } = findByPropsLazy("createMessageRecord"); +const createMessageRecord = findByCodeLazy(".createFromServer(", ".isBlockedForMessage", "messageReference:"); export default definePlugin({ name: "ValidReply", diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index ac629e749..6e8e4bbf5 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -22,7 +22,7 @@ import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { wordsToTitle } from "@utils/text"; -import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types"; +import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common"; @@ -155,6 +155,7 @@ export default definePlugin({ name: "VcNarrator", description: "Announces when users join, leave, or move voice channels via narrator", authors: [Devs.Ven], + reporterTestable: ReporterTestable.None, flux: { VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 09254d511..b2e7d56df 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -184,16 +184,16 @@ export default definePlugin({ patches: [ // Profiles Modal pfp - { - find: "User Profile Modal - Context Menu", + ...[".MODAL,hasProfileEffect", ".FULL_SIZE,hasProfileEffect:"].map(find => ({ + find, replacement: { match: /\{src:(\i)(?=,avatarDecoration)/, replace: "{src:$1,onClick:()=>$self.openImage($1)" } - }, + })), // Banners - { - find: ".NITRO_BANNER,", + ...[".NITRO_BANNER,", "=!1,canUsePremiumCustomization:"].map(find => ({ + find, replacement: { // style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl, match: /style:\{(?=backgroundImage:(null!=\i)\?"url\("\.concat\((\i),)/, @@ -201,7 +201,7 @@ export default definePlugin({ // onClick: () => shouldShowBanner && ev.target.style.backgroundImage && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0, 'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,' } - }, + })), // User DMs "User Profile" popup in the right { find: ".avatarPositionPanel", @@ -210,11 +210,19 @@ export default definePlugin({ replace: "$1style:($2)?{cursor:\"pointer\"}:{},onClick:$2?()=>{$self.openImage($3)}" } }, + { + find: ".canUsePremiumProfileCustomization,{avatarSrc:", + replacement: { + match: /children:\(0,\i\.jsx\)\(\i,{src:(\i)/, + replace: "style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)},$&" + + } + }, // Group DMs top small & large icon { find: /\.recipients\.length>=2(?! `${m},onClick:()=>$self.openImage(${iconUrl})` } }, diff --git a/src/plugins/voiceMessages/DesktopRecorder.tsx b/src/plugins/voiceMessages/DesktopRecorder.tsx index a69739a41..18581ad16 100644 --- a/src/plugins/voiceMessages/DesktopRecorder.tsx +++ b/src/plugins/voiceMessages/DesktopRecorder.tsx @@ -21,6 +21,7 @@ import { Button, showToast, Toasts, useState } from "@webpack/common"; import type { VoiceRecorder } from "."; import { settings } from "./settings"; +import { MediaEngineStore } from "./utils"; const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative; @@ -41,6 +42,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC { echoCancellation: settings.store.echoCancellation, noiseCancellation: settings.store.noiseSuppression, + deviceId: MediaEngineStore.getInputDeviceId(), }, (success: boolean) => { if (success) diff --git a/src/plugins/voiceMessages/WebRecorder.tsx b/src/plugins/voiceMessages/WebRecorder.tsx index 423a2699a..eb77b1990 100644 --- a/src/plugins/voiceMessages/WebRecorder.tsx +++ b/src/plugins/voiceMessages/WebRecorder.tsx @@ -20,6 +20,7 @@ import { Button, useState } from "@webpack/common"; import type { VoiceRecorder } from "."; import { settings } from "./settings"; +import { MediaEngineStore } from "./utils"; export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => { const [recording, setRecording] = useState(false); @@ -40,6 +41,7 @@ export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChang audio: { echoCancellation: settings.store.echoCancellation, noiseSuppression: settings.store.noiseSuppression, + deviceId: MediaEngineStore.getInputDeviceId() } }).then(stream => { const chunks = [] as Blob[]; diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 40e877df9..8365bb51a 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -27,7 +27,7 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { chooseFile } from "@utils/web"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; +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 +37,7 @@ import { cl } from "./utils"; import { VoicePreview } from "./VoicePreview"; import { VoiceRecorderWeb } from "./WebRecorder"; -const CloudUtils = findByPropsLazy("CloudUpload"); +const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished); const PendingReplyStore = findStoreLazy("PendingReplyStore"); const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel"); @@ -89,9 +89,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) { const reply = PendingReplyStore.getPendingReply(channelId); if (reply) FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId }); - const upload = new CloudUtils.CloudUpload({ + const upload = new CloudUpload({ file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }), - isClip: false, isThumbnail: false, platform: 1, }, channelId, false, 0); diff --git a/src/plugins/voiceMessages/utils.ts b/src/plugins/voiceMessages/utils.ts index dcfd6f26c..ef571bbf3 100644 --- a/src/plugins/voiceMessages/utils.ts +++ b/src/plugins/voiceMessages/utils.ts @@ -17,5 +17,7 @@ */ import { classNameFactory } from "@api/Styles"; +import { findStoreLazy } from "@webpack"; +export const MediaEngineStore = findStoreLazy("MediaEngineStore"); export const cl = classNameFactory("vc-vmsg-"); diff --git a/src/plugins/watchTogetherAdblock.desktop/README.md b/src/plugins/watchTogetherAdblock.desktop/README.md new file mode 100644 index 000000000..4c64df67b --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/README.md @@ -0,0 +1,6 @@ +# WatchTogetherAdblock + +Block ads in the YouTube WatchTogether activity via AdGuard + +Note that this only works for yourself, other users in the activity will still see ads. +Powered by a modified version of [Adguard's BlockYoutubeAdsShortcut](https://github.com/AdguardTeam/BlockYouTubeAdsShortcut) diff --git a/src/plugins/watchTogetherAdblock.desktop/adguard.js b/src/plugins/watchTogetherAdblock.desktop/adguard.js new file mode 100644 index 000000000..945f76bd5 --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/adguard.js @@ -0,0 +1,262 @@ +/* eslint-disable */ + +/** + * This file is part of AdGuard's Block YouTube Ads (https://github.com/AdguardTeam/BlockYouTubeAdsShortcut). + * + * Copyright (C) AdGuard Team + * + * AdGuard's Block YouTube Ads 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. + * + * AdGuard's Block YouTube Ads 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 AdGuard's Block YouTube Ads. If not, see . + */ + +const LOGO_ID = "block-youtube-ads-logo"; +const hiddenCSS = [ + "#__ffYoutube1", + "#__ffYoutube2", + "#__ffYoutube3", + "#__ffYoutube4", + "#feed-pyv-container", + "#feedmodule-PRO", + "#homepage-chrome-side-promo", + "#merch-shelf", + "#offer-module", + '#pla-shelf > ytd-pla-shelf-renderer[class="style-scope ytd-watch"]', + "#pla-shelf", + "#premium-yva", + "#promo-info", + "#promo-list", + "#promotion-shelf", + "#related > ytd-watch-next-secondary-results-renderer > #items > ytd-compact-promoted-video-renderer.ytd-watch-next-secondary-results-renderer", + "#search-pva", + "#shelf-pyv-container", + "#video-masthead", + "#watch-branded-actions", + "#watch-buy-urls", + "#watch-channel-brand-div", + "#watch7-branded-banner", + "#YtKevlarVisibilityIdentifier", + "#YtSparklesVisibilityIdentifier", + ".carousel-offer-url-container", + ".companion-ad-container", + ".GoogleActiveViewElement", + '.list-view[style="margin: 7px 0pt;"]', + ".promoted-sparkles-text-search-root-container", + ".promoted-videos", + ".searchView.list-view", + ".sparkles-light-cta", + ".watch-extra-info-column", + ".watch-extra-info-right", + ".ytd-carousel-ad-renderer", + ".ytd-compact-promoted-video-renderer", + ".ytd-companion-slot-renderer", + ".ytd-merch-shelf-renderer", + ".ytd-player-legacy-desktop-watch-ads-renderer", + ".ytd-promoted-sparkles-text-search-renderer", + ".ytd-promoted-video-renderer", + ".ytd-search-pyv-renderer", + ".ytd-video-masthead-ad-v3-renderer", + ".ytp-ad-action-interstitial-background-container", + ".ytp-ad-action-interstitial-slot", + ".ytp-ad-image-overlay", + ".ytp-ad-overlay-container", + ".ytp-ad-progress", + ".ytp-ad-progress-list", + '[class*="ytd-display-ad-"]', + '[layout*="display-ad-"]', + 'a[href^="http://www.youtube.com/cthru?"]', + 'a[href^="https://www.youtube.com/cthru?"]', + "ytd-action-companion-ad-renderer", + "ytd-banner-promo-renderer", + "ytd-compact-promoted-video-renderer", + "ytd-companion-slot-renderer", + "ytd-display-ad-renderer", + "ytd-promoted-sparkles-text-search-renderer", + "ytd-promoted-sparkles-web-renderer", + "ytd-search-pyv-renderer", + "ytd-single-option-survey-renderer", + "ytd-video-masthead-ad-advertiser-info-renderer", + "ytd-video-masthead-ad-v3-renderer", + "YTM-PROMOTED-VIDEO-RENDERER", +]; +/** +* Adds CSS to the page +*/ +const hideElements = () => { + const selectors = hiddenCSS; + if (!selectors) { + return; + } + const rule = selectors.join(", ") + " { display: none!important; }"; + const style = document.createElement("style"); + style.innerHTML = rule; + document.head.appendChild(style); +}; +/** +* Calls the "callback" function on every DOM change, but not for the tracked events +* @param {Function} callback callback function +*/ +const observeDomChanges = callback => { + const domMutationObserver = new MutationObserver(mutations => { + callback(mutations); + }); + domMutationObserver.observe(document.documentElement, { + childList: true, + subtree: true, + }); +}; +/** +* This function is supposed to be called on every DOM change +*/ +const hideDynamicAds = () => { + const elements = document.querySelectorAll("#contents > ytd-rich-item-renderer ytd-display-ad-renderer"); + if (elements.length === 0) { + return; + } + elements.forEach(el => { + if (el.parentNode && el.parentNode.parentNode) { + const parent = el.parentNode.parentNode; + if (parent.localName === "ytd-rich-item-renderer") { + parent.style.display = "none"; + } + } + }); +}; +/** +* This function checks if the video ads are currently running +* and auto-clicks the skip button. +*/ +const autoSkipAds = () => { + // If there's a video that plays the ad at this moment, scroll this ad + if (document.querySelector(".ad-showing")) { + const video = document.querySelector("video"); + if (video && video.duration) { + video.currentTime = video.duration; + // Skip button should appear after that, + // now simply click it automatically + setTimeout(() => { + const skipBtn = document.querySelector("button.ytp-ad-skip-button"); + if (skipBtn) { + skipBtn.click(); + } + }, 100); + } + } +}; +/** +* This function overrides a property on the specified object. +* +* @param {object} obj object to look for properties in +* @param {string} propertyName property to override +* @param {*} overrideValue value to set +*/ +const overrideObject = (obj, propertyName, overrideValue) => { + if (!obj) { + return false; + } + let overriden = false; + for (const key in obj) { + // eslint-disable-next-line no-prototype-builtins + if (obj.hasOwnProperty(key) && key === propertyName) { + obj[key] = overrideValue; + overriden = true; + // eslint-disable-next-line no-prototype-builtins + } else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") { + if (overrideObject(obj[key], propertyName, overrideValue)) { + overriden = true; + } + } + } + return overriden; +}; +/** +* Overrides JSON.parse and Response.json functions. +* Examines these functions arguments, looks for properties with the specified name there +* and if it exists, changes it's value to what was specified. +* +* @param {string} propertyName name of the property +* @param {*} overrideValue new value for the property +*/ +const jsonOverride = (propertyName, overrideValue) => { + const nativeJSONParse = JSON.parse; + JSON.parse = (...args) => { + const obj = nativeJSONParse.apply(this, args); + // Override it's props and return back to the caller + overrideObject(obj, propertyName, overrideValue); + return obj; + }; + // Override Response.prototype.json + const nativeResponseJson = Response.prototype.json; + Response.prototype.json = new Proxy(nativeResponseJson, { + apply(...args) { + // Call the target function, get the original Promise + const promise = Reflect.apply(...args); + // Create a new one and override the JSON inside + return new Promise((resolve, reject) => { + promise.then(data => { + overrideObject(data, propertyName, overrideValue); + resolve(data); + }).catch(error => reject(error)); + }); + }, + }); +}; +const addAdGuardLogoStyle = () => { }; +const addAdGuardLogo = () => { + if (document.getElementById(LOGO_ID)) { + return; + } + const logo = document.createElement("span"); + logo.innerHTML = "__logo_text__"; + logo.setAttribute("id", LOGO_ID); + if (window.location.hostname === "m.youtube.com") { + const btn = document.querySelector("header.mobile-topbar-header > button"); + if (btn) { + btn.parentNode?.insertBefore(logo, btn.nextSibling); + addAdGuardLogoStyle(); + } + } else if (window.location.hostname === "www.youtube.com") { + const code = document.getElementById("country-code"); + if (code) { + code.innerHTML = ""; + code.appendChild(logo); + addAdGuardLogoStyle(); + } + } else if (window.location.hostname === "music.youtube.com") { + const el = document.querySelector(".ytmusic-nav-bar#left-content"); + if (el) { + el.appendChild(logo); + addAdGuardLogoStyle(); + } + } else if (window.location.hostname === "www.youtube-nocookie.com") { + const code = document.querySelector("#yt-masthead #logo-container .content-region"); + if (code) { + code.innerHTML = ""; + code.appendChild(logo); + addAdGuardLogoStyle(); + } + } +}; +// Removes ads metadata from YouTube XHR requests +jsonOverride("adPlacements", []); +jsonOverride("playerAds", []); +// Applies CSS that hides YouTube ad elements +hideElements(); +// Some changes should be re-evaluated on every page change +addAdGuardLogo(); +hideDynamicAds(); +autoSkipAds(); +observeDomChanges(() => { + addAdGuardLogo(); + hideDynamicAds(); + autoSkipAds(); +}); diff --git a/src/plugins/watchTogetherAdblock.desktop/index.ts b/src/plugins/watchTogetherAdblock.desktop/index.ts new file mode 100644 index 000000000..2dbc13d4a --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/index.ts @@ -0,0 +1,15 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +// The entire code of this plugin can be found in native.ts +export default definePlugin({ + name: "WatchTogetherAdblock", + description: "Block ads in the YouTube WatchTogether activity via AdGuard", + authors: [Devs.ImLvna], +}); diff --git a/src/plugins/watchTogetherAdblock.desktop/native.ts b/src/plugins/watchTogetherAdblock.desktop/native.ts new file mode 100644 index 000000000..c4106c349 --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/native.ts @@ -0,0 +1,21 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { RendererSettings } from "@main/settings"; +import { app } from "electron"; +import adguard from "file://adguard.js?minify"; + +app.on("browser-window-created", (_, win) => { + win.webContents.on("frame-created", (_, { frame }) => { + frame.once("dom-ready", () => { + if (frame.url.includes("discordsays") && frame.url.includes("youtube.com")) { + if (!RendererSettings.store.plugins?.WatchTogetherAdblock?.enabled) return; + + frame.executeJavaScript(adguard); + } + }); + }); +}); diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index ac4689036..f99fd9720 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -197,8 +197,8 @@ export default definePlugin({ { find: '"MediaEngineWebRTC");', replacement: { - match: /supports\(\i\)\{switch\(\i\)\{case (\i).Features/, - replace: "$&.DISABLE_VIDEO:return true;case $1.Features" + match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/, + replace: "$&.DISABLE_VIDEO:return true;$1" } } ], diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay.desktop/index.ts index 5251959f2..a68373a6a 100644 --- a/src/plugins/xsOverlay.desktop/index.ts +++ b/src/plugins/xsOverlay.desktop/index.ts @@ -8,12 +8,12 @@ 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 } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import { findByCodeLazy, findLazy } from "@webpack"; import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; -const { ChannelTypes } = findByPropsLazy("ChannelTypes"); +const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10); interface Message { guild_id: string, @@ -68,7 +68,7 @@ interface Call { ringing: string[]; } -const Notifs = findByPropsLazy("makeTextChatNotification"); +const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1"); const XSLog = new Logger("XSOverlay"); const settings = definePluginSettings({ @@ -143,7 +143,9 @@ export default definePlugin({ description: "Forwards discord notifications to XSOverlay, for easy viewing in VR", authors: [Devs.Nyako], tags: ["vr", "notify"], + reporterTestable: ReporterTestable.None, settings, + flux: { CALL_UPDATE({ call }: { call: Call; }) { if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) { @@ -302,7 +304,7 @@ function shouldNotify(message: Message, channel: string) { const currentUser = UserStore.getCurrentUser(); if (message.author.id === currentUser.id) return false; if (message.author.bot && !settings.store.botNotifications) return false; - return Notifs.shouldNotify(message, channel); + return notificationsShouldNotify(message, channel); } function calculateHeight(content: string) { diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index 1ae4762d7..22a381360 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -32,6 +32,11 @@ export class Logger { constructor(public name: string, public color: string = "white") { } private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { + if (IS_REPORTER && IS_WEB) { + console[level]("[Vencord]", this.name + ":", ...args); + return; + } + console[level]( `%c Vencord %c %c ${this.name} ${customFmt}`, `background: ${levelColor}; color: black; font-weight: bold; border-radius: 5px;`, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2f686d69d..09fc0285b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -38,7 +38,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ id: 0n, }, Ven: { - name: "Vendicated", + name: "Vee", id: 343383572805058560n }, Arjix: { @@ -186,7 +186,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ id: 296776625432035328n, }, TheSun: { - name: "ActuallyTheSun", + name: "sunnie", id: 406028027768733696n }, axyie: { @@ -327,7 +327,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ id: 305288513941667851n }, ImLvna: { - name: "Luna <3", + name: "lillith <3", id: 799319081723232267n }, rad: { @@ -402,6 +402,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "maisy", id: 257109471589957632n, }, + Lexi: { + name: "Lexi", + id: 506101469787717658n + }, Mopi: { name: "Mopi", id: 1022189106614243350n @@ -438,6 +442,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Elvyra", id: 708275751816003615n, }, + HappyEnderman: { + name: "Happy enderman", + id: 1083437693347827764n + }, + Vishnya: { + name: "Vishnya", + id: 282541644484575233n + }, Inbestigator: { name: "Inbestigator", id: 761777382041714690n @@ -514,6 +526,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "verticalsync", id: 328165170536775680n }, + nekohaxx: { + name: "nekohaxx", + id: 1176270221628153886n + } } satisfies Record); // iife so #__PURE__ works correctly diff --git a/src/utils/dependencies.ts b/src/utils/dependencies.ts index f05900e14..d8c361e88 100644 --- a/src/utils/dependencies.ts +++ b/src/utils/dependencies.ts @@ -17,7 +17,6 @@ */ import { makeLazy } from "./lazy"; -import { EXTENSION_BASE_URL } from "./web-metadata"; /* Add dynamically loaded dependencies for plugins here. @@ -67,15 +66,6 @@ export interface ApngFrameData { playTime: number; } -// On web (extensions), use extension uri as basepath (load files from extension) -// On desktop (electron), load from cdn -export const rnnoiseDist = IS_EXTENSION - ? new URL("/third-party/rnnoise", EXTENSION_BASE_URL).toString() - : "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist"; -export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`; -export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`; - - // The below code is only used on the Desktop (electron) build of Vencord. // Browser (extension) builds do not contain these remote imports. diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 57202ba3c..3ac5991d5 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -120,6 +120,8 @@ export function openImageModal(url: string, props?: Partial } + // FIXME: wtf is this? do we need to pass some proper component?? + renderForwardComponent={() => null} shouldHideMediaOptions={false} shouldAnimate {...props} diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index a61785df9..e46e44ad7 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -35,8 +35,8 @@ const unconfigurable = ["arguments", "caller", "prototype"]; const handler: ProxyHandler = {}; -const kGET = Symbol.for("vencord.lazy.get"); -const kCACHE = Symbol.for("vencord.lazy.cached"); +export const SYM_LAZY_GET = Symbol.for("vencord.lazy.get"); +export const SYM_LAZY_CACHED = Symbol.for("vencord.lazy.cached"); for (const method of [ "apply", @@ -53,11 +53,11 @@ for (const method of [ "setPrototypeOf" ]) { handler[method] = - (target: any, ...args: any[]) => Reflect[method](target[kGET](), ...args); + (target: any, ...args: any[]) => Reflect[method](target[SYM_LAZY_GET](), ...args); } handler.ownKeys = target => { - const v = target[kGET](); + const v = target[SYM_LAZY_GET](); const keys = Reflect.ownKeys(v); for (const key of unconfigurable) { if (!keys.includes(key)) keys.push(key); @@ -69,7 +69,7 @@ handler.getOwnPropertyDescriptor = (target, p) => { if (typeof p === "string" && unconfigurable.includes(p)) return Reflect.getOwnPropertyDescriptor(target, p); - const descriptor = Reflect.getOwnPropertyDescriptor(target[kGET](), p); + const descriptor = Reflect.getOwnPropertyDescriptor(target[SYM_LAZY_GET](), p); if (descriptor) Object.defineProperty(target, p, descriptor); return descriptor; @@ -92,31 +92,34 @@ export function proxyLazy(factory: () => T, attempts = 5, isChild = false): T let tries = 0; const proxyDummy = Object.assign(function () { }, { - [kCACHE]: void 0 as T | undefined, - [kGET]() { - if (!proxyDummy[kCACHE] && attempts > tries++) { - proxyDummy[kCACHE] = factory(); - if (!proxyDummy[kCACHE] && attempts === tries) + [SYM_LAZY_CACHED]: void 0 as T | undefined, + [SYM_LAZY_GET]() { + if (!proxyDummy[SYM_LAZY_CACHED] && attempts > tries++) { + proxyDummy[SYM_LAZY_CACHED] = factory(); + if (!proxyDummy[SYM_LAZY_CACHED] && attempts === tries) console.error("Lazy factory failed:", factory); } - return proxyDummy[kCACHE]; + return proxyDummy[SYM_LAZY_CACHED]; } }); return new Proxy(proxyDummy, { ...handler, get(target, p, receiver) { + if (p === SYM_LAZY_CACHED || p === SYM_LAZY_GET) + return Reflect.get(target, p, receiver); + // if we're still in the same tick, it means the lazy was immediately used. // thus, we lazy proxy the get access to make things like destructuring work as expected // meow here will also be a lazy // `const { meow } = findByPropsLazy("meow");` if (!isChild && isSameTick) return proxyLazy( - () => Reflect.get(target[kGET](), p, receiver), + () => Reflect.get(target[SYM_LAZY_GET](), p, receiver), attempts, true ); - const lazyTarget = target[kGET](); + const lazyTarget = target[SYM_LAZY_GET](); if (typeof lazyTarget === "object" || typeof lazyTarget === "function") { return Reflect.get(lazyTarget, p, receiver); } diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index b4d0f59fb..4203068c9 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import { LazyComponent } from "./react"; @@ -111,6 +111,7 @@ export type ImageModal = ComponentType<{ animated?: boolean; responsive?: boolean; renderLinkComponent(props: any): ReactNode; + renderForwardComponent(props: any): ReactNode; maxWidth?: number; maxHeight?: number; shouldAnimate?: boolean; @@ -118,7 +119,7 @@ export type ImageModal = ComponentType<{ shouldHideMediaOptions?: boolean; }>; -export const ImageModal = findExportedComponentLazy("ImageModal") as ImageModal; +export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE", "responsive") as ImageModal; export const ModalRoot = LazyComponent(() => Modals.ModalRoot); export const ModalHeader = LazyComponent(() => Modals.ModalHeader); diff --git a/src/utils/types.ts b/src/utils/types.ts index 6e1524196..2fa4a826e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -85,6 +85,10 @@ export interface PluginDef { * Whether this plugin is required and forcefully enabled */ required?: boolean; + /** + * Whether this plugin should be hidden from the user + */ + hidden?: boolean; /** * Whether this plugin should be enabled by default, but can be disabled */ @@ -94,6 +98,10 @@ export interface PluginDef { * @default StartAt.WebpackReady */ startAt?: StartAt, + /** + * Which parts of the plugin can be tested by the reporter. Defaults to all parts + */ + reporterTestable?: number; /** * Optionally provide settings that the user can configure in the Plugins tab of settings. * @deprecated Use `settings` instead @@ -144,6 +152,13 @@ export const enum StartAt { WebpackReady = "WebpackReady" } +export const enum ReporterTestable { + None = 1 << 1, + Start = 1 << 2, + Patches = 1 << 3, + FluxEvents = 1 << 4 +} + export const enum OptionType { STRING, NUMBER, diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 9a89af362..8957c254b 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -22,7 +22,7 @@ import { LazyComponent } from "@utils/react"; import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack"; export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T { - if (IS_DEV) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); let myValue: T = function () { throw new Error(`Vencord could not find the ${name} Component`); @@ -38,7 +38,7 @@ export function waitForComponent = React.Comp } export function waitForStore(name: string, cb: (v: any) => void) { - if (IS_DEV) lazyWebpackSearchHistory.push(["waitForStore", [name]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForStore", [name]]); waitFor(filters.byStoreName(name), cb, { isIndirect: true }); } diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index 9a4a3a799..d528390ef 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -17,12 +17,16 @@ */ // eslint-disable-next-line path-alias/no-relative -import { findByPropsLazy, waitFor } from "../webpack"; +import { filters, mapMangledModuleLazy, waitFor } from "../webpack"; import type * as t from "./types/menu"; export let Menu = {} as t.Menu; waitFor(["MenuItem", "MenuSliderControl"], m => Menu = m); -export const ContextMenuApi: t.ContextMenuApi = findByPropsLazy("closeContextMenu", "openContextMenu"); +export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', { + closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"), + openContextMenu: filters.byCode("renderLazy:"), + openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100 +}); diff --git a/src/webpack/common/settingsStores.ts b/src/webpack/common/settingsStores.ts index 4a48efda6..b00620b27 100644 --- a/src/webpack/common/settingsStores.ts +++ b/src/webpack/common/settingsStores.ts @@ -4,12 +4,9 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { findByPropsLazy } from "@webpack"; +import { findLazy } from "@webpack"; -import * as t from "./types/settingsStores"; - - -export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact") as Record; -export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame") as Record; - -export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators"); +export const UserSettingsActionCreators = { + FrecencyUserSettingsActionCreators: findLazy(m => m.ProtoClass?.typeName?.endsWith(".FrecencyUserSettings")), + PreloadedUserSettingsActionCreators: findLazy(m => m.ProtoClass?.typeName?.endsWith(".PreloadedUserSettings")), +}; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 123c62b05..d61a95d84 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -19,7 +19,7 @@ import type * as Stores from "discord-types/stores"; // eslint-disable-next-line path-alias/no-relative -import { findByPropsLazy } from "../webpack"; +import { findByCodeLazy, findByPropsLazy } from "../webpack"; import { waitForStore } from "./internal"; import * as t from "./types/stores"; @@ -27,7 +27,7 @@ export const Flux: t.Flux = findByPropsLazy("connectStores"); export type GenericStore = t.FluxStore & Record; -export const { DraftType }: { DraftType: typeof t.DraftType; } = findByPropsLazy("DraftType"); +export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand"); export let MessageStore: Omit & { getMessages(chanId: string): any; @@ -67,7 +67,7 @@ export let DraftStore: t.DraftStore; * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); */ // eslint-disable-next-line prefer-destructuring -export const useStateFromStores: t.useStateFromStores = findByPropsLazy("useStateFromStores").useStateFromStores; +export const useStateFromStores: t.useStateFromStores = findByCodeLazy("useStateFromStores"); waitForStore("DraftStore", s => DraftStore = s); waitForStore("UserStore", s => UserStore = s); diff --git a/src/webpack/common/types/fluxEvents.d.ts b/src/webpack/common/types/fluxEvents.d.ts index 36bcb2cde..1bd50f461 100644 --- a/src/webpack/common/types/fluxEvents.d.ts +++ b/src/webpack/common/types/fluxEvents.d.ts @@ -37,4 +37,4 @@ function makeFluxEventList() { */ // 46kb worth of events ??????? -export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_DETECTION_MODAL_SEEN" | "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_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_SUPPORT_CHANGED" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "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_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PIP_MODE_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" | "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" | "ALLOW_SPAM_MESSAGES_FOR_USER" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "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_COMMANDS_FETCH" | "APPLICATION_COMMANDS_FETCH_FOR_APPLICATION" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_FETCH" | "APPLICATION_COMMAND_REGISTRY_UPDATE" | "APPLICATION_COMMAND_SEARCH_STORE_QUERY" | "APPLICATION_COMMAND_SEARCH_STORE_UI_UPDATE" | "APPLICATION_COMMAND_SEARCH_STORE_UPDATE" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_DIRECTORY_FETCH_SUCCESS" | "APPLICATION_STORE_DIRECTORY_MUTE" | "APPLICATION_STORE_DIRECTORY_UNMUTE" | "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_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_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_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_USER" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "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_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "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_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_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "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_ADD" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_REMOVE" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "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_CLOSE" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_OPEN" | "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_HIGHLIGHTS_FETCH_START" | "CHANNEL_HIGHLIGHTS_FETCH_SUCCESS" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MESSAGE_PREVIEW_LOAD_MESSAGES" | "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_MUSIC_MUTED" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "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_TOGGLE_MEMBERS_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECK_LAUNCHABLE_GAME" | "CLEAR_AUTHENTICATION_ERRORS" | "CLEAR_CACHES" | "CLEAR_GUILD_CACHE" | "CLEAR_INTERACTION_MODAL_STATE" | "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_CLEAR_CLIPS_SESSION" | "CLIPS_SAVE_CLIP" | "CLIPS_SETTINGS_UPDATE" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SET" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CREATE_PENDING_REPLY" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "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_ENABLED" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "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_SETTINGS_UPDATE" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISCOVER_GUILDS_FETCH_FAILURE" | "DISCOVER_GUILDS_FETCH_START" | "DISCOVER_GUILDS_FETCH_SUCCESS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_NITRODUCTION_TOOLTIP" | "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_HEARTBEAT_FAILURE" | "DROPS_HEARTBEAT_SUCCESS" | "DROPS_PLATFORM_AVAILABILITY_SUCCESS" | "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_FREE_INDICATOR" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_FREE_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_INBOUND_UPDATE" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMOJI_DELETE" | "EMOJI_FETCH_FAILURE" | "EMOJI_FETCH_SUCCESS" | "EMOJI_TRACK_USAGE" | "EMOJI_UPLOAD_START" | "EMOJI_UPLOAD_STOP" | "ENABLE_AUTOMATIC_ACK" | "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" | "EXPERIMENT_TRIGGER" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "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_ICON_UPDATE" | "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_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_ANALYTICS_MEMBER_INSIGHTS_FETCH_SUCCESS" | "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_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_HOME_ENSURE_HOME_SESSION" | "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_DISABLE_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "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_STATUS" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_MEMBERS_CHUNK" | "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" | "GUILD_MOVE_BY_ID" | "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_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RECOMMENDATION_FETCH" | "GUILD_RECOMMENDATION_FETCH_FAILURE" | "GUILD_RECOMMENDATION_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTIONS_CONFIGURATIONS_FETCH_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_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_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "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_LOADED_BANS" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INTEGRATIONS_WITH_COMMANDS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_EDUCATION_UPSELL_DISMISSED" | "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_STEP" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_ROLES_CLEAR_PERMISSIONS" | "GUILD_SETTINGS_ROLES_EDIT_SECTION_UPDATE" | "GUILD_SETTINGS_ROLES_INIT" | "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_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_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_CLOSE" | "GUILD_SETTINGS_VANITY_URL_ERROR" | "GUILD_SETTINGS_VANITY_URL_RESET" | "GUILD_SETTINGS_VANITY_URL_SET" | "GUILD_SOUNDBOARD_DELETE_SUCCESS" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_FETCH_FAILURE" | "GUILD_SOUNDBOARD_FETCH_SUCCESS" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_UPDATE_SUCCESS" | "GUILD_SOUNDBOARD_UPLOAD_SUCCESS" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "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_UNREADS_SET_LAST_CLEARED" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIDE_SPAM_MESSAGES_FOR_USER" | "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" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "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_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" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "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" | "LOBBY_CONNECT" | "LOBBY_CREATE" | "LOBBY_DELETE" | "LOBBY_DISCONNECT" | "LOBBY_MEMBER_CONNECT" | "LOBBY_MEMBER_DISCONNECT" | "LOBBY_MEMBER_UPDATE" | "LOBBY_MESSAGE" | "LOBBY_UPDATE" | "LOBBY_VOICE_CONNECT" | "LOBBY_VOICE_DISCONNECT" | "LOBBY_VOICE_SERVER_UPDATE" | "LOBBY_VOICE_STATE_UPDATE" | "LOCAL_ACTIVITY_UPDATE" | "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_MFA_WEBAUTHN" | "LOGIN_MFA_WEBAUTHN_TO_TOTP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "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_ENABLE_SOUNDSHARE" | "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_AV1" | "MEDIA_ENGINE_SET_DESKTOP_SOURCE" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "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" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MENTION_MODAL_CLOSE" | "MENTION_MODAL_OPEN" | "MESSAGE_ACK" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_TODO_ADD" | "MESSAGE_TODO_CLEANUP" | "MESSAGE_TODO_COMPLETE" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_SUCCESS" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MFA_WEBAUTHN_CREDENTIALS_LOADING" | "MFA_WEBAUTHN_CREDENTIAL_CREATE" | "MFA_WEBAUTHN_CREDENTIAL_DELETE" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "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" | "NITRODUCTION_PERSISTENT_ONBOARDING_TOGGLE_COLLAPSE" | "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_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_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "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_GAMES_CHANGE" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_READY" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "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_TEXT_CHAT_NOTIFICATION_MODE" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SET_UI_LOCKED" | "OVERLAY_START_SESSION" | "PARTNER_REQUIREMENTS_FETCH_FAILURE" | "PARTNER_REQUIREMENTS_FETCH_START" | "PARTNER_REQUIREMENTS_FETCH_SUCCESS" | "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_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_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_PANEL_TOGGLE_SECTION" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASE_CONFIRMATION_MODAL_CLOSE" | "PURCHASE_CONFIRMATION_MODAL_OPEN" | "PUSH_NOTIFICATION_CLICK" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECENT_MENTION_DELETE" | "REGISTER" | "REGISTER_FAILURE" | "REGISTER_SAVE_FORM" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REQUEST_FORUM_UNREADS" | "RESET_ALL_NITRODUCTION_TOOLTIPS" | "RESET_HAS_COMPLETED_STEP" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "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" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "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_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_START" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_GUILD_FOLDER_EXPANDED" | "SET_HAS_COMPLETED_STEP" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_LOGIN_CREDENTIALS" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_SOUNDPACK" | "SET_TTS_SPEECH_RATE" | "SET_VAD_PERMISSION" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "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_ADD_FAVORITE_SOUND" | "SOUNDBOARD_REMOVE_FAVORITE_SOUND" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "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" | "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_APPLICATION_INTERACTION_FAKE_USER" | "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" | "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" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "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" | "UPDATE_APP_COLORS" | "UPDATE_AVAILABLE" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CONSENTS" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_HAS_FLOW_START_EVENT_BEEN_EMITTED" | "UPDATE_MANUALLY" | "UPDATE_NOT_AVAILABLE" | "UPDATE_TOKEN" | "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_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACHIEVEMENT_UPDATE" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "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_CLIENT_ADD" | "USER_PROFILE_ACCESSIBILITY_TOOLTIP_VIEWED" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "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_SET_DISABLE_SUBMIT" | "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_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "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_PROFILE_CHANGES" | "USER_SETTINGS_THEME_OVERRIDE" | "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_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"; +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/stores.d.ts b/src/webpack/common/types/stores.d.ts index 059924f5a..037b2d81c 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -39,10 +39,37 @@ export class FluxStore { syncWith: GenericFunction; waitFor: GenericFunction; __getLocalVars(): Record; + + static getAll(): FluxStore[]; +} + +export class FluxEmitter { + constructor(); + + changeSentinel: number; + changedStores: Set; + isBatchEmitting: boolean; + isDispatching: boolean; + isPaused: boolean; + pauseTimer: NodeJS.Timeout | null; + reactChangedStores: Set; + + 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 { @@ -63,7 +90,7 @@ export interface CustomEmoji { originalName?: string; require_colons: boolean; roles: string[]; - type: "GUILD_EMOJI"; + type: 1; } export interface UnicodeEmoji { @@ -75,7 +102,7 @@ export interface UnicodeEmoji { }; index: number; surrogates: string; - type: "UNICODE"; + type: 0; uniqueName: string; useSpriteSheet: boolean; get allNamesString(): string; diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 39af843c5..1cd2bf69d 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -168,17 +168,8 @@ export interface Clipboard { export interface NavigationRouter { back(): void; forward(): void; - hasNavigated(): boolean; - getHistory(): { - action: string; - length: 50; - [key: string]: any; - }; transitionTo(path: string, ...args: unknown[]): void; transitionToGuild(guildId: string, ...args: unknown[]): void; - replaceWith(...args: unknown[]): void; - getLastRouteChangeSource(): any; - getLastRouteChangeSourceLocationStack(): any; } export interface IconUtils { @@ -224,3 +215,9 @@ export interface IconUtils { getApplicationIconSource: any; getAnimatableSourceWithFallback: any; } + +export interface Constants { + Endpoints: Record; + UserFlags: Record; + FriendsSections: Record; +} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 72a71f31c..a724769c8 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,10 +16,11 @@ * along with this program. If not, see . */ -import type { Channel, User } from "discord-types/general"; +import { canonicalizeMatch } from "@utils/patches"; +import type { Channel } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative -import { _resolveReady, filters, findByCodeLazy, findByProps, findByPropsLazy, findLazy, proxyLazyWebpack, waitFor } from "../webpack"; +import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack"; import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; @@ -36,15 +37,15 @@ waitFor(["dispatch", "subscribe"], m => { }); export let ComponentDispatch; -waitFor(["ComponentDispatch", "ComponentDispatcher"], m => ComponentDispatch = m.ComponentDispatch); +waitFor(["dispatchToLastSubscribed"], m => ComponentDispatch = m); - -export const Constants = findByPropsLazy("Endpoints"); - -export const RestAPI: t.RestAPI = proxyLazyWebpack(() => { - const mod = findByProps("getAPIBaseURL"); - return mod.HTTP ?? mod; +export const Constants: t.Constants = mapMangledModuleLazy('ME:"/users/@me"', { + Endpoints: filters.byProps("USER", "ME"), + UserFlags: filters.byProps("STAFF", "SPAMMER"), + FriendsSections: m => m.PENDING === "PENDING" && m.ADD_FRIEND }); + +export const RestAPI: t.RestAPI = findLazy(m => typeof m === "object" && m.del && m.put); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage"); @@ -118,33 +119,50 @@ export function showToast(message: string, type = ToastType.MESSAGE) { }); } -export const UserUtils = findByPropsLazy("getUser", "fetchCurrentUser") as { getUser: (id: string) => Promise; }; +export const UserUtils = { + getUser: findByCodeLazy(".USER(") +}; export const UploadManager = findByPropsLazy("clearAll", "addFile"); -export const UploadHandler = findByPropsLazy("showUploadFileSizeExceededError", "promptToUpload") as { - promptToUpload: (files: File[], channel: Channel, draftType: Number) => void; +export const UploadHandler = { + promptToUpload: findByCodeLazy(".ATTACHMENT_TOO_MANY_ERROR_TITLE,") as (files: File[], channel: Channel, draftType: Number) => void }; export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetImage") as { fetchAssetIds: (applicationId: string, e: string[]) => Promise; }; -export const Clipboard: t.Clipboard = findByPropsLazy("SUPPORTS_COPY", "copy"); +export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', { + copy: filters.byCode(".copy("), + SUPPORTS_COPY: e => typeof e === "boolean" +}); -export const NavigationRouter: t.NavigationRouter = findByPropsLazy("transitionTo", "replaceWith", "transitionToGuild"); +export const NavigationRouter: t.NavigationRouter = mapMangledModuleLazy("Transitioning to ", { + transitionTo: filters.byCode("transitionTo -"), + transitionToGuild: filters.byCode("transitionToGuild -"), + back: filters.byCode("goBack()"), + forward: filters.byCode("goForward()"), +}); export let SettingsRouter: any; waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m); -export const { Permissions: PermissionsBits } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; }; +export const PermissionsBits: t.PermissionsBits = findLazy(m => typeof m.ADMINISTRATOR === "bigint"); export const zustandCreate = findByCodeLazy("will be removed in v4"); -const persistFilter = filters.byCode("[zustand persist middleware]"); -export const { persist: zustandPersist } = findLazy(m => m.persist && persistFilter(m.persist)); +export const zustandPersist = findByCodeLazy("[zustand persist middleware]"); export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); +export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const InviteActions = findByPropsLazy("resolveInvite"); export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL"); + +const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i/); +// TODO: type +export const ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", { + closeExpressionPicker: filters.byCode("setState({activeView:null"), + openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()), +}); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 311e6f2bc..48f1b8147 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -209,14 +209,33 @@ function patchFactories(factories: Record findByProps("blah")); console.log(mod.blah); */ export function proxyLazyWebpack(factory: () => any, attempts?: number) { - if (IS_DEV) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["proxyLazyWebpack", [factory]]); return proxyLazy(factory, attempts); } @@ -278,7 +308,7 @@ export function proxyLazyWebpack(factory: () => any, attempts?: number) * @returns Result of factory function */ export function LazyComponentWebpack(factory: () => any, attempts?: number) { - if (IS_DEV) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["LazyComponentWebpack", [factory]]); return LazyComponent(factory, attempts); } @@ -287,7 +317,7 @@ export function LazyComponentWebpack(factory: () => any, * Find the first module that matches the filter, lazily */ export function findLazy(filter: FilterFn) { - if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["find", [filter]]); return proxyLazy(() => find(filter)); } @@ -306,7 +336,7 @@ export function findByProps(...props: string[]) { * Find the first module that has the specified properties, lazily */ export function findByPropsLazy(...props: string[]) { - if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByProps", props]); return proxyLazy(() => findByProps(...props)); } @@ -325,7 +355,7 @@ export function findByCode(...code: string[]) { * Find the first function that includes all the given code, lazily */ export function findByCodeLazy(...code: string[]) { - if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByCode", code]); return proxyLazy(() => findByCode(...code)); } @@ -344,7 +374,7 @@ export function findStore(name: string) { * Find a store by its displayName, lazily */ export function findStoreLazy(name: string) { - if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findStore", [name]]); return proxyLazy(() => findStore(name)); } @@ -363,7 +393,7 @@ export function findComponentByCode(...code: string[]) { * Finds the first component that matches the filter, lazily. */ export function findComponentLazy(filter: FilterFn) { - if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponent", [filter]]); return LazyComponent(() => { @@ -378,7 +408,7 @@ export function findComponentLazy(filter: FilterFn) { * Finds the first component that includes all the given code, lazily */ export function findComponentByCodeLazy(...code: string[]) { - if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponentByCode", code]); return LazyComponent(() => { const res = find(filters.componentByCode(...code), { isIndirect: true }); @@ -392,7 +422,7 @@ export function findComponentByCodeLazy(...code: string[ * Finds the first component that is exported by the first prop name, lazily */ export function findExportedComponentLazy(...props: string[]) { - if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findExportedComponent", props]); return LazyComponent(() => { const res = find(filters.byProps(...props), { isIndirect: true }); @@ -402,14 +432,68 @@ export function findExportedComponentLazy(...props: stri }); } -export const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\))|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/; -export const ChunkIdsRegex = /\("(.+?)"\)/g; +/** + * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) + * then maps it into an easily usable module via the specified mappers. + * + * @param code The code to look for + * @param mappers Mappers to create the non mangled exports + * @returns Unmangled exports as specified in mappers + * + * @example mapMangledModule("headerIdIsManaged:", { + * openModal: filters.byCode("headerIdIsManaged:"), + * closeModal: filters.byCode("key==") + * }) + */ +export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule(code: string, mappers: Record): Record { + const exports = {} as Record; + + const id = findModuleId(code); + if (id === null) + return exports; + + const mod = wreq(id as any); + outer: + for (const key in mod) { + const member = mod[key]; + for (const newName in mappers) { + // if the current mapper matches this module + if (mappers[newName](member)) { + exports[newName] = member; + continue outer; + } + } + } + return exports; +}); + +/** + * {@link mapMangledModule}, lazy. + + * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) + * then maps it into an easily usable module via the specified mappers. + * + * @param code The code to look for + * @param mappers Mappers to create the non mangled exports + * @returns Unmangled exports as specified in mappers + * + * @example mapMangledModule("headerIdIsManaged:", { + * openModal: filters.byCode("headerIdIsManaged:"), + * closeModal: filters.byCode("key==") + * }) + */ +export function mapMangledModuleLazy(code: string, mappers: Record): Record { + return proxyLazy(() => mapMangledModule(code, mappers)); +} + +export const DefaultExtractAndLoadChunksRegex = /(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/; +export const ChunkIdsRegex = /\("([^"]+?)"\)/g; /** * Extract and load chunks using their entry point * @param code An array of all the code the module factory containing the lazy chunk loading must include - * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory - * @returns A promise that resolves when the chunks were loaded + * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory + * @returns A promise that resolves with a boolean whether the chunks were loaded */ export async function extractAndLoadChunks(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) { const module = findModuleFactory(...code); @@ -417,7 +501,11 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def const err = new Error("extractAndLoadChunks: Couldn't find module factory"); logger.warn(err, "Code:", code, "Matcher:", matcher); - return; + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + if (IS_DEV && !devToolsOpen) + throw err; + + return false; } const match = module.toString().match(canonicalizeMatch(matcher)); @@ -429,10 +517,10 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def if (IS_DEV && !devToolsOpen) throw err; - return; + return false; } - const [, rawChunkIdsArray, rawChunkIdsSingle, entryPointId] = match; + const [, rawChunkIds, entryPointId] = match; if (Number.isNaN(Number(entryPointId))) { const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number"); logger.warn(err, "Code:", code, "Matcher:", matcher); @@ -441,16 +529,27 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def if (IS_DEV && !devToolsOpen) throw err; - return; + return false; } - const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle; if (rawChunkIds) { const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map((m: any) => m[1]); await Promise.all(chunkIds.map(id => wreq.e(id))); } + if (wreq.m[entryPointId] == null) { + const err = new Error("extractAndLoadChunks: Entry point is not loaded in the module factories, perhaps one of the chunks failed to load"); + logger.warn(err, "Code:", code, "Matcher:", matcher); + + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + if (IS_DEV && !devToolsOpen) + throw err; + + return false; + } + wreq(entryPointId); + return true; } /** @@ -458,11 +557,11 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def * * Extract and load chunks using their entry point * @param code An array of all the code the module factory containing the lazy chunk loading must include - * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the lazy chunk loading found in the module factory - * @returns A function that returns a promise that resolves when the chunks were loaded, on first call + * @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory + * @returns A function that returns a promise that resolves with a boolean whether the chunks were loaded, on first call */ export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) { - if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]); + if (IS_REPORTER) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]); return makeLazy(() => extractAndLoadChunks(code, matcher)); } @@ -472,7 +571,7 @@ export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtrac * then call the callback with the module as the first argument */ export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) { - if (IS_DEV && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]); + if (IS_REPORTER && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]); if (typeof filter === "string") filter = filters.byProps(filter);