diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.jforgejo/ISSUE_TEMPLATE/blank.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/blank.yml rename to .jforgejo/ISSUE_TEMPLATE/blank.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.jforgejo/ISSUE_TEMPLATE/bug_report.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.yml rename to .jforgejo/ISSUE_TEMPLATE/bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.jforgejo/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yml rename to .jforgejo/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/developer-banner.png b/.jforgejo/ISSUE_TEMPLATE/developer-banner.png similarity index 100% rename from .github/ISSUE_TEMPLATE/developer-banner.png rename to .jforgejo/ISSUE_TEMPLATE/developer-banner.png diff --git a/.github/workflows/build.yml b/.jforgejo/workflows/build.yml similarity index 100% rename from .github/workflows/build.yml rename to .jforgejo/workflows/build.yml diff --git a/.github/workflows/codeberg-mirror.yml b/.jforgejo/workflows/codeberg-mirror.yml similarity index 100% rename from .github/workflows/codeberg-mirror.yml rename to .jforgejo/workflows/codeberg-mirror.yml diff --git a/.github/workflows/publish.yml b/.jforgejo/workflows/publish.yml similarity index 100% rename from .github/workflows/publish.yml rename to .jforgejo/workflows/publish.yml diff --git a/.github/workflows/reportBrokenPlugins.yml b/.jforgejo/workflows/reportBrokenPlugins.yml similarity index 100% rename from .github/workflows/reportBrokenPlugins.yml rename to .jforgejo/workflows/reportBrokenPlugins.yml diff --git a/.github/workflows/test.yml b/.jforgejo/workflows/test.yml similarity index 100% rename from .github/workflows/test.yml rename to .jforgejo/workflows/test.yml diff --git a/README.md b/README.md index 3a2b1492..05454c9e 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,30 @@ # A fork. A fork. We're a fork. +Installing is the same as the Vencord devs laid out: [(Install)](https://docs.vencord.dev/installing/) +* Beyond that, be sure to clone *this* repository instead of their upstream one. + * `git clone https://git.dorkbutt.lol/dorkbutt/vencord` +* After completing the steps, go to Settings > Vencord > Plugins and search for +"FORKED - usrbg" and enable it. Be sure to tweak its settings! + # Vencord +![](https://img.shields.io/github/package-json/v/Vendicated/Vencord?style=for-the-badge&logo=github&logoColor=d3869b&label=&color=1d2021&labelColor=282828) [![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Vee/cord) 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) | +![](https://github.com/user-attachments/assets/3fac98c0-c411-4d2a-97a3-13b7da8687a2) ## Features -- Super easy to install (Download Installer, open, click install button, done) -- 100+ plugins built in: [See a list](https://vencord.dev/plugins) - - Some highlights: SpotifyControls, MessageLogger, Experiments, GameActivityToggle, Translate, NoTrack, QuickReply, Free Emotes/Stickers, PermissionsViewer, CustomCommands, ShowHiddenChannels, PronounDB +- Easy to install +- [100+ built in plugins](https://vencord.dev/plugins) - Fairly lightweight despite the many inbuilt plugins - Excellent Browser Support: Run Vencord in your Browser via extension or UserScript -- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!) +- Works on any Discord branch: Stable, Canary or PTB all work - Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes) -- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry +- Privacy friendly: blocks Discord analytics & crash reporting out of the box and has no telemetry - Maintained very actively, broken plugins are usually fixed within 12 hours - Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional) diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index 79f0f2cd..c99c176c 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -20,16 +20,13 @@ /// 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 { debounce, localStorage } from "../src/utils"; import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; import { getTheme, Theme } from "../src/utils/discord"; import { getThemeInfo } from "../src/main/themes"; import { Settings } from "../src/Vencord"; - -// Discord deletes this so need to store in variable -const { localStorage } = window; +import { getStylusWebStoreUrl } from "@utils/web"; // listeners for ipc.on const cssListeners = new Set<(css: string) => void>(); @@ -45,12 +42,13 @@ window.VencordNative = { themes: { uploadTheme: (fileName: string, fileData: string) => DataStore.set(fileName, fileData, themeStore), deleteTheme: (fileName: string) => DataStore.del(fileName, themeStore), - getThemesDir: async () => "", getThemesList: () => DataStore.entries(themeStore).then(entries => entries.map(([name, css]) => getThemeInfo(css, name.toString())) ), getThemeData: (fileName: string) => DataStore.get(fileName, themeStore), getSystemValues: async () => ({}), + + openFolder: async () => Promise.reject("themes:openFolder is not supported on web"), }, native: { @@ -77,6 +75,14 @@ window.VencordNative = { addThemeChangeListener: NOOP, openFile: NOOP_ASYNC, async openEditor() { + if (IS_USERSCRIPT) { + const shouldOpenWebStore = confirm("QuickCSS is not supported on the Userscript. You can instead use the Stylus extension.\n\nDo you want to open the Stylus web store page?"); + if (shouldOpenWebStore) { + window.open(getStylusWebStoreUrl(), "_blank"); + } + return; + } + const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`; const win = open("about:blank", "VencordQuickCss", features); if (!win) { @@ -92,7 +98,7 @@ window.VencordNative = { ? "vs-light" : "vs-dark"; - win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn); + win.document.write(monacoHtmlLocal); }, }, @@ -106,8 +112,9 @@ window.VencordNative = { } }, set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)), - getSettingsDir: async () => "LocalStorage" + openFolder: async () => Promise.reject("settings:openFolder is not supported on web"), }, pluginHelpers: {} as any, + csp: {} as any, }; diff --git a/package.json b/package.json index 4c6d5023..577bf39d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.2", + "version": "1.12.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -53,8 +53,8 @@ "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@types/yazl": "^2.4.5", + "@vencord/discord-types": "link:packages/discord-types", "diff": "^7.0.0", - "discord-types": "^1.3.26", "esbuild": "^0.25.1", "eslint": "9.20.1", "eslint-import-resolver-alias": "^1.1.2", diff --git a/packages/discord-types/LICENSE b/packages/discord-types/LICENSE new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/packages/discord-types/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/packages/discord-types/README.md b/packages/discord-types/README.md new file mode 100644 index 00000000..82a98dd8 --- /dev/null +++ b/packages/discord-types/README.md @@ -0,0 +1,42 @@ +# Discord Types + +This package provides TypeScript types for the Webpack modules of Discord's web app. + +While it was primarily created for Vencord, other client mods could also benefit from this, so it is published as a standalone package! + +## Installation + +```bash +npm install -D @vencord/discord-types +yarn add -D @vencord/discord-types +pnpm add -D @vencord/discord-types +``` + +## Example Usage + +```ts +import type { UserStore } from "@vencord/discord-types"; + +const userStore: UserStore = findStore("UserStore"); // findStore is up to you to implement, this library only provides types and no runtime code +``` + +## Enums + +This library also exports some const enums that you can use from Typescript code: +```ts +import { ApplicationCommandType } from "@vencord/discord-types/enums"; + +console.log(ApplicationCommandType.CHAT_INPUT); // 1 +``` + +### License + +This package is licensed under the [LGPL-3.0](./LICENSE) (or later) license. + +A very short summary of the license is that you can use this package as a library in both open source and closed source projects, +similar to an MIT-licensed project. +However, if you modify the code of this package, you must release source code of your modified version under the same license. + +### Credit + +This package was inspired by Swishilicous' [discord-types](https://www.npmjs.com/package/discord-types) package. diff --git a/packages/discord-types/enums/commands.ts b/packages/discord-types/enums/commands.ts new file mode 100644 index 00000000..298f9b7a --- /dev/null +++ b/packages/discord-types/enums/commands.ts @@ -0,0 +1,32 @@ +export const enum ApplicationCommandOptionType { + SUB_COMMAND = 1, + SUB_COMMAND_GROUP = 2, + STRING = 3, + INTEGER = 4, + BOOLEAN = 5, + USER = 6, + CHANNEL = 7, + ROLE = 8, + MENTIONABLE = 9, + NUMBER = 10, + ATTACHMENT = 11, +} + +export const enum ApplicationCommandInputType { + BUILT_IN = 0, + BUILT_IN_TEXT = 1, + BUILT_IN_INTEGRATION = 2, + BOT = 3, + PLACEHOLDER = 4, +} + +export const enum ApplicationCommandType { + CHAT_INPUT = 1, + USER = 2, + MESSAGE = 3, +} + +export const enum ApplicationIntegrationType { + GUILD_INSTALL = 0, + USER_INSTALL = 1 +} diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts new file mode 100644 index 00000000..62074d46 --- /dev/null +++ b/packages/discord-types/enums/index.ts @@ -0,0 +1 @@ +export * from "./commands"; diff --git a/packages/discord-types/package.json b/packages/discord-types/package.json new file mode 100644 index 00000000..7b8726fd --- /dev/null +++ b/packages/discord-types/package.json @@ -0,0 +1,19 @@ +{ + "name": "@vencord/discord-types", + "author": "Vencord Contributors", + "description": "Typescript definitions for the webpack modules of the Discord Web app", + "version": "1.0.0", + "license": "LGPL-3.0-or-later", + "types": "src/index.d.ts", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/Vendicated/Vencord.git", + "directory": "packages/discord-types" + }, + "dependencies": { + "@types/react": "^19.0.10", + "moment": "^2.22.2", + "type-fest": "^4.41.0" + } +} diff --git a/packages/discord-types/src/classes.d.ts b/packages/discord-types/src/classes.d.ts new file mode 100644 index 00000000..752ba3f0 --- /dev/null +++ b/packages/discord-types/src/classes.d.ts @@ -0,0 +1,21 @@ +export interface ImageModalClasses { + image: string, + modal: string, +} + +export interface ButtonWrapperClasses { + hoverScale: string; + buttonWrapper: string; + button: string; + iconMask: string; + buttonContent: string; + icon: string; + pulseIcon: string; + pulseButton: string; + notificationDot: string; + sparkleContainer: string; + sparkleStar: string; + sparklePlus: string; + sparkle: string; + active: string; +} diff --git a/packages/discord-types/src/common/Application.d.ts b/packages/discord-types/src/common/Application.d.ts new file mode 100644 index 00000000..d2ec1e7e --- /dev/null +++ b/packages/discord-types/src/common/Application.d.ts @@ -0,0 +1,23 @@ +import { User } from "./User"; + +export interface Application { + id: string; + name: string; + description?: string | null; + type: number | null; + icon: string | null | undefined; + is_discoverable: boolean; + is_monetized: boolean; + is_verified: boolean; + bot?: User; + deeplink_uri?: string; + flags?: number; + privacy_policy_url?: string; + terms_of_service_url?: string; + install_params?: ApplicationInstallParams; +} + +export interface ApplicationInstallParams { + permissions: string | null; + scopes: string[]; +} diff --git a/packages/discord-types/src/common/Channel.d.ts b/packages/discord-types/src/common/Channel.d.ts new file mode 100644 index 00000000..7ad5ce53 --- /dev/null +++ b/packages/discord-types/src/common/Channel.d.ts @@ -0,0 +1,83 @@ +import { DiscordRecord } from "./Record"; + +export class Channel extends DiscordRecord { + constructor(channel: object); + application_id: number | undefined; + bitrate: number; + defaultAutoArchiveDuration: number | undefined; + flags: number; + guild_id: string; + icon: string; + id: string; + lastMessageId: string; + lastPinTimestamp: string | undefined; + member: unknown; + memberCount: number | undefined; + memberIdsPreview: string[] | undefined; + memberListId: unknown; + messageCount: number | undefined; + name: string; + nicks: Record; + nsfw: boolean; + originChannelId: unknown; + ownerId: string; + parent_id: string; + permissionOverwrites: { + [role: string]: { + id: string; + type: number; + deny: bigint; + allow: bigint; + }; + }; + position: number; + rateLimitPerUser: number; + rawRecipients: { + id: string; + avatar: string; + username: string; + public_flags: number; + discriminator: string; + }[]; + recipients: string[]; + rtcRegion: string; + threadMetadata: { + locked: boolean; + archived: boolean; + invitable: boolean; + createTimestamp: string | undefined; + autoArchiveDuration: number; + archiveTimestamp: string | undefined; + }; + topic: string; + type: number; + userLimit: number; + videoQualityMode: undefined; + + get accessPermissions(): bigint; + get lastActiveTimestamp(): number; + + computeLurkerPermissionsAllowList(): unknown; + getApplicationId(): unknown; + getGuildId(): string; + getRecipientId(): unknown; + hasFlag(flag: number): boolean; + isActiveThread(): boolean; + isArchivedThread(): boolean; + isCategory(): boolean; + isDM(): boolean; + isDirectory(): boolean; + isForumChannel(): boolean; + isGroupDM(): boolean; + isGuildStageVoice(): boolean; + isGuildVoice(): boolean; + isListenModeCapable(): boolean; + isManaged(): boolean; + isMultiUserDM(): boolean; + isNSFW(): boolean; + isOwner(): boolean; + isPrivate(): boolean; + isSystemDM(): boolean; + isThread(): boolean; + isVocal(): boolean; +} diff --git a/packages/discord-types/src/common/Guild.d.ts b/packages/discord-types/src/common/Guild.d.ts new file mode 100644 index 00000000..5b5c3c74 --- /dev/null +++ b/packages/discord-types/src/common/Guild.d.ts @@ -0,0 +1,64 @@ +import { Role } from './Role'; +import { DiscordRecord } from './Record'; + +// copy(Object.keys(findByProps("CREATOR_MONETIZABLE")).map(JSON.stringify).join("|")) +export type GuildFeatures = + "INVITE_SPLASH" | "VIP_REGIONS" | "VANITY_URL" | "MORE_EMOJI" | "MORE_STICKERS" | "MORE_SOUNDBOARD" | "VERIFIED" | "COMMERCE" | "DISCOVERABLE" | "COMMUNITY" | "FEATURABLE" | "NEWS" | "HUB" | "PARTNERED" | "ANIMATED_ICON" | "BANNER" | "ENABLED_DISCOVERABLE_BEFORE" | "WELCOME_SCREEN_ENABLED" | "MEMBER_VERIFICATION_GATE_ENABLED" | "PREVIEW_ENABLED" | "ROLE_SUBSCRIPTIONS_ENABLED" | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" | "CREATOR_MONETIZABLE" | "CREATOR_MONETIZABLE_PROVISIONAL" | "CREATOR_MONETIZABLE_WHITEGLOVE" | "CREATOR_MONETIZABLE_DISABLED" | "CREATOR_MONETIZABLE_RESTRICTED" | "CREATOR_STORE_PAGE" | "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" | "PRODUCTS_AVAILABLE_FOR_PURCHASE" | "GUILD_WEB_PAGE_VANITY_URL" | "THREADS_ENABLED" | "THREADS_ENABLED_TESTING" | "NEW_THREAD_PERMISSIONS" | "ROLE_ICONS" | "TEXT_IN_STAGE_ENABLED" | "TEXT_IN_VOICE_ENABLED" | "HAS_DIRECTORY_ENTRY" | "ANIMATED_BANNER" | "LINKED_TO_HUB" | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" | "GUILD_HOME_DEPRECATION_OVERRIDE" | "GUILD_HOME_TEST" | "GUILD_HOME_OVERRIDE" | "GUILD_ONBOARDING" | "GUILD_ONBOARDING_EVER_ENABLED" | "GUILD_ONBOARDING_HAS_PROMPTS" | "GUILD_SERVER_GUIDE" | "INTERNAL_EMPLOYEE_ONLY" | "AUTO_MODERATION" | "INVITES_DISABLED" | "BURST_REACTIONS" | "SOUNDBOARD" | "SHARD" | "ACTIVITY_FEED_ENABLED_BY_USER" | "ACTIVITY_FEED_DISABLED_BY_USER" | "SUMMARIES_ENABLED_GA" | "LEADERBOARD_ENABLED" | "SUMMARIES_ENABLED_BY_USER" | "SUMMARIES_OPT_OUT_EXPERIENCE" | "CHANNEL_ICON_EMOJIS_GENERATED" | "NON_COMMUNITY_RAID_ALERTS" | "RAID_ALERTS_DISABLED" | "AUTOMOD_TRIGGER_USER_PROFILE" | "ENABLED_MODERATION_EXPERIENCE_FOR_NON_COMMUNITY" | "GUILD_PRODUCTS_ALLOW_ARCHIVED_FILE" | "CLAN" | "MEMBER_VERIFICATION_MANUAL_APPROVAL" | "FORWARDING_DISABLED" | "MEMBER_VERIFICATION_ROLLOUT_TEST" | "AUDIO_BITRATE_128_KBPS" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS" | "VIDEO_BITRATE_ENHANCED" | "MAX_FILE_SIZE_50_MB" | "MAX_FILE_SIZE_100_MB" | "GUILD_TAGS" | "ENHANCED_ROLE_COLORS" | "PREMIUM_TIER_3_OVERRIDE" | "REPORT_TO_MOD_PILOT" | "TIERLESS_BOOSTING_SYSTEM_MESSAGE"; +export type GuildPremiumFeatures = + "ANIMATED_ICON" | "STAGE_CHANNEL_VIEWERS_150" | "ROLE_ICONS" | "GUILD_TAGS" | "BANNER" | "MAX_FILE_SIZE_50_MB" | "VIDEO_QUALITY_720_60FPS" | "STAGE_CHANNEL_VIEWERS_50" | "VIDEO_QUALITY_1080_60FPS" | "MAX_FILE_SIZE_100_MB" | "VANITY_URL" | "VIDEO_BITRATE_ENHANCED" | "STAGE_CHANNEL_VIEWERS_300" | "AUDIO_BITRATE_128_KBPS" | "ANIMATED_BANNER" | "TIERLESS_BOOSTING" | "ENHANCED_ROLE_COLORS" | "INVITE_SPLASH" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS"; + +export class Guild extends DiscordRecord { + constructor(guild: object); + afkChannelId: string | undefined; + afkTimeout: number; + applicationCommandCounts: { + 0: number; + 1: number; + 2: number; + }; + application_id: unknown; + banner: string | undefined; + defaultMessageNotifications: number; + description: string | undefined; + discoverySplash: string | undefined; + explicitContentFilter: number; + features: Set; + homeHeader: string | undefined; + hubType: unknown; + icon: string | undefined; + id: string; + joinedAt: Date; + latestOnboardingQuestionId: string | undefined; + maxMembers: number; + maxStageVideoChannelUsers: number; + maxVideoChannelUsers: number; + mfaLevel: number; + moderatorReporting: unknown; + name: string; + nsfwLevel: number; + ownerConfiguredContentLevel: number; + ownerId: string; + preferredLocale: string; + premiumFeatures: { + additionalEmojiSlots: number; + additionalSoundSlots: number; + additionalStickerSlots: number; + features: Array; + }; + premiumProgressBarEnabled: boolean; + premiumSubscriberCount: number; + premiumTier: number; + profile: { + badge: string | undefined; + tag: string | undefined; + } | undefined; + publicUpdatesChannelId: string | undefined; + roles: Record; + rulesChannelId: string | undefined; + safetyAlertsChannelId: string | undefined; + splash: string | undefined; + systemChannelFlags: number; + systemChannelId: string | undefined; + vanityURLCode: string | undefined; + verificationLevel: number; +} diff --git a/packages/discord-types/src/common/GuildMember.d.ts b/packages/discord-types/src/common/GuildMember.d.ts new file mode 100644 index 00000000..5cd47682 --- /dev/null +++ b/packages/discord-types/src/common/GuildMember.d.ts @@ -0,0 +1,26 @@ +export interface GuildMember { + avatar: string | undefined; + avatarDecoration: string | undefined; + banner: string | undefined; + bio: string; + colorRoleId: string | undefined; + colorString: string; + colorStrings: { + primaryColor: string | undefined; + secondaryColor: string | undefined; + tertiaryColor: string | undefined; + }; + communicationDisabledUntil: string | undefined; + flags: number; + fullProfileLoadedTimestamp: number; + guildId: string; + highestRoleId: string; + hoistRoleId: string; + iconRoleId: string; + isPending: boolean | undefined; + joinedAt: string | undefined; + nick: string | undefined; + premiumSince: string | undefined; + roles: string[]; + userId: string; +} diff --git a/packages/discord-types/src/common/Record.d.ts b/packages/discord-types/src/common/Record.d.ts new file mode 100644 index 00000000..8f033058 --- /dev/null +++ b/packages/discord-types/src/common/Record.d.ts @@ -0,0 +1,12 @@ +type Updater = (value: any) => any; + +/** + * Common Record class extended by various Discord data structures, like User, Channel, Guild, etc. + */ +export class DiscordRecord { + toJS(): Record; + + set(key: string, value: any): this; + merge(data: Record): this; + update(key: string, defaultValueOrUpdater: Updater | any, updater?: Updater): this; +} diff --git a/packages/discord-types/src/common/Role.d.ts b/packages/discord-types/src/common/Role.d.ts new file mode 100644 index 00000000..7b038984 --- /dev/null +++ b/packages/discord-types/src/common/Role.d.ts @@ -0,0 +1,33 @@ +export interface Role { + color: number; + colorString: string | undefined; + colorStrings: { + primaryColor: string | undefined; + secondaryColor: string | undefined; + tertiaryColor: string | undefined; + }; + colors: { + primary_color: number | undefined; + secondary_color: number | undefined; + tertiary_color: number | undefined; + }; + flags: number; + hoist: boolean; + icon: string | undefined; + id: string; + managed: boolean; + mentionable: boolean; + name: string; + originalPosition: number; + permissions: bigint; + position: number; + /** + * probably incomplete + */ + tags: { + bot_id: string; + integration_id: string; + premium_subscriber: unknown; + } | undefined; + unicodeEmoji: string | undefined; +} diff --git a/packages/discord-types/src/common/User.d.ts b/packages/discord-types/src/common/User.d.ts new file mode 100644 index 00000000..07eb4718 --- /dev/null +++ b/packages/discord-types/src/common/User.d.ts @@ -0,0 +1,65 @@ +// TODO: a lot of optional params can also be null, not just undef + +import { DiscordRecord } from "./Record"; + +export class User extends DiscordRecord { + constructor(user: object); + accentColor: number; + avatar: string; + banner: string | null | undefined; + bio: string; + bot: boolean; + desktop: boolean; + discriminator: string; + email: string | undefined; + flags: number; + globalName: string | undefined; + guildMemberAvatars: Record; + id: string; + mfaEnabled: boolean; + mobile: boolean; + nsfwAllowed: boolean | undefined; + phone: string | undefined; + premiumType: number | undefined; + premiumUsageFlags: number; + publicFlags: number; + purchasedFlags: number; + system: boolean; + username: string; + verified: boolean; + + get createdAt(): Date; + get hasPremiumPerks(): boolean; + get tag(): string; + get usernameNormalized(): string; + + addGuildAvatarHash(guildId: string, avatarHash: string): User; + getAvatarSource(guildId: string, canAnimate?: boolean): { uri: string; }; + getAvatarURL(guildId?: string | null, t?: unknown, canAnimate?: boolean): string; + hasAvatarForGuild(guildId: string): boolean; + hasDisabledPremium(): boolean; + hasFlag(flag: number): boolean; + hasFreePremium(): boolean; + hasHadSKU(e: unknown): boolean; + hasPremiumUsageFlag(flag: number): boolean; + hasPurchasedFlag(flag: number): boolean; + hasUrgentMessages(): boolean; + isClaimed(): boolean; + isLocalBot(): boolean; + isNonUserBot(): boolean; + isPhoneVerified(): boolean; + isStaff(): boolean; + isSystemUser(): boolean; + isVerifiedBot(): boolean; + removeGuildAvatarHash(guildId: string): User; + toString(): string; +} + +export interface UserJSON { + avatar: string; + avatarDecoration: unknown | undefined; + discriminator: string; + id: string; + publicFlags: number; + username: string; +} diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts new file mode 100644 index 00000000..5e50e96e --- /dev/null +++ b/packages/discord-types/src/common/index.d.ts @@ -0,0 +1,8 @@ +export * from "./Application"; +export * from "./Channel"; +export * from "./Guild"; +export * from "./GuildMember"; +export * from "./messages"; +export * from "./Role"; +export * from "./User"; +export * from "./Record"; diff --git a/packages/discord-types/src/common/messages/Commands.d.ts b/packages/discord-types/src/common/messages/Commands.d.ts new file mode 100644 index 00000000..8dc9f9a7 --- /dev/null +++ b/packages/discord-types/src/common/messages/Commands.d.ts @@ -0,0 +1,61 @@ +import { Channel } from "../Channel"; +import { Guild } from "../Guild"; +import { Promisable } from "type-fest"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "../../../enums"; + +export interface CommandContext { + channel: Channel; + guild?: Guild; +} + +export interface CommandOption { + name: string; + displayName?: string; + type: ApplicationCommandOptionType; + description: string; + displayDescription?: string; + required?: boolean; + options?: CommandOption[]; + choices?: Array; +} + +export interface ChoicesOption { + label: string; + value: string; + name: string; + displayName?: string; +} + +export interface CommandReturnValue { + content: string; + // TODO: implement + // cancel?: boolean; +} + +export interface CommandArgument { + type: ApplicationCommandOptionType; + name: string; + value: string; + focused: undefined; + options: CommandArgument[]; +} + +export interface Command { + id?: string; + applicationId?: string; + type?: ApplicationCommandType; + inputType?: ApplicationCommandInputType; + plugin?: string; + + name: string; + untranslatedName?: string; + displayName?: string; + description: string; + untranslatedDescription?: string; + displayDescription?: string; + + options?: CommandOption[]; + predicate?(ctx: CommandContext): boolean; + + execute(args: CommandArgument[], ctx: CommandContext): Promisable; +} diff --git a/packages/discord-types/src/common/messages/Embed.d.ts b/packages/discord-types/src/common/messages/Embed.d.ts new file mode 100644 index 00000000..4edd9ced --- /dev/null +++ b/packages/discord-types/src/common/messages/Embed.d.ts @@ -0,0 +1,70 @@ +export interface Embed { + author?: { + name: string; + url: string; + iconURL: string | undefined; + iconProxyURL: string | undefined; + }; + color: string; + fields: []; + id: string; + image?: { + height: number; + width: number; + url: string; + proxyURL: string; + }; + provider?: { + name: string; + url: string | undefined; + }; + rawDescription: string; + rawTitle: string; + referenceId: unknown; + timestamp: string; + thumbnail?: { + height: number; + proxyURL: string | undefined; + url: string; + width: number; + }; + type: string; + url: string | undefined; + video?: { + height: number; + width: number; + url: string; + proxyURL: string | undefined; + }; +} + +export interface EmbedJSON { + author?: { + name: string; + url: string; + icon_url: string; + proxy_icon_url: string; + }; + title: string; + color: string; + description: string; + type: string; + url: string | undefined; + provider?: { + name: string; + url: string; + }; + timestamp: string; + thumbnail?: { + height: number; + width: number; + url: string; + proxy_url: string | undefined; + }; + video?: { + height: number; + width: number; + url: string; + proxy_url: string | undefined; + }; +} diff --git a/packages/discord-types/src/common/messages/Emoji.d.ts b/packages/discord-types/src/common/messages/Emoji.d.ts new file mode 100644 index 00000000..793e90f8 --- /dev/null +++ b/packages/discord-types/src/common/messages/Emoji.d.ts @@ -0,0 +1,42 @@ +export type Emoji = CustomEmoji | UnicodeEmoji; + +export interface CustomEmoji { + type: 1; + allNamesString: string; + animated: boolean; + available: boolean; + guildId: string; + id: string; + managed: boolean; + name: string; + originalName?: string; + require_colons: boolean; + roles: string[]; +} + +export interface UnicodeEmoji { + type: 0; + diversityChildren: Record; + emojiObject: { + names: string[]; + surrogates: string; + unicodeVersion: number; + }; + index: number; + surrogates: string; + uniqueName: string; + useSpriteSheet: boolean; + get allNamesString(): string; + get animated(): boolean; + get defaultDiversityChild(): any; + get hasDiversity(): boolean | undefined; + get hasDiversityParent(): boolean | undefined; + get hasMultiDiversity(): boolean | undefined; + get hasMultiDiversityParent(): boolean | undefined; + get managed(): boolean; + get name(): string; + get names(): string[]; + get optionallyDiverseSequence(): string | undefined; + get unicodeVersion(): number; + get url(): string; +} diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts new file mode 100644 index 00000000..38010caf --- /dev/null +++ b/packages/discord-types/src/common/messages/Message.d.ts @@ -0,0 +1,191 @@ +import { CommandOption } from './Commands'; +import { User, UserJSON } from '../User'; +import { Embed, EmbedJSON } from './Embed'; +import { DiscordRecord } from "../Record"; + +/** + * TODO: looks like discord has moved over to Date instead of Moment; + */ +export class Message extends DiscordRecord { + constructor(message: object); + activity: unknown; + application: unknown; + applicationId: string | unknown; + attachments: MessageAttachment[]; + author: User; + blocked: boolean; + bot: boolean; + call: { + duration: moment.Duration; + endedTimestamp: moment.Moment; + participants: string[]; + }; + channel_id: string; + /** + * NOTE: not fully typed + */ + codedLinks: { + code?: string; + type: string; + }[]; + colorString: unknown; + components: unknown[]; + content: string; + customRenderedContent: unknown; + editedTimestamp: Date; + embeds: Embed[]; + flags: number; + giftCodes: string[]; + id: string; + interaction: { + id: string; + name: string; + type: number; + user: User; + }[] | undefined; + interactionData: { + application_command: { + application_id: string; + default_member_permissions: unknown; + default_permission: boolean; + description: string; + dm_permission: unknown; + id: string; + name: string; + options: CommandOption[]; + permissions: unknown[]; + type: number; + version: string; + }; + attachments: MessageAttachment[]; + guild_id: string | undefined; + id: string; + name: string; + options: { + focused: unknown; + name: string; + type: number; + value: string; + }[]; + type: number; + version: string; + }[]; + interactionError: unknown[]; + isSearchHit: boolean; + loggingName: unknown; + mentionChannels: string[]; + mentionEveryone: boolean; + mentionRoles: string[]; + mentioned: boolean; + mentions: string[]; + messageReference: { + guild_id?: string; + channel_id: string; + message_id: string; + } | undefined; + nick: unknown; // probably a string + nonce: string | undefined; + pinned: boolean; + reactions: MessageReaction[]; + state: string; + stickerItems: { + format_type: number; + id: string; + name: string; + }[]; + stickers: unknown[]; + timestamp: moment.Moment; + tts: boolean; + type: number; + webhookId: string | undefined; + + /** + * Doesn't actually update the original message; it just returns a new message instance with the added reaction. + */ + addReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; + /** + * Searches each reaction and if the provided string has an index above -1 it'll return the reaction object. + */ + getReaction(name: string): MessageReaction; + /** + * Doesn't actually update the original message; it just returns the message instance without the reaction searched with the provided emoji object. + */ + removeReactionsForEmoji(emoji: ReactionEmoji): Message; + /** + * Doesn't actually update the original message; it just returns the message instance without the reaction. + */ + removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; + + getChannelId(): string; + hasFlag(flag: number): boolean; + isCommandType(): boolean; + isEdited(): boolean; + isSystemDM(): boolean; +} + +/** A smaller Message object found in FluxDispatcher and elsewhere. */ +export interface MessageJSON { + attachments: MessageAttachment[]; + author: UserJSON; + channel_id: string; + components: unknown[]; + content: string; + edited_timestamp: string; + embeds: EmbedJSON[]; + flags: number; + guild_id: string | undefined; + id: string; + loggingName: unknown; + member: { + avatar: string | undefined; + communication_disabled_until: string | undefined; + deaf: boolean; + hoisted_role: string | undefined; + is_pending: boolean; + joined_at: string; + mute: boolean; + nick: string | boolean; + pending: boolean; + premium_since: string | undefined; + roles: string[]; + } | undefined; + mention_everyone: boolean; + mention_roles: string[]; + mentions: UserJSON[]; + message_reference: { + guild_id?: string; + channel_id: string; + message_id: string; + } | undefined; + nonce: string | undefined; + pinned: boolean; + referenced_message: MessageJSON | undefined; + state: string; + timestamp: string; + tts: boolean; + type: number; +} + +export interface MessageAttachment { + filename: string; + id: string; + proxy_url: string; + size: number; + spoiler: boolean; + url: string; + content_type?: string; + width?: number; + height?: number; +} + +export interface ReactionEmoji { + id: string | undefined; + name: string; + animated: boolean; +} + +export interface MessageReaction { + count: number; + emoji: ReactionEmoji; + me: boolean; +} diff --git a/packages/discord-types/src/common/messages/index.d.ts b/packages/discord-types/src/common/messages/index.d.ts new file mode 100644 index 00000000..245e971e --- /dev/null +++ b/packages/discord-types/src/common/messages/index.d.ts @@ -0,0 +1,4 @@ +export * from "./Commands"; +export * from "./Message"; +export * from "./Embed"; +export * from "./Emoji"; diff --git a/src/webpack/common/types/components.d.ts b/packages/discord-types/src/components.d.ts similarity index 88% rename from src/webpack/common/types/components.d.ts rename to packages/discord-types/src/components.d.ts index 7a9e848b..8b2fd1cf 100644 --- a/src/webpack/common/types/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -1,25 +1,7 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react"; - -export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; +// copy(find(m => Array.isArray(m) && m.includes("heading-sm/normal")).map(JSON.stringify).join("|")) +export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-sm/extrabold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-md/extrabold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-lg/extrabold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/semibold" | "heading-xl/bold" | "heading-xl/extrabold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/semibold" | "heading-xxl/bold" | "heading-xxl/extrabold" | "eyebrow" | "heading-deprecated-12/normal" | "heading-deprecated-12/medium" | "heading-deprecated-12/semibold" | "heading-deprecated-12/bold" | "heading-deprecated-12/extrabold" | "redesign/heading-18/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "redesign/message-preview/normal" | "redesign/message-preview/medium" | "redesign/message-preview/semibold" | "redesign/message-preview/bold" | "redesign/channel-title/normal" | "redesign/channel-title/medium" | "redesign/channel-title/semibold" | "redesign/channel-title/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; @@ -536,3 +518,10 @@ export type Icon = ComponentType>; +export type ColorPicker = ComponentType<{ + color: number | null; + showEyeDropper?: boolean; + suggestedColors?: string[]; + label?: ReactNode; + onChange(value: number | null): void; +}>; diff --git a/packages/discord-types/src/flux.d.ts b/packages/discord-types/src/flux.d.ts new file mode 100644 index 00000000..bb5600dd --- /dev/null +++ b/packages/discord-types/src/flux.d.ts @@ -0,0 +1,30 @@ +import { FluxStore } from "./stores/FluxStore"; + +export class FluxEmitter { + constructor(); + + changeSentinel: number; + changedStores: Set; + 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; +} diff --git a/packages/discord-types/src/fluxEvents.d.ts b/packages/discord-types/src/fluxEvents.d.ts new file mode 100644 index 00000000..c210f4b9 --- /dev/null +++ b/packages/discord-types/src/fluxEvents.d.ts @@ -0,0 +1,22 @@ +/* +function makeFluxEventList() { + // prefill MESSAGE_CREATE so that typescript infers this is a String Set + // without explicitly typing so that this function is also valid javascript + const events = new Set(["MESSAGE_CREATE"]); + + const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; + for (const nodeId in nodes) { + for (const event in nodes[nodeId].actionHandler) { + events.add(event); + } + } + for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { + events.add(event); + } + + return Array.from(events, e => JSON.stringify(e)).sort().join("|"); +} +copy(makeFluxEventList()) +*/ + +export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_CONTRAST" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CROSSFADES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACKNOWLEDGE_CHANNEL_SAFETY_WARNING_TOOLTIP" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_AV_ERRORS_CHANGED" | "ACTIVE_BOGO_PROMOTION_FETCH" | "ACTIVE_BOGO_PROMOTION_FETCH_FAIL" | "ACTIVE_BOGO_PROMOTION_FETCH_SUCCESS" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_INVITE_MODAL_QUERY" | "ACTIVITY_INVITE_MODAL_SEND" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_LAYOUT_MODE_UPDATE" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_POPOUT_WINDOW_OPEN" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "ADYEN_CASH_APP_PAY_SUBMIT_SUCCESS" | "ADYEN_CREATE_CASH_APP_PAY_COMPONENT_SUCCESS" | "ADYEN_CREATE_CLIENT_SUCCESS" | "ADYEN_TEARDOWN_CLIENT" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "APEX_EXPERIMENT_CLEAR_SERVER_ASSIGNMENTS" | "APEX_EXPERIMENT_OVERRIDE_CLEAR" | "APEX_EXPERIMENT_OVERRIDE_CREATE" | "APEX_EXPERIMENT_OVERRIDE_DELETE" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_ASSETS_FETCH" | "APPLICATION_ASSETS_FETCH_SUCCESS" | "APPLICATION_ASSETS_UPDATE" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_EXECUTE_BAD_VERSION" | "APPLICATION_COMMAND_INDEX_FETCH_FAILURE" | "APPLICATION_COMMAND_INDEX_FETCH_REQUEST" | "APPLICATION_COMMAND_INDEX_FETCH_SUCCESS" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_DIRECTORY_FETCH_APPLICATION" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_FAILURE" | "APPLICATION_DIRECTORY_FETCH_APPLICATION_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_CATEGORIES_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_COLLECTIONS_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SEARCH" | "APPLICATION_DIRECTORY_FETCH_SEARCH_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SEARCH_SUCCESS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_FAILURE" | "APPLICATION_DIRECTORY_FETCH_SIMILAR_APPLICATIONS_SUCCESS" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCHING" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_FAILED" | "APPLICATION_STORE_DIRECTORY_LAYOUT_FETCH_SUCCESS" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLICATION_UPDATE" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_RESET" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_DM_OPEN" | "APP_ICON_EDITOR_RESET" | "APP_ICON_TRACK_IMPRESSION" | "APP_ICON_UPDATED" | "APP_LAUNCHER_ADD_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_DISMISS" | "APP_LAUNCHER_REMOVE_FAILED_APP_DM_LOAD" | "APP_LAUNCHER_SET_ACTIVE_COMMAND" | "APP_LAUNCHER_SHOW" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_FAILURE" | "APP_RECOMMENDATIONS_FETCH_RECOMMENDATIONS_SUCCESS" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ACTIVE_INPUT_PROFILE" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_BYPASS_SYSTEM_INPUT_PROCESSING" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_KRISP_MODEL_OVERRIDE" | "AUDIO_SET_KRISP_SUPPRESSION_LEVEL" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SELF_MUTE" | "AUDIO_SET_SIDECHAIN_COMPRESSION" | "AUDIO_SET_SIDECHAIN_COMPRESSION_STRENGTH" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_LOCAL_SOUNDBOARD_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_TARGET" | "AUDIT_LOG_FILTER_BY_USER" | "AUTHENTICATOR_CREATE" | "AUTHENTICATOR_DELETE" | "AUTHENTICATOR_UPDATE" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "AUTO_UPDATER_QUIT_AND_INSTALL" | "BACKGROUND_SYNC" | "BACKGROUND_SYNC_CHANNEL_MESSAGES" | "BASIC_GUILD_FETCH" | "BASIC_GUILD_FETCH_FAILURE" | "BASIC_GUILD_FETCH_SUCCESS" | "BILLING_CREATE_REFERRAL_SUCCESS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_IP_LOCATION_FAILURE" | "BILLING_IP_LOCATION_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_NITRO_AFFINITY_FETCHED" | "BILLING_NITRO_AFFINITY_FETCH_SUCCEEDED" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PREVIOUS_PREMIUM_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_REFERRALS_REMAINING_FETCH_FAIL" | "BILLING_REFERRALS_REMAINING_FETCH_START" | "BILLING_REFERRALS_REMAINING_FETCH_SUCCESS" | "BILLING_REFERRAL_RESOLVE_FAIL" | "BILLING_REFERRAL_RESOLVE_SUCCESS" | "BILLING_REFERRAL_TRIAL_OFFER_UPDATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_IP_LOCATION" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_FAILURE" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_START" | "BILLING_SUBSCRIPTION_REWARD_ELIGIBILITY_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_OFFER_FETCH_FAIL" | "BILLING_USER_OFFER_FETCH_START" | "BILLING_USER_OFFER_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_FAIL" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_FAIL" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_PICKER_ANIMATION_ADD" | "BURST_REACTION_PICKER_ANIMATION_CLEAR" | "CACHED_EMOJIS_LOADED" | "CACHED_STICKERS_LOADED" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CACHE_LOADED_LAZY_NO_CACHE" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CONNECT_MULTIPLE" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_FETCH_FAILED" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_MARK_SEEN" | "CHANGE_LOG_RESOLVED" | "CHANGE_LOG_SET_CONFIG" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MEMBER_COUNT_UPDATE" | "CHANNEL_MUTE_EXPIRED" | "CHANNEL_PERMISSIONS_DELETE_OVERWRITE_SUCCESS" | "CHANNEL_PERMISSIONS_PUT_OVERWRITE_SUCCESS" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_JUMP_TO_VOICE_CHANNEL_MESSAGE" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTCIPANTS_LIST_OPEN" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SAFETY_WARNING_FEEDBACK" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_STATUSES" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_TOGGLE_SUMMARIES_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECKOUT_RECOVERY_STATUS_FETCH" | "CHECKOUT_RECOVERY_STATUS_FETCH_FAILURE" | "CHECKOUT_RECOVERY_STATUS_FETCH_SUCCESS" | "CHECK_LAUNCHABLE_GAME" | "CLEAR_CACHES" | "CLEAR_CHANNEL_SAFETY_WARNINGS" | "CLEAR_CONSUMED_ENTITLEMENT" | "CLEAR_CONVERSATION_SUMMARIES" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_LAST_SESSION_VOICE_CHANNEL_ID" | "CLEAR_MENTIONS" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLEAR_THEME_OVERRIDE" | "CLEAR_VIDEO_STREAM_READY_TIMEOUT" | "CLICKER_GAME_ADD_POINTS" | "CLICKER_GAME_PURCHASE_ITEM" | "CLICKER_GAME_PURCHASE_ITEM_UPGRADE" | "CLICKER_GAME_REDEEM_PRIZE_FAIL" | "CLICKER_GAME_REDEEM_PRIZE_START" | "CLICKER_GAME_REDEEM_PRIZE_SUCCESS" | "CLICKER_GAME_RESET" | "CLICKER_GAME_SET_MUTED" | "CLICKER_GAME_SET_VOLUME" | "CLICKER_GAME_UNLOCK_ACHIEVEMENT" | "CLICKER_GAME_UPDATE_ITEM_METADATA" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIPS_ALLOW_VOICE_RECORDING_UPDATE" | "CLIPS_CLASSIFY_HARDWARE" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_CLEAR_NEW_CLIP_IDS" | "CLIPS_DELETE_CLIP" | "CLIPS_DISMISS_EDUCATION" | "CLIPS_INIT" | "CLIPS_INIT_FAILURE" | "CLIPS_LOAD_DIRECTORY_SUCCESS" | "CLIPS_RESTART" | "CLIPS_SAVE_ANIMATION_END" | "CLIPS_SAVE_CLIP" | "CLIPS_SAVE_CLIP_ERROR" | "CLIPS_SAVE_CLIP_PLACEHOLDER" | "CLIPS_SAVE_CLIP_PLACEHOLDER_ERROR" | "CLIPS_SAVE_CLIP_START" | "CLIPS_SETTINGS_UPDATE" | "CLIPS_SHOW_CALL_WARNING" | "CLIPS_UPDATE_METADATA" | "CLOSE_AGE_VERIFICATION_MODAL" | "CLOSE_SUSPENDED_USER" | "COLLECTIBLES_CATEGORIES_FETCH" | "COLLECTIBLES_CATEGORIES_FETCH_FAILURE" | "COLLECTIBLES_CATEGORIES_FETCH_SUCCESS" | "COLLECTIBLES_CLAIM" | "COLLECTIBLES_CLAIM_FAILURE" | "COLLECTIBLES_CLAIM_SUCCESS" | "COLLECTIBLES_MARKETING_FETCH" | "COLLECTIBLES_MARKETING_FETCH_SUCCESS" | "COLLECTIBLES_PRODUCT_DETAILS_OPEN" | "COLLECTIBLES_PRODUCT_FETCH" | "COLLECTIBLES_PRODUCT_FETCH_FAILURE" | "COLLECTIBLES_PRODUCT_FETCH_SUCCESS" | "COLLECTIBLES_PURCHASES_FETCH" | "COLLECTIBLES_PURCHASES_FETCH_FAILURE" | "COLLECTIBLES_PURCHASES_FETCH_SUCCESS" | "COLLECTIBLES_SET_SHOP_HOME_CONFIG_OVERRIDE" | "COLLECTIBLES_SHOP_CLOSE" | "COLLECTIBLES_SHOP_HOME_FETCH" | "COLLECTIBLES_SHOP_HOME_FETCH_FAILURE" | "COLLECTIBLES_SHOP_HOME_FETCH_SUCCESS" | "COLLECTIBLES_SHOP_OPEN" | "COLLECTIBLES_SKIP_NUM_CATEGORIES" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "COMPLETE_NEW_MEMBER_ACTION" | "CONNECTED_DEVICE_DONT_SWITCH" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SWITCH" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_STATE_UPDATE" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONSUMABLES_CLEAR_ERROR" | "CONSUMABLES_ENTITLEMENT_FETCH_COMPLETED" | "CONSUMABLES_ENTITLEMENT_FETCH_FAILED" | "CONSUMABLES_ENTITLEMENT_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_FAILED" | "CONSUMABLES_PRICE_FETCH_STARTED" | "CONSUMABLES_PRICE_FETCH_SUCCEEDED" | "CONTENT_INVENTORY_CLEAR_DELETE_HISTORY_ERROR" | "CONTENT_INVENTORY_CLEAR_FEED" | "CONTENT_INVENTORY_DEBUG_CLEAR_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_LOG_IMPRESSIONS" | "CONTENT_INVENTORY_DEBUG_TOGGLE_FAST_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DEBUG_TOGGLE_IMPRESSION_CAPPING" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_FAILURE" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_START" | "CONTENT_INVENTORY_DELETE_OUTBOX_ENTRY_SUCCESS" | "CONTENT_INVENTORY_FETCH_OUTBOX_FAILURE" | "CONTENT_INVENTORY_FETCH_OUTBOX_START" | "CONTENT_INVENTORY_FETCH_OUTBOX_SUCCESS" | "CONTENT_INVENTORY_FORCE_SHOW_GAME_SHARING" | "CONTENT_INVENTORY_INBOX_STALE" | "CONTENT_INVENTORY_MANUAL_REFRESH" | "CONTENT_INVENTORY_SET_FEED" | "CONTENT_INVENTORY_SET_FEED_STATE" | "CONTENT_INVENTORY_SET_FILTERS" | "CONTENT_INVENTORY_TOGGLE_FEED_HIDDEN" | "CONTENT_INVENTORY_TRACK_ITEM_IMPRESSIONS" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CONVERSATION_SUMMARY_UPDATE" | "CREATE_PENDING_REPLY" | "CREATE_PENDING_SCHEDULED_MESSAGE" | "CREATE_REFERRALS_SUCCESS" | "CREATE_SHALLOW_PENDING_REPLY" | "CREATOR_MONETIZATION_NAG_ACTIVATE_ELIGIBLITY_FETCH_SUCCESS" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_FAILURE" | "CREATOR_MONETIZATION_PRICE_TIERS_FETCH_SUCCESS" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "CUSTOM_ACTIVITY_LINK_FETCH_SUCCESS" | "DCF_DAILY_CAP_OVERRIDE" | "DCF_EVENT_LOGGED" | "DCF_HANDLE_DC_DISMISSED" | "DCF_HANDLE_DC_SHOWN" | "DCF_NEW_USER_MIN_AGE_REQUIRED_OVERRIDE" | "DCF_OVERRIDE_LAST_DC_DISMISSED" | "DCF_RESET" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DELETE_PENDING_SCHEDULED_MESSAGE" | "DELETE_SUMMARY" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_FAILURE" | "DETECTABLE_GAME_SUPPLEMENTAL_FETCH_SUCCESS" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_UPDATE_FILTER" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_DESIGN_TOGGLE_SET" | "DEV_TOOLS_DESIGN_TOGGLE_WEB_SET" | "DEV_TOOLS_DEV_SETTING_SET" | "DEV_TOOLS_FRIENDS_LIST_GIFT_INTENTS_SHOWN_RESET" | "DEV_TOOLS_FRIENDS_TAB_BADGE_COOLDOWN_RESET" | "DEV_TOOLS_GIFT_MESSAGE_COOLDOWN_RESET" | "DEV_TOOLS_SETTINGS_UPDATE" | "DEV_TOOLS_SET_FRIEND_ANNIVERSARY_COUNT" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISMISS_CHANNEL_SAFETY_WARNINGS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_MEDIA_POST_SHARE_PROMPT" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_FOCUSED_LAYOUT" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMBEDDED_ACTIVITY_SET_PANEL_MODE" | "EMBEDDED_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_UPDATE_POPOUT_WINDOW_LAYOUT" | "EMBEDDED_ACTIVITY_UPDATE_V2" | "EMOJI_AUTOSUGGESTION_UPDATE" | "EMOJI_DELETE" | "EMOJI_FETCH_FAILURE" | "EMOJI_FETCH_SUCCESS" | "EMOJI_INTERACTION_INITIATED" | "EMOJI_TRACK_USAGE" | "EMOJI_UPLOAD_START" | "EMOJI_UPLOAD_STOP" | "ENABLE_AUTOMATIC_ACK" | "ENTITLEMENTS_FETCH_FOR_USER_FAIL" | "ENTITLEMENTS_FETCH_FOR_USER_START" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "FAMILY_CENTER_FETCH_START" | "FAMILY_CENTER_HANDLE_TAB_SELECT" | "FAMILY_CENTER_INITIAL_LOAD" | "FAMILY_CENTER_LINKED_USERS_FETCH_SUCCESS" | "FAMILY_CENTER_LINK_CODE_FETCH_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_REMOVE_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_SUCCESS" | "FAMILY_CENTER_REQUEST_LINK_UPDATE_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_FETCH_SUCCESS" | "FAMILY_CENTER_TEEN_ACTIVITY_MORE_FETCH_SUCCESS" | "FEEDBACK_OVERRIDE_CLEAR" | "FEEDBACK_OVERRIDE_SET" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_CHAT_WALLPAPERS_FAILURE" | "FETCH_CHAT_WALLPAPERS_START" | "FETCH_CHAT_WALLPAPERS_SUCCESS" | "FETCH_GUILD_EVENT" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_FAILURE" | "FETCH_INTEGRATION_APPLICATION_IDS_FOR_MY_GUILDS_SUCCESS" | "FETCH_SCHEDULED_MESSAGES" | "FETCH_SCHEDULED_MESSAGES_FAILURE" | "FETCH_SCHEDULED_MESSAGES_SUCCESS" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_LIST_GIFT_INTENTS_SHOWN" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIENDS_TAB_BADGE_DISMISS" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_CONSOLE_SELECT_DEVICE" | "GAME_DETECTION_DEBUGGING_START" | "GAME_DETECTION_DEBUGGING_STOP" | "GAME_DETECTION_DEBUGGING_TICK" | "GAME_DETECTION_WATCH_CANDIDATE_GAMES_START" | "GAME_ICON_UPDATE" | "GAME_INVITE_CLEAR_UNSEEN" | "GAME_INVITE_CREATE" | "GAME_INVITE_DELETE" | "GAME_INVITE_DELETE_MANY" | "GAME_INVITE_UPDATE_STATUS" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GAME_PROFILE_OPEN" | "GAME_RELATIONSHIP_ADD" | "GAME_RELATIONSHIP_REMOVE" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIFT_INTENT_FLOW_PURCHASED_GIFT" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_CLEAR" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_COUNT_SUCCESS" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_FAILURE" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_LAYOUT_RESET" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_START" | "GLOBAL_DISCOVERY_SERVERS_SEARCH_SUCCESS" | "GUILD_ACK" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_SUCCESS" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLIED_BOOSTS_UPDATE" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_FAIL" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FOLDER_COLLAPSE" | "GUILD_FOLDER_CREATE_LOCAL" | "GUILD_FOLDER_DELETE_LOCAL" | "GUILD_FOLDER_EDIT_LOCAL" | "GUILD_GEO_RESTRICTED" | "GUILD_HOME_SETTINGS_FETCH_FAIL" | "GUILD_HOME_SETTINGS_FETCH_START" | "GUILD_HOME_SETTINGS_FETCH_SUCCESS" | "GUILD_HOME_SETTINGS_TOGGLE_ENABLED" | "GUILD_HOME_SETTINGS_UPDATE_FAIL" | "GUILD_HOME_SETTINGS_UPDATE_START" | "GUILD_HOME_SETTINGS_UPDATE_SUCCESS" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR_DECORATION" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PROFILE_EFFECT_ID" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_TAB" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_BY_ID_FETCH_SUCCESS" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_LOCAL_RING_START" | "GUILD_MEMBERS_CHUNK_BATCH" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_REMOVE_LOCAL" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE_BY_ID" | "GUILD_MUTE_EXPIRED" | "GUILD_NEW_MEMBER_ACTIONS_DELETE_SUCCESS" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_FAIL" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_START" | "GUILD_NEW_MEMBER_ACTIONS_FETCH_SUCCESS" | "GUILD_NEW_MEMBER_ACTION_UPDATE_SUCCESS" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POWERUPS_ACK_NOTIFICATION" | "GUILD_POWERUPS_RESET_NOTIFICATIONS" | "GUILD_POWERUP_CATALOG_FETCH_SUCCESS" | "GUILD_POWERUP_ENTITLEMENTS_CREATE" | "GUILD_POWERUP_ENTITLEMENTS_DELETE" | "GUILD_PRODUCTS_FETCH" | "GUILD_PRODUCTS_FETCH_FAILURE" | "GUILD_PRODUCTS_FETCH_SUCCESS" | "GUILD_PRODUCT_CREATE" | "GUILD_PRODUCT_DELETE" | "GUILD_PRODUCT_FETCH" | "GUILD_PRODUCT_FETCH_FAILURE" | "GUILD_PRODUCT_FETCH_SUCCESS" | "GUILD_PRODUCT_UPDATE" | "GUILD_PROFILE_FETCH" | "GUILD_PROFILE_FETCH_FAILURE" | "GUILD_PROFILE_FETCH_SUCCESS" | "GUILD_PROFILE_UPDATE" | "GUILD_PROFILE_UPDATE_FAILURE" | "GUILD_PROFILE_UPDATE_SUCCESS" | "GUILD_PROFILE_UPDATE_VISIBILITY" | "GUILD_PROFILE_UPDATE_VISIBILITY_FAILURE" | "GUILD_PROFILE_UPDATE_VISIBILITY_SUCCESS" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RESOURCE_CHANNEL_UPDATE_SUCCESS" | "GUILD_RING_START" | "GUILD_RING_STOP" | "GUILD_ROLE_CONNECTIONS_CONFIGURATIONS_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTIONS_MODAL_SHOW" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_TEMPLATES" | "GUILD_ROLE_SUBSCRIPTIONS_STASH_TEMPLATE_CHANNELS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTIONS_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE" | "GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_COUNTS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SEARCH_RECENT_MEMBERS" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_RESET" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_FAILED" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SUBMIT" | "GUILD_SETTINGS_DEFAULT_CHANNELS_TOGGLE" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_JOIN_RULES_APPLY_SET_PENDING_FORM_FIELDS" | "GUILD_SETTINGS_JOIN_RULES_INVITE_SET_PENDING_RULES" | "GUILD_SETTINGS_JOIN_RULES_SET_CONTENT_LEVEL" | "GUILD_SETTINGS_JOIN_RULES_SET_SELECTED_TYPE" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_BANS_BATCH" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_ADD_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_ADD_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DELETE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_DELETE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_DISMISS_RESOURCE_CHANNEL_SUGGESTION" | "GUILD_SETTINGS_ONBOARDING_EDUCATION_UPSELL_DISMISSED" | "GUILD_SETTINGS_ONBOARDING_HOME_SETTINGS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_EDIT" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_ERRORS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_FAILED" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SUBMIT" | "GUILD_SETTINGS_ONBOARDING_REORDER_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_REORDER_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_SET_MODE" | "GUILD_SETTINGS_ONBOARDING_STEP" | "GUILD_SETTINGS_ONBOARDING_UPDATE_NEW_MEMBER_ACTION" | "GUILD_SETTINGS_ONBOARDING_UPDATE_RESOURCE_CHANNEL" | "GUILD_SETTINGS_ONBOARDING_UPDATE_WELCOME_MESSAGE" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_PROFILE_UPDATE" | "GUILD_SETTINGS_ROLES_CLEAR_PERMISSIONS" | "GUILD_SETTINGS_ROLES_INIT" | "GUILD_SETTINGS_ROLES_ROLE_STYLE_UPDATE" | "GUILD_SETTINGS_ROLES_SAVE_FAIL" | "GUILD_SETTINGS_ROLES_SAVE_SUCCESS" | "GUILD_SETTINGS_ROLES_SORT_UPDATE" | "GUILD_SETTINGS_ROLES_SUBMITTING" | "GUILD_SETTINGS_ROLES_UPDATE_COLOR" | "GUILD_SETTINGS_ROLES_UPDATE_COLORS" | "GUILD_SETTINGS_ROLES_UPDATE_DESCRIPTION" | "GUILD_SETTINGS_ROLES_UPDATE_NAME" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSIONS" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSION_SET" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_CONNECTION_CONFIGURATIONS" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_ICON" | "GUILD_SETTINGS_ROLES_UPDATE_SETTINGS" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAFETY_PAGE" | "GUILD_SETTINGS_SAFETY_SET_SUBSECTION" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SETTINGS_VANITY_URL_ERROR" | "GUILD_SETTINGS_VANITY_URL_RESET" | "GUILD_SETTINGS_VANITY_URL_SET" | "GUILD_SETTINGS_WIDGET_UPDATE" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_SOUNDS_UPDATE" | "GUILD_SOUNDBOARD_SOUND_CREATE" | "GUILD_SOUNDBOARD_SOUND_DELETE" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_LOCALLY" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_SOUND_UPDATE" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_ADD_MEMBER_UPDATES" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_SUBSCRIPTIONS_REMOVE_MEMBER_UPDATES" | "GUILD_TAG_CHANGED_COACHMARK_SEEN" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_TOP_READ_CHANNELS_FETCH_SUCCESS" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UNLOCKED_POWERUPS_FETCH_SUCCESS" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HABITUAL_DND_CLEAR" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIGH_FIVE_COMPLETE" | "HIGH_FIVE_COMPLETE_CLEAR" | "HIGH_FIVE_QUEUE" | "HIGH_FIVE_REMOVE" | "HIGH_FIVE_SET_ENABLED" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INITIALIZE_MEMBER_SAFETY_STORE" | "INITIATE_AGE_VERIFICATION" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CLEAR" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_CREATE" | "INTEGRATION_DELETE" | "INTEGRATION_PERMISSION_SETTINGS_APPLICATION_PERMISSIONS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_CLEAR" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_SUCCESS" | "INTEGRATION_PERMISSION_SETTINGS_COMMAND_UPDATE" | "INTEGRATION_PERMISSION_SETTINGS_EDIT" | "INTEGRATION_PERMISSION_SETTINGS_INIT" | "INTEGRATION_PERMISSION_SETTINGS_RESET" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_COMMAND" | "INTEGRATION_SETTINGS_START_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_COMMAND" | "INTEGRATION_SETTINGS_STOP_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_INTEGRATION" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_IFRAME_MODAL_CLOSE" | "INTERACTION_IFRAME_MODAL_CREATE" | "INTERACTION_IFRAME_MODAL_KEY_CREATE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "INVITE_SUGGESTIONS_SEARCH" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAB_FEATURE_TOGGLE" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_SET_WIDGET_META" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_CHANNELS" | "LOAD_DATA_HARVEST_TYPE_FAILURE" | "LOAD_DATA_HARVEST_TYPE_START" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_ICYMI_HYDRATED" | "LOAD_INVITE_SUGGESTIONS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES_V2" | "LOAD_USER_AFFINITIES_V2_FAILURE" | "LOAD_USER_AFFINITIES_V2_SUCCESS" | "LOCAL_ACTIVITY_UPDATE" | "LOCAL_MESSAGES_LOADED" | "LOCAL_MESSAGE_CREATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_STEP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGIN_SUSPENDED_USER" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_CONNECTION_STATS" | "MEDIA_ENGINE_CONNECTION_STATS_HISTORY_RESET" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_ENABLE_HARDWARE_MUTE_NOTICE" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_GO_LIVE_SOURCE" | "MEDIA_ENGINE_SET_HARDWARE_ENCODING" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_USE_SYSTEM_SCREENSHARE_PICKER" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEDIA_ENGINE_VOICE_ACTIVITY_DETECTION_ERROR" | "MEDIA_PLAYBACK_POSITION_UPDATE" | "MEDIA_PLAYBACK_RATE_UPDATE" | "MEDIA_POST_EMBED_FETCH" | "MEDIA_POST_EMBED_FETCH_FAILURE" | "MEDIA_POST_EMBED_FETCH_SUCCESS" | "MEDIA_SESSION_JOINED" | "MEMBER_SAFETY_GUILD_MEMBER_SEARCH_SUCCESS" | "MEMBER_SAFETY_GUILD_MEMBER_UPDATE_BATCH" | "MEMBER_SAFETY_NEW_MEMBER_TIMESTAMP_REFRESH" | "MEMBER_SAFETY_PAGINATION_TOKEN_UPDATE" | "MEMBER_SAFETY_PAGINATION_UPDATE" | "MEMBER_SAFETY_SEARCH_STATE_UPDATE" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MESSAGE_ACK" | "MESSAGE_ACKED" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_EXPLICIT_CONTENT_FP_CREATE" | "MESSAGE_EXPLICIT_CONTENT_FP_SUBMIT" | "MESSAGE_EXPLICIT_CONTENT_SCAN_TIMEOUT" | "MESSAGE_GIFT_INTENT_SHOWN" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_NOTIFICATION_SHOWN" | "MESSAGE_PREVIEWS_LOADED" | "MESSAGE_PREVIEWS_LOCALLY_LOADED" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_MANY" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REMINDER_DUE" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REQUEST_ACK" | "MESSAGE_REQUEST_CLEAR_ACK" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_SUCCESS" | "MFA_SEEN_BACKUP_CODE_PROMPT" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MOBILE_NATIVE_UPDATE_CHECK_FINISHED" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MOD_VIEW_SEARCH_FINISH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NATIVE_APP_MODAL_OPENED" | "NATIVE_APP_MODAL_OPENING" | "NATIVE_APP_MODAL_OPEN_FAILED" | "NATIVE_SCREEN_SHARE_PICKER_CANCEL" | "NATIVE_SCREEN_SHARE_PICKER_ERROR" | "NATIVE_SCREEN_SHARE_PICKER_PRESENT" | "NATIVE_SCREEN_SHARE_PICKER_RELEASE" | "NATIVE_SCREEN_SHARE_PICKER_UPDATE" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_CLEAR_GUILD_MENTIONS" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_REFRESH" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CENTER_TAB_FOCUSED" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOTIFICATION_SETTINGS_UPDATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_FORCE_RENDER_MODE" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_MESSAGE_EVENT_ACTION" | "OVERLAY_NOTIFICATION_EVENT" | "OVERLAY_READY" | "OVERLAY_RELOAD" | "OVERLAY_RENDER_DEBUG_CLEAR_TRACKED_PIDS" | "OVERLAY_RENDER_DEBUG_MODE" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_ASSOCIATED_GAME" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_CLICK_ZONES" | "OVERLAY_SET_DISABLE_CLICKABLE_REGIONS" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_GAME_INVITE_NOTIFICATION" | "OVERLAY_SET_GPU_BOOST_REQUESTED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_INVITE_MESSAGE" | "OVERLAY_SET_LIMITED_INTERACTION_OVERRIDE" | "OVERLAY_SET_NOTIFICATION_DISABLED_SETTING" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_SHOW_KEYBIND_INDICATORS" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SOUNDBOARD_SOUNDS_FETCH_REQUEST" | "OVERLAY_START_SESSION" | "OVERLAY_SUCCESSFULLY_SHOWN" | "OVERLAY_UPDATE_OVERLAY_METHOD" | "OVERLAY_UPDATE_OVERLAY_STATE" | "OVERLAY_WIDGET_CHANGED" | "PASSIVE_UPDATE_V2" | "PASSWORDLESS_FAILURE" | "PASSWORDLESS_START" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_RESIZE" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_ADD_STYLESHEET" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "POTIONS_SET_CONFETTI_MODE" | "POTIONS_TRIGGER_MESSAGE_CONFETTI" | "PREMIUM_MARKETING_DATA_READY" | "PREMIUM_MARKETING_PREVIEW" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_SUBSCRIPTIONS_ADD" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_EFFECTS_SET_TRY_IT_OUT" | "PROXY_BLOCKED_REQUEST" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_FAILURE" | "PURCHASED_ITEMS_FESTIVITY_FETCH_WOW_MOMENT_MEDIA_SUCCESS" | "PURCHASED_ITEMS_FESTIVITY_IS_FETCHING_WOW_MOMENT_MEDIA" | "PURCHASED_ITEMS_FESTIVITY_SET_CAN_PLAY_WOW_MOMENT" | "PUSH_NOTIFICATION_CLICK" | "QUESTS_CLAIM_REWARD_BEGIN" | "QUESTS_CLAIM_REWARD_FAILURE" | "QUESTS_CLAIM_REWARD_SUCCESS" | "QUESTS_DELIVERY_OVERRIDE" | "QUESTS_DISMISS_CONTENT_BEGIN" | "QUESTS_DISMISS_CONTENT_FAILURE" | "QUESTS_DISMISS_CONTENT_SUCCESS" | "QUESTS_DISMISS_PROGRESS_TRACKING_FAILURE_NOTICE" | "QUESTS_ENROLL_BEGIN" | "QUESTS_ENROLL_FAILURE" | "QUESTS_ENROLL_SUCCESS" | "QUESTS_FETCH_CLAIMED_QUESTS_BEGIN" | "QUESTS_FETCH_CLAIMED_QUESTS_FAILURE" | "QUESTS_FETCH_CLAIMED_QUESTS_SUCCESS" | "QUESTS_FETCH_CURRENT_QUESTS_BEGIN" | "QUESTS_FETCH_CURRENT_QUESTS_FAILURE" | "QUESTS_FETCH_CURRENT_QUESTS_SUCCESS" | "QUESTS_FETCH_QUEST_TO_DELIVER_BEGIN" | "QUESTS_FETCH_QUEST_TO_DELIVER_FAILURE" | "QUESTS_FETCH_QUEST_TO_DELIVER_SUCCESS" | "QUESTS_FETCH_REWARD_CODE_BEGIN" | "QUESTS_FETCH_REWARD_CODE_FAILURE" | "QUESTS_FETCH_REWARD_CODE_SUCCESS" | "QUESTS_PREVIEW_UPDATE_SUCCESS" | "QUESTS_SELECT_TASK_PLATFORM" | "QUESTS_SEND_HEARTBEAT_FAILURE" | "QUESTS_SEND_HEARTBEAT_SUCCESS" | "QUESTS_UPDATE_OPTIMISTIC_PROGRESS" | "QUESTS_USER_COMPLETION_UPDATE" | "QUESTS_USER_STATUS_UPDATE" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECEIVE_CHANNEL_AFFINITIES" | "RECEIVE_CHANNEL_SUMMARIES" | "RECEIVE_CHANNEL_SUMMARIES_BULK" | "RECEIVE_CHANNEL_SUMMARY" | "RECENT_MENTION_DELETE" | "RECOMPUTE_READ_STATES" | "REFERRALS_FETCH_ELIGIBLE_USER_FAIL" | "REFERRALS_FETCH_ELIGIBLE_USER_START" | "REFERRALS_FETCH_ELIGIBLE_USER_SUCCESS" | "REGISTER" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_IGNORE_USER_SUCCESS" | "RELATIONSHIP_PENDING_INCOMING_REMOVED" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REPORT_AV_ERROR" | "REPORT_TO_MOD_REPORT_MESSAGE_SUCCESS" | "REQUEST_CHANNEL_AFFINITIES" | "REQUEST_CHANNEL_SUMMARIES" | "REQUEST_CHANNEL_SUMMARIES_BULK" | "REQUEST_CHANNEL_SUMMARY" | "REQUEST_FORUM_UNREADS" | "REQUEST_SOUNDBOARD_SOUNDS" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESET_PREVIEW_CLIENT_THEME" | "RESET_SOCKET" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_CLIENT_CONNECT" | "RTC_CONNECTION_CLIENT_DISCONNECT" | "RTC_CONNECTION_FLAGS" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_PLATFORM" | "RTC_CONNECTION_REMOTE_VIDEO_SINK_WANTS" | "RTC_CONNECTION_ROSTER_MAP_UPDATE" | "RTC_CONNECTION_SECURE_FRAMES_UPDATE" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_USERS_MERGED" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_DEBUG_SET_SIMULCAST_OVERRIDE" | "RTC_LATENCY_TEST_COMPLETE" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_DETECTION" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAFETY_HUB_APPEAL_CLOSE" | "SAFETY_HUB_APPEAL_OPEN" | "SAFETY_HUB_APPEAL_SIGNAL_CUSTOM_INPUT_CHANGE" | "SAFETY_HUB_APPEAL_SIGNAL_SELECT" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_CLOSE" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_MODAL_OPEN" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_START_POLL" | "SAFETY_HUB_AUTOMATED_UNDERAGE_APPEAL_SUBMIT_SUCCESS" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_CHECK_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_FETCH_CLASSIFICATION_FAILURE" | "SAFETY_HUB_FETCH_CLASSIFICATION_START" | "SAFETY_HUB_FETCH_CLASSIFICATION_SUCCESS" | "SAFETY_HUB_FETCH_FAILURE" | "SAFETY_HUB_FETCH_START" | "SAFETY_HUB_FETCH_SUCCESS" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_FAILURE" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_START" | "SAFETY_HUB_REQUEST_AUTOMATED_UNDERAGE_APPEAL_SUCCESS" | "SAFETY_HUB_REQUEST_REVIEW_FAILURE" | "SAFETY_HUB_REQUEST_REVIEW_START" | "SAFETY_HUB_REQUEST_REVIEW_SUCCESS" | "SAVED_MESSAGES_UPDATE" | "SAVED_MESSAGE_CREATE" | "SAVED_MESSAGE_DELETE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SCHEDULED_MESSAGES_CREATE_SUCCESS" | "SCHEDULED_MESSAGES_DELETE_FAILURE" | "SCHEDULED_MESSAGES_DELETE_START" | "SCHEDULED_MESSAGES_DELETE_SUCCESS" | "SEARCH_ADD_HISTORY" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MESSAGES_CLEAR_ALL" | "SEARCH_MESSAGES_FAILURE" | "SEARCH_MESSAGES_INDEXING" | "SEARCH_MESSAGES_START" | "SEARCH_MESSAGES_SUCCESS" | "SEARCH_RECENT_MESSAGES_CLEAR" | "SEARCH_REMOVE_HISTORY" | "SEARCH_RESULTS_QUERY_UPDATE" | "SEARCH_SCREEN_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_SET_SHOW_NO_RESULTS_ALT" | "SEARCH_START" | "SECURE_FRAMES_SETTINGS_UPDATE" | "SECURE_FRAMES_TRANSIENT_KEY_CREATE" | "SECURE_FRAMES_TRANSIENT_KEY_DELETE" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_ADD" | "SECURE_FRAMES_UPLOADED_KEY_VERSION_CLEAR" | "SECURE_FRAMES_USER_VERIFIED_KEYS_DELETE" | "SECURE_FRAMES_VERIFIED_KEY_CREATE" | "SECURE_FRAMES_VERIFIED_KEY_DELETE" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELECT_HOME_RESOURCE_CHANNEL" | "SELECT_NEW_MEMBER_ACTION_CHANNEL" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_CREATED_AT_OVERRIDE" | "SET_GUILD_FOLDER_EXPANDED" | "SET_GUILD_LEADERBOARD" | "SET_HIGHLIGHTED_SUMMARY" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_PREMIUM_TYPE_OVERRIDE" | "SET_PREVIOUS_GO_LIVE_SETTINGS" | "SET_RECENTLY_ACTIVE_COLLAPSED" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_RPC_NOTIFICATION_SETTINGS" | "SET_SELECTED_SUMMARY" | "SET_SOUNDPACK" | "SET_STREAM_APP_INTENT" | "SET_SUMMARY_FEEDBACK" | "SET_THEME_OVERRIDE" | "SET_TTS_SPEECH_RATE" | "SET_USER_LEADERBOARD_LAST_UPDATE_REQUESTED" | "SET_VAD_PERMISSION" | "SHARED_CANVAS_CLEAR_DRAWABLES" | "SHARED_CANVAS_DRAW_LINE_POINT" | "SHARED_CANVAS_SET_DRAW_MODE" | "SHARED_CANVAS_UPDATE_EMOJI_HOSE" | "SHARED_CANVAS_UPDATE_LINE_POINTS" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CLOSE_GUILD" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SIDEBAR_VIEW_GUILD" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_AWAIT_CONFIRMATION" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH" | "SKU_PURCHASE_PREVIEW_FETCH_FAILURE" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS" | "SOUNDBOARD_FETCH_DEFAULT_SOUNDS_SUCCESS" | "SOUNDBOARD_MUTE_JOIN_SOUND" | "SOUNDBOARD_SET_OVERLAY_ENABLED" | "SOUNDBOARD_SOUNDS_RECEIVED" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPELLCHECK_UNLEARN_WORD" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_NEW_TRACK" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "STAGE_MUSIC_MUTE" | "STAGE_MUSIC_PLAY" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_LISTINGS_FETCH_FAIL" | "STORE_LISTINGS_FETCH_START" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SURVEY_SEEN" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_EXPANDED_STATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOGGLE_OVERLAY_CANVAS" | "TOGGLE_TOPICS_BAR" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TRY_ACK" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPCOMING_GUILD_EVENT_NOTICE_HIDE" | "UPCOMING_GUILD_EVENT_NOTICE_SEEN" | "UPDATE_AVAILABLE" | "UPDATE_BACKGROUND_GRADIENT_PRESET" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CHANNEL_LIST_SUBTITLES" | "UPDATE_CHAT_WALLPAPER_FLAG_COMPLETE" | "UPDATE_CHAT_WALLPAPER_FLAG_START" | "UPDATE_CHAT_WALLPAPER_OVERRIDES" | "UPDATE_CLIENT_PREMIUM_TYPE" | "UPDATE_CONSENTS" | "UPDATE_DATA_HARVEST_TYPE" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_MANUALLY" | "UPDATE_MOBILE_PENDING_THEME_INDEX" | "UPDATE_NOT_AVAILABLE" | "UPDATE_STRANGER_STATUS" | "UPDATE_THEME_PREFERENCES" | "UPDATE_TOKEN" | "UPDATE_VISIBLE_MESSAGES" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_FILE_UPDATE" | "UPLOAD_ITEM_CANCEL_REQUEST" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLICATION_REMOVE" | "USER_APPLICATION_UPDATE" | "USER_APPLIED_BOOSTS_FETCH_START" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_REQUEST" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_CALLBACK" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_INTEGRATION_JOINING_ERROR" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_COACHMARK_CLEAR" | "USER_GUILD_JOIN_REQUEST_COACHMARK_SHOW" | "USER_GUILD_JOIN_REQUEST_COOLDOWN_FETCH" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_BROWSER_CHECKOUT_DONE" | "USER_PAYMENT_BROWSER_CHECKOUT_STARTED" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_EFFECTS_FETCH" | "USER_PROFILE_EFFECTS_FETCH_FAILURE" | "USER_PROFILE_EFFECTS_FETCH_SUCCESS" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_PIN_BADGES_ON_CLIENT" | "USER_PROFILE_SIDEBAR_TOGGLE_SECTION" | "USER_PROFILE_UPDATE_FAILURE" | "USER_PROFILE_UPDATE_START" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_RESET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_GLOBAL_NAME" | "USER_SETTINGS_ACCOUNT_SET_PENDING_LEGACY_USERNAME_DISABLED" | "USER_SETTINGS_ACCOUNT_SET_PENDING_NAMEPLATE" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_SINGLE_TRY_IT_OUT_COLLECTIBLES_ITEM" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PRESET" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_PROFILE_EFFECT_ID" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_RESET_PENDING_PRIMARY_GUILD_CHANGES" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SETTINGS_SET_PENDING_PRIMARY_GUILD_ID" | "USER_SOUNDBOARD_SET_VOLUME" | "USER_UPDATE" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIDEO_SIZE_UPDATE" | "VIDEO_STREAM_READY_TIMEOUT" | "VIEW_HISTORY_MARK_VIEW" | "VIRTUAL_CURRENCY_BALANCE_FETCH" | "VIRTUAL_CURRENCY_BALANCE_FETCH_FAIL" | "VIRTUAL_CURRENCY_BALANCE_FETCH_SUCCESS" | "VIRTUAL_CURRENCY_BALANCE_UPDATE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_CLOSE" | "VIRTUAL_CURRENCY_EARNED_ORBS_COACHMARK_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_OPEN" | "VIRTUAL_CURRENCY_ONBOARDING_MODAL_RESET" | "VIRTUAL_CURRENCY_REDEEM_FAIL" | "VIRTUAL_CURRENCY_REDEEM_START" | "VIRTUAL_CURRENCY_REDEEM_SUCCESS" | "VIRTUAL_CURRENCY_SET_BALANCE_PILL_OVERLAY" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_CHANNEL_STATUS_UPDATE" | "VOICE_FILTER_APPLIED" | "VOICE_FILTER_APPLY_FAILED" | "VOICE_FILTER_CATALOG_FETCH_FAILED" | "VOICE_FILTER_CATALOG_FETCH_SUCCESS" | "VOICE_FILTER_DEV_TOOLS_SET_UPDATE_TIME" | "VOICE_FILTER_DOWNLOAD_FAILED" | "VOICE_FILTER_DOWNLOAD_PROGRESS" | "VOICE_FILTER_DOWNLOAD_STARTED" | "VOICE_FILTER_FILE_READY" | "VOICE_FILTER_LAGGING" | "VOICE_FILTER_LOOPBACK_TOGGLE" | "VOICE_FILTER_NATIVE_MODULE_STATE_CHANGE" | "VOICE_FILTER_REQUEST_SWITCH" | "VOICE_FILTER_UPDATE_LIMITED_TIME_VOICES" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SETTINGS_CLEAR" | "WELCOME_SCREEN_SETTINGS_RESET" | "WELCOME_SCREEN_SETTINGS_UPDATE" | "WELCOME_SCREEN_SUBMIT" | "WELCOME_SCREEN_SUBMIT_FAILURE" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD" | "WINDOW_VISIBILITY_CHANGE" | "WRITE_CACHES"; diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts new file mode 100644 index 00000000..6d9356b4 --- /dev/null +++ b/packages/discord-types/src/index.d.ts @@ -0,0 +1,9 @@ +export * from "./common"; +export * from "./classes"; +export * from "./components"; +export * from "./flux"; +export * from "./fluxEvents"; +export * from "./menu"; +export * from "./stores"; +export * from "./utils"; +export * as Webpack from "../webpack"; diff --git a/src/webpack/common/types/menu.d.ts b/packages/discord-types/src/menu.d.ts similarity index 71% rename from src/webpack/common/types/menu.d.ts rename to packages/discord-types/src/menu.d.ts index 5ae9062c..0866e1c3 100644 --- a/src/webpack/common/types/menu.d.ts +++ b/packages/discord-types/src/menu.d.ts @@ -1,21 +1,3 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - import type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; type RC = ComponentType>>; @@ -73,7 +55,7 @@ export interface Menu { renderValue?(value: number): string, }>; MenuSearchControl: RC<{ - query: string + query: string; onChange(query: string): void; placeholder?: string; }>; diff --git a/packages/discord-types/src/stores/ChannelStore.d.ts b/packages/discord-types/src/stores/ChannelStore.d.ts new file mode 100644 index 00000000..1507ba5e --- /dev/null +++ b/packages/discord-types/src/stores/ChannelStore.d.ts @@ -0,0 +1,24 @@ +import { Channel, FluxStore } from ".."; + +export class ChannelStore extends FluxStore { + getChannel(channelId: string): Channel; + getBasicChannel(channelId: string): Channel | undefined; + hasChannel(channelId: string): boolean; + + getChannelIds(guildId?: string | null): string[]; + getMutableBasicGuildChannelsForGuild(guildId: string): Record; + getMutableGuildChannelsForGuild(guildId: string): Record; + getAllThreadsForGuild(guildId: string): Channel[]; + getAllThreadsForParent(channelId: string): Channel[]; + + getDMFromUserId(userId: string): string; + getDMChannelFromUserId(userId: string): Channel | undefined; + getDMUserIds(): string[]; + getMutableDMsByUserIds(): Record; + getMutablePrivateChannels(): Record; + getSortedPrivateChannels(): Channel[]; + + getGuildChannelsVersion(guildId: string): number; + getPrivateChannelsVersion(): number; + getInitialOverlayState(): Record; +} diff --git a/packages/discord-types/src/stores/DraftStore.d.ts b/packages/discord-types/src/stores/DraftStore.d.ts new file mode 100644 index 00000000..98b34cdf --- /dev/null +++ b/packages/discord-types/src/stores/DraftStore.d.ts @@ -0,0 +1,43 @@ +import { FluxStore } from ".."; + +export enum DraftType { + ChannelMessage = 0, + ThreadSettings = 1, + FirstThreadMessage = 2, + ApplicationLauncherCommand = 3, + Poll = 4, + SlashCommand = 5, + ForwardContextMessage = 6 +} + +export interface Draft { + timestamp: number; + draft: string; +} + +export interface ThreadSettingsDraft { + timestamp: number; + parentMessageId?: string; + name?: string; + isPrivate?: boolean; + parentChannelId?: string; + location?: string; +} + +export type ChannelDrafts = { + [DraftType.ThreadSettings]: ThreadSettingsDraft; +} & { + [key in Exclude]: Draft; +}; + +export type UserDrafts = Partial>; +export type DraftState = Partial>; + +export class DraftStore extends FluxStore { + getState(): DraftState; + getRecentlyEditedDrafts(type: DraftType): Array; + getDraft(channelId: string, type: DraftType): string; + + getThreadSettings(channelId: string): ThreadSettingsDraft | null | undefined; + getThreadDraftWithParentMessageId(parentMessageId: string): ThreadSettingsDraft | null | undefined; +} diff --git a/packages/discord-types/src/stores/EmojiStore.d.ts b/packages/discord-types/src/stores/EmojiStore.d.ts new file mode 100644 index 00000000..93161b2f --- /dev/null +++ b/packages/discord-types/src/stores/EmojiStore.d.ts @@ -0,0 +1,57 @@ +import { Channel, CustomEmoji, Emoji, FluxStore } from ".."; + +export class EmojiStore extends FluxStore { + getCustomEmojiById(id?: string | null): CustomEmoji | undefined; + getUsableCustomEmojiById(id?: string | null): CustomEmoji | undefined; + getGuilds(): Record; + getGuildEmoji(guildId?: string | null): CustomEmoji[]; + getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[]; + getTopEmoji(guildId?: string | null): CustomEmoji[]; + getTopEmojisMetadata(guildId?: string | null): { + emojiIds: string[]; + topEmojisTTL: number; + }; + hasPendingUsage(): boolean; + hasUsableEmojiInAnyGuild(): boolean; + searchWithoutFetchingLatest(data: any): any; + getSearchResultsOrder(...args: any[]): any; + getState(): { + pendingUsages: { key: string, timestamp: number; }[]; + }; + searchWithoutFetchingLatest(data: { + channel: Channel; + query: string; + count?: number; + intention: number; + includeExternalGuilds?: boolean; + matchComparator?(name: string): boolean; + }): Record<"locked" | "unlocked", Emoji[]>; + + getDisambiguatedEmojiContext(): { + backfillTopEmojis: Record; + customEmojis: Record; + emojisById: Record; + emojisByName: Record; + emoticonRegex: RegExp | null; + emoticonsByName: Record; + escapedEmoticonNames: string; + favoriteNamesAndIds?: any; + favorites?: any; + frequentlyUsed?: any; + groupedCustomEmojis: Record; + guildId?: string; + isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean; + newlyAddedEmoji: Record; + topEmojis?: any; + unicodeAliases: Record; + get favoriteEmojisWithoutFetchingLatest(): Emoji[]; + }; +} diff --git a/packages/discord-types/src/stores/FluxStore.d.ts b/packages/discord-types/src/stores/FluxStore.d.ts new file mode 100644 index 00000000..da55ac0b --- /dev/null +++ b/packages/discord-types/src/stores/FluxStore.d.ts @@ -0,0 +1,44 @@ +import { FluxDispatcher, FluxEvents } from ".."; + +type Callback = () => void; + +/* + For some reason, this causes type errors when you try to destructure it: + ```ts + interface FluxEvent { + type: FluxEvents; + [key: string]: any; + } + ``` + */ +export type FluxEvent = any; + +export type ActionHandler = (event: FluxEvent) => void; +export type ActionHandlers = Partial>; + +export class FluxStore { + constructor(dispatcher: FluxDispatcher, actionHandlers?: ActionHandlers); + + getName(): string; + + addChangeListener(callback: Callback): void; + /** Listener will be removed once the callback returns false. */ + addConditionalChangeListener(callback: () => boolean, preemptive?: boolean): void; + addReactChangeListener(callback: Callback): void; + removeChangeListener(callback: Callback): void; + removeReactChangeListener(callback: Callback): void; + + doEmitChanges(event: FluxEvent): void; + emitChange(): void; + + getDispatchToken(): string; + initialize(): void; + initializeIfNeeded(): void; + /** this is a setter */ + mustEmitChanges(actionHandler: ActionHandler | undefined): void; + registerActionHandlers(actionHandlers: ActionHandlers): void; + syncWith(stores: FluxStore[], callback: Callback, timeout?: number): void; + waitFor(...stores: FluxStore[]): void; + + static getAll(): FluxStore[]; +} diff --git a/packages/discord-types/src/stores/GuildMemberStore.d.ts b/packages/discord-types/src/stores/GuildMemberStore.d.ts new file mode 100644 index 00000000..9dec139a --- /dev/null +++ b/packages/discord-types/src/stores/GuildMemberStore.d.ts @@ -0,0 +1,27 @@ +import { FluxStore, GuildMember } from ".."; + +export class GuildMemberStore extends FluxStore { + /** @returns Format: [guildId-userId: Timestamp (string)] */ + getCommunicationDisabledUserMap(): Record; + getCommunicationDisabledVersion(): number; + + getMutableAllGuildsAndMembers(): Record>; + + getMember(guildId: string, userId: string): GuildMember | null; + getTrueMember(guildId: string, userId: string): GuildMember | null; + getMemberIds(guildId: string): string[]; + getMembers(guildId: string): GuildMember[]; + + getCachedSelfMember(guildId: string): GuildMember | null; + getSelfMember(guildId: string): GuildMember | null; + getSelfMemberJoinedAt(guildId: string): Date | null; + + getNick(guildId: string, userId: string): string | null; + getNicknameGuildsMapping(userId: string): Record; + getNicknames(userId: string): string[]; + + isMember(guildId: string, userId: string): boolean; + isMember(guildId: string, userId: string): boolean; + isGuestOrLurker(guildId: string, userId: string): boolean; + isCurrentUserGuest(guildId: string): boolean; +} diff --git a/packages/discord-types/src/stores/GuildRoleStore.d.ts b/packages/discord-types/src/stores/GuildRoleStore.d.ts new file mode 100644 index 00000000..bf0d4042 --- /dev/null +++ b/packages/discord-types/src/stores/GuildRoleStore.d.ts @@ -0,0 +1,7 @@ +import { FluxStore, Role } from ".."; + +export class GuildRoleStore extends FluxStore { + getRole(guildId: string, roleId: string): Role; + getRoles(guildId: string): Record; + getAllGuildRoles(): Record>; +} diff --git a/packages/discord-types/src/stores/GuildStore.d.ts b/packages/discord-types/src/stores/GuildStore.d.ts new file mode 100644 index 00000000..d1a3b9b3 --- /dev/null +++ b/packages/discord-types/src/stores/GuildStore.d.ts @@ -0,0 +1,8 @@ +import { Guild, FluxStore } from ".."; + +export class GuildStore extends FluxStore { + getGuild(guildId: string): Guild; + getGuildCount(): number; + getGuilds(): Record; + getGuildIds(): string[]; +} diff --git a/packages/discord-types/src/stores/MessageStore.d.ts b/packages/discord-types/src/stores/MessageStore.d.ts new file mode 100644 index 00000000..d4823fc8 --- /dev/null +++ b/packages/discord-types/src/stores/MessageStore.d.ts @@ -0,0 +1,13 @@ +import { MessageJSON, FluxStore, Message } from ".."; + +export class MessageStore extends FluxStore { + getMessage(channelId: string, messageId: string): Message; + /** @returns This return object is fucking huge; I'll type it later. */ + getMessages(channelId: string): unknown; + getRawMessages(channelId: string): Record; + hasCurrentUserSentMessage(channelId: string): boolean; + hasPresent(channelId: string): boolean; + isLoadingMessages(channelId: string): boolean; + jumpedMessageId(channelId: string): string | undefined; + whenReady(channelId: string, callback: () => void): void; +} diff --git a/packages/discord-types/src/stores/RelationshipStore.d.ts b/packages/discord-types/src/stores/RelationshipStore.d.ts new file mode 100644 index 00000000..5d1a08af --- /dev/null +++ b/packages/discord-types/src/stores/RelationshipStore.d.ts @@ -0,0 +1,21 @@ +import { FluxStore } from ".."; + +export class RelationshipStore extends FluxStore { + getFriendIDs(): string[]; + getIgnoredIDs(): string[]; + getBlockedIDs(): string[]; + + getPendingCount(): number; + getRelationshipCount(): number; + + /** Related to friend nicknames. */ + getNickname(userId: string): string; + /** @returns Enum value from constants.RelationshipTypes */ + getRelationshipType(userId: string): number; + isFriend(userId: string): boolean; + isBlocked(userId: string): boolean; + isIgnored(userId: string): boolean; + getSince(userId: string): string; + + getMutableRelationships(): Map; +} diff --git a/packages/discord-types/src/stores/SelectedChannelStore.d.ts b/packages/discord-types/src/stores/SelectedChannelStore.d.ts new file mode 100644 index 00000000..13ac98ac --- /dev/null +++ b/packages/discord-types/src/stores/SelectedChannelStore.d.ts @@ -0,0 +1,14 @@ +import { FluxStore } from ".."; + +export class SelectedChannelStore extends FluxStore { + getChannelId(guildId?: string | null): string; + getVoiceChannelId(): string | undefined; + getCurrentlySelectedChannelId(guildId?: string): string | undefined; + getMostRecentSelectedTextChannelId(guildId: string): string | undefined; + getLastSelectedChannelId(guildId?: string): string; + // yes this returns a string + getLastSelectedChannels(guildId?: string): string; + + /** If you follow an announcement channel, this will return whichever channel you chose as destination */ + getLastChannelFollowingDestination(): { guildId?: string; channelId?: string; } | undefined; +} diff --git a/packages/discord-types/src/stores/SelectedGuildStore.d.ts b/packages/discord-types/src/stores/SelectedGuildStore.d.ts new file mode 100644 index 00000000..0ee9c207 --- /dev/null +++ b/packages/discord-types/src/stores/SelectedGuildStore.d.ts @@ -0,0 +1,14 @@ +import { FluxStore } from ".."; + +export interface SelectedGuildState { + selectedGuildTimestampMillis: Record; + selectedGuildId: string | null; + lastSelectedGuildId: string | null; +} + +export class SelectedGuildStore extends FluxStore { + getGuildId(): string | null; + getLastSelectedGuildId(): string | null; + getLastSelectedTimestamp(guildId: string): number | null; + getState(): SelectedGuildState | undefined; +} diff --git a/packages/discord-types/src/stores/ThemeStore.d.ts b/packages/discord-types/src/stores/ThemeStore.d.ts new file mode 100644 index 00000000..2900f7f6 --- /dev/null +++ b/packages/discord-types/src/stores/ThemeStore.d.ts @@ -0,0 +1,18 @@ +import { FluxStore } from ".."; + +export type ThemePreference = "dark" | "light" | "unknown"; +export type SystemTheme = "dark" | "light"; +export type Theme = "light" | "dark" | "darker" | "midnight"; + +export interface ThemeState { + theme: Theme; + status: 0 | 1; + preferences: Record; +} +export class ThemeStore extends FluxStore { + get theme(): Theme; + get darkSidebar(): boolean; + get systemTheme(): SystemTheme; + themePreferenceForSystemTheme(preference: ThemePreference): Theme; + getState(): ThemeState; +} diff --git a/packages/discord-types/src/stores/UserProfileStore.d.ts b/packages/discord-types/src/stores/UserProfileStore.d.ts new file mode 100644 index 00000000..d9efc47b --- /dev/null +++ b/packages/discord-types/src/stores/UserProfileStore.d.ts @@ -0,0 +1,151 @@ +import { FluxStore, Guild, User, Application, ApplicationInstallParams } from ".."; +import { ApplicationIntegrationType } from "../../enums"; + +export interface MutualFriend { + /** + * the userid of the mutual friend + */ + key: string; + /** + * the status of the mutual friend + */ + status: "online" | "offline" | "idle" | "dnd"; + /** + * the user object of the mutual friend + */ + user: User; +} + +export interface MutualGuild { + /** + * the guild object of the mutual guild + */ + guild: Guild; + /** + * the user's nickname in the guild, if any + */ + nick: string | null; + +} + +export interface ProfileBadge { + id: string; + description: string; + icon: string; + link?: string; +} + +export interface ConnectedAccount { + type: "twitch" | "youtube" | "skype" | "steam" | "leagueoflegends" | "battlenet" | "bluesky" | "bungie" | "reddit" | "twitter" | "twitter_legacy" | "spotify" | "facebook" | "xbox" | "samsung" | "contacts" | "instagram" | "mastodon" | "soundcloud" | "github" | "playstation" | "playstation-stg" | "epicgames" | "riotgames" | "roblox" | "paypal" | "ebay" | "tiktok" | "crunchyroll" | "domain" | "amazon-music"; + /** + * underlying id of connected account + * eg. account uuid + */ + id: string; + /** + * display name of connected account + */ + name: string; + verified: boolean; + metadata?: Record; +} + +export interface ProfileApplication { + id: string; + customInstallUrl: string | undefined; + installParams: ApplicationInstallParams | undefined; + flags: number; + popularApplicationCommandIds?: string[]; + integrationTypesConfig: Record>; + primarySkuId: string | undefined; + storefront_available: boolean; +} + +export interface UserProfileBase extends Pick { + accentColor: number | null; + /** + * often empty for guild profiles, get the user profile for badges + */ + badges: ProfileBadge[]; + bio: string | undefined; + popoutAnimationParticleType: string | null; + profileEffectExpiresAt: number | Date | undefined; + profileEffectId: undefined | string; + /** + * often an empty string when not set + */ + pronouns: string | "" | undefined; + themeColors: [number, number] | undefined; + userId: string; +} + +export interface ApplicationRoleConnection { + application: Application; + application_metadata: Record; + metadata: Record; + platform_name: string; + platform_username: string; +} + +export interface UserProfile extends UserProfileBase, Pick { + /** If this is a bot user profile, this will be its application */ + application: ProfileApplication | null; + applicationRoleConnections: ApplicationRoleConnection[] | undefined; + connectedAccounts: ConnectedAccount[] | undefined; + fetchStartedAt: number; + fetchEndedAt: number; + legacyUsername: string | undefined; + premiumGuildSince: Date | null; + premiumSince: Date | null; +} + +export class UserProfileStore extends FluxStore { + /** + * @param userId the user ID of the profile being fetched. + * @param guildId the guild ID to of the profile being fetched. + * defaults to the internal symbol `NO GUILD ID` if nullish + * + * @returns true if the profile is being fetched, false otherwise. + */ + isFetchingProfile(userId: string, guildId?: string): boolean; + /** + * Check if mutual friends for {@link userId} are currently being fetched. + * + * @param userId the user ID of the mutual friends being fetched. + * + * @returns true if mutual friends are being fetched, false otherwise. + */ + isFetchingFriends(userId: string): boolean; + + get isSubmitting(): boolean; + + getUserProfile(userId: string): UserProfile | undefined; + + getGuildMemberProfile(userId: string, guildId: string | undefined): UserProfileBase | null; + /** + * Get the mutual friends of a user. + * + * @param userId the user ID of the user to get the mutual friends of. + * + * @returns an array of mutual friends, or undefined if the user has no mutual friends + */ + getMutualFriends(userId: string): MutualFriend[] | undefined; + /** + * Get the count of mutual friends for a user. + * + * @param userId the user ID of the user to get the mutual friends count of. + * + * @returns the count of mutual friends, or undefined if the user has no mutual friends + */ + getMutualFriendsCount(userId: string): number | undefined; + /** + * Get the mutual guilds of a user. + * + * @param userId the user ID of the user to get the mutual guilds of. + * + * @returns an array of mutual guilds, or undefined if the user has no mutual guilds + */ + getMutualGuilds(userId: string): MutualGuild[] | undefined; +} diff --git a/packages/discord-types/src/stores/UserStore.d.ts b/packages/discord-types/src/stores/UserStore.d.ts new file mode 100644 index 00000000..323a1df0 --- /dev/null +++ b/packages/discord-types/src/stores/UserStore.d.ts @@ -0,0 +1,10 @@ +import { FluxStore, User } from ".."; + +export class UserStore extends FluxStore { + filter(filter: (user: User) => boolean, sort?: boolean): Record; + findByTag(username: string, discriminator: string): User; + forEach(action: (user: User) => void): void; + getCurrentUser(): User; + getUser(userId: string): User; + getUsers(): Record; +} diff --git a/packages/discord-types/src/stores/WindowStore.d.ts b/packages/discord-types/src/stores/WindowStore.d.ts new file mode 100644 index 00000000..53a40e96 --- /dev/null +++ b/packages/discord-types/src/stores/WindowStore.d.ts @@ -0,0 +1,7 @@ +import { FluxStore } from ".."; + +export class WindowStore extends FluxStore { + isElementFullScreen(): boolean; + isFocused(): boolean; + windowSize(): Record<"width" | "height", number>; +} diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts new file mode 100644 index 00000000..23045832 --- /dev/null +++ b/packages/discord-types/src/stores/index.d.ts @@ -0,0 +1,33 @@ +// please keep in alphabetical order +export * from "./ChannelStore"; +export * from "./DraftStore"; +export * from "./EmojiStore"; +export * from "./FluxStore"; +export * from "./GuildMemberStore"; +export * from "./GuildRoleStore"; +export * from "./GuildStore"; +export * from "./MessageStore"; +export * from "./RelationshipStore"; +export * from "./SelectedChannelStore"; +export * from "./SelectedGuildStore"; +export * from "./ThemeStore"; +export * from "./UserProfileStore"; +export * from "./UserStore"; +export * from "./WindowStore"; + +/** + * React hook that returns stateful data for one or more stores + * You might need a custom comparator (4th argument) if your store data is an object + * @param stores The stores to listen to + * @param mapper A function that returns the data you need + * @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook + * @param isEqual A custom comparator for the data returned by mapper + * + * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); + */ +export type useStateFromStores = ( + stores: any[], + mapper: () => T, + dependencies?: any, + isEqual?: (old: T, newer: T) => boolean +) => T; diff --git a/src/webpack/common/types/utils.d.ts b/packages/discord-types/src/utils.d.ts similarity index 92% rename from src/webpack/common/types/utils.d.ts rename to packages/discord-types/src/utils.d.ts index cfea5d76..77b6f88b 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -1,22 +1,4 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { Channel, Guild, GuildMember, Message, User } from "discord-types/general"; +import { Channel, Guild, GuildMember, Message, User } from "."; import type { ReactNode } from "react"; import { LiteralUnion } from "type-fest"; @@ -335,3 +317,19 @@ export interface DateUtils { dateFormat(date: Date, format: string): string; diffAsUnits(start: Date, end: Date, stopAtOneSecond?: boolean): Record<"days" | "hours" | "minutes" | "seconds", number>; } + +export interface CommandOptions { + type: number; + name: string; + description: string; + required?: boolean; + choices?: { + name: string; + values: string | number; + }[]; + options?: CommandOptions[]; + channel_types?: number[]; + min_value?: number; + max_value?: number; + autocomplete?: boolean; +} diff --git a/src/webpack/wreq.d.ts b/packages/discord-types/webpack/index.d.ts similarity index 90% rename from src/webpack/wreq.d.ts rename to packages/discord-types/webpack/index.d.ts index 2b356f9d..4a0011e8 100644 --- a/src/webpack/wreq.d.ts +++ b/packages/discord-types/webpack/index.d.ts @@ -1,11 +1,9 @@ /* - * Vencord, a Discord client mod + * @vencord/discord-types * Copyright (c) 2024 Vendicated, Nuckyz and contributors - * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-License-Identifier: LGPL-3.0-or-later */ -import { SYM_ORIGINAL_FACTORY, SYM_PATCHED_BY, SYM_PATCHED_SOURCE } from "./patchWebpack"; - export type ModuleExports = any; export type Module = { @@ -215,24 +213,3 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & { /** rspack unique id */ ruid: string; }; - -// Utility section for Vencord - -export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial> & { - /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ - m: Record; -}; - -/** exports can be anything, however initially it is always an empty object */ -export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & { - [SYM_PATCHED_SOURCE]?: string; - [SYM_PATCHED_BY]?: Set; -}; - -export type PatchedModuleFactory = AnyModuleFactory & { - [SYM_ORIGINAL_FACTORY]: AnyModuleFactory; - [SYM_PATCHED_SOURCE]?: string; - [SYM_PATCHED_BY]?: Set; -}; - -export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory; diff --git a/packages/vencord-types/package.json b/packages/vencord-types/package.json index b3bbe315..64586919 100644 --- a/packages/vencord-types/package.json +++ b/packages/vencord-types/package.json @@ -21,7 +21,6 @@ "@types/node": "^22.13.4", "@types/react": "18.3.1", "@types/react-dom": "18.3.1", - "discord-types": "^1.3.26", "standalone-electron-types": "^34.2.0", "type-fest": "^4.35.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e493972..cde57a1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,12 +65,12 @@ importers: '@types/yazl': specifier: ^2.4.5 version: 2.4.6 + '@vencord/discord-types': + specifier: link:packages/discord-types + version: link:packages/discord-types diff: specifier: ^7.0.0 version: 7.0.0 - discord-types: - specifier: ^1.3.26 - version: 1.3.26 esbuild: specifier: ^0.25.1 version: 0.25.1 @@ -141,6 +141,18 @@ importers: specifier: ^0.3.5 version: 0.3.5 + packages/discord-types: + dependencies: + '@types/react': + specifier: ^19.0.10 + version: 19.0.12 + moment: + specifier: ^2.22.2 + version: 2.30.1 + type-fest: + specifier: ^4.41.0 + version: 4.41.0 + packages/vencord-types: dependencies: '@types/lodash': @@ -155,9 +167,6 @@ importers: '@types/react-dom': specifier: 18.3.1 version: 18.3.1 - discord-types: - specifier: ^1.3.26 - version: 1.3.26 standalone-electron-types: specifier: ^34.2.0 version: 34.2.0 @@ -522,9 +531,6 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react@17.0.2': - resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==} - '@types/react@18.3.1': resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} @@ -956,9 +962,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - discord-types@1.3.26: - resolution: {integrity: sha512-ToG51AOCH+JTQf7b+8vuYQe5Iqwz7nZ7StpECAZ/VZcI1ZhQk13pvt9KkRTfRv1xNvwJ2qib4e3+RifQlo8VPQ==} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -2328,6 +2331,10 @@ packages: resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==} engines: {node: '>=16'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2764,11 +2771,6 @@ snapshots: dependencies: '@types/react': 19.0.12 - '@types/react@17.0.2': - dependencies: - '@types/prop-types': 15.7.14 - csstype: 3.1.3 - '@types/react@18.3.1': dependencies: '@types/prop-types': 15.7.14 @@ -3255,11 +3257,6 @@ snapshots: dependencies: path-type: 4.0.0 - discord-types@1.3.26: - dependencies: - '@types/react': 17.0.2 - moment: 2.30.1 - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4923,6 +4920,8 @@ snapshots: type-fest@4.38.0: {} + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 7d21cd24..db0a1ce0 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -31,6 +31,7 @@ const defines = stringifyValues({ IS_UPDATER_DISABLED, IS_WEB: false, IS_EXTENSION: false, + IS_USERSCRIPT: false, VERSION, BUILD_TIMESTAMP }); @@ -50,7 +51,7 @@ const nodeCommonOpts = { format: "cjs", platform: "node", target: ["esnext"], - // @ts-ignore this is never undefined + // @ts-expect-error this is never undefined external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external] }; diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index 824194cf..b22df8ab 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -43,6 +43,7 @@ const commonOptions = { define: stringifyValues({ IS_WEB: true, IS_EXTENSION: false, + IS_USERSCRIPT: false, IS_STANDALONE: true, IS_DEV, IS_REPORTER, @@ -98,6 +99,7 @@ const buildConfigs = [ inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], define: { ...commonOptions.define, + IS_USERSCRIPT: "true", window: "unsafeWindow", }, outfile: "dist/Vencord.user.js", diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 5c1732aa..516f6a1b 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -363,6 +363,6 @@ export const commonRendererPlugins = [ banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), - // @ts-ignore this is never undefined + // @ts-expect-error this is never undefined ...commonOpts.plugins ]; diff --git a/src/Vencord.ts b/src/Vencord.ts index 48ecce97..f18c7347 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -134,7 +134,11 @@ async function init() { if (!IS_WEB && !IS_UPDATER_DISABLED) { runUpdateCheck(); - setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes + + // this tends to get really annoying, so only do this if the user has auto-update without notification enabled + if (Settings.autoUpdate && !Settings.autoUpdateNotification) { + setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes + } } if (IS_DEV) { diff --git a/src/VencordNative.ts b/src/VencordNative.ts index 3bed5a59..048b30c7 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -5,6 +5,7 @@ */ import type { Settings } from "@api/Settings"; +import { CspRequestResult } from "@main/csp/manager"; import { PluginIpcMappings } from "@main/ipcPlugins"; import type { UserThemeHeader } from "@main/themes"; import { IpcEvents } from "@shared/IpcEvents"; @@ -33,10 +34,11 @@ export default { themes: { uploadTheme: (fileName: string, fileData: string) => invoke(IpcEvents.UPLOAD_THEME, fileName, fileData), deleteTheme: (fileName: string) => invoke(IpcEvents.DELETE_THEME, fileName), - getThemesDir: () => invoke(IpcEvents.GET_THEMES_DIR), getThemesList: () => invoke>(IpcEvents.GET_THEMES_LIST), getThemeData: (fileName: string) => invoke(IpcEvents.GET_THEME_DATA, fileName), getSystemValues: () => invoke>(IpcEvents.GET_THEME_SYSTEM_VALUES), + + openFolder: () => invoke(IpcEvents.OPEN_THEMES_FOLDER), }, updater: { @@ -49,7 +51,8 @@ export default { settings: { get: () => sendSync(IpcEvents.GET_SETTINGS), set: (settings: Settings, pathToNotify?: string) => invoke(IpcEvents.SET_SETTINGS, settings, pathToNotify), - getSettingsDir: () => invoke(IpcEvents.GET_SETTINGS_DIR), + + openFolder: () => invoke(IpcEvents.OPEN_SETTINGS_FOLDER), }, quickCss: { @@ -73,5 +76,17 @@ export default { openExternal: (url: string) => invoke(IpcEvents.OPEN_EXTERNAL, url) }, + csp: { + /** + * Note: Only supports full explicit matches, not wildcards. + * + * If `*.example.com` is allowed, `isDomainAllowed("https://sub.example.com")` will return false. + */ + isDomainAllowed: (url: string, directives: string[]) => invoke(IpcEvents.CSP_IS_DOMAIN_ALLOWED, url, directives), + removeOverride: (url: string) => invoke(IpcEvents.CSP_REMOVE_OVERRIDE, url), + requestAddOverride: (url: string, directives: string[], callerName: string) => + invoke(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName), + }, + pluginHelpers: PluginHelpers }; diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 6f4285ff..1cacd06b 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -8,9 +8,9 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; +import { Channel } from "@vencord/discord-types"; import { waitFor } from "@webpack"; import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index ac1dafc9..8ec23135 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -17,13 +17,11 @@ */ import { mergeDefaults } from "@utils/mergeDefaults"; +import { CommandArgument, Message } from "@vencord/discord-types"; import { findByCodeLazy } from "@webpack"; import { MessageActions, SnowflakeUtils } from "@webpack/common"; -import { Message } from "discord-types/general"; import type { PartialDeep } from "type-fest"; -import { Argument } from "./types"; - const createBotMessage = findByCodeLazy('username:"Clyde"'); export function generateId() { @@ -51,8 +49,8 @@ export function sendBotMessage(channelId: string, message: PartialDeep) * @param fallbackValue Fallback value in case this option wasn't passed * @returns Value */ -export function findOption(args: Argument[], name: string): T & {} | undefined; -export function findOption(args: Argument[], name: string, fallbackValue: T): T & {}; -export function findOption(args: Argument[], name: string, fallbackValue?: any) { +export function findOption(args: CommandArgument[], name: string): T & {} | undefined; +export function findOption(args: CommandArgument[], name: string, fallbackValue: T): T & {}; +export function findOption(args: CommandArgument[], name: string, fallbackValue?: any) { return (args.find(a => a.name === name)?.value ?? fallbackValue) as any; } diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index af6a6fdf..231b3daf 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -18,38 +18,39 @@ import { Logger } from "@utils/Logger"; import { makeCodeblock } from "@utils/text"; +import { CommandArgument, CommandContext, CommandOption } from "@vencord/discord-types"; import { sendBotMessage } from "./commandHelpers"; -import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, VencordCommand } from "./types"; export * from "./commandHelpers"; export * from "./types"; -export let BUILT_IN: Command[]; -export const commands = {} as Record; +export let BUILT_IN: VencordCommand[]; +export const commands = {} as Record; // hack for plugins being evaluated before we can grab these from webpack -const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option; -const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option; +const OptPlaceholder = Symbol("OptionalMessageOption") as any as CommandOption; +const ReqPlaceholder = Symbol("RequiredMessageOption") as any as CommandOption; /** * Optional message option named "message" you can use in commands. * Used in "tableflip" or "shrug" * @see {@link RequiredMessageOption} */ -export let OptionalMessageOption: Option = OptPlaceholder; +export let OptionalMessageOption: CommandOption = OptPlaceholder; /** * Required message option named "message" you can use in commands. * Used in "me" * @see {@link OptionalMessageOption} */ -export let RequiredMessageOption: Option = ReqPlaceholder; +export let RequiredMessageOption: CommandOption = ReqPlaceholder; // Discord's command list has random gaps for some reason, which can cause issues while rendering the commands // Add this offset to every added command to keep them unique let commandIdOffset: number; -export const _init = function (cmds: Command[]) { +export const _init = function (cmds: VencordCommand[]) { try { BUILT_IN = cmds; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; @@ -61,7 +62,7 @@ export const _init = function (cmds: Command[]) { return cmds; } as never; -export const _handleCommand = function (cmd: Command, args: Argument[], ctx: CommandContext) { +export const _handleCommand = function (cmd: VencordCommand, args: CommandArgument[], ctx: CommandContext) { if (!cmd.isVencordCommand) return cmd.execute(args, ctx); @@ -92,7 +93,7 @@ export const _handleCommand = function (cmd: Command, args: Argument[], ctx: Com * Prepare a Command Option for Discord by filling missing fields * @param opt */ -export function prepareOption(opt: O): O { +export function prepareOption(opt: O): O { opt.displayName ||= opt.name; opt.displayDescription ||= opt.description; opt.options?.forEach((opt, i, opts) => { @@ -109,7 +110,7 @@ export function prepareOption(opt: O): O { // Yes, Discord registers individual commands for each subcommand // TODO: This probably doesn't support nested subcommands. If that is ever needed, // investigate -function registerSubCommands(cmd: Command, plugin: string) { +function registerSubCommands(cmd: VencordCommand, plugin: string) { cmd.options?.forEach(o => { if (o.type !== ApplicationCommandOptionType.SUB_COMMAND) throw new Error("When specifying sub-command options, all options must be sub-commands."); @@ -132,7 +133,7 @@ function registerSubCommands(cmd: Command, plugin: string) { }); } -export function registerCommand(command: C, plugin: string) { +export function registerCommand(command: C, plugin: string) { if (!BUILT_IN) { console.warn( "[CommandsAPI]", diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts index 70b73775..c5ecb35e 100644 --- a/src/api/Commands/types.ts +++ b/src/api/Commands/types.ts @@ -1,106 +1,12 @@ /* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ -import { Channel, Guild } from "discord-types/general"; -import { Promisable } from "type-fest"; +import { Command } from "@vencord/discord-types"; +export { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@vencord/discord-types/enums"; -export interface CommandContext { - channel: Channel; - guild?: Guild; -} - -export const enum ApplicationCommandOptionType { - SUB_COMMAND = 1, - SUB_COMMAND_GROUP = 2, - STRING = 3, - INTEGER = 4, - BOOLEAN = 5, - USER = 6, - CHANNEL = 7, - ROLE = 8, - MENTIONABLE = 9, - NUMBER = 10, - ATTACHMENT = 11, -} - -export const enum ApplicationCommandInputType { - BUILT_IN = 0, - BUILT_IN_TEXT = 1, - BUILT_IN_INTEGRATION = 2, - BOT = 3, - PLACEHOLDER = 4, -} - -export interface Option { - name: string; - displayName?: string; - type: ApplicationCommandOptionType; - description: string; - displayDescription?: string; - required?: boolean; - options?: Option[]; - choices?: Array; -} - -export interface ChoicesOption { - label: string; - value: string; - name: string; - displayName?: string; -} - -export const enum ApplicationCommandType { - CHAT_INPUT = 1, - USER = 2, - MESSAGE = 3, -} - -export interface CommandReturnValue { - content: string; - /** TODO: implement */ - cancel?: boolean; -} - -export interface Argument { - type: ApplicationCommandOptionType; - name: string; - value: string; - focused: undefined; - options: Argument[]; -} - -export interface Command { - id?: string; - applicationId?: string; - type?: ApplicationCommandType; - inputType?: ApplicationCommandInputType; - plugin?: string; +export interface VencordCommand extends Command { isVencordCommand?: boolean; - - name: string; - untranslatedName?: string; - displayName?: string; - description: string; - untranslatedDescription?: string; - displayDescription?: string; - - options?: Option[]; - predicate?(ctx: CommandContext): boolean; - - execute(args: Argument[], ctx: CommandContext): Promisable; } diff --git a/src/api/DataStore/index.ts b/src/api/DataStore/index.ts index 47ae39db..80ebb065 100644 --- a/src/api/DataStore/index.ts +++ b/src/api/DataStore/index.ts @@ -22,9 +22,9 @@ export function promisifyRequest( request: IDBRequest | IDBTransaction, ): Promise { return new Promise((resolve, reject) => { - // @ts-ignore - file size hacks + // @ts-expect-error - file size hacks request.oncomplete = request.onsuccess = () => resolve(request.result); - // @ts-ignore - file size hacks + // @ts-expect-error - file size hacks request.onabort = request.onerror = () => reject(request.error); }); } diff --git a/src/api/MemberListDecorators.tsx b/src/api/MemberListDecorators.tsx index ada60776..2367e4cf 100644 --- a/src/api/MemberListDecorators.tsx +++ b/src/api/MemberListDecorators.tsx @@ -17,7 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { Channel, User } from "discord-types/general/index.js"; +import { Channel, User } from "@vencord/discord-types"; import { JSX } from "react"; interface DecoratorProps { diff --git a/src/api/MessageAccessories.tsx b/src/api/MessageAccessories.tsx index 71664e93..d2bc081e 100644 --- a/src/api/MessageAccessories.tsx +++ b/src/api/MessageAccessories.tsx @@ -48,7 +48,7 @@ export function _modifyAccessories( ) { for (const [key, accessory] of accessories.entries()) { const res = ( - + ); diff --git a/src/api/MessageDecorations.tsx b/src/api/MessageDecorations.tsx index 1b94c18d..8cf492c7 100644 --- a/src/api/MessageDecorations.tsx +++ b/src/api/MessageDecorations.tsx @@ -17,7 +17,7 @@ */ import ErrorBoundary from "@components/ErrorBoundary"; -import { Channel, Message } from "discord-types/general/index.js"; +import { Channel, Message } from "@vencord/discord-types"; import { JSX } from "react"; export interface MessageDecorationProps { diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 1b55ff34..8b1d9e78 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -17,9 +17,8 @@ */ import { Logger } from "@utils/Logger"; +import type { Channel, CustomEmoji, Message } from "@vencord/discord-types"; import { MessageStore } from "@webpack/common"; -import { CustomEmoji } from "@webpack/types"; -import type { Channel, Message } from "discord-types/general"; import type { Promisable } from "type-fest"; const MessageEventsLogger = new Logger("MessageEvents", "#e5c890"); diff --git a/src/api/MessagePopover.tsx b/src/api/MessagePopover.tsx index 71787954..c7b2a090 100644 --- a/src/api/MessagePopover.tsx +++ b/src/api/MessagePopover.tsx @@ -18,7 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; -import { Channel, Message } from "discord-types/general"; +import { Channel, Message } from "@vencord/discord-types"; import type { ComponentType, MouseEventHandler } from "react"; const logger = new Logger("MessagePopover"); diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts index 284a2088..7c4b3d5c 100644 --- a/src/api/MessageUpdater.ts +++ b/src/api/MessageUpdater.ts @@ -4,9 +4,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { FluxStore, Message } from "@vencord/discord-types"; import { MessageCache, MessageStore } from "@webpack/common"; -import { FluxStore } from "@webpack/types"; -import { Message } from "discord-types/general"; /** * Update and re-render a message diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index ad5c9cbc..10d3c0cf 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -3,8 +3,8 @@ all: unset; display: flex; flex-direction: column; - color: var(--text-normal); - background-color: var(--background-secondary-alt); + color: var(--text-default); + background-color: var(--background-base-lower-alt); border-radius: 6px; overflow: hidden; cursor: pointer; @@ -12,7 +12,7 @@ } .visual-refresh .vc-notification-root { - background-color: var(--bg-overlay-floating, var(--background-base-low)); + background-color: var(--background-base-low); } .vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) { diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 08d2f8ca..5c8965bf 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -268,7 +268,7 @@ type ResolveUseSettings = { [Key in keyof T]: Key extends string ? T[Key] extends Record - // @ts-ignore "Type instantiation is excessively deep and possibly infinite" + // @ts-expect-error "Type instantiation is excessively deep and possibly infinite" ? UseSettings extends string ? `${Key}.${UseSettings}` : never : Key : never; diff --git a/src/components/DonateButton.tsx b/src/components/DonateButton.tsx index ee2f3ed3..92af9831 100644 --- a/src/components/DonateButton.tsx +++ b/src/components/DonateButton.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see . */ +import { ButtonProps } from "@vencord/discord-types"; import { Button } from "@webpack/common"; -import { ButtonProps } from "@webpack/types"; import { Heart } from "./Heart"; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 0ca20440..7058b5fd 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -75,10 +75,15 @@ const ErrorBoundary = LazyComponent(() => { logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack); } + get isNoop() { + if (IS_DEV) return false; + return this.props.noop; + } + render() { if (this.state.error === NO_ERROR) return this.props.children; - if (this.props.noop) return null; + if (this.isNoop) return null; if (this.props.fallback) return ( diff --git a/src/components/ErrorCard.css b/src/components/ErrorCard.css index 5146aa03..2eea2f06 100644 --- a/src/components/ErrorCard.css +++ b/src/components/ErrorCard.css @@ -3,5 +3,9 @@ background-color: #e7828430; border: 1px solid #e78284; border-radius: 5px; - color: var(--text-normal, white); + color: var(--text-default, white); + + & a:hover { + text-decoration: underline; + } } diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 0f4eb07d..2eb7ab00 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -28,6 +28,9 @@ export function Link(props: React.PropsWithChildren) { props.style.pointerEvents = "none"; props["aria-disabled"] = true; } + + props.rel ??= "noreferrer"; + return ( {props.children} diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx index fe0e987c..378ee34c 100644 --- a/src/components/PluginSettings/ContributorModal.tsx +++ b/src/components/PluginSettings/ContributorModal.tsx @@ -14,8 +14,8 @@ import { DevsById } from "@utils/constants"; import { fetchUserProfile } from "@utils/discord"; import { classes, pluralise } from "@utils/misc"; import { ModalContent, ModalRoot, openModal } from "@utils/modal"; +import { User } from "@vencord/discord-types"; import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; -import { User } from "discord-types/general"; import Plugins from "~plugins"; diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 7baeba08..ddbcf8b8 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -26,12 +26,12 @@ import { Flex } from "@components/Flex"; import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; -import { classes, isObjectEmpty } from "@utils/misc"; +import { isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; -import { User } from "discord-types/general"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; @@ -212,7 +212,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti const pluginMeta = PluginMeta[plugin.name]; return ( - + {plugin.name} @@ -268,9 +268,9 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti {!!plugin.settingsAboutComponent && ( -
+
- + diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 27eb10a3..acfb0609 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -45,7 +45,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => const cl = classNameFactory("vc-plugins-"); const logger = new Logger("PluginSettings", "#a6d189"); -const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error"); +const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); @@ -62,7 +62,7 @@ function showErrorToast(message: string) { function ReloadRequiredCard({ required }: { required: boolean; }) { return ( - + {required ? ( <> Restart required! @@ -349,7 +349,7 @@ export default function PluginSettings() { select={onStatusChange} isSelected={v => v === searchValue.status} closeOnSelect={true} - className={InputStyles.inputDefault} + className={InputStyles.input} />
diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index a4f9aeee..c3d97051 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -66,13 +66,6 @@ gap: 0.25em; } -.vc-plugins-restart-card { - padding: 1em; - background: var(--info-warning-background); - border: 1px solid var(--info-warning-foreground); - color: var(--info-warning-text); -} - .vc-plugins-restart-button { margin-top: 0.5em; background: var(--info-warning-foreground) !important; diff --git a/src/components/VencordSettings/CloudTab.tsx b/src/components/VencordSettings/CloudTab.tsx index a13c3f6c..809d4062 100644 --- a/src/components/VencordSettings/CloudTab.tsx +++ b/src/components/VencordSettings/CloudTab.tsx @@ -21,7 +21,7 @@ import { Settings, useSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Grid } from "@components/Grid"; import { Link } from "@components/Link"; -import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; +import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { Margins } from "@utils/margins"; import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync"; import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common"; @@ -38,6 +38,8 @@ function validateUrl(url: string) { } async function eraseAllData() { + if (!await checkCloudUrlCsp()) return; + const res = await fetch(new URL("/v1/", getCloudUrl()), { method: "DELETE", headers: { Authorization: await getCloudAuth() } diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index 55822069..83364d19 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -148,7 +148,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError )} {compileResult && - + {compileResult[1]} } @@ -194,7 +194,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) { error={error ?? replacementError} /> {!isFunc && ( -
+
Cheat Sheet {Object.entries({ "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index f718ab11..6d41ab86 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -18,17 +18,21 @@ import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; import { Link } from "@components/Link"; import { openPluginModal } from "@components/PluginSettings/PluginModal"; import type { UserThemeHeader } from "@main/themes"; +import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; -import { showItemInFolder } from "@utils/native"; -import { useAwaiter } from "@utils/react"; +import { classes } from "@utils/misc"; +import { relaunch } from "@utils/native"; +import { useForceUpdater } from "@utils/react"; +import { getStylusWebStoreUrl } from "@utils/web"; import { findLazy } from "@webpack"; -import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; +import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; import Plugins from "~plugins"; @@ -48,62 +52,6 @@ const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue & const cl = classNameFactory("vc-settings-theme-"); -function Validator({ link }: { link: string; }) { - const [res, err, pending] = useAwaiter(() => fetch(link).then(res => { - if (res.status > 300) throw `${res.status} ${res.statusText}`; - const contentType = res.headers.get("Content-Type"); - if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain")) - throw "Not a CSS file. Remember to use the raw link!"; - - return "Okay!"; - })); - - const text = pending - ? "Checking..." - : err - ? `Error: ${err instanceof Error ? err.message : String(err)}` - : "Valid!"; - - return {text}; -} - -function Validators({ themeLinks }: { themeLinks: string[]; }) { - if (!themeLinks.length) return null; - - return ( - <> - Validator - This section will tell you whether your themes can successfully be loaded -
- {themeLinks.map(rawLink => { - const { label, link } = (() => { - const match = /^@(light|dark) (.*)/.exec(rawLink); - if (!match) return { label: rawLink, link: rawLink }; - - const [, mode, link] = match; - return { label: `[${mode} mode only] ${link}`, link }; - })(); - - return - - {label} - - - ; - })} -
- - ); -} - interface ThemeCardProps { theme: UserThemeHeader; enabled: boolean; @@ -159,7 +107,6 @@ function ThemesTab() { const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); const [themeText, setThemeText] = useState(settings.themeLinks.join("\n")); const [userThemes, setUserThemes] = useState(null); - const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir); useEffect(() => { refreshLocalThemes(); @@ -219,6 +166,12 @@ function ThemesTab() { If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder. + + External Resources + For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked. + Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts. + + <> @@ -241,8 +194,7 @@ function ThemesTab() { ) : ( showItemInFolder(themeDir!)} - disabled={themeDirPending} + action={() => VencordNative.themes.openFolder()} Icon={FolderIcon} /> )} @@ -301,7 +253,13 @@ function ThemesTab() { function renderOnlineThemes() { return ( <> - + + + This section is for advanced users. If you are having difficulties using it, use the + Local Themes tab instead. + + + Paste links to css files here One link per line You can prefix lines with @light or @dark to toggle them based on your Discord theme @@ -313,12 +271,11 @@ function ThemesTab() { value={themeText} onChange={setThemeText} className={"vc-settings-theme-links"} - placeholder="Theme Links" + placeholder="Enter Theme Links..." spellCheck={false} onBlur={onBlur} rows={10} /> - ); @@ -347,10 +304,99 @@ function ThemesTab() { + {currentTab === ThemeTab.LOCAL && renderLocalThemes()} {currentTab === ThemeTab.ONLINE && renderOnlineThemes()} ); } -export default wrapTab(ThemesTab, "Themes"); +export function CspErrorCard() { + if (IS_WEB) return null; + + const errors = useCspErrors(); + const forceUpdate = useForceUpdater(); + + if (!errors.length) return null; + + const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/"); + + const allowUrl = async (url: string) => { + const { origin: baseUrl, host } = new URL(url); + + const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes"); + if (result !== "ok") return; + + CspBlockedUrls.forEach(url => { + if (new URL(url).host === host) { + CspBlockedUrls.delete(url); + } + }); + + forceUpdate(); + + Alerts.show({ + title: "Restart Required", + body: "A restart is required to apply this change", + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: relaunch + }); + }; + + const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); + + return ( + + Blocked Resources + Some images, styles, or fonts were blocked because they come from disallowed domains. + It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them. + + After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change. + + + Blocked URLs +
+ {errors.map((url, i) => ( +
+ {i !== 0 && } +
+ {url} + +
+
+ ))} +
+ + {hasImgurHtmlDomain && ( + <> + + + Imgur links should be direct links in the form of https://i.imgur.com/... + + To obtain a direct link, right-click the image and select "Copy image address". + + )} +
+ ); +} + +function UserscriptThemesTab() { + return ( + + + Themes are not supported on the Userscript! + + + You can instead install themes with the Stylus extension! + + + + ); +} + +export default IS_USERSCRIPT + ? wrapTab(UserscriptThemesTab, "Themes") + : wrapTab(ThemesTab, "Themes"); diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx index e29d7dfd..f5e3d4b2 100644 --- a/src/components/VencordSettings/UpdaterTab.tsx +++ b/src/components/VencordSettings/UpdaterTab.tsx @@ -94,7 +94,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof {message} - {author}
))} @@ -225,7 +225,7 @@ function Updater() { Repo - + {repoPending ? repo : err diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 54da5a3f..047ba17d 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -26,8 +26,7 @@ import { gitRemote } from "@shared/vencordUserAgent"; import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants"; import { Margins } from "@utils/margins"; import { identity, isPluginDev } from "@utils/misc"; -import { relaunch, showItemInFolder } from "@utils/native"; -import { useAwaiter } from "@utils/react"; +import { relaunch } from "@utils/native"; import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common"; import BadgeAPI from "../../plugins/_api/badges"; @@ -53,9 +52,6 @@ type KeysOfType = { }[keyof Object]; function VencordSettings() { - const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, { - fallbackValue: "Loading..." - }); const settings = useSettings(); const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []); @@ -171,7 +167,7 @@ function VencordSettings() { showItemInFolder(settingsDir)} + action={() => VencordNative.settings.openFolder()} /> )} ; + +export const ConnectSrc = ["connect-src"]; +export const ImageSrc = [...ConnectSrc, "img-src"]; +export const CssSrc = ["style-src", "font-src"]; +export const ImageAndCssSrc = [...ImageSrc, ...CssSrc]; +export const ImageScriptsAndCssSrc = [...ImageAndCssSrc, "script-src", "worker-src"]; + +// Plugins can whitelist their own domains by importing this object in their native.ts +// script and just adding to it. But generally, you should just edit this file instead + +export const CspPolicies: PolicyMap = { + "http://localhost:*": ImageAndCssSrc, + "http://127.0.0.1:*": ImageAndCssSrc, + "localhost:*": ImageAndCssSrc, + "127.0.0.1:*": ImageAndCssSrc, + + "*.github.io": ImageAndCssSrc, // GitHub pages, used by most themes + "github.com": ImageAndCssSrc, // GitHub content (stuff uploaded to markdown forms), used by most themes + "raw.githubusercontent.com": ImageAndCssSrc, // GitHub raw, used by some themes + "*.gitlab.io": ImageAndCssSrc, // GitLab pages, used by some themes + "gitlab.com": ImageAndCssSrc, // GitLab raw, used by some themes + "*.codeberg.page": ImageAndCssSrc, // Codeberg pages, used by some themes + "codeberg.org": ImageAndCssSrc, // Codeberg raw, used by some themes + + "*.githack.com": ImageAndCssSrc, // githack (namely raw.githack.com), used by some themes + "jsdelivr.net": ImageAndCssSrc, // jsDelivr, used by very few themes + + "fonts.googleapis.com": CssSrc, // Google Fonts, used by many themes + + "i.imgur.com": ImageSrc, // Imgur, used by some themes + "i.ibb.co": ImageSrc, // ImgBB, used by some themes + "i.pinimg.com": ImageSrc, // Pinterest, used by some themes + "*.tenor.com": ImageSrc, // Tenor, used by some themes + "files.catbox.moe": ImageAndCssSrc, // Catbox, used by some themes + + "cdn.discordapp.com": ImageAndCssSrc, // Discord CDN, used by Vencord and some themes to load media + "media.discordapp.net": ImageSrc, // Discord media CDN, possible alternative to Discord CDN + + // CDNs used for some things by Vencord. + // FIXME: we really should not be using CDNs anymore + "cdnjs.cloudflare.com": ImageScriptsAndCssSrc, + "cdn.jsdelivr.net": ImageScriptsAndCssSrc, + + // Function Specific + "api.github.com": ConnectSrc, // used for updating Vencord itself + "ws.audioscrobbler.com": ConnectSrc, // Last.fm API + "translate-pa.googleapis.com": ConnectSrc, // Google Translate API + "*.vencord.dev": ImageSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev) + "manti.vendicated.dev": ImageSrc, // ReviewDB API + "decor.fieryflames.dev": ConnectSrc, // Decor API + "ugc.decor.fieryflames.dev": ImageSrc, // Decor CDN + "sponsor.ajay.app": ConnectSrc, // Dearrow API + "dearrow-thumb.ajay.app": ImageSrc, // Dearrow Thumbnail CDN + "usrbg.is-hardly.online": ImageSrc, // USRBG API + "icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search) + + // forked addition + "*.dorkbutt.lol": ImageAndCssSrc, +}; + +const findHeader = (headers: PolicyMap, headerName: Lowercase) => { + return Object.keys(headers).find(h => h.toLowerCase() === headerName); +}; + +const parsePolicy = (policy: string): PolicyMap => { + const result: PolicyMap = {}; + policy.split(";").forEach(directive => { + const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g); + if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) { + result[directiveKey] = directiveValue; + } + }); + + return result; +}; + +const stringifyPolicy = (policy: PolicyMap): string => + Object.entries(policy) + .filter(([, values]) => values?.length) + .map(directive => directive.flat().join(" ")) + .join("; "); + + +const patchCsp = (headers: PolicyMap) => { + const reportOnlyHeader = findHeader(headers, "content-security-policy-report-only"); + if (reportOnlyHeader) + delete headers[reportOnlyHeader]; + + const header = findHeader(headers, "content-security-policy"); + + if (header) { + const csp = parsePolicy(headers[header][0]); + + const pushDirective = (directive: string, ...values: string[]) => { + csp[directive] ??= [...(csp["default-src"] ?? [])]; + csp[directive].push(...values); + }; + + pushDirective("style-src", "'unsafe-inline'"); + // we could make unsafe-inline safe by using strict-dynamic with a random nonce on our Vencord loader script https://content-security-policy.com/strict-dynamic/ + // HOWEVER, at the time of writing (24 Jan 2025), Discord is INSANE and also uses unsafe-inline + // Once they stop using it, we also should + pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'"); + + for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { + pushDirective(directive, "blob:", "data:", "vencord:"); + } + + for (const [host, directives] of Object.entries(NativeSettings.store.customCspRules)) { + for (const directive of directives) { + pushDirective(directive, host); + } + } + + for (const [host, directives] of Object.entries(CspPolicies)) { + for (const directive of directives) { + pushDirective(directive, host); + } + } + + headers[header] = [stringifyPolicy(csp)]; + } +}; + +export function initCsp() { + session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { + if (responseHeaders) { + if (resourceType === "mainFrame") + patchCsp(responseHeaders); + + // Fix hosts that don't properly set the css content type, such as + // raw.githubusercontent.com + if (resourceType === "stylesheet") { + const header = findHeader(responseHeaders, "content-type"); + if (header) + responseHeaders[header] = ["text/css"]; + } + } + + cb({ cancel: false, responseHeaders }); + }); + + // assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones. + // For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it + // impossible to load css from github raw despite our fix above + session.defaultSession.webRequest.onHeadersReceived = () => { }; +} diff --git a/src/main/csp/manager.ts b/src/main/csp/manager.ts new file mode 100644 index 00000000..e6b1a0e3 --- /dev/null +++ b/src/main/csp/manager.ts @@ -0,0 +1,125 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { NativeSettings } from "@main/settings"; +import { IpcEvents } from "@shared/IpcEvents"; +import { dialog, ipcMain, IpcMainInvokeEvent } from "electron"; + +import { CspPolicies, ImageAndCssSrc } from "."; + +export type CspRequestResult = "invalid" | "cancelled" | "unchecked" | "ok" | "conflict"; + +export function registerCspIpcHandlers() { + ipcMain.handle(IpcEvents.CSP_REMOVE_OVERRIDE, removeCspRule); + ipcMain.handle(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, addCspRule); + ipcMain.handle(IpcEvents.CSP_IS_DOMAIN_ALLOWED, isDomainAllowed); +} + +function validate(url: string, directives: string[]) { + try { + const { host } = new URL(url); + + if (/[;'"\\]/.test(host)) return false; + } catch { + return false; + } + + if (directives.length === 0) return false; + if (directives.some(d => !ImageAndCssSrc.includes(d))) return false; + + return true; +} + +function getMessage(url: string, directives: string[], callerName: string) { + const domain = new URL(url).host; + + const message = `${callerName} wants to allow connections to ${domain}`; + + let detail = + `Unless you recognise and fully trust ${domain}, you should cancel this request!\n\n` + + `You will have to fully close and restart ${IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} for the changes to take effect.`; + + if (directives.length === 1 && directives[0] === "connect-src") { + return { message, detail }; + } + + const contentTypes = directives + .filter(type => type !== "connect-src") + .map(type => { + switch (type) { + case "img-src": + return "Images"; + case "style-src": + return "CSS & Themes"; + case "font-src": + return "Fonts"; + default: + throw new Error(`Illegal CSP directive: ${type}`); + } + }) + .sort() + .join(", "); + + detail = `The following types of content will be allowed to load from ${domain}:\n${contentTypes}\n\n${detail}`; + + return { message, detail }; +} + +async function addCspRule(_: IpcMainInvokeEvent, url: string, directives: string[], callerName: string): Promise { + if (!validate(url, directives)) { + return "invalid"; + } + + const domain = new URL(url).host; + + if (domain in NativeSettings.store.customCspRules) { + return "conflict"; + } + + const { checkboxChecked, response } = await dialog.showMessageBox({ + ...getMessage(url, directives, callerName), + type: callerName ? "info" : "warning", + title: "Vencord Host Permissions", + buttons: ["Cancel", "Allow"], + defaultId: 0, + cancelId: 0, + checkboxLabel: `I fully trust ${domain} and understand the risks of allowing connections to it.`, + checkboxChecked: false, + }); + + if (response !== 1) { + return "cancelled"; + } + + if (!checkboxChecked) { + return "unchecked"; + } + + NativeSettings.store.customCspRules[domain] = directives; + return "ok"; +} + +function removeCspRule(_: IpcMainInvokeEvent, domain: string) { + if (domain in NativeSettings.store.customCspRules) { + delete NativeSettings.store.customCspRules[domain]; + return true; + } + + return false; +} + +function isDomainAllowed(_: IpcMainInvokeEvent, url: string, directives: string[]) { + try { + const domain = new URL(url).host; + + const ruleForDomain = CspPolicies[domain] ?? NativeSettings.store.customCspRules[domain]; + if (!ruleForDomain) return false; + + return directives.every(d => ruleForDomain.includes(d)); + } catch (e) { + return false; + } +} diff --git a/src/main/index.ts b/src/main/index.ts index 4cc2e0db..95301ff7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,9 +16,11 @@ * along with this program. If not, see . */ -import { app, protocol, session } from "electron"; +import { app, net, protocol } from "electron"; import { join } from "path"; +import { pathToFileURL } from "url"; +import { initCsp } from "./csp"; import { ensureSafePath } from "./ipcMain"; import { RendererSettings } from "./settings"; import { IS_VANILLA, THEMES_DIR } from "./utils/constants"; @@ -26,21 +28,27 @@ import { installExt } from "./utils/extensions"; if (IS_VESKTOP || !IS_VANILLA) { app.whenReady().then(() => { - // Source Maps! Maybe there's a better way but since the renderer is executed - // from a string I don't think any other form of sourcemaps would work - protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => { - let url = unsafeUrl.slice("vencord://".length); + protocol.handle("vencord", ({ url: unsafeUrl }) => { + let url = decodeURI(unsafeUrl).slice("vencord://".length).replace(/\?v=\d+$/, ""); + if (url.endsWith("/")) url = url.slice(0, -1); + if (url.startsWith("/themes/")) { const theme = url.slice("/themes/".length); + const safeUrl = ensureSafePath(THEMES_DIR, theme); if (!safeUrl) { - cb({ statusCode: 403 }); - return; + return new Response(null, { + status: 404 + }); } - cb(safeUrl.replace(/\?v=\d+$/, "")); - return; + + return net.fetch(pathToFileURL(safeUrl).toString()); } + + // Source Maps! Maybe there's a better way but since the renderer is executed + // from a string I don't think any other form of sourcemaps would work + switch (url) { case "renderer.js.map": case "vencordDesktopRenderer.js.map": @@ -48,10 +56,11 @@ if (IS_VESKTOP || !IS_VANILLA) { case "vencordDesktopPreload.js.map": case "patcher.js.map": case "vencordDesktopMain.js.map": - cb(join(__dirname, url)); - break; + return net.fetch(pathToFileURL(join(__dirname, url)).toString()); default: - cb({ statusCode: 403 }); + return new Response(null, { + status: 404 + }); } }); @@ -63,70 +72,7 @@ if (IS_VESKTOP || !IS_VANILLA) { } catch { } - const findHeader = (headers: Record, headerName: Lowercase) => { - return Object.keys(headers).find(h => h.toLowerCase() === headerName); - }; - - // Remove CSP - type PolicyResult = Record; - - const parsePolicy = (policy: string): PolicyResult => { - const result: PolicyResult = {}; - policy.split(";").forEach(directive => { - const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g); - if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) { - result[directiveKey] = directiveValue; - } - }); - - return result; - }; - const stringifyPolicy = (policy: PolicyResult): string => - Object.entries(policy) - .filter(([, values]) => values?.length) - .map(directive => directive.flat().join(" ")) - .join("; "); - - const patchCsp = (headers: Record) => { - const header = findHeader(headers, "content-security-policy"); - - if (header) { - const csp = parsePolicy(headers[header][0]); - - for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { - csp[directive] ??= []; - csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'"); - } - - // TODO: Restrict this to only imported packages with fixed version. - // Perhaps auto generate with esbuild - csp["script-src"] ??= []; - csp["script-src"].push("'unsafe-eval'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"); - headers[header] = [stringifyPolicy(csp)]; - } - }; - - session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { - if (responseHeaders) { - if (resourceType === "mainFrame") - patchCsp(responseHeaders); - - // Fix hosts that don't properly set the css content type, such as - // raw.githubusercontent.com - if (resourceType === "stylesheet") { - const header = findHeader(responseHeaders, "content-type"); - if (header) - responseHeaders[header] = ["text/css"]; - } - } - - cb({ cancel: false, responseHeaders }); - }); - - // assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones. - // For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it - // impossible to load css from github raw despite our fix above - session.defaultSession.webRequest.onHeadersReceived = () => { }; + initCsp(); }); } diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 62785867..6990cea9 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -28,14 +28,17 @@ import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs"; import { open, readdir, readFile } from "fs/promises"; import { join, normalize } from "path"; +import { registerCspIpcHandlers } from "./csp/manager"; import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes"; -import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, THEMES_DIR } from "./utils/constants"; +import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, THEMES_DIR } from "./utils/constants"; import { makeLinksOpenExternally } from "./utils/externalLinks"; mkdirSync(THEMES_DIR, { recursive: true }); +registerCspIpcHandlers(); + export function ensureSafePath(basePath: string, path: string) { - const normalizedBasePath = normalize(basePath); + const normalizedBasePath = normalize(basePath + "/"); const newPath = join(basePath, path); const normalizedPath = normalize(newPath); return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null; @@ -89,7 +92,6 @@ ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) => writeFileSync(QUICKCSS_PATH, css) ); -ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR); ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes()); ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName)); ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({ @@ -97,6 +99,8 @@ ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({ "os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}` })); +ipcMain.handle(IpcEvents.OPEN_THEMES_FOLDER, () => shell.openPath(THEMES_DIR)); +ipcMain.handle(IpcEvents.OPEN_SETTINGS_FOLDER, () => shell.openPath(SETTINGS_DIR)); export function initIpc(mainWindow: BrowserWindow) { let quickCssWatcher: FSWatcher | undefined; diff --git a/src/main/patcher.ts b/src/main/patcher.ts index 60b169af..8868caa7 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -38,7 +38,7 @@ const asarPath = join(dirname(injectorPath), "..", asarName); const discordPkg = require(join(asarPath, "package.json")); require.main!.filename = join(asarPath, discordPkg.main); -// @ts-ignore Untyped method? Dies from cringe +// @ts-expect-error Untyped method? Dies from cringe app.setAppPath(asarPath); if (!IS_VANILLA) { diff --git a/src/main/settings.ts b/src/main/settings.ts index 3d367a94..962bbaa7 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -36,7 +36,6 @@ RendererSettings.addGlobalChangeListener(() => { } }); -ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR); ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = RendererSettings.plain); ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string) => { @@ -49,16 +48,18 @@ export interface NativeSettings { [setting: string]: any; }; }; + customCspRules: Record; } const DefaultNativeSettings: NativeSettings = { - plugins: {} + plugins: {}, + customCspRules: {} }; const nativeSettings = readSettings("native", NATIVE_SETTINGS_FILE); mergeDefaults(nativeSettings, DefaultNativeSettings); -export const NativeSettings = new SettingsStore(nativeSettings); +export const NativeSettings = new SettingsStore(nativeSettings as NativeSettings); NativeSettings.addGlobalChangeListener(() => { try { diff --git a/src/main/updater/common.ts b/src/main/updater/common.ts index 41b9837c..1a0b8da9 100644 --- a/src/main/updater/common.ts +++ b/src/main/updater/common.ts @@ -35,7 +35,10 @@ export function serializeErrors(func: (...args: any[]) => any) { ok: false, error: e instanceof Error ? { // prototypes get lost, so turn error into plain object - ...e + ...e, + message: e.message, + name: e.name, + stack: e.stack } : e }; } diff --git a/src/main/updater/http.ts b/src/main/updater/http.ts index 9d42b5c6..a112dde3 100644 --- a/src/main/updater/http.ts +++ b/src/main/updater/http.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { get } from "@main/utils/simpleGet"; +import { fetchBuffer, fetchJson } from "@main/utils/http"; import { IpcEvents } from "@shared/IpcEvents"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; import { ipcMain } from "electron"; @@ -31,8 +31,8 @@ import { serializeErrors, VENCORD_FILES } from "./common"; const API_BASE = `https://api.github.com/repos/${gitRemote}`; let PendingUpdates = [] as [string, string][]; -async function githubGet(endpoint: string) { - return get(API_BASE + endpoint, { +async function githubGet(endpoint: string) { + return fetchJson(API_BASE + endpoint, { headers: { Accept: "application/vnd.github+json", // "All API requests MUST include a valid User-Agent header. @@ -46,9 +46,8 @@ async function calculateGitChanges() { const isOutdated = await fetchUpdates(); if (!isOutdated) return []; - const res = await githubGet(`/compare/${gitHash}...HEAD`); + const data = await githubGet(`/compare/${gitHash}...HEAD`); - const data = JSON.parse(res.toString("utf-8")); return data.commits.map((c: any) => ({ // github api only sends the long sha hash: c.sha.slice(0, 7), @@ -58,9 +57,8 @@ async function calculateGitChanges() { } async function fetchUpdates() { - const release = await githubGet("/releases/latest"); + const data = await githubGet("/releases/latest"); - const data = JSON.parse(release.toString()); const hash = data.name.slice(data.name.lastIndexOf(" ") + 1); if (hash === gitHash) return false; @@ -70,16 +68,20 @@ async function fetchUpdates() { PendingUpdates.push([name, browser_download_url]); } }); + return true; } async function applyUpdates() { - await Promise.all(PendingUpdates.map( - async ([name, data]) => writeFile( - join(__dirname, name), - await get(data) - ) - )); + const fileContents = await Promise.all(PendingUpdates.map(async ([name, url]) => { + const contents = await fetchBuffer(url); + return [join(__dirname, name), contents] as const; + })); + + await Promise.all(fileContents.map(async ([filename, contents]) => + writeFile(filename, contents)) + ); + PendingUpdates = []; return true; } diff --git a/src/main/utils/extensions.ts b/src/main/utils/extensions.ts index 1323bd37..1d7559fb 100644 --- a/src/main/utils/extensions.ts +++ b/src/main/utils/extensions.ts @@ -24,7 +24,7 @@ import { join } from "path"; import { DATA_DIR } from "./constants"; import { crxToZip } from "./crxToZip"; -import { get } from "./simpleGet"; +import { fetchBuffer } from "./http"; const extensionCacheDir = join(DATA_DIR, "ExtensionCache"); @@ -69,13 +69,14 @@ export async function installExt(id: string) { } catch (err) { const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`; - const buf = await get(url, { + const buf = await fetchBuffer(url, { headers: { "User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)` } }); - await extract(crxToZip(buf), extDir).catch(console.error); + await extract(crxToZip(buf), extDir) + .catch(err => console.error(`Failed to extract extension ${id}`, err)); } session.defaultSession.loadExtension(extDir); diff --git a/src/main/utils/http.ts b/src/main/utils/http.ts new file mode 100644 index 00000000..05dbca40 --- /dev/null +++ b/src/main/utils/http.ts @@ -0,0 +1,70 @@ +/* + * 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 { createWriteStream } from "original-fs"; +import { Readable } from "stream"; +import { finished } from "stream/promises"; + +type Url = string | URL; + +export async function checkedFetch(url: Url, options?: RequestInit) { + try { + var res = await fetch(url, options); + } catch (err) { + if (err instanceof Error && err.cause) { + err = err.cause; + } + + throw new Error(`${options?.method ?? "GET"} ${url} failed: ${err}`); + } + + if (res.ok) { + return res; + } + + let message = `${options?.method ?? "GET"} ${url}: ${res.status} ${res.statusText}`; + try { + const reason = await res.text(); + message += `\n${reason}`; + } catch { } + + throw new Error(message); +} + +export async function fetchJson(url: Url, options?: RequestInit) { + const res = await checkedFetch(url, options); + return res.json() as Promise; +} + +export async function fetchBuffer(url: Url, options?: RequestInit) { + const res = await checkedFetch(url, options); + const buf = await res.arrayBuffer(); + + return Buffer.from(buf); +} + +export async function downloadToFile(url: Url, path: string, options?: RequestInit) { + const res = await checkedFetch(url, options); + if (!res.body) { + throw new Error(`Download ${url}: response body is empty`); + } + + // @ts-expect-error weird type conflict + const body = Readable.fromWeb(res.body); + await finished(body.pipe(createWriteStream(path))); +} diff --git a/src/main/utils/simpleGet.ts b/src/main/utils/simpleGet.ts deleted file mode 100644 index 1a8302c0..00000000 --- a/src/main/utils/simpleGet.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import https from "https"; - -export function get(url: string, options: https.RequestOptions = {}) { - return new Promise((resolve, reject) => { - https.get(url, options, res => { - const { statusCode, statusMessage, headers } = res; - if (statusCode! >= 400) - return void reject(`${statusCode}: ${statusMessage} - ${url}`); - if (statusCode! >= 300) - return void resolve(get(headers.location!, options)); - - const chunks = [] as Buffer[]; - res.on("error", reject); - - res.on("data", chunk => chunks.push(chunk)); - res.once("end", () => resolve(Buffer.concat(chunks))); - }); - }); -} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 8745584e..8011e331 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -30,10 +30,10 @@ import { Margins } from "@utils/margins"; import { shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; +import { User } from "@vencord/discord-types"; import { Forms, Toasts, UserStore } from "@webpack/common"; -import { User } from "discord-types/general"; -const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png"; +const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index 3d55f62e..a49658fb 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},` } diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index 30920a06..ad1f255b 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { WebpackRequire } from "@webpack/wreq.d"; +import { WebpackRequire } from "@vencord/discord-types/webpack"; const settings = definePluginSettings({ disableAnalytics: { diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index a9e34f78..0eb8e13c 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -213,7 +213,7 @@ export default definePlugin({ get chromiumVersion() { try { return VencordNative.native.getVersions().chrome - // @ts-ignore Typescript will add userAgentData IMMEDIATELY + // @ts-expect-error Typescript will add userAgentData IMMEDIATELY || navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version || null; } catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 825bbc20..82e09b88 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -32,8 +32,8 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; +import { Channel } from "@vencord/discord-types"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; -import { Channel } from "discord-types/general"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -196,7 +196,6 @@ export default definePlugin({ } } - // @ts-ignore outdated type const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles; if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; @@ -319,7 +318,7 @@ export default definePlugin({ if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null; return ( - + Please do not private message Vencord plugin developers for support!
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")} diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index f871b8ee..ce9f6b51 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -9,9 +9,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common"; -import { User } from "discord-types/general"; interface UserProfileProps { popoutProps: Record; diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index d24de222..8237be8b 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -75,11 +75,7 @@ export default definePlugin({ find: "async uploadFiles(", replacement: [ { - match: /async uploadFiles\((\i),\i\){/, - replace: "$&$1.forEach($self.anonymise);" - }, - { - match: /async uploadFilesSimple\((\i)\){/, + match: /async uploadFiles\((\i)\){/, replace: "$&$1.forEach($self.anonymise);" } ], diff --git a/src/plugins/appleMusic.desktop/native.ts b/src/plugins/appleMusic.desktop/native.ts index 5a547997..c7dd40d2 100644 --- a/src/plugins/appleMusic.desktop/native.ts +++ b/src/plugins/appleMusic.desktop/native.ts @@ -27,7 +27,7 @@ interface RemoteData { let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; const APPLE_MUSIC_BUNDLE_REGEX = /