Compare commits
117 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b79b102833 | |||
| 2a33d719ee | |||
|
|
8943c90cb0 | ||
|
|
f040133412 | ||
|
|
631c763fc4 | ||
|
|
79c5cf5304 | ||
|
|
c5a1bbd7db | ||
|
|
7c839be64f | ||
|
|
cb845b5224 | ||
|
|
746c824020 | ||
|
|
228b85a0c8 | ||
|
|
2e9d67f0b4 | ||
|
|
edd68fe08e | ||
|
|
80872f4ab9 | ||
|
|
8c2dc84f3b | ||
|
|
3005906a28 | ||
|
|
56d25b03f9 | ||
|
|
fbc2dbe781 | ||
|
|
9c0af5adee | ||
|
|
479d01a1b9 | ||
|
|
8eabb11125 | ||
|
|
98058f0cae | ||
|
|
50eb62045a | ||
|
|
4e8d22b4d5 | ||
|
|
51c23ff796 | ||
|
|
dc72ee3809 | ||
|
|
4c7acbbbc7 | ||
|
|
0f29eab3ea | ||
|
|
c38aac23fd | ||
|
|
84957b0e88 | ||
|
|
a4e1d026ea | ||
|
|
b225f2ec6c | ||
|
|
efecbae75b | ||
|
|
1cfc3fb8f8 | ||
|
|
26074b7f18 | ||
|
|
e857f6806f | ||
|
|
b6e96a4d3b | ||
|
|
1d00ba4161 | ||
|
|
77b016de36 | ||
|
|
b7f19bbe37 | ||
|
|
9700ec9cd2 | ||
|
|
65c85a5222 | ||
|
|
8789973bf5 | ||
|
|
4ff3614dc0 | ||
|
|
5c69d340d9 | ||
|
|
75a2506c51 | ||
|
|
9b0ae0fd90 | ||
|
|
f0f75aa918 | ||
|
|
8ebfd9a190 | ||
|
|
17b90beee1 | ||
|
|
8807564053 | ||
|
|
aca30bcb9a | ||
|
|
67aff64fed | ||
|
|
c5888c25f7 | ||
|
|
19c1eaed18 | ||
|
|
5dee746986 | ||
|
|
76a60e07e9 | ||
|
|
abe910d80d | ||
|
|
4a35cf1769 | ||
|
|
643656d798 | ||
|
|
0bb2ed6b72 | ||
|
|
330c3cead7 | ||
|
|
93294673de | ||
|
|
204f916b2a | ||
|
|
f356f647ff | ||
|
|
aad88fe9cd | ||
|
|
72329f901c | ||
|
|
27b2e97e3f | ||
|
|
d9e2732a8d | ||
|
|
0c89314d49 | ||
|
|
4403aee3c1 | ||
|
|
7e028267f1 | ||
|
|
c7e799e935 | ||
|
|
98efe13b97 | ||
|
|
164fd43cc4 | ||
|
|
fe2ed0776f | ||
|
|
74d78d89ed | ||
|
|
6a66b7f54f | ||
|
|
36c15d619e | ||
|
|
a2253cb4ae | ||
|
|
6380111f32 | ||
|
|
1ebd412392 | ||
|
|
a02d1afdf0 | ||
|
|
38e46f89cf | ||
|
|
22d3fb10e9 | ||
|
|
fa672a347d | ||
|
|
cb36cf5706 | ||
|
|
29a2bcbf07 | ||
|
|
03fe7d15cf | ||
|
|
6fb685b959 | ||
|
|
8b36013264 | ||
|
|
50e2ad776b | ||
|
|
4ab084c0ac | ||
|
|
113a1f4b86 | ||
|
|
25cd6b7069 | ||
|
|
f6f0624e52 | ||
|
|
cdda1224ff | ||
|
|
d0869c41cd | ||
|
|
828358bd2e | ||
|
|
3f51ee1b2a | ||
|
|
a33e81d1cb | ||
|
|
f3874d0a26 | ||
|
|
ad810df978 | ||
|
|
1a98d54e3a | ||
|
|
5dd6722528 | ||
|
|
6787e98003 | ||
|
|
18f083b7e6 | ||
|
|
19f4d7cdac | ||
|
|
8e446e44ab | ||
|
|
a17803c1c4 | ||
|
|
dc9064326b | ||
|
|
c55833d95f | ||
|
|
44f7ccafcb | ||
|
|
643122e323 | ||
|
|
310d8e6140 | ||
|
|
1142cab05c | ||
|
|
c653e36137 |
326 changed files with 6617 additions and 4377 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -22,4 +22,4 @@ lerna-debug.log*
|
||||||
src/userplugins
|
src/userplugins
|
||||||
|
|
||||||
ExtensionCache/
|
ExtensionCache/
|
||||||
settings/
|
/settings
|
||||||
|
|
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
|
@ -13,11 +13,14 @@
|
||||||
"typescript.format.semicolons": "insert",
|
"typescript.format.semicolons": "insert",
|
||||||
"typescript.preferences.quoteStyle": "double",
|
"typescript.preferences.quoteStyle": "double",
|
||||||
"javascript.preferences.quoteStyle": "double",
|
"javascript.preferences.quoteStyle": "double",
|
||||||
|
|
||||||
"gitlens.remotes": [
|
"gitlens.remotes": [
|
||||||
{
|
{
|
||||||
"domain": "codeberg.org",
|
"domain": "codeberg.org",
|
||||||
"type": "Gitea"
|
"type": "Gitea"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"css.format.spaceAroundSelectorSeparator": true,
|
||||||
|
"[css]": {
|
||||||
|
"editor.defaultFormatter": "vscode.css-language-features"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.12.5",
|
"version": "1.13.0",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
@ -53,8 +53,8 @@
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/yazl": "^2.4.5",
|
"@types/yazl": "^2.4.5",
|
||||||
|
"@vencord/discord-types": "link:packages/discord-types",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"discord-types": "^1.3.26",
|
|
||||||
"esbuild": "^0.25.1",
|
"esbuild": "^0.25.1",
|
||||||
"eslint": "9.20.1",
|
"eslint": "9.20.1",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
|
|
|
||||||
1
packages/discord-types/CONTRIBUTING.md
Normal file
1
packages/discord-types/CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces
|
||||||
165
packages/discord-types/LICENSE
Normal file
165
packages/discord-types/LICENSE
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
42
packages/discord-types/README.md
Normal file
42
packages/discord-types/README.md
Normal file
|
|
@ -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.
|
||||||
30
packages/discord-types/enums/activity.ts
Normal file
30
packages/discord-types/enums/activity.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
export const enum ActivityType {
|
||||||
|
PLAYING = 0,
|
||||||
|
STREAMING = 1,
|
||||||
|
LISTENING = 2,
|
||||||
|
WATCHING = 3,
|
||||||
|
CUSTOM_STATUS = 4,
|
||||||
|
COMPETING = 5,
|
||||||
|
HANG_STATUS = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum ActivityFlags {
|
||||||
|
INSTANCE = 1 << 0,
|
||||||
|
JOIN = 1 << 1,
|
||||||
|
/** @deprecated */
|
||||||
|
SPECTATE = 1 << 2,
|
||||||
|
/** @deprecated */
|
||||||
|
JOIN_REQUEST = 1 << 3,
|
||||||
|
SYNC = 1 << 4,
|
||||||
|
PLAY = 1 << 5,
|
||||||
|
PARTY_PRIVACY_FRIENDS = 1 << 6,
|
||||||
|
PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7,
|
||||||
|
EMBEDDED = 1 << 8,
|
||||||
|
CONTEXTLESS = 1 << 9
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum ActivityStatusDisplayType {
|
||||||
|
NAME = 0,
|
||||||
|
STATE = 1,
|
||||||
|
DETAILS = 2
|
||||||
|
}
|
||||||
15
packages/discord-types/enums/channel.ts
Normal file
15
packages/discord-types/enums/channel.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
export const enum ChannelType {
|
||||||
|
GUILD_TEXT = 0,
|
||||||
|
DM = 1,
|
||||||
|
GUILD_VOICE = 2,
|
||||||
|
GROUP_DM = 3,
|
||||||
|
GUILD_CATEGORY = 4,
|
||||||
|
GUILD_ANNOUNCEMENT = 5,
|
||||||
|
ANNOUNCEMENT_THREAD = 10,
|
||||||
|
PUBLIC_THREAD = 11,
|
||||||
|
PRIVATE_THREAD = 12,
|
||||||
|
GUILD_STAGE_VOICE = 13,
|
||||||
|
GUILD_DIRECTORY = 14,
|
||||||
|
GUILD_FORUM = 15,
|
||||||
|
GUILD_MEDIA = 16
|
||||||
|
}
|
||||||
32
packages/discord-types/enums/commands.ts
Normal file
32
packages/discord-types/enums/commands.ts
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
5
packages/discord-types/enums/index.ts
Normal file
5
packages/discord-types/enums/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./activity";
|
||||||
|
export * from "./channel";
|
||||||
|
export * from "./commands";
|
||||||
|
export * from "./messages";
|
||||||
|
export * from "./misc";
|
||||||
596
packages/discord-types/enums/messages.ts
Normal file
596
packages/discord-types/enums/messages.ts
Normal file
|
|
@ -0,0 +1,596 @@
|
||||||
|
export const enum StickerType {
|
||||||
|
/** an official sticker in a pack */
|
||||||
|
STANDARD = 1,
|
||||||
|
/** a sticker uploaded to a guild for the guild's members */
|
||||||
|
GUILD = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum StickerFormatType {
|
||||||
|
PNG = 1,
|
||||||
|
APNG = 2,
|
||||||
|
LOTTIE = 3,
|
||||||
|
GIF = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum MessageType {
|
||||||
|
/**
|
||||||
|
* A default message (see below)
|
||||||
|
*
|
||||||
|
* Value: 0
|
||||||
|
* Name: DEFAULT
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
DEFAULT = 0,
|
||||||
|
/**
|
||||||
|
* A message sent when a user is added to a group DM or thread
|
||||||
|
*
|
||||||
|
* Value: 1
|
||||||
|
* Name: RECIPIENT_ADD
|
||||||
|
* Rendered Content: "{author} added {mentions [0] } to the {group/thread}."
|
||||||
|
* Deletable: false
|
||||||
|
*/
|
||||||
|
RECIPIENT_ADD = 1,
|
||||||
|
/**
|
||||||
|
* A message sent when a user is removed from a group DM or thread
|
||||||
|
*
|
||||||
|
* Value: 2
|
||||||
|
* Name: RECIPIENT_REMOVE
|
||||||
|
* Rendered Content: "{author} removed {mentions [0] } from the {group/thread}."
|
||||||
|
* Deletable: false
|
||||||
|
*/
|
||||||
|
RECIPIENT_REMOVE = 2,
|
||||||
|
/**
|
||||||
|
* A message sent when a user creates a call in a private channel
|
||||||
|
*
|
||||||
|
* Value: 3
|
||||||
|
* Name: CALL
|
||||||
|
* Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}."
|
||||||
|
* Deletable: false
|
||||||
|
*/
|
||||||
|
CALL = 3,
|
||||||
|
/**
|
||||||
|
* A message sent when a group DM or thread's name is changed
|
||||||
|
*
|
||||||
|
* Value: 4
|
||||||
|
* Name: CHANNEL_NAME_CHANGE
|
||||||
|
* Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} "
|
||||||
|
* Deletable: false
|
||||||
|
*/
|
||||||
|
CHANNEL_NAME_CHANGE = 4,
|
||||||
|
/**
|
||||||
|
* A message sent when a group DM's icon is changed
|
||||||
|
*
|
||||||
|
* Value: 5
|
||||||
|
* Name: CHANNEL_ICON_CHANGE
|
||||||
|
* Rendered Content: "{author} changed the channel icon."
|
||||||
|
* Deletable: false
|
||||||
|
*/
|
||||||
|
CHANNEL_ICON_CHANGE = 5,
|
||||||
|
/**
|
||||||
|
* A message sent when a message is pinned in a channel
|
||||||
|
*
|
||||||
|
* Value: 6
|
||||||
|
* Name: CHANNEL_PINNED_MESSAGE
|
||||||
|
* Rendered Content: "{author} pinned a message to this channel."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CHANNEL_PINNED_MESSAGE = 6,
|
||||||
|
/**
|
||||||
|
* A message sent when a user joins a guild
|
||||||
|
*
|
||||||
|
* Value: 7
|
||||||
|
* Name: USER_JOIN
|
||||||
|
* Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
USER_JOIN = 7,
|
||||||
|
/**
|
||||||
|
* A message sent when a user subscribes to (boosts) a guild
|
||||||
|
*
|
||||||
|
* Value: 8
|
||||||
|
* Name: PREMIUM_GUILD_SUBSCRIPTION
|
||||||
|
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
PREMIUM_GUILD_SUBSCRIPTION = 8,
|
||||||
|
/**
|
||||||
|
* A message sent when a user subscribes to (boosts) a guild to tier 1
|
||||||
|
*
|
||||||
|
* Value: 9
|
||||||
|
* Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1
|
||||||
|
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9,
|
||||||
|
/**
|
||||||
|
* A message sent when a user subscribes to (boosts) a guild to tier 2
|
||||||
|
*
|
||||||
|
* Value: 10
|
||||||
|
* Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2
|
||||||
|
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10,
|
||||||
|
/**
|
||||||
|
* A message sent when a user subscribes to (boosts) a guild to tier 3
|
||||||
|
*
|
||||||
|
* Value: 11
|
||||||
|
* Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3
|
||||||
|
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11,
|
||||||
|
/**
|
||||||
|
* A message sent when a news channel is followed
|
||||||
|
*
|
||||||
|
* Value: 12
|
||||||
|
* Name: CHANNEL_FOLLOW_ADD
|
||||||
|
* Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CHANNEL_FOLLOW_ADD = 12,
|
||||||
|
/**
|
||||||
|
* A message sent when a guild is disqualified from discovery
|
||||||
|
*
|
||||||
|
* Value: 14
|
||||||
|
* Name: GUILD_DISCOVERY_DISQUALIFIED
|
||||||
|
* Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_DISCOVERY_DISQUALIFIED = 14,
|
||||||
|
/**
|
||||||
|
* A message sent when a guild requalifies for discovery
|
||||||
|
*
|
||||||
|
* Value: 15
|
||||||
|
* Name: GUILD_DISCOVERY_REQUALIFIED
|
||||||
|
* Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_DISCOVERY_REQUALIFIED = 15,
|
||||||
|
/**
|
||||||
|
* A message sent when a guild has failed discovery requirements for a week
|
||||||
|
*
|
||||||
|
* Value: 16
|
||||||
|
* Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING
|
||||||
|
* Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16,
|
||||||
|
/**
|
||||||
|
* A message sent when a guild has failed discovery requirements for 3 weeks
|
||||||
|
*
|
||||||
|
* Value: 17
|
||||||
|
* Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING
|
||||||
|
* Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17,
|
||||||
|
/**
|
||||||
|
* A message sent when a thread is created
|
||||||
|
*
|
||||||
|
* Value: 18
|
||||||
|
* Name: THREAD_CREATED
|
||||||
|
* Rendered Content: "{author} started a thread: {content} . See all threads."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
THREAD_CREATED = 18,
|
||||||
|
/**
|
||||||
|
* A message sent when a user replies to a message
|
||||||
|
*
|
||||||
|
* Value: 19
|
||||||
|
* Name: REPLY
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
REPLY = 19,
|
||||||
|
/**
|
||||||
|
* A message sent when a user uses a slash command
|
||||||
|
*
|
||||||
|
* Value: 20
|
||||||
|
* Name: CHAT_INPUT_COMMAND
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CHAT_INPUT_COMMAND = 20,
|
||||||
|
/**
|
||||||
|
* A message sent when a thread starter message is added to a thread
|
||||||
|
*
|
||||||
|
* Value: 21
|
||||||
|
* Name: THREAD_STARTER_MESSAGE
|
||||||
|
* Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread"
|
||||||
|
* Deletable: false
|
||||||
|
*/
|
||||||
|
THREAD_STARTER_MESSAGE = 21,
|
||||||
|
/**
|
||||||
|
* A message sent to remind users to invite friends to a guild
|
||||||
|
*
|
||||||
|
* Value: 22
|
||||||
|
* Name: GUILD_INVITE_REMINDER
|
||||||
|
* Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_INVITE_REMINDER = 22,
|
||||||
|
/**
|
||||||
|
* A message sent when a user uses a context menu command
|
||||||
|
*
|
||||||
|
* Value: 23
|
||||||
|
* Name: CONTEXT_MENU_COMMAND
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CONTEXT_MENU_COMMAND = 23,
|
||||||
|
/**
|
||||||
|
* A message sent when auto moderation takes an action
|
||||||
|
*
|
||||||
|
* Value: 24
|
||||||
|
* Name: AUTO_MODERATION_ACTION
|
||||||
|
* Rendered Content: Special embed rendered from embeds[0]
|
||||||
|
* Deletable: true 1
|
||||||
|
*/
|
||||||
|
AUTO_MODERATION_ACTION = 24,
|
||||||
|
/**
|
||||||
|
* A message sent when a user purchases or renews a role subscription
|
||||||
|
*
|
||||||
|
* Value: 25
|
||||||
|
* Name: ROLE_SUBSCRIPTION_PURCHASE
|
||||||
|
* Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
ROLE_SUBSCRIPTION_PURCHASE = 25,
|
||||||
|
/**
|
||||||
|
* A message sent when a user is upsold to a premium interaction
|
||||||
|
*
|
||||||
|
* Value: 26
|
||||||
|
* Name: INTERACTION_PREMIUM_UPSELL
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
INTERACTION_PREMIUM_UPSELL = 26,
|
||||||
|
/**
|
||||||
|
* A message sent when a stage channel starts
|
||||||
|
*
|
||||||
|
* Value: 27
|
||||||
|
* Name: STAGE_START
|
||||||
|
* Rendered Content: "{author} started {content} "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
STAGE_START = 27,
|
||||||
|
/**
|
||||||
|
* A message sent when a stage channel ends
|
||||||
|
*
|
||||||
|
* Value: 28
|
||||||
|
* Name: STAGE_END
|
||||||
|
* Rendered Content: "{author} ended {content} "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
STAGE_END = 28,
|
||||||
|
/**
|
||||||
|
* A message sent when a user starts speaking in a stage channel
|
||||||
|
*
|
||||||
|
* Value: 29
|
||||||
|
* Name: STAGE_SPEAKER
|
||||||
|
* Rendered Content: "{author} is now a speaker."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
STAGE_SPEAKER = 29,
|
||||||
|
/**
|
||||||
|
* A message sent when a user raises their hand in a stage channel
|
||||||
|
*
|
||||||
|
* Value: 30
|
||||||
|
* Name: STAGE_RAISE_HAND
|
||||||
|
* Rendered Content: "{author} requested to speak."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
STAGE_RAISE_HAND = 30,
|
||||||
|
/**
|
||||||
|
* A message sent when a stage channel's topic is changed
|
||||||
|
*
|
||||||
|
* Value: 31
|
||||||
|
* Name: STAGE_TOPIC
|
||||||
|
* Rendered Content: "{author} changed the Stage topic: {content} "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
STAGE_TOPIC = 31,
|
||||||
|
/**
|
||||||
|
* A message sent when a user purchases an application premium subscription
|
||||||
|
*
|
||||||
|
* Value: 32
|
||||||
|
* Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION
|
||||||
|
* Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32,
|
||||||
|
/**
|
||||||
|
* A message sent when a user gifts a premium (Nitro) referral
|
||||||
|
*
|
||||||
|
* Value: 35
|
||||||
|
* Name: PREMIUM_REFERRAL
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
PREMIUM_REFERRAL = 35,
|
||||||
|
/**
|
||||||
|
* A message sent when a user enabled lockdown for the guild
|
||||||
|
*
|
||||||
|
* Value: 36
|
||||||
|
* Name: GUILD_INCIDENT_ALERT_MODE_ENABLED
|
||||||
|
* Rendered Content: "{author} enabled security actions until {content}."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_INCIDENT_ALERT_MODE_ENABLED = 36,
|
||||||
|
/**
|
||||||
|
* A message sent when a user disables lockdown for the guild
|
||||||
|
*
|
||||||
|
* Value: 37
|
||||||
|
* Name: GUILD_INCIDENT_ALERT_MODE_DISABLED
|
||||||
|
* Rendered Content: "{author} disabled security actions."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_INCIDENT_ALERT_MODE_DISABLED = 37,
|
||||||
|
/**
|
||||||
|
* A message sent when a user reports a raid for the guild
|
||||||
|
*
|
||||||
|
* Value: 38
|
||||||
|
* Name: GUILD_INCIDENT_REPORT_RAID
|
||||||
|
* Rendered Content: "{author} reported a raid in {guild}."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_INCIDENT_REPORT_RAID = 38,
|
||||||
|
/**
|
||||||
|
* A message sent when a user reports a false alarm for the guild
|
||||||
|
*
|
||||||
|
* Value: 39
|
||||||
|
* Name: GUILD_INCIDENT_REPORT_FALSE_ALARM
|
||||||
|
* Rendered Content: "{author} reported a false alarm in {guild}."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_INCIDENT_REPORT_FALSE_ALARM = 39,
|
||||||
|
/**
|
||||||
|
* A message sent when no one sends a message in the current channel for 1 hour
|
||||||
|
*
|
||||||
|
* Value: 40
|
||||||
|
* Name: GUILD_DEADCHAT_REVIVE_PROMPT
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_DEADCHAT_REVIVE_PROMPT = 40,
|
||||||
|
/**
|
||||||
|
* A message sent when a user buys another user a gift
|
||||||
|
*
|
||||||
|
* Value: 41
|
||||||
|
* Name: CUSTOM_GIFT
|
||||||
|
* Rendered Content: Special embed rendered from embeds[0].url and gift_info
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CUSTOM_GIFT = 41,
|
||||||
|
/**
|
||||||
|
* Value: 42
|
||||||
|
* Name: GUILD_GAMING_STATS_PROMPT
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_GAMING_STATS_PROMPT = 42,
|
||||||
|
/**
|
||||||
|
* A message sent when a user purchases a guild product
|
||||||
|
*
|
||||||
|
* Value: 44
|
||||||
|
* Name: PURCHASE_NOTIFICATION
|
||||||
|
* Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
PURCHASE_NOTIFICATION = 44,
|
||||||
|
/**
|
||||||
|
* A message sent when a poll is finalized
|
||||||
|
*
|
||||||
|
* Value: 46
|
||||||
|
* Name: POLL_RESULT
|
||||||
|
* Rendered Content: Special embed rendered from embeds[0]
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
POLL_RESULT = 46,
|
||||||
|
/**
|
||||||
|
* A message sent by the Discord Updates account when a new changelog is posted
|
||||||
|
*
|
||||||
|
* Value: 47
|
||||||
|
* Name: CHANGELOG
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CHANGELOG = 47,
|
||||||
|
/**
|
||||||
|
* A message sent when a Nitro promotion is triggered
|
||||||
|
*
|
||||||
|
* Value: 48
|
||||||
|
* Name: NITRO_NOTIFICATION
|
||||||
|
* Rendered Content: Special embed rendered from content
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
NITRO_NOTIFICATION = 48,
|
||||||
|
/**
|
||||||
|
* A message sent when a voice channel is linked to a lobby
|
||||||
|
*
|
||||||
|
* Value: 49
|
||||||
|
* Name: CHANNEL_LINKED_TO_LOBBY
|
||||||
|
* Rendered Content: "{content}"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
CHANNEL_LINKED_TO_LOBBY = 49,
|
||||||
|
/**
|
||||||
|
* A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary
|
||||||
|
*
|
||||||
|
* Value: 50
|
||||||
|
* Name: GIFTING_PROMPT
|
||||||
|
* Rendered Content: Special embed
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GIFTING_PROMPT = 50,
|
||||||
|
/**
|
||||||
|
* A local-only message sent when a user receives an in-game message NUX
|
||||||
|
*
|
||||||
|
* Value: 51
|
||||||
|
* Name: IN_GAME_MESSAGE_NUX
|
||||||
|
* Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
IN_GAME_MESSAGE_NUX = 51,
|
||||||
|
/**
|
||||||
|
* A message sent when a user accepts a guild join request
|
||||||
|
*
|
||||||
|
* Value: 52
|
||||||
|
* Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2
|
||||||
|
* Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52,
|
||||||
|
/**
|
||||||
|
* A message sent when a user rejects a guild join request
|
||||||
|
*
|
||||||
|
* Value: 53
|
||||||
|
* Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2
|
||||||
|
* Rendered Content: "{join_request.user}'s application to {content} was rejected."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53,
|
||||||
|
/**
|
||||||
|
* A message sent when a user withdraws a guild join request
|
||||||
|
*
|
||||||
|
* Value: 54
|
||||||
|
* Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2
|
||||||
|
* Rendered Content: "{join_request.user}'s application to {content} has been withdrawn."
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54,
|
||||||
|
/**
|
||||||
|
* A message sent when a user upgrades to HD streaming
|
||||||
|
*
|
||||||
|
* Value: 55
|
||||||
|
* Name: HD_STREAMING_UPGRADED
|
||||||
|
* Rendered Content: "{author} activated HD Splash Potion "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
HD_STREAMING_UPGRADED = 55,
|
||||||
|
/**
|
||||||
|
* A message sent when a user resolves a moderation report by deleting the offending message
|
||||||
|
*
|
||||||
|
* Value: 58
|
||||||
|
* Name: REPORT_TO_MOD_DELETED_MESSAGE
|
||||||
|
* Rendered Content: "{author} deleted the message"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
REPORT_TO_MOD_DELETED_MESSAGE = 58,
|
||||||
|
/**
|
||||||
|
* A message sent when a user resolves a moderation report by timing out the offending user
|
||||||
|
*
|
||||||
|
* Value: 59
|
||||||
|
* Name: REPORT_TO_MOD_TIMEOUT_USER
|
||||||
|
* Rendered Content: "{author} timed out {mentions [0] }"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
REPORT_TO_MOD_TIMEOUT_USER = 59,
|
||||||
|
/**
|
||||||
|
* A message sent when a user resolves a moderation report by kicking the offending user
|
||||||
|
*
|
||||||
|
* Value: 60
|
||||||
|
* Name: REPORT_TO_MOD_KICK_USER
|
||||||
|
* Rendered Content: "{author} kicked {mentions [0] }"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
REPORT_TO_MOD_KICK_USER = 60,
|
||||||
|
/**
|
||||||
|
* A message sent when a user resolves a moderation report by banning the offending user
|
||||||
|
*
|
||||||
|
* Value: 61
|
||||||
|
* Name: REPORT_TO_MOD_BAN_USER
|
||||||
|
* Rendered Content: "{author} banned {mentions [0] }"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
REPORT_TO_MOD_BAN_USER = 61,
|
||||||
|
/**
|
||||||
|
* A message sent when a user resolves a moderation report
|
||||||
|
*
|
||||||
|
* Value: 62
|
||||||
|
* Name: REPORT_TO_MOD_CLOSED_REPORT
|
||||||
|
* Rendered Content: "{author} resolved this flag"
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
REPORT_TO_MOD_CLOSED_REPORT = 62,
|
||||||
|
/**
|
||||||
|
* A message sent when a user adds a new emoji to a guild
|
||||||
|
*
|
||||||
|
* Value: 63
|
||||||
|
* Name: EMOJI_ADDED
|
||||||
|
* Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: "
|
||||||
|
* Deletable: true
|
||||||
|
*/
|
||||||
|
EMOJI_ADDED = 63,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum MessageFlags {
|
||||||
|
/**
|
||||||
|
* Message has been published to subscribed channels (via Channel Following)
|
||||||
|
*
|
||||||
|
* Value: 1 << 0
|
||||||
|
*/
|
||||||
|
CROSSPOSTED = 1 << 0,
|
||||||
|
/**
|
||||||
|
* Message originated from a message in another channel (via Channel Following)
|
||||||
|
*/
|
||||||
|
IS_CROSSPOST = 1 << 1,
|
||||||
|
/**
|
||||||
|
* Embeds will not be included when serializing this message
|
||||||
|
*/
|
||||||
|
SUPPRESS_EMBEDS = 1 << 2,
|
||||||
|
/**
|
||||||
|
* Source message for this crosspost has been deleted (via Channel Following)
|
||||||
|
*/
|
||||||
|
SOURCE_MESSAGE_DELETED = 1 << 3,
|
||||||
|
/**
|
||||||
|
* Message came from the urgent message system
|
||||||
|
*/
|
||||||
|
URGENT = 1 << 4,
|
||||||
|
/**
|
||||||
|
* Message has an associated thread, with the same ID as the message
|
||||||
|
*/
|
||||||
|
HAS_THREAD = 1 << 5,
|
||||||
|
/**
|
||||||
|
* Message is only visible to the user who invoked the interaction
|
||||||
|
*/
|
||||||
|
EPHEMERAL = 1 << 6,
|
||||||
|
/**
|
||||||
|
* Message is an interaction response and the bot is "thinking"
|
||||||
|
*/
|
||||||
|
LOADING = 1 << 7,
|
||||||
|
/**
|
||||||
|
* Some roles were not mentioned and added to the thread
|
||||||
|
*/
|
||||||
|
FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8,
|
||||||
|
/**
|
||||||
|
* Message is hidden from the guild's feed
|
||||||
|
*/
|
||||||
|
GUILD_FEED_HIDDEN = 1 << 9,
|
||||||
|
/**
|
||||||
|
* Message contains a link that impersonates Discord
|
||||||
|
*/
|
||||||
|
SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10,
|
||||||
|
/**
|
||||||
|
* Message will not trigger push and desktop notifications
|
||||||
|
*/
|
||||||
|
SUPPRESS_NOTIFICATIONS = 1 << 12,
|
||||||
|
/**
|
||||||
|
* Message's audio attachment is rendered as a voice message
|
||||||
|
*/
|
||||||
|
IS_VOICE_MESSAGE = 1 << 13,
|
||||||
|
/**
|
||||||
|
* Message has a forwarded message snapshot attached
|
||||||
|
*/
|
||||||
|
HAS_SNAPSHOT = 1 << 14,
|
||||||
|
/**
|
||||||
|
* Message contains components from version 2 of the UI kit
|
||||||
|
*/
|
||||||
|
IS_COMPONENTS_V2 = 1 << 15,
|
||||||
|
/**
|
||||||
|
* Message was triggered by the social layer integration
|
||||||
|
*/
|
||||||
|
SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16,
|
||||||
|
}
|
||||||
4
packages/discord-types/enums/misc.ts
Normal file
4
packages/discord-types/enums/misc.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const enum CloudUploadPlatform {
|
||||||
|
REACT_NATIVE = 0,
|
||||||
|
WEB = 1,
|
||||||
|
}
|
||||||
19
packages/discord-types/package.json
Normal file
19
packages/discord-types/package.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/discord-types/src/classes.d.ts
vendored
Normal file
16
packages/discord-types/src/classes.d.ts
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
36
packages/discord-types/src/common/Activity.d.ts
vendored
Normal file
36
packages/discord-types/src/common/Activity.d.ts
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums";
|
||||||
|
|
||||||
|
export interface ActivityAssets {
|
||||||
|
large_image?: string;
|
||||||
|
large_text?: string;
|
||||||
|
small_image?: string;
|
||||||
|
small_text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityButton {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Activity {
|
||||||
|
name: string;
|
||||||
|
application_id: string;
|
||||||
|
type: ActivityType;
|
||||||
|
state?: string;
|
||||||
|
state_url?: string;
|
||||||
|
details?: string;
|
||||||
|
details_url?: string;
|
||||||
|
url?: string;
|
||||||
|
flags: ActivityFlags;
|
||||||
|
status_display_type?: ActivityStatusDisplayType;
|
||||||
|
timestamps?: {
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
};
|
||||||
|
assets?: ActivityAssets;
|
||||||
|
buttons?: string[];
|
||||||
|
metadata?: {
|
||||||
|
button_urls?: Array<string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
23
packages/discord-types/src/common/Application.d.ts
vendored
Normal file
23
packages/discord-types/src/common/Application.d.ts
vendored
Normal file
|
|
@ -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[];
|
||||||
|
}
|
||||||
83
packages/discord-types/src/common/Channel.d.ts
vendored
Normal file
83
packages/discord-types/src/common/Channel.d.ts
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { DiscordRecord } from "./Record";
|
||||||
|
|
||||||
|
export class Channel extends DiscordRecord {
|
||||||
|
constructor(channel: object);
|
||||||
|
application_id: number | undefined;
|
||||||
|
bitrate: number;
|
||||||
|
defaultAutoArchiveDuration: number | undefined;
|
||||||
|
flags: number;
|
||||||
|
guild_id: string;
|
||||||
|
icon: string;
|
||||||
|
id: string;
|
||||||
|
lastMessageId: string;
|
||||||
|
lastPinTimestamp: string | undefined;
|
||||||
|
member: unknown;
|
||||||
|
memberCount: number | undefined;
|
||||||
|
memberIdsPreview: string[] | undefined;
|
||||||
|
memberListId: unknown;
|
||||||
|
messageCount: number | undefined;
|
||||||
|
name: string;
|
||||||
|
nicks: Record<string, unknown>;
|
||||||
|
nsfw: boolean;
|
||||||
|
originChannelId: unknown;
|
||||||
|
ownerId: string;
|
||||||
|
parent_id: string;
|
||||||
|
permissionOverwrites: {
|
||||||
|
[role: string]: {
|
||||||
|
id: string;
|
||||||
|
type: number;
|
||||||
|
deny: bigint;
|
||||||
|
allow: bigint;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
position: number;
|
||||||
|
rateLimitPerUser: number;
|
||||||
|
rawRecipients: {
|
||||||
|
id: string;
|
||||||
|
avatar: string;
|
||||||
|
username: string;
|
||||||
|
public_flags: number;
|
||||||
|
discriminator: string;
|
||||||
|
}[];
|
||||||
|
recipients: string[];
|
||||||
|
rtcRegion: string;
|
||||||
|
threadMetadata: {
|
||||||
|
locked: boolean;
|
||||||
|
archived: boolean;
|
||||||
|
invitable: boolean;
|
||||||
|
createTimestamp: string | undefined;
|
||||||
|
autoArchiveDuration: number;
|
||||||
|
archiveTimestamp: string | undefined;
|
||||||
|
};
|
||||||
|
topic: string;
|
||||||
|
type: number;
|
||||||
|
userLimit: number;
|
||||||
|
videoQualityMode: undefined;
|
||||||
|
|
||||||
|
get accessPermissions(): bigint;
|
||||||
|
get lastActiveTimestamp(): number;
|
||||||
|
|
||||||
|
computeLurkerPermissionsAllowList(): unknown;
|
||||||
|
getApplicationId(): unknown;
|
||||||
|
getGuildId(): string;
|
||||||
|
getRecipientId(): unknown;
|
||||||
|
hasFlag(flag: number): boolean;
|
||||||
|
isActiveThread(): boolean;
|
||||||
|
isArchivedThread(): boolean;
|
||||||
|
isCategory(): boolean;
|
||||||
|
isDM(): boolean;
|
||||||
|
isDirectory(): boolean;
|
||||||
|
isForumChannel(): boolean;
|
||||||
|
isGroupDM(): boolean;
|
||||||
|
isGuildStageVoice(): boolean;
|
||||||
|
isGuildVoice(): boolean;
|
||||||
|
isListenModeCapable(): boolean;
|
||||||
|
isManaged(): boolean;
|
||||||
|
isMultiUserDM(): boolean;
|
||||||
|
isNSFW(): boolean;
|
||||||
|
isOwner(): boolean;
|
||||||
|
isPrivate(): boolean;
|
||||||
|
isSystemDM(): boolean;
|
||||||
|
isThread(): boolean;
|
||||||
|
isVocal(): boolean;
|
||||||
|
}
|
||||||
64
packages/discord-types/src/common/Guild.d.ts
vendored
Normal file
64
packages/discord-types/src/common/Guild.d.ts
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Role } from './Role';
|
||||||
|
import { DiscordRecord } from './Record';
|
||||||
|
|
||||||
|
// copy(Object.keys(findByProps("CREATOR_MONETIZABLE")).map(JSON.stringify).join("|"))
|
||||||
|
export type GuildFeatures =
|
||||||
|
"INVITE_SPLASH" | "VIP_REGIONS" | "VANITY_URL" | "MORE_EMOJI" | "MORE_STICKERS" | "MORE_SOUNDBOARD" | "VERIFIED" | "COMMERCE" | "DISCOVERABLE" | "COMMUNITY" | "FEATURABLE" | "NEWS" | "HUB" | "PARTNERED" | "ANIMATED_ICON" | "BANNER" | "ENABLED_DISCOVERABLE_BEFORE" | "WELCOME_SCREEN_ENABLED" | "MEMBER_VERIFICATION_GATE_ENABLED" | "PREVIEW_ENABLED" | "ROLE_SUBSCRIPTIONS_ENABLED" | "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" | "CREATOR_MONETIZABLE" | "CREATOR_MONETIZABLE_PROVISIONAL" | "CREATOR_MONETIZABLE_WHITEGLOVE" | "CREATOR_MONETIZABLE_DISABLED" | "CREATOR_MONETIZABLE_RESTRICTED" | "CREATOR_STORE_PAGE" | "CREATOR_MONETIZABLE_PENDING_NEW_OWNER_ONBOARDING" | "PRODUCTS_AVAILABLE_FOR_PURCHASE" | "GUILD_WEB_PAGE_VANITY_URL" | "THREADS_ENABLED" | "THREADS_ENABLED_TESTING" | "NEW_THREAD_PERMISSIONS" | "ROLE_ICONS" | "TEXT_IN_STAGE_ENABLED" | "TEXT_IN_VOICE_ENABLED" | "HAS_DIRECTORY_ENTRY" | "ANIMATED_BANNER" | "LINKED_TO_HUB" | "EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT" | "GUILD_HOME_DEPRECATION_OVERRIDE" | "GUILD_HOME_TEST" | "GUILD_HOME_OVERRIDE" | "GUILD_ONBOARDING" | "GUILD_ONBOARDING_EVER_ENABLED" | "GUILD_ONBOARDING_HAS_PROMPTS" | "GUILD_SERVER_GUIDE" | "INTERNAL_EMPLOYEE_ONLY" | "AUTO_MODERATION" | "INVITES_DISABLED" | "BURST_REACTIONS" | "SOUNDBOARD" | "SHARD" | "ACTIVITY_FEED_ENABLED_BY_USER" | "ACTIVITY_FEED_DISABLED_BY_USER" | "SUMMARIES_ENABLED_GA" | "LEADERBOARD_ENABLED" | "SUMMARIES_ENABLED_BY_USER" | "SUMMARIES_OPT_OUT_EXPERIENCE" | "CHANNEL_ICON_EMOJIS_GENERATED" | "NON_COMMUNITY_RAID_ALERTS" | "RAID_ALERTS_DISABLED" | "AUTOMOD_TRIGGER_USER_PROFILE" | "ENABLED_MODERATION_EXPERIENCE_FOR_NON_COMMUNITY" | "GUILD_PRODUCTS_ALLOW_ARCHIVED_FILE" | "CLAN" | "MEMBER_VERIFICATION_MANUAL_APPROVAL" | "FORWARDING_DISABLED" | "MEMBER_VERIFICATION_ROLLOUT_TEST" | "AUDIO_BITRATE_128_KBPS" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS" | "VIDEO_BITRATE_ENHANCED" | "MAX_FILE_SIZE_50_MB" | "MAX_FILE_SIZE_100_MB" | "GUILD_TAGS" | "ENHANCED_ROLE_COLORS" | "PREMIUM_TIER_3_OVERRIDE" | "REPORT_TO_MOD_PILOT" | "TIERLESS_BOOSTING_SYSTEM_MESSAGE";
|
||||||
|
export type GuildPremiumFeatures =
|
||||||
|
"ANIMATED_ICON" | "STAGE_CHANNEL_VIEWERS_150" | "ROLE_ICONS" | "GUILD_TAGS" | "BANNER" | "MAX_FILE_SIZE_50_MB" | "VIDEO_QUALITY_720_60FPS" | "STAGE_CHANNEL_VIEWERS_50" | "VIDEO_QUALITY_1080_60FPS" | "MAX_FILE_SIZE_100_MB" | "VANITY_URL" | "VIDEO_BITRATE_ENHANCED" | "STAGE_CHANNEL_VIEWERS_300" | "AUDIO_BITRATE_128_KBPS" | "ANIMATED_BANNER" | "TIERLESS_BOOSTING" | "ENHANCED_ROLE_COLORS" | "INVITE_SPLASH" | "AUDIO_BITRATE_256_KBPS" | "AUDIO_BITRATE_384_KBPS";
|
||||||
|
|
||||||
|
export class Guild extends DiscordRecord {
|
||||||
|
constructor(guild: object);
|
||||||
|
afkChannelId: string | undefined;
|
||||||
|
afkTimeout: number;
|
||||||
|
applicationCommandCounts: {
|
||||||
|
0: number;
|
||||||
|
1: number;
|
||||||
|
2: number;
|
||||||
|
};
|
||||||
|
application_id: unknown;
|
||||||
|
banner: string | undefined;
|
||||||
|
defaultMessageNotifications: number;
|
||||||
|
description: string | undefined;
|
||||||
|
discoverySplash: string | undefined;
|
||||||
|
explicitContentFilter: number;
|
||||||
|
features: Set<GuildFeatures>;
|
||||||
|
homeHeader: string | undefined;
|
||||||
|
hubType: unknown;
|
||||||
|
icon: string | undefined;
|
||||||
|
id: string;
|
||||||
|
joinedAt: Date;
|
||||||
|
latestOnboardingQuestionId: string | undefined;
|
||||||
|
maxMembers: number;
|
||||||
|
maxStageVideoChannelUsers: number;
|
||||||
|
maxVideoChannelUsers: number;
|
||||||
|
mfaLevel: number;
|
||||||
|
moderatorReporting: unknown;
|
||||||
|
name: string;
|
||||||
|
nsfwLevel: number;
|
||||||
|
ownerConfiguredContentLevel: number;
|
||||||
|
ownerId: string;
|
||||||
|
preferredLocale: string;
|
||||||
|
premiumFeatures: {
|
||||||
|
additionalEmojiSlots: number;
|
||||||
|
additionalSoundSlots: number;
|
||||||
|
additionalStickerSlots: number;
|
||||||
|
features: Array<GuildPremiumFeatures>;
|
||||||
|
};
|
||||||
|
premiumProgressBarEnabled: boolean;
|
||||||
|
premiumSubscriberCount: number;
|
||||||
|
premiumTier: number;
|
||||||
|
profile: {
|
||||||
|
badge: string | undefined;
|
||||||
|
tag: string | undefined;
|
||||||
|
} | undefined;
|
||||||
|
publicUpdatesChannelId: string | undefined;
|
||||||
|
roles: Record<string, Role>;
|
||||||
|
rulesChannelId: string | undefined;
|
||||||
|
safetyAlertsChannelId: string | undefined;
|
||||||
|
splash: string | undefined;
|
||||||
|
systemChannelFlags: number;
|
||||||
|
systemChannelId: string | undefined;
|
||||||
|
vanityURLCode: string | undefined;
|
||||||
|
verificationLevel: number;
|
||||||
|
}
|
||||||
26
packages/discord-types/src/common/GuildMember.d.ts
vendored
Normal file
26
packages/discord-types/src/common/GuildMember.d.ts
vendored
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
12
packages/discord-types/src/common/Record.d.ts
vendored
Normal file
12
packages/discord-types/src/common/Record.d.ts
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
type Updater = (value: any) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common Record class extended by various Discord data structures, like User, Channel, Guild, etc.
|
||||||
|
*/
|
||||||
|
export class DiscordRecord {
|
||||||
|
toJS(): Record<string, any>;
|
||||||
|
|
||||||
|
set(key: string, value: any): this;
|
||||||
|
merge(data: Record<string, any>): this;
|
||||||
|
update(key: string, defaultValueOrUpdater: Updater | any, updater?: Updater): this;
|
||||||
|
}
|
||||||
33
packages/discord-types/src/common/Role.d.ts
vendored
Normal file
33
packages/discord-types/src/common/Role.d.ts
vendored
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
65
packages/discord-types/src/common/User.d.ts
vendored
Normal file
65
packages/discord-types/src/common/User.d.ts
vendored
Normal file
|
|
@ -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<string, string>;
|
||||||
|
id: string;
|
||||||
|
mfaEnabled: boolean;
|
||||||
|
mobile: boolean;
|
||||||
|
nsfwAllowed: boolean | undefined;
|
||||||
|
phone: string | undefined;
|
||||||
|
premiumType: number | undefined;
|
||||||
|
premiumUsageFlags: number;
|
||||||
|
publicFlags: number;
|
||||||
|
purchasedFlags: number;
|
||||||
|
system: boolean;
|
||||||
|
username: string;
|
||||||
|
verified: boolean;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
9
packages/discord-types/src/common/index.d.ts
vendored
Normal file
9
packages/discord-types/src/common/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export * from "./Activity";
|
||||||
|
export * from "./Application";
|
||||||
|
export * from "./Channel";
|
||||||
|
export * from "./Guild";
|
||||||
|
export * from "./GuildMember";
|
||||||
|
export * from "./messages";
|
||||||
|
export * from "./Role";
|
||||||
|
export * from "./User";
|
||||||
|
export * from "./Record";
|
||||||
61
packages/discord-types/src/common/messages/Commands.d.ts
vendored
Normal file
61
packages/discord-types/src/common/messages/Commands.d.ts
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { Channel } from "../Channel";
|
||||||
|
import { Guild } from "../Guild";
|
||||||
|
import { Promisable } from "type-fest";
|
||||||
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "../../../enums";
|
||||||
|
|
||||||
|
export interface CommandContext {
|
||||||
|
channel: Channel;
|
||||||
|
guild?: Guild;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandOption {
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
type: ApplicationCommandOptionType;
|
||||||
|
description: string;
|
||||||
|
displayDescription?: string;
|
||||||
|
required?: boolean;
|
||||||
|
options?: CommandOption[];
|
||||||
|
choices?: Array<ChoicesOption>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChoicesOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandReturnValue {
|
||||||
|
content: string;
|
||||||
|
// TODO: implement
|
||||||
|
// cancel?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandArgument {
|
||||||
|
type: ApplicationCommandOptionType;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
focused: undefined;
|
||||||
|
options: CommandArgument[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Command {
|
||||||
|
id?: string;
|
||||||
|
applicationId?: string;
|
||||||
|
type?: ApplicationCommandType;
|
||||||
|
inputType?: ApplicationCommandInputType;
|
||||||
|
plugin?: string;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
untranslatedName?: string;
|
||||||
|
displayName?: string;
|
||||||
|
description: string;
|
||||||
|
untranslatedDescription?: string;
|
||||||
|
displayDescription?: string;
|
||||||
|
|
||||||
|
options?: CommandOption[];
|
||||||
|
predicate?(ctx: CommandContext): boolean;
|
||||||
|
|
||||||
|
execute(args: CommandArgument[], ctx: CommandContext): Promisable<void | CommandReturnValue>;
|
||||||
|
}
|
||||||
70
packages/discord-types/src/common/messages/Embed.d.ts
vendored
Normal file
70
packages/discord-types/src/common/messages/Embed.d.ts
vendored
Normal file
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
42
packages/discord-types/src/common/messages/Emoji.d.ts
vendored
Normal file
42
packages/discord-types/src/common/messages/Emoji.d.ts
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
export type Emoji = CustomEmoji | UnicodeEmoji;
|
||||||
|
|
||||||
|
export interface CustomEmoji {
|
||||||
|
type: 1;
|
||||||
|
allNamesString: string;
|
||||||
|
animated: boolean;
|
||||||
|
available: boolean;
|
||||||
|
guildId: string;
|
||||||
|
id: string;
|
||||||
|
managed: boolean;
|
||||||
|
name: string;
|
||||||
|
originalName?: string;
|
||||||
|
require_colons: boolean;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnicodeEmoji {
|
||||||
|
type: 0;
|
||||||
|
diversityChildren: Record<any, any>;
|
||||||
|
emojiObject: {
|
||||||
|
names: string[];
|
||||||
|
surrogates: string;
|
||||||
|
unicodeVersion: number;
|
||||||
|
};
|
||||||
|
index: number;
|
||||||
|
surrogates: string;
|
||||||
|
uniqueName: string;
|
||||||
|
useSpriteSheet: boolean;
|
||||||
|
get allNamesString(): string;
|
||||||
|
get animated(): boolean;
|
||||||
|
get defaultDiversityChild(): any;
|
||||||
|
get hasDiversity(): boolean | undefined;
|
||||||
|
get hasDiversityParent(): boolean | undefined;
|
||||||
|
get hasMultiDiversity(): boolean | undefined;
|
||||||
|
get hasMultiDiversityParent(): boolean | undefined;
|
||||||
|
get managed(): boolean;
|
||||||
|
get name(): string;
|
||||||
|
get names(): string[];
|
||||||
|
get optionallyDiverseSequence(): string | undefined;
|
||||||
|
get unicodeVersion(): number;
|
||||||
|
get url(): string;
|
||||||
|
}
|
||||||
204
packages/discord-types/src/common/messages/Message.d.ts
vendored
Normal file
204
packages/discord-types/src/common/messages/Message.d.ts
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { CommandOption } from './Commands';
|
||||||
|
import { User, UserJSON } from '../User';
|
||||||
|
import { Embed, EmbedJSON } from './Embed';
|
||||||
|
import { DiscordRecord } from "../Record";
|
||||||
|
import { MessageFlags, MessageType, StickerFormatType } from "../../../enums";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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: MessageFlags;
|
||||||
|
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;
|
||||||
|
messageSnapshots: {
|
||||||
|
message: Message;
|
||||||
|
}[];
|
||||||
|
nick: unknown; // probably a string
|
||||||
|
nonce: string | undefined;
|
||||||
|
pinned: boolean;
|
||||||
|
reactions: MessageReaction[];
|
||||||
|
state: string;
|
||||||
|
stickerItems: {
|
||||||
|
format_type: StickerFormatType;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
stickers: unknown[];
|
||||||
|
timestamp: moment.Moment;
|
||||||
|
tts: boolean;
|
||||||
|
type: MessageType;
|
||||||
|
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: MessageFlags): boolean;
|
||||||
|
isCommandType(): boolean;
|
||||||
|
isEdited(): boolean;
|
||||||
|
isSystemDM(): boolean;
|
||||||
|
|
||||||
|
/** Vencord added */
|
||||||
|
deleted?: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|")
|
||||||
|
export type MessageTypeSets = Record<
|
||||||
|
"UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE",
|
||||||
|
Set<MessageType>
|
||||||
|
>;
|
||||||
35
packages/discord-types/src/common/messages/Sticker.d.ts
vendored
Normal file
35
packages/discord-types/src/common/messages/Sticker.d.ts
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { StickerFormatType, StickerType } from "../../../enums";
|
||||||
|
|
||||||
|
interface BaseSticker {
|
||||||
|
asset: string;
|
||||||
|
available: boolean;
|
||||||
|
description: string;
|
||||||
|
format_type: StickerFormatType;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
sort_value?: number;
|
||||||
|
/** a comma separated string */
|
||||||
|
tags: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PackSticker extends BaseSticker {
|
||||||
|
pack_id: string;
|
||||||
|
type: StickerType.STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuildSticker extends BaseSticker {
|
||||||
|
guild_id: string;
|
||||||
|
type: StickerType.GUILD;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Sticker = PackSticker | GuildSticker;
|
||||||
|
|
||||||
|
export interface PremiumStickerPack {
|
||||||
|
banner_asset_id?: string;
|
||||||
|
cover_sticker_id?: string;
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
sku_id: string;
|
||||||
|
stickers: PackSticker[];
|
||||||
|
}
|
||||||
5
packages/discord-types/src/common/messages/index.d.ts
vendored
Normal file
5
packages/discord-types/src/common/messages/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./Commands";
|
||||||
|
export * from "./Message";
|
||||||
|
export * from "./Embed";
|
||||||
|
export * from "./Emoji";
|
||||||
|
export * from "./Sticker";
|
||||||
|
|
@ -1,25 +1,7 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react";
|
import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref, RefObject } from "react";
|
||||||
|
|
||||||
|
// 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-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";
|
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 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}`;
|
export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`;
|
||||||
|
|
||||||
|
|
@ -61,12 +43,7 @@ export type FormDivider = ComponentType<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type FormText = ComponentType<PropsWithChildren<{
|
export type FormText = ComponentType<TextProps>;
|
||||||
disabled?: boolean;
|
|
||||||
selectable?: boolean;
|
|
||||||
/** defaults to FormText.Types.DEFAULT */
|
|
||||||
type?: string;
|
|
||||||
}> & TextProps> & { Types: FormTextTypes; };
|
|
||||||
|
|
||||||
export type Tooltip = ComponentType<{
|
export type Tooltip = ComponentType<{
|
||||||
text: ReactNode | ComponentType;
|
text: ReactNode | ComponentType;
|
||||||
|
|
@ -262,8 +239,10 @@ export type TextInput = ComponentType<PropsWithChildren<{
|
||||||
Sizes: Record<"DEFAULT" | "MINI", string>;
|
Sizes: Record<"DEFAULT" | "MINI", string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FIXME: this is wrong, it's not actually just HTMLTextAreaElement
|
||||||
export type TextArea = ComponentType<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & {
|
export type TextArea = ComponentType<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & {
|
||||||
onChange(v: string): void;
|
onChange(v: string): void;
|
||||||
|
inputRef?: Ref<HTMLTextAreaElement>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface SelectOption {
|
interface SelectOption {
|
||||||
|
|
@ -490,19 +469,66 @@ export type MaskedLink = ComponentType<PropsWithChildren<{
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
export type ScrollerThin = ComponentType<PropsWithChildren<{
|
export interface ScrollerBaseProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
|
|
||||||
dir?: "ltr";
|
dir?: "ltr";
|
||||||
orientation?: "horizontal" | "vertical" | "auto";
|
|
||||||
paddingFix?: boolean;
|
paddingFix?: boolean;
|
||||||
fade?: boolean;
|
|
||||||
|
|
||||||
onClose?(): void;
|
onClose?(): void;
|
||||||
onScroll?(): void;
|
onScroll?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScrollerThin = ComponentType<PropsWithChildren<ScrollerBaseProps & {
|
||||||
|
orientation?: "horizontal" | "vertical" | "auto";
|
||||||
|
fade?: boolean;
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
|
interface BaseListItem {
|
||||||
|
anchorId: any;
|
||||||
|
listIndex: number;
|
||||||
|
offsetTop: number;
|
||||||
|
section: number;
|
||||||
|
}
|
||||||
|
interface ListSection extends BaseListItem {
|
||||||
|
type: "section";
|
||||||
|
}
|
||||||
|
interface ListRow extends BaseListItem {
|
||||||
|
type: "row";
|
||||||
|
row: number;
|
||||||
|
rowIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListScrollerThin = ComponentType<ScrollerBaseProps & {
|
||||||
|
sections: number[];
|
||||||
|
renderSection?: (item: ListSection) => React.ReactNode;
|
||||||
|
renderRow: (item: ListRow) => React.ReactNode;
|
||||||
|
renderFooter?: (item: any) => React.ReactNode;
|
||||||
|
renderSidebar?: (listVisible: boolean, sidebarVisible: boolean) => React.ReactNode;
|
||||||
|
wrapSection?: (section: number, children: React.ReactNode) => React.ReactNode;
|
||||||
|
|
||||||
|
sectionHeight: number;
|
||||||
|
rowHeight: number;
|
||||||
|
footerHeight?: number;
|
||||||
|
sidebarHeight?: number;
|
||||||
|
|
||||||
|
chunkSize?: number;
|
||||||
|
|
||||||
|
paddingTop?: number;
|
||||||
|
paddingBottom?: number;
|
||||||
|
fade?: boolean;
|
||||||
|
onResize?: Function;
|
||||||
|
getAnchorId?: any;
|
||||||
|
|
||||||
|
innerTag?: string;
|
||||||
|
innerId?: string;
|
||||||
|
innerClassName?: string;
|
||||||
|
innerRole?: string;
|
||||||
|
innerAriaLabel?: string;
|
||||||
|
// Yes, Discord uses this casing
|
||||||
|
innerAriaMultiselectable?: boolean;
|
||||||
|
innerAriaOrientation?: "vertical" | "horizontal";
|
||||||
|
}>;
|
||||||
|
|
||||||
export type Clickable = <T extends "a" | "div" | "span" | "li" = "div">(props: PropsWithChildren<ComponentPropsWithRef<T>> & {
|
export type Clickable = <T extends "a" | "div" | "span" | "li" = "div">(props: PropsWithChildren<ComponentPropsWithRef<T>> & {
|
||||||
tag?: T;
|
tag?: T;
|
||||||
}) => ReactNode;
|
}) => ReactNode;
|
||||||
30
packages/discord-types/src/flux.d.ts
vendored
Normal file
30
packages/discord-types/src/flux.d.ts
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { FluxStore } from "./stores/FluxStore";
|
||||||
|
|
||||||
|
export class FluxEmitter {
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
changeSentinel: number;
|
||||||
|
changedStores: Set<FluxStore>;
|
||||||
|
isBatchEmitting: boolean;
|
||||||
|
isDispatching: boolean;
|
||||||
|
isPaused: boolean;
|
||||||
|
pauseTimer: NodeJS.Timeout | null;
|
||||||
|
reactChangedStores: Set<FluxStore>;
|
||||||
|
|
||||||
|
batched(batch: (...args: any[]) => void): void;
|
||||||
|
destroy(): void;
|
||||||
|
emit(): void;
|
||||||
|
emitNonReactOnce(): void;
|
||||||
|
emitReactOnce(): void;
|
||||||
|
getChangeSentinel(): number;
|
||||||
|
getIsPaused(): boolean;
|
||||||
|
injectBatchEmitChanges(batch: (...args: any[]) => void): void;
|
||||||
|
markChanged(store: FluxStore): void;
|
||||||
|
pause(): void;
|
||||||
|
resume(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Flux {
|
||||||
|
Store: typeof FluxStore;
|
||||||
|
Emitter: FluxEmitter;
|
||||||
|
}
|
||||||
22
packages/discord-types/src/fluxEvents.d.ts
vendored
Normal file
22
packages/discord-types/src/fluxEvents.d.ts
vendored
Normal file
File diff suppressed because one or more lines are too long
10
packages/discord-types/src/index.d.ts
vendored
Normal file
10
packages/discord-types/src/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export * from "./common";
|
||||||
|
export * from "./classes";
|
||||||
|
export * from "./components";
|
||||||
|
export * from "./flux";
|
||||||
|
export * from "./fluxEvents";
|
||||||
|
export * from "./menu";
|
||||||
|
export * from "./modules";
|
||||||
|
export * from "./stores";
|
||||||
|
export * from "./utils";
|
||||||
|
export * as Webpack from "../webpack";
|
||||||
|
|
@ -1,21 +1,3 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react";
|
import type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react";
|
||||||
|
|
||||||
type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>;
|
type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>;
|
||||||
|
|
@ -73,7 +55,7 @@ export interface Menu {
|
||||||
renderValue?(value: number): string,
|
renderValue?(value: number): string,
|
||||||
}>;
|
}>;
|
||||||
MenuSearchControl: RC<{
|
MenuSearchControl: RC<{
|
||||||
query: string
|
query: string;
|
||||||
onChange(query: string): void;
|
onChange(query: string): void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}>;
|
}>;
|
||||||
74
packages/discord-types/src/modules/CloudUpload.d.ts
vendored
Normal file
74
packages/discord-types/src/modules/CloudUpload.d.ts
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import EventEmitter from "events";
|
||||||
|
import { CloudUploadPlatform } from "../../enums";
|
||||||
|
|
||||||
|
interface BaseUploadItem {
|
||||||
|
platform: CloudUploadPlatform;
|
||||||
|
id?: string;
|
||||||
|
origin?: string;
|
||||||
|
isThumbnail?: boolean;
|
||||||
|
clip?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReactNativeUploadItem extends BaseUploadItem {
|
||||||
|
platform: CloudUploadPlatform.REACT_NATIVE;
|
||||||
|
uri: string;
|
||||||
|
filename?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
durationSecs?: number;
|
||||||
|
waveform?: string;
|
||||||
|
isRemix?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebUploadItem extends BaseUploadItem {
|
||||||
|
platform: CloudUploadPlatform.WEB;
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem;
|
||||||
|
|
||||||
|
export class CloudUpload extends EventEmitter {
|
||||||
|
constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number);
|
||||||
|
|
||||||
|
channelId: string;
|
||||||
|
classification: string;
|
||||||
|
clip: unknown;
|
||||||
|
contentHash: unknown;
|
||||||
|
currentSize: number;
|
||||||
|
description: string | null;
|
||||||
|
durationSecs: number | undefined;
|
||||||
|
etag: string | undefined;
|
||||||
|
error: unknown;
|
||||||
|
filename: string;
|
||||||
|
id: string;
|
||||||
|
isImage: boolean;
|
||||||
|
isRemix: boolean | undefined;
|
||||||
|
isThumbnail: boolean;
|
||||||
|
isVideo: boolean;
|
||||||
|
item: {
|
||||||
|
file: File;
|
||||||
|
platform: CloudUploadPlatform;
|
||||||
|
origin: string;
|
||||||
|
};
|
||||||
|
loaded: number;
|
||||||
|
mimeType: string;
|
||||||
|
origin: string;
|
||||||
|
postCompressionSize: number | undefined;
|
||||||
|
preCompressionSize: number;
|
||||||
|
responseUrl: string;
|
||||||
|
sensitive: boolean;
|
||||||
|
showLargeMessageDialog: boolean;
|
||||||
|
spoiler: boolean;
|
||||||
|
startTime: number;
|
||||||
|
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT";
|
||||||
|
uniqueId: string;
|
||||||
|
uploadedFilename: string;
|
||||||
|
waveform: string | undefined;
|
||||||
|
|
||||||
|
// there are many more methods than just these but I didn't find them particularly useful
|
||||||
|
upload(): Promise<void>;
|
||||||
|
cancel(): void;
|
||||||
|
delete(): Promise<void>;
|
||||||
|
getSize(): number;
|
||||||
|
maybeConvertToWebP(): Promise<void>;
|
||||||
|
removeFromMsgDraft(): void;
|
||||||
|
}
|
||||||
1
packages/discord-types/src/modules/index.d.ts
vendored
Normal file
1
packages/discord-types/src/modules/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./CloudUpload";
|
||||||
11
packages/discord-types/src/stores/AuthenticationStore.d.ts
vendored
Normal file
11
packages/discord-types/src/stores/AuthenticationStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
export class AuthenticationStore extends FluxStore {
|
||||||
|
/**
|
||||||
|
* Gets the id of the current user
|
||||||
|
*/
|
||||||
|
getId(): string;
|
||||||
|
|
||||||
|
// This Store has a lot more methods related to everything Auth, but they really should
|
||||||
|
// not be needed, so they are not typed
|
||||||
|
}
|
||||||
24
packages/discord-types/src/stores/ChannelStore.d.ts
vendored
Normal file
24
packages/discord-types/src/stores/ChannelStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Channel, FluxStore } from "..";
|
||||||
|
|
||||||
|
export class ChannelStore extends FluxStore {
|
||||||
|
getChannel(channelId: string): Channel;
|
||||||
|
getBasicChannel(channelId: string): Channel | undefined;
|
||||||
|
hasChannel(channelId: string): boolean;
|
||||||
|
|
||||||
|
getChannelIds(guildId?: string | null): string[];
|
||||||
|
getMutableBasicGuildChannelsForGuild(guildId: string): Record<string, Channel>;
|
||||||
|
getMutableGuildChannelsForGuild(guildId: string): Record<string, Channel>;
|
||||||
|
getAllThreadsForGuild(guildId: string): Channel[];
|
||||||
|
getAllThreadsForParent(channelId: string): Channel[];
|
||||||
|
|
||||||
|
getDMFromUserId(userId: string): string;
|
||||||
|
getDMChannelFromUserId(userId: string): Channel | undefined;
|
||||||
|
getDMUserIds(): string[];
|
||||||
|
getMutableDMsByUserIds(): Record<string, string>;
|
||||||
|
getMutablePrivateChannels(): Record<string, Channel>;
|
||||||
|
getSortedPrivateChannels(): Channel[];
|
||||||
|
|
||||||
|
getGuildChannelsVersion(guildId: string): number;
|
||||||
|
getPrivateChannelsVersion(): number;
|
||||||
|
getInitialOverlayState(): Record<string, Channel>;
|
||||||
|
}
|
||||||
43
packages/discord-types/src/stores/DraftStore.d.ts
vendored
Normal file
43
packages/discord-types/src/stores/DraftStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
export enum DraftType {
|
||||||
|
ChannelMessage = 0,
|
||||||
|
ThreadSettings = 1,
|
||||||
|
FirstThreadMessage = 2,
|
||||||
|
ApplicationLauncherCommand = 3,
|
||||||
|
Poll = 4,
|
||||||
|
SlashCommand = 5,
|
||||||
|
ForwardContextMessage = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Draft {
|
||||||
|
timestamp: number;
|
||||||
|
draft: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThreadSettingsDraft {
|
||||||
|
timestamp: number;
|
||||||
|
parentMessageId?: string;
|
||||||
|
name?: string;
|
||||||
|
isPrivate?: boolean;
|
||||||
|
parentChannelId?: string;
|
||||||
|
location?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChannelDrafts = {
|
||||||
|
[DraftType.ThreadSettings]: ThreadSettingsDraft;
|
||||||
|
} & {
|
||||||
|
[key in Exclude<DraftType, DraftType.ThreadSettings>]: Draft;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserDrafts = Partial<Record<string, ChannelDrafts>>;
|
||||||
|
export type DraftState = Partial<Record<string, UserDrafts>>;
|
||||||
|
|
||||||
|
export class DraftStore extends FluxStore {
|
||||||
|
getState(): DraftState;
|
||||||
|
getRecentlyEditedDrafts(type: DraftType): Array<Draft & { channelId: string; }>;
|
||||||
|
getDraft(channelId: string, type: DraftType): string;
|
||||||
|
|
||||||
|
getThreadSettings(channelId: string): ThreadSettingsDraft | null | undefined;
|
||||||
|
getThreadDraftWithParentMessageId(parentMessageId: string): ThreadSettingsDraft | null | undefined;
|
||||||
|
}
|
||||||
57
packages/discord-types/src/stores/EmojiStore.d.ts
vendored
Normal file
57
packages/discord-types/src/stores/EmojiStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Channel, CustomEmoji, Emoji, FluxStore } from "..";
|
||||||
|
|
||||||
|
export class EmojiStore extends FluxStore {
|
||||||
|
getCustomEmojiById(id?: string | null): CustomEmoji | undefined;
|
||||||
|
getUsableCustomEmojiById(id?: string | null): CustomEmoji | undefined;
|
||||||
|
getGuilds(): Record<string, {
|
||||||
|
id: string;
|
||||||
|
get emojis(): CustomEmoji[];
|
||||||
|
get rawEmojis(): CustomEmoji[];
|
||||||
|
get usableEmojis(): CustomEmoji[];
|
||||||
|
get emoticons(): any[];
|
||||||
|
getEmoji(id: string): CustomEmoji | undefined;
|
||||||
|
isUsable(emoji: CustomEmoji): boolean;
|
||||||
|
}>;
|
||||||
|
getGuildEmoji(guildId?: string | null): CustomEmoji[];
|
||||||
|
getNewlyAddedEmoji(guildId?: string | null): CustomEmoji[];
|
||||||
|
getTopEmoji(guildId?: string | null): CustomEmoji[];
|
||||||
|
getTopEmojisMetadata(guildId?: string | null): {
|
||||||
|
emojiIds: string[];
|
||||||
|
topEmojisTTL: number;
|
||||||
|
};
|
||||||
|
hasPendingUsage(): boolean;
|
||||||
|
hasUsableEmojiInAnyGuild(): boolean;
|
||||||
|
searchWithoutFetchingLatest(data: any): any;
|
||||||
|
getSearchResultsOrder(...args: any[]): any;
|
||||||
|
getState(): {
|
||||||
|
pendingUsages: { key: string, timestamp: number; }[];
|
||||||
|
};
|
||||||
|
searchWithoutFetchingLatest(data: {
|
||||||
|
channel: Channel;
|
||||||
|
query: string;
|
||||||
|
count?: number;
|
||||||
|
intention: number;
|
||||||
|
includeExternalGuilds?: boolean;
|
||||||
|
matchComparator?(name: string): boolean;
|
||||||
|
}): Record<"locked" | "unlocked", Emoji[]>;
|
||||||
|
|
||||||
|
getDisambiguatedEmojiContext(): {
|
||||||
|
backfillTopEmojis: Record<any, any>;
|
||||||
|
customEmojis: Record<string, CustomEmoji>;
|
||||||
|
emojisById: Record<string, CustomEmoji>;
|
||||||
|
emojisByName: Record<string, CustomEmoji>;
|
||||||
|
emoticonRegex: RegExp | null;
|
||||||
|
emoticonsByName: Record<string, any>;
|
||||||
|
escapedEmoticonNames: string;
|
||||||
|
favoriteNamesAndIds?: any;
|
||||||
|
favorites?: any;
|
||||||
|
frequentlyUsed?: any;
|
||||||
|
groupedCustomEmojis: Record<string, CustomEmoji[]>;
|
||||||
|
guildId?: string;
|
||||||
|
isFavoriteEmojiWithoutFetchingLatest(e: Emoji): boolean;
|
||||||
|
newlyAddedEmoji: Record<string, CustomEmoji[]>;
|
||||||
|
topEmojis?: any;
|
||||||
|
unicodeAliases: Record<string, string>;
|
||||||
|
get favoriteEmojisWithoutFetchingLatest(): Emoji[];
|
||||||
|
};
|
||||||
|
}
|
||||||
44
packages/discord-types/src/stores/FluxStore.d.ts
vendored
Normal file
44
packages/discord-types/src/stores/FluxStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { FluxDispatcher, FluxEvents } from "..";
|
||||||
|
|
||||||
|
type Callback = () => void;
|
||||||
|
|
||||||
|
/*
|
||||||
|
For some reason, this causes type errors when you try to destructure it:
|
||||||
|
```ts
|
||||||
|
interface FluxEvent {
|
||||||
|
type: FluxEvents;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
export type FluxEvent = any;
|
||||||
|
|
||||||
|
export type ActionHandler = (event: FluxEvent) => void;
|
||||||
|
export type ActionHandlers = Partial<Record<FluxEvents, ActionHandler>>;
|
||||||
|
|
||||||
|
export class FluxStore {
|
||||||
|
constructor(dispatcher: FluxDispatcher, actionHandlers?: ActionHandlers);
|
||||||
|
|
||||||
|
getName(): string;
|
||||||
|
|
||||||
|
addChangeListener(callback: Callback): void;
|
||||||
|
/** Listener will be removed once the callback returns false. */
|
||||||
|
addConditionalChangeListener(callback: () => boolean, preemptive?: boolean): void;
|
||||||
|
addReactChangeListener(callback: Callback): void;
|
||||||
|
removeChangeListener(callback: Callback): void;
|
||||||
|
removeReactChangeListener(callback: Callback): void;
|
||||||
|
|
||||||
|
doEmitChanges(event: FluxEvent): void;
|
||||||
|
emitChange(): void;
|
||||||
|
|
||||||
|
getDispatchToken(): string;
|
||||||
|
initialize(): void;
|
||||||
|
initializeIfNeeded(): void;
|
||||||
|
/** this is a setter */
|
||||||
|
mustEmitChanges(actionHandler: ActionHandler | undefined): void;
|
||||||
|
registerActionHandlers(actionHandlers: ActionHandlers): void;
|
||||||
|
syncWith(stores: FluxStore[], callback: Callback, timeout?: number): void;
|
||||||
|
waitFor(...stores: FluxStore[]): void;
|
||||||
|
|
||||||
|
static getAll(): FluxStore[];
|
||||||
|
}
|
||||||
27
packages/discord-types/src/stores/GuildMemberStore.d.ts
vendored
Normal file
27
packages/discord-types/src/stores/GuildMemberStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { FluxStore, GuildMember } from "..";
|
||||||
|
|
||||||
|
export class GuildMemberStore extends FluxStore {
|
||||||
|
/** @returns Format: [guildId-userId: Timestamp (string)] */
|
||||||
|
getCommunicationDisabledUserMap(): Record<string, string>;
|
||||||
|
getCommunicationDisabledVersion(): number;
|
||||||
|
|
||||||
|
getMutableAllGuildsAndMembers(): Record<string, Record<string, GuildMember>>;
|
||||||
|
|
||||||
|
getMember(guildId: string, userId: string): GuildMember | null;
|
||||||
|
getTrueMember(guildId: string, userId: string): GuildMember | null;
|
||||||
|
getMemberIds(guildId: string): string[];
|
||||||
|
getMembers(guildId: string): GuildMember[];
|
||||||
|
|
||||||
|
getCachedSelfMember(guildId: string): GuildMember | null;
|
||||||
|
getSelfMember(guildId: string): GuildMember | null;
|
||||||
|
getSelfMemberJoinedAt(guildId: string): Date | null;
|
||||||
|
|
||||||
|
getNick(guildId: string, userId: string): string | null;
|
||||||
|
getNicknameGuildsMapping(userId: string): Record<string, string[]>;
|
||||||
|
getNicknames(userId: string): string[];
|
||||||
|
|
||||||
|
isMember(guildId: string, userId: string): boolean;
|
||||||
|
isMember(guildId: string, userId: string): boolean;
|
||||||
|
isGuestOrLurker(guildId: string, userId: string): boolean;
|
||||||
|
isCurrentUserGuest(guildId: string): boolean;
|
||||||
|
}
|
||||||
8
packages/discord-types/src/stores/GuildRoleStore.d.ts
vendored
Normal file
8
packages/discord-types/src/stores/GuildRoleStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { FluxStore, Role } from "..";
|
||||||
|
|
||||||
|
// TODO: add the rest of the methods for GuildRoleStore
|
||||||
|
export class GuildRoleStore extends FluxStore {
|
||||||
|
getRole(guildId: string, roleId: string): Role;
|
||||||
|
getSortedRoles(guildId: string): Role[];
|
||||||
|
getRolesSnapshot(guildId: string): Record<string, Role>;
|
||||||
|
}
|
||||||
8
packages/discord-types/src/stores/GuildStore.d.ts
vendored
Normal file
8
packages/discord-types/src/stores/GuildStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Guild, FluxStore } from "..";
|
||||||
|
|
||||||
|
export class GuildStore extends FluxStore {
|
||||||
|
getGuild(guildId: string): Guild;
|
||||||
|
getGuildCount(): number;
|
||||||
|
getGuilds(): Record<string, Guild>;
|
||||||
|
getGuildIds(): string[];
|
||||||
|
}
|
||||||
13
packages/discord-types/src/stores/MessageStore.d.ts
vendored
Normal file
13
packages/discord-types/src/stores/MessageStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { MessageJSON, FluxStore, Message } from "..";
|
||||||
|
|
||||||
|
export class MessageStore extends FluxStore {
|
||||||
|
getMessage(channelId: string, messageId: string): Message;
|
||||||
|
/** @returns This return object is fucking huge; I'll type it later. */
|
||||||
|
getMessages(channelId: string): unknown;
|
||||||
|
getRawMessages(channelId: string): Record<string | number, MessageJSON>;
|
||||||
|
hasCurrentUserSentMessage(channelId: string): boolean;
|
||||||
|
hasPresent(channelId: string): boolean;
|
||||||
|
isLoadingMessages(channelId: string): boolean;
|
||||||
|
jumpedMessageId(channelId: string): string | undefined;
|
||||||
|
whenReady(channelId: string, callback: () => void): void;
|
||||||
|
}
|
||||||
26
packages/discord-types/src/stores/RelationshipStore.d.ts
vendored
Normal file
26
packages/discord-types/src/stores/RelationshipStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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;
|
||||||
|
/**
|
||||||
|
* @see {@link isBlocked}
|
||||||
|
* @see {@link isIgnored}
|
||||||
|
*/
|
||||||
|
isBlockedOrIgnored(userId: string): boolean;
|
||||||
|
getSince(userId: string): string;
|
||||||
|
|
||||||
|
getMutableRelationships(): Map<string, number>;
|
||||||
|
}
|
||||||
14
packages/discord-types/src/stores/SelectedChannelStore.d.ts
vendored
Normal file
14
packages/discord-types/src/stores/SelectedChannelStore.d.ts
vendored
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
14
packages/discord-types/src/stores/SelectedGuildStore.d.ts
vendored
Normal file
14
packages/discord-types/src/stores/SelectedGuildStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
export interface SelectedGuildState {
|
||||||
|
selectedGuildTimestampMillis: Record<string | number, number>;
|
||||||
|
selectedGuildId: string | null;
|
||||||
|
lastSelectedGuildId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SelectedGuildStore extends FluxStore {
|
||||||
|
getGuildId(): string | null;
|
||||||
|
getLastSelectedGuildId(): string | null;
|
||||||
|
getLastSelectedTimestamp(guildId: string): number | null;
|
||||||
|
getState(): SelectedGuildState | undefined;
|
||||||
|
}
|
||||||
15
packages/discord-types/src/stores/StickersStore.d.ts
vendored
Normal file
15
packages/discord-types/src/stores/StickersStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { FluxStore, GuildSticker, PremiumStickerPack, Sticker } from "..";
|
||||||
|
|
||||||
|
export type StickerGuildMap = Map<string, GuildSticker[]>;
|
||||||
|
|
||||||
|
export class StickersStore extends FluxStore {
|
||||||
|
getAllGuildStickers(): StickerGuildMap;
|
||||||
|
getRawStickersByGuild(): StickerGuildMap;
|
||||||
|
getPremiumPacks(): PremiumStickerPack[];
|
||||||
|
|
||||||
|
getStickerById(id: string): Sticker | undefined;
|
||||||
|
getStickerPack(id: string): PremiumStickerPack | undefined;
|
||||||
|
getStickersByGuildId(guildId: string): Sticker[] | undefined;
|
||||||
|
|
||||||
|
isPremiumPack(id: string): boolean;
|
||||||
|
}
|
||||||
11
packages/discord-types/src/stores/StreamerModeStore.d.ts
vendored
Normal file
11
packages/discord-types/src/stores/StreamerModeStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { FluxStore } from "@vencord/discord-types";
|
||||||
|
|
||||||
|
export class StreamerModeStore extends FluxStore {
|
||||||
|
get autoToggle(): boolean;
|
||||||
|
get disableNotifications(): boolean;
|
||||||
|
get disableSounds(): boolean;
|
||||||
|
get enableContentProtection(): boolean;
|
||||||
|
get enabled(): boolean;
|
||||||
|
get hideInstantInvites(): boolean;
|
||||||
|
get hidePersonalInformation(): boolean;
|
||||||
|
}
|
||||||
18
packages/discord-types/src/stores/ThemeStore.d.ts
vendored
Normal file
18
packages/discord-types/src/stores/ThemeStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
export type ThemePreference = "dark" | "light" | "unknown";
|
||||||
|
export type SystemTheme = "dark" | "light";
|
||||||
|
export type Theme = "light" | "dark" | "darker" | "midnight";
|
||||||
|
|
||||||
|
export interface ThemeState {
|
||||||
|
theme: Theme;
|
||||||
|
status: 0 | 1;
|
||||||
|
preferences: Record<ThemePreference, Theme>;
|
||||||
|
}
|
||||||
|
export class ThemeStore extends FluxStore {
|
||||||
|
get theme(): Theme;
|
||||||
|
get darkSidebar(): boolean;
|
||||||
|
get systemTheme(): SystemTheme;
|
||||||
|
themePreferenceForSystemTheme(preference: ThemePreference): Theme;
|
||||||
|
getState(): ThemeState;
|
||||||
|
}
|
||||||
9
packages/discord-types/src/stores/TypingStore.d.ts
vendored
Normal file
9
packages/discord-types/src/stores/TypingStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
export class TypingStore extends FluxStore {
|
||||||
|
/**
|
||||||
|
* returns a map of user ids to timeout ids
|
||||||
|
*/
|
||||||
|
getTypingUsers(channelId: string): Record<string, number>;
|
||||||
|
isTyping(channelId: string, userId: string): boolean;
|
||||||
|
}
|
||||||
151
packages/discord-types/src/stores/UserProfileStore.d.ts
vendored
Normal file
151
packages/discord-types/src/stores/UserProfileStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { FluxStore, Guild, User, Application, ApplicationInstallParams } from "..";
|
||||||
|
import { ApplicationIntegrationType } from "../../enums";
|
||||||
|
|
||||||
|
export interface MutualFriend {
|
||||||
|
/**
|
||||||
|
* the userid of the mutual friend
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
/**
|
||||||
|
* the status of the mutual friend
|
||||||
|
*/
|
||||||
|
status: "online" | "offline" | "idle" | "dnd";
|
||||||
|
/**
|
||||||
|
* the user object of the mutual friend
|
||||||
|
*/
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MutualGuild {
|
||||||
|
/**
|
||||||
|
* the guild object of the mutual guild
|
||||||
|
*/
|
||||||
|
guild: Guild;
|
||||||
|
/**
|
||||||
|
* the user's nickname in the guild, if any
|
||||||
|
*/
|
||||||
|
nick: string | null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileBadge {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
link?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConnectedAccount {
|
||||||
|
type: "twitch" | "youtube" | "skype" | "steam" | "leagueoflegends" | "battlenet" | "bluesky" | "bungie" | "reddit" | "twitter" | "twitter_legacy" | "spotify" | "facebook" | "xbox" | "samsung" | "contacts" | "instagram" | "mastodon" | "soundcloud" | "github" | "playstation" | "playstation-stg" | "epicgames" | "riotgames" | "roblox" | "paypal" | "ebay" | "tiktok" | "crunchyroll" | "domain" | "amazon-music";
|
||||||
|
/**
|
||||||
|
* underlying id of connected account
|
||||||
|
* eg. account uuid
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* display name of connected account
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
verified: boolean;
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileApplication {
|
||||||
|
id: string;
|
||||||
|
customInstallUrl: string | undefined;
|
||||||
|
installParams: ApplicationInstallParams | undefined;
|
||||||
|
flags: number;
|
||||||
|
popularApplicationCommandIds?: string[];
|
||||||
|
integrationTypesConfig: Record<ApplicationIntegrationType, Partial<{
|
||||||
|
oauth2_install_params: ApplicationInstallParams;
|
||||||
|
}>>;
|
||||||
|
primarySkuId: string | undefined;
|
||||||
|
storefront_available: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserProfileBase extends Pick<User, "banner"> {
|
||||||
|
accentColor: number | null;
|
||||||
|
/**
|
||||||
|
* often empty for guild profiles, get the user profile for badges
|
||||||
|
*/
|
||||||
|
badges: ProfileBadge[];
|
||||||
|
bio: string | undefined;
|
||||||
|
popoutAnimationParticleType: string | null;
|
||||||
|
profileEffectExpiresAt: number | Date | undefined;
|
||||||
|
profileEffectId: undefined | string;
|
||||||
|
/**
|
||||||
|
* often an empty string when not set
|
||||||
|
*/
|
||||||
|
pronouns: string | "" | undefined;
|
||||||
|
themeColors: [number, number] | undefined;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApplicationRoleConnection {
|
||||||
|
application: Application;
|
||||||
|
application_metadata: Record<string, any>;
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
platform_name: string;
|
||||||
|
platform_username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserProfile extends UserProfileBase, Pick<User, "premiumType"> {
|
||||||
|
/** If this is a bot user profile, this will be its application */
|
||||||
|
application: ProfileApplication | null;
|
||||||
|
applicationRoleConnections: ApplicationRoleConnection[] | undefined;
|
||||||
|
connectedAccounts: ConnectedAccount[] | undefined;
|
||||||
|
fetchStartedAt: number;
|
||||||
|
fetchEndedAt: number;
|
||||||
|
legacyUsername: string | undefined;
|
||||||
|
premiumGuildSince: Date | null;
|
||||||
|
premiumSince: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserProfileStore extends FluxStore {
|
||||||
|
/**
|
||||||
|
* @param userId the user ID of the profile being fetched.
|
||||||
|
* @param guildId the guild ID to of the profile being fetched.
|
||||||
|
* defaults to the internal symbol `NO GUILD ID` if nullish
|
||||||
|
*
|
||||||
|
* @returns true if the profile is being fetched, false otherwise.
|
||||||
|
*/
|
||||||
|
isFetchingProfile(userId: string, guildId?: string): boolean;
|
||||||
|
/**
|
||||||
|
* Check if mutual friends for {@link userId} are currently being fetched.
|
||||||
|
*
|
||||||
|
* @param userId the user ID of the mutual friends being fetched.
|
||||||
|
*
|
||||||
|
* @returns true if mutual friends are being fetched, false otherwise.
|
||||||
|
*/
|
||||||
|
isFetchingFriends(userId: string): boolean;
|
||||||
|
|
||||||
|
get isSubmitting(): boolean;
|
||||||
|
|
||||||
|
getUserProfile(userId: string): UserProfile | undefined;
|
||||||
|
|
||||||
|
getGuildMemberProfile(userId: string, guildId: string | undefined): UserProfileBase | null;
|
||||||
|
/**
|
||||||
|
* Get the mutual friends of a user.
|
||||||
|
*
|
||||||
|
* @param userId the user ID of the user to get the mutual friends of.
|
||||||
|
*
|
||||||
|
* @returns an array of mutual friends, or undefined if the user has no mutual friends
|
||||||
|
*/
|
||||||
|
getMutualFriends(userId: string): MutualFriend[] | undefined;
|
||||||
|
/**
|
||||||
|
* Get the count of mutual friends for a user.
|
||||||
|
*
|
||||||
|
* @param userId the user ID of the user to get the mutual friends count of.
|
||||||
|
*
|
||||||
|
* @returns the count of mutual friends, or undefined if the user has no mutual friends
|
||||||
|
*/
|
||||||
|
getMutualFriendsCount(userId: string): number | undefined;
|
||||||
|
/**
|
||||||
|
* Get the mutual guilds of a user.
|
||||||
|
*
|
||||||
|
* @param userId the user ID of the user to get the mutual guilds of.
|
||||||
|
*
|
||||||
|
* @returns an array of mutual guilds, or undefined if the user has no mutual guilds
|
||||||
|
*/
|
||||||
|
getMutualGuilds(userId: string): MutualGuild[] | undefined;
|
||||||
|
}
|
||||||
10
packages/discord-types/src/stores/UserStore.d.ts
vendored
Normal file
10
packages/discord-types/src/stores/UserStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { FluxStore, User } from "..";
|
||||||
|
|
||||||
|
export class UserStore extends FluxStore {
|
||||||
|
filter(filter: (user: User) => boolean, sort?: boolean): Record<string, User>;
|
||||||
|
findByTag(username: string, discriminator: string): User;
|
||||||
|
forEach(action: (user: User) => void): void;
|
||||||
|
getCurrentUser(): User;
|
||||||
|
getUser(userId: string): User;
|
||||||
|
getUsers(): Record<string, User>;
|
||||||
|
}
|
||||||
42
packages/discord-types/src/stores/VoiceStateStore.d.ts
vendored
Normal file
42
packages/discord-types/src/stores/VoiceStateStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { DiscordRecord } from "../common";
|
||||||
|
import { FluxStore } from "./FluxStore";
|
||||||
|
|
||||||
|
export type UserVoiceStateRecords = Record<string, VoiceState>;
|
||||||
|
export type VoiceStates = Record<string, UserVoiceStateRecords>;
|
||||||
|
|
||||||
|
export interface VoiceState extends DiscordRecord {
|
||||||
|
userId: string;
|
||||||
|
channelId: string | null | undefined;
|
||||||
|
sessionId: string | null | undefined;
|
||||||
|
mute: boolean;
|
||||||
|
deaf: boolean;
|
||||||
|
selfMute: boolean;
|
||||||
|
selfDeaf: boolean;
|
||||||
|
selfVideo: boolean;
|
||||||
|
selfStream: boolean | undefined;
|
||||||
|
suppress: boolean;
|
||||||
|
requestToSpeakTimestamp: string | null | undefined;
|
||||||
|
discoverable: boolean;
|
||||||
|
|
||||||
|
isVoiceMuted(): boolean;
|
||||||
|
isVoiceDeafened(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VoiceStateStore extends FluxStore {
|
||||||
|
getAllVoiceStates(): VoiceStates;
|
||||||
|
|
||||||
|
getVoiceStates(guildId?: string | null): UserVoiceStateRecords;
|
||||||
|
getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords;
|
||||||
|
getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords;
|
||||||
|
|
||||||
|
getVoiceState(guildId: string | null, userId: string): VoiceState | undefined;
|
||||||
|
getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined;
|
||||||
|
getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined;
|
||||||
|
getVoiceStateForUser(userId: string): VoiceState | undefined;
|
||||||
|
|
||||||
|
getCurrentClientVoiceChannelId(guildId: string | null): string | undefined;
|
||||||
|
isCurrentClientInVoiceChannel(): boolean;
|
||||||
|
|
||||||
|
isInChannel(channelId: string, userId?: string): boolean;
|
||||||
|
hasVideo(channelId: string): boolean;
|
||||||
|
}
|
||||||
7
packages/discord-types/src/stores/WindowStore.d.ts
vendored
Normal file
7
packages/discord-types/src/stores/WindowStore.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { FluxStore } from "..";
|
||||||
|
|
||||||
|
export class WindowStore extends FluxStore {
|
||||||
|
isElementFullScreen(): boolean;
|
||||||
|
isFocused(): boolean;
|
||||||
|
windowSize(): Record<"width" | "height", number>;
|
||||||
|
}
|
||||||
38
packages/discord-types/src/stores/index.d.ts
vendored
Normal file
38
packages/discord-types/src/stores/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// please keep in alphabetical order
|
||||||
|
export * from "./AuthenticationStore";
|
||||||
|
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 "./StickersStore";
|
||||||
|
export * from "./StreamerModeStore";
|
||||||
|
export * from "./ThemeStore";
|
||||||
|
export * from "./TypingStore";
|
||||||
|
export * from "./UserProfileStore";
|
||||||
|
export * from "./UserStore";
|
||||||
|
export * from "./VoiceStateStore";
|
||||||
|
export * from "./WindowStore";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React hook that returns stateful data for one or more stores
|
||||||
|
* You might need a custom comparator (4th argument) if your store data is an object
|
||||||
|
* @param stores The stores to listen to
|
||||||
|
* @param mapper A function that returns the data you need
|
||||||
|
* @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook
|
||||||
|
* @param isEqual A custom comparator for the data returned by mapper
|
||||||
|
*
|
||||||
|
* @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id);
|
||||||
|
*/
|
||||||
|
export type useStateFromStores = <T>(
|
||||||
|
stores: any[],
|
||||||
|
mapper: () => T,
|
||||||
|
dependencies?: any,
|
||||||
|
isEqual?: (old: T, newer: T) => boolean
|
||||||
|
) => T;
|
||||||
|
|
@ -1,22 +1,4 @@
|
||||||
/*
|
import { Channel, Guild, GuildMember, Message, User } from ".";
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Channel, Guild, GuildMember, Message, User } from "discord-types/general";
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { LiteralUnion } from "type-fest";
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
|
|
@ -24,13 +6,15 @@ import type { FluxEvents } from "./fluxEvents";
|
||||||
|
|
||||||
export { FluxEvents };
|
export { FluxEvents };
|
||||||
|
|
||||||
|
type FluxEventsAutoComplete = LiteralUnion<FluxEvents, string>;
|
||||||
|
|
||||||
export interface FluxDispatcher {
|
export interface FluxDispatcher {
|
||||||
_actionHandlers: any;
|
_actionHandlers: any;
|
||||||
_subscriptions: any;
|
_subscriptions: any;
|
||||||
dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise<void>;
|
dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise<void>;
|
||||||
isDispatching(): boolean;
|
isDispatching(): boolean;
|
||||||
subscribe(event: FluxEvents, callback: (data: any) => void): void;
|
subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
|
||||||
unsubscribe(event: FluxEvents, callback: (data: any) => void): void;
|
unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
|
||||||
wait(callback: () => void): void;
|
wait(callback: () => void): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,3 +319,19 @@ export interface DateUtils {
|
||||||
dateFormat(date: Date, format: string): string;
|
dateFormat(date: Date, format: string): string;
|
||||||
diffAsUnits(start: Date, end: Date, stopAtOneSecond?: boolean): Record<"days" | "hours" | "minutes" | "seconds", number>;
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a Discord client mod
|
* @vencord/discord-types
|
||||||
* Copyright (c) 2024 Vendicated, Nuckyz and contributors
|
* 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 ModuleExports = any;
|
||||||
|
|
||||||
export type Module = {
|
export type Module = {
|
||||||
|
|
@ -215,24 +213,3 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
|
||||||
/** rspack unique id */
|
/** rspack unique id */
|
||||||
ruid: string;
|
ruid: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Utility section for Vencord
|
|
||||||
|
|
||||||
export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m">> & {
|
|
||||||
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
|
|
||||||
m: Record<PropertyKey, AnyModuleFactory>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** exports can be anything, however initially it is always an empty object */
|
|
||||||
export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void) & {
|
|
||||||
[SYM_PATCHED_SOURCE]?: string;
|
|
||||||
[SYM_PATCHED_BY]?: Set<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PatchedModuleFactory = AnyModuleFactory & {
|
|
||||||
[SYM_ORIGINAL_FACTORY]: AnyModuleFactory;
|
|
||||||
[SYM_PATCHED_SOURCE]?: string;
|
|
||||||
[SYM_PATCHED_BY]?: Set<string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory;
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
"@types/node": "^22.13.4",
|
"@types/node": "^22.13.4",
|
||||||
"@types/react": "18.3.1",
|
"@types/react": "18.3.1",
|
||||||
"@types/react-dom": "18.3.1",
|
"@types/react-dom": "18.3.1",
|
||||||
"discord-types": "^1.3.26",
|
|
||||||
"standalone-electron-types": "^34.2.0",
|
"standalone-electron-types": "^34.2.0",
|
||||||
"type-fest": "^4.35.0"
|
"type-fest": "^4.35.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
pnpm-lock.yaml
generated
43
pnpm-lock.yaml
generated
|
|
@ -65,12 +65,12 @@ importers:
|
||||||
'@types/yazl':
|
'@types/yazl':
|
||||||
specifier: ^2.4.5
|
specifier: ^2.4.5
|
||||||
version: 2.4.6
|
version: 2.4.6
|
||||||
|
'@vencord/discord-types':
|
||||||
|
specifier: link:packages/discord-types
|
||||||
|
version: link:packages/discord-types
|
||||||
diff:
|
diff:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
discord-types:
|
|
||||||
specifier: ^1.3.26
|
|
||||||
version: 1.3.26
|
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: ^0.25.1
|
specifier: ^0.25.1
|
||||||
version: 0.25.1
|
version: 0.25.1
|
||||||
|
|
@ -141,6 +141,18 @@ importers:
|
||||||
specifier: ^0.3.5
|
specifier: ^0.3.5
|
||||||
version: 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:
|
packages/vencord-types:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/lodash':
|
'@types/lodash':
|
||||||
|
|
@ -155,9 +167,6 @@ importers:
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: 18.3.1
|
specifier: 18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
discord-types:
|
|
||||||
specifier: ^1.3.26
|
|
||||||
version: 1.3.26
|
|
||||||
standalone-electron-types:
|
standalone-electron-types:
|
||||||
specifier: ^34.2.0
|
specifier: ^34.2.0
|
||||||
version: 34.2.0
|
version: 34.2.0
|
||||||
|
|
@ -522,9 +531,6 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^19.0.0
|
'@types/react': ^19.0.0
|
||||||
|
|
||||||
'@types/react@17.0.2':
|
|
||||||
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
|
||||||
|
|
||||||
'@types/react@18.3.1':
|
'@types/react@18.3.1':
|
||||||
resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==}
|
resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==}
|
||||||
|
|
||||||
|
|
@ -956,9 +962,6 @@ packages:
|
||||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
discord-types@1.3.26:
|
|
||||||
resolution: {integrity: sha512-ToG51AOCH+JTQf7b+8vuYQe5Iqwz7nZ7StpECAZ/VZcI1ZhQk13pvt9KkRTfRv1xNvwJ2qib4e3+RifQlo8VPQ==}
|
|
||||||
|
|
||||||
doctrine@2.1.0:
|
doctrine@2.1.0:
|
||||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
@ -2328,6 +2331,10 @@ packages:
|
||||||
resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==}
|
resolution: {integrity: sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
type-fest@4.41.0:
|
||||||
|
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
typed-array-buffer@1.0.3:
|
typed-array-buffer@1.0.3:
|
||||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -2764,11 +2771,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.0.12
|
'@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':
|
'@types/react@18.3.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.14
|
'@types/prop-types': 15.7.14
|
||||||
|
|
@ -3255,11 +3257,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-type: 4.0.0
|
path-type: 4.0.0
|
||||||
|
|
||||||
discord-types@1.3.26:
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 17.0.2
|
|
||||||
moment: 2.30.1
|
|
||||||
|
|
||||||
doctrine@2.1.0:
|
doctrine@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
|
|
@ -4923,6 +4920,8 @@ snapshots:
|
||||||
|
|
||||||
type-fest@4.38.0: {}
|
type-fest@4.38.0: {}
|
||||||
|
|
||||||
|
type-fest@4.41.0: {}
|
||||||
|
|
||||||
typed-array-buffer@1.0.3:
|
typed-array-buffer@1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const nodeCommonOpts = {
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
// @ts-ignore this is never undefined
|
// @ts-expect-error this is never undefined
|
||||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,6 @@ export const commonRendererPlugins = [
|
||||||
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
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(/^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"),
|
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
|
...commonOpts.plugins
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// DO NOT REMOVE UNLESS YOU WISH TO FACE THE WRATH OF THE CIRCULAR DEPENDENCY DEMON!!!!!!!
|
||||||
|
import "~plugins";
|
||||||
|
|
||||||
export * as Api from "./api";
|
export * as Api from "./api";
|
||||||
export * as Components from "./components";
|
export * as Components from "./components";
|
||||||
export * as Plugins from "./plugins";
|
export * as Plugins from "./plugins";
|
||||||
|
|
@ -29,7 +32,8 @@ export { PlainSettings, Settings };
|
||||||
import "./utils/quickCss";
|
import "./utils/quickCss";
|
||||||
import "./webpack/patchWebpack";
|
import "./webpack/patchWebpack";
|
||||||
|
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/settings/tabs/updater";
|
||||||
|
import { IS_WINDOWS } from "@utils/constants";
|
||||||
import { StartAt } from "@utils/types";
|
import { StartAt } from "@utils/types";
|
||||||
|
|
||||||
import { get as dsGet } from "./api/DataStore";
|
import { get as dsGet } from "./api/DataStore";
|
||||||
|
|
@ -161,7 +165,7 @@ init();
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
startAllPlugins(StartAt.DOMContentLoaded);
|
startAllPlugins(StartAt.DOMContentLoaded);
|
||||||
|
|
||||||
if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) {
|
if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) {
|
||||||
document.head.append(Object.assign(document.createElement("style"), {
|
document.head.append(Object.assign(document.createElement("style"), {
|
||||||
id: "vencord-native-titlebar-style",
|
id: "vencord-native-titlebar-style",
|
||||||
textContent: "[class*=titleBar]{display: none!important}"
|
textContent: "[class*=titleBar]{display: none!important}"
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import BadgeAPIPlugin from "plugins/_api/badges";
|
||||||
import { ComponentType, HTMLProps } from "react";
|
import { ComponentType, HTMLProps } from "react";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
|
||||||
|
|
||||||
export const enum BadgePosition {
|
export const enum BadgePosition {
|
||||||
START,
|
START,
|
||||||
END
|
END
|
||||||
|
|
@ -35,7 +34,9 @@ export interface ProfileBadge {
|
||||||
image?: string;
|
image?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
/** Action to perform when you click the badge */
|
/** Action to perform when you click the badge */
|
||||||
onClick?(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, props: BadgeUserArgs): void;
|
onClick?(event: React.MouseEvent, props: ProfileBadge & BadgeUserArgs): void;
|
||||||
|
/** Action to perform when you right click the badge */
|
||||||
|
onContextMenu?(event: React.MouseEvent, props: BadgeUserArgs & BadgeUserArgs): void;
|
||||||
/** Should the user display this badge? */
|
/** Should the user display this badge? */
|
||||||
shouldShow?(userInfo: BadgeUserArgs): boolean;
|
shouldShow?(userInfo: BadgeUserArgs): boolean;
|
||||||
/** Optional props (e.g. style) for the badge, ignored for component badges */
|
/** Optional props (e.g. style) for the badge, ignored for component badges */
|
||||||
|
|
@ -77,21 +78,34 @@ export function removeProfileBadge(badge: ProfileBadge) {
|
||||||
export function _getBadges(args: BadgeUserArgs) {
|
export function _getBadges(args: BadgeUserArgs) {
|
||||||
const badges = [] as ProfileBadge[];
|
const badges = [] as ProfileBadge[];
|
||||||
for (const badge of Badges) {
|
for (const badge of Badges) {
|
||||||
if (!badge.shouldShow || badge.shouldShow(args)) {
|
if (badge.shouldShow && !badge.shouldShow(args)) {
|
||||||
const b = badge.getBadges
|
continue;
|
||||||
? badge.getBadges(args).map(b => {
|
}
|
||||||
b.component &&= ErrorBoundary.wrap(b.component, { noop: true });
|
|
||||||
return b;
|
|
||||||
})
|
|
||||||
: [{ ...badge, ...args }];
|
|
||||||
|
|
||||||
badge.position === BadgePosition.START
|
const b = badge.getBadges
|
||||||
? badges.unshift(...b)
|
? badge.getBadges(args).map(badge => ({
|
||||||
: badges.push(...b);
|
...args,
|
||||||
|
...badge,
|
||||||
|
component: badge.component && ErrorBoundary.wrap(badge.component, { noop: true })
|
||||||
|
}))
|
||||||
|
: [{ ...args, ...badge }];
|
||||||
|
|
||||||
|
if (badge.position === BadgePosition.START) {
|
||||||
|
badges.unshift(...b);
|
||||||
|
} else {
|
||||||
|
badges.push(...b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
|
|
||||||
if (donorBadges) badges.unshift(...donorBadges);
|
const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId);
|
||||||
|
if (donorBadges) {
|
||||||
|
badges.unshift(
|
||||||
|
...donorBadges.map(badge => ({
|
||||||
|
...args,
|
||||||
|
...badge,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import "./ChatButton.css";
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { Channel } from "@vencord/discord-types";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
|
||||||
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||||
|
import { CommandArgument, Message } from "@vencord/discord-types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { MessageActions, SnowflakeUtils } from "@webpack/common";
|
import { MessageActions, SnowflakeUtils } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
|
|
||||||
import { Argument } from "./types";
|
|
||||||
|
|
||||||
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||||
|
|
||||||
export function generateId() {
|
export function generateId() {
|
||||||
|
|
@ -51,8 +49,8 @@ export function sendBotMessage(channelId: string, message: PartialDeep<Message>)
|
||||||
* @param fallbackValue Fallback value in case this option wasn't passed
|
* @param fallbackValue Fallback value in case this option wasn't passed
|
||||||
* @returns Value
|
* @returns Value
|
||||||
*/
|
*/
|
||||||
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
|
export function findOption<T>(args: CommandArgument[], name: string): T & {} | undefined;
|
||||||
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
export function findOption<T>(args: CommandArgument[], name: string, fallbackValue: T): T & {};
|
||||||
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
|
export function findOption(args: CommandArgument[], name: string, fallbackValue?: any) {
|
||||||
return (args.find(a => a.name === name)?.value ?? fallbackValue) as any;
|
return (args.find(a => a.name === name)?.value ?? fallbackValue) as any;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,38 +18,39 @@
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
|
import { CommandArgument, CommandContext, CommandOption } from "@vencord/discord-types";
|
||||||
|
|
||||||
import { sendBotMessage } from "./commandHelpers";
|
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 "./commandHelpers";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
|
||||||
export let BUILT_IN: Command[];
|
export let BUILT_IN: VencordCommand[];
|
||||||
export const commands = {} as Record<string, Command>;
|
export const commands = {} as Record<string, VencordCommand>;
|
||||||
|
|
||||||
// hack for plugins being evaluated before we can grab these from webpack
|
// hack for plugins being evaluated before we can grab these from webpack
|
||||||
const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option;
|
const OptPlaceholder = Symbol("OptionalMessageOption") as any as CommandOption;
|
||||||
const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option;
|
const ReqPlaceholder = Symbol("RequiredMessageOption") as any as CommandOption;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional message option named "message" you can use in commands.
|
* Optional message option named "message" you can use in commands.
|
||||||
* Used in "tableflip" or "shrug"
|
* Used in "tableflip" or "shrug"
|
||||||
* @see {@link RequiredMessageOption}
|
* @see {@link RequiredMessageOption}
|
||||||
*/
|
*/
|
||||||
export let OptionalMessageOption: Option = OptPlaceholder;
|
export let OptionalMessageOption: CommandOption = OptPlaceholder;
|
||||||
/**
|
/**
|
||||||
* Required message option named "message" you can use in commands.
|
* Required message option named "message" you can use in commands.
|
||||||
* Used in "me"
|
* Used in "me"
|
||||||
* @see {@link OptionalMessageOption}
|
* @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
|
// 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
|
// Add this offset to every added command to keep them unique
|
||||||
let commandIdOffset: number;
|
let commandIdOffset: number;
|
||||||
|
|
||||||
export const _init = function (cmds: Command[]) {
|
export const _init = function (cmds: VencordCommand[]) {
|
||||||
try {
|
try {
|
||||||
BUILT_IN = cmds;
|
BUILT_IN = cmds;
|
||||||
OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
|
OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
|
||||||
|
|
@ -61,7 +62,7 @@ export const _init = function (cmds: Command[]) {
|
||||||
return cmds;
|
return cmds;
|
||||||
} as never;
|
} 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)
|
if (!cmd.isVencordCommand)
|
||||||
return cmd.execute(args, ctx);
|
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
|
* Prepare a Command Option for Discord by filling missing fields
|
||||||
* @param opt
|
* @param opt
|
||||||
*/
|
*/
|
||||||
export function prepareOption<O extends Option | Command>(opt: O): O {
|
export function prepareOption<O extends CommandOption | VencordCommand>(opt: O): O {
|
||||||
opt.displayName ||= opt.name;
|
opt.displayName ||= opt.name;
|
||||||
opt.displayDescription ||= opt.description;
|
opt.displayDescription ||= opt.description;
|
||||||
opt.options?.forEach((opt, i, opts) => {
|
opt.options?.forEach((opt, i, opts) => {
|
||||||
|
|
@ -109,7 +110,7 @@ export function prepareOption<O extends Option | Command>(opt: O): O {
|
||||||
// Yes, Discord registers individual commands for each subcommand
|
// Yes, Discord registers individual commands for each subcommand
|
||||||
// TODO: This probably doesn't support nested subcommands. If that is ever needed,
|
// TODO: This probably doesn't support nested subcommands. If that is ever needed,
|
||||||
// investigate
|
// investigate
|
||||||
function registerSubCommands(cmd: Command, plugin: string) {
|
function registerSubCommands(cmd: VencordCommand, plugin: string) {
|
||||||
cmd.options?.forEach(o => {
|
cmd.options?.forEach(o => {
|
||||||
if (o.type !== ApplicationCommandOptionType.SUB_COMMAND)
|
if (o.type !== ApplicationCommandOptionType.SUB_COMMAND)
|
||||||
throw new Error("When specifying sub-command options, all options must be sub-commands.");
|
throw new Error("When specifying sub-command options, all options must be sub-commands.");
|
||||||
|
|
@ -132,7 +133,7 @@ function registerSubCommands(cmd: Command, plugin: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerCommand<C extends Command>(command: C, plugin: string) {
|
export function registerCommand<C extends VencordCommand>(command: C, plugin: string) {
|
||||||
if (!BUILT_IN) {
|
if (!BUILT_IN) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[CommandsAPI]",
|
"[CommandsAPI]",
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a modification for Discord's desktop app
|
* Vencord, a Discord client mod
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
*
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
* This program is free software: you can redistribute it and/or modify
|
*/
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Channel, Guild } from "discord-types/general";
|
import { Command } from "@vencord/discord-types";
|
||||||
import { Promisable } from "type-fest";
|
export { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@vencord/discord-types/enums";
|
||||||
|
|
||||||
export interface CommandContext {
|
export interface VencordCommand extends Command {
|
||||||
channel: Channel;
|
|
||||||
guild?: Guild;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum ApplicationCommandOptionType {
|
|
||||||
SUB_COMMAND = 1,
|
|
||||||
SUB_COMMAND_GROUP = 2,
|
|
||||||
STRING = 3,
|
|
||||||
INTEGER = 4,
|
|
||||||
BOOLEAN = 5,
|
|
||||||
USER = 6,
|
|
||||||
CHANNEL = 7,
|
|
||||||
ROLE = 8,
|
|
||||||
MENTIONABLE = 9,
|
|
||||||
NUMBER = 10,
|
|
||||||
ATTACHMENT = 11,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum ApplicationCommandInputType {
|
|
||||||
BUILT_IN = 0,
|
|
||||||
BUILT_IN_TEXT = 1,
|
|
||||||
BUILT_IN_INTEGRATION = 2,
|
|
||||||
BOT = 3,
|
|
||||||
PLACEHOLDER = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Option {
|
|
||||||
name: string;
|
|
||||||
displayName?: string;
|
|
||||||
type: ApplicationCommandOptionType;
|
|
||||||
description: string;
|
|
||||||
displayDescription?: string;
|
|
||||||
required?: boolean;
|
|
||||||
options?: Option[];
|
|
||||||
choices?: Array<ChoicesOption>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChoicesOption {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
name: string;
|
|
||||||
displayName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum ApplicationCommandType {
|
|
||||||
CHAT_INPUT = 1,
|
|
||||||
USER = 2,
|
|
||||||
MESSAGE = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommandReturnValue {
|
|
||||||
content: string;
|
|
||||||
/** TODO: implement */
|
|
||||||
cancel?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Argument {
|
|
||||||
type: ApplicationCommandOptionType;
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
focused: undefined;
|
|
||||||
options: Argument[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Command {
|
|
||||||
id?: string;
|
|
||||||
applicationId?: string;
|
|
||||||
type?: ApplicationCommandType;
|
|
||||||
inputType?: ApplicationCommandInputType;
|
|
||||||
plugin?: string;
|
|
||||||
isVencordCommand?: boolean;
|
isVencordCommand?: boolean;
|
||||||
|
|
||||||
name: string;
|
|
||||||
untranslatedName?: string;
|
|
||||||
displayName?: string;
|
|
||||||
description: string;
|
|
||||||
untranslatedDescription?: string;
|
|
||||||
displayDescription?: string;
|
|
||||||
|
|
||||||
options?: Option[];
|
|
||||||
predicate?(ctx: CommandContext): boolean;
|
|
||||||
|
|
||||||
execute(args: Argument[], ctx: CommandContext): Promisable<void | CommandReturnValue>;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ export function promisifyRequest<T = undefined>(
|
||||||
request: IDBRequest<T> | IDBTransaction,
|
request: IDBRequest<T> | IDBTransaction,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
// @ts-ignore - file size hacks
|
// @ts-expect-error - file size hacks
|
||||||
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
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);
|
request.onabort = request.onerror = () => reject(request.error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
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";
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
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";
|
import { JSX } from "react";
|
||||||
|
|
||||||
export interface MessageDecorationProps {
|
export interface MessageDecorationProps {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
import type { Channel, CloudUpload, CustomEmoji, Message } from "@vencord/discord-types";
|
||||||
import { MessageStore } from "@webpack/common";
|
import { MessageStore } from "@webpack/common";
|
||||||
import { CustomEmoji } from "@webpack/types";
|
|
||||||
import type { Channel, Message } from "discord-types/general";
|
|
||||||
import type { Promisable } from "type-fest";
|
import type { Promisable } from "type-fest";
|
||||||
|
|
||||||
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
|
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
|
||||||
|
|
@ -31,30 +30,6 @@ export interface MessageObject {
|
||||||
tts: boolean;
|
tts: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Upload {
|
|
||||||
classification: string;
|
|
||||||
currentSize: number;
|
|
||||||
description: string | null;
|
|
||||||
filename: string;
|
|
||||||
id: string;
|
|
||||||
isImage: boolean;
|
|
||||||
isVideo: boolean;
|
|
||||||
item: {
|
|
||||||
file: File;
|
|
||||||
platform: number;
|
|
||||||
};
|
|
||||||
loaded: number;
|
|
||||||
mimeType: string;
|
|
||||||
preCompressionSize: number;
|
|
||||||
responseUrl: string;
|
|
||||||
sensitive: boolean;
|
|
||||||
showLargeMessageDialog: boolean;
|
|
||||||
spoiler: boolean;
|
|
||||||
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED";
|
|
||||||
uniqueId: string;
|
|
||||||
uploadedFilename: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageReplyOptions {
|
export interface MessageReplyOptions {
|
||||||
messageReference: Message["messageReference"];
|
messageReference: Message["messageReference"];
|
||||||
allowedMentions?: {
|
allowedMentions?: {
|
||||||
|
|
@ -63,9 +38,9 @@ export interface MessageReplyOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageExtra {
|
export interface MessageOptions {
|
||||||
stickers?: string[];
|
stickers?: string[];
|
||||||
uploads?: Upload[];
|
uploads?: CloudUpload[];
|
||||||
replyOptions: MessageReplyOptions;
|
replyOptions: MessageReplyOptions;
|
||||||
content: string;
|
content: string;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
|
|
@ -73,17 +48,17 @@ export interface MessageExtra {
|
||||||
openWarningPopout: (props: any) => any;
|
openWarningPopout: (props: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
export type MessageSendListener = (channelId: string, messageObj: MessageObject, options: MessageOptions) => Promisable<void | { cancel: boolean; }>;
|
||||||
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||||
|
|
||||||
const sendListeners = new Set<MessageSendListener>();
|
const sendListeners = new Set<MessageSendListener>();
|
||||||
const editListeners = new Set<MessageEditListener>();
|
const editListeners = new Set<MessageEditListener>();
|
||||||
|
|
||||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
export async function _handlePreSend(channelId: string, messageObj: MessageObject, options: MessageOptions, replyOptions: MessageReplyOptions) {
|
||||||
extra.replyOptions = replyOptions;
|
options.replyOptions = replyOptions;
|
||||||
for (const listener of sendListeners) {
|
for (const listener of sendListeners) {
|
||||||
try {
|
try {
|
||||||
const result = await listener(channelId, messageObj, extra);
|
const result = await listener(channelId, messageObj, options);
|
||||||
if (result?.cancel) {
|
if (result?.cancel) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
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";
|
import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,8 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Message } from "@vencord/discord-types";
|
||||||
import { MessageCache, MessageStore } from "@webpack/common";
|
import { MessageCache, MessageStore } from "@webpack/common";
|
||||||
import { FluxStore } from "@webpack/types";
|
|
||||||
import { Message } from "discord-types/general";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update and re-render a message
|
* Update and re-render a message
|
||||||
|
|
@ -25,5 +24,5 @@ export function updateMessage(channelId: string, messageId: string, fields?: Par
|
||||||
});
|
});
|
||||||
|
|
||||||
MessageCache.commit(newChannelMessageCache);
|
MessageCache.commit(newChannelMessageCache);
|
||||||
(MessageStore as unknown as FluxStore).emitChange();
|
MessageStore.emitChange();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { isPrimitiveReactNode } from "@utils/react";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
let NoticesModule: any;
|
let NoticesModule: any;
|
||||||
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
||||||
|
|
@ -36,7 +39,11 @@ export function nextNotice() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showNotice(message: string, buttonText: string, onOkClick: () => void) {
|
export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) {
|
||||||
noticesQueue.push(["GENERIC", message, buttonText, onOkClick]);
|
const notice = isPrimitiveReactNode(message)
|
||||||
|
? message
|
||||||
|
: <ErrorBoundary fallback={() => "Error Showing Notice"}>{message}</ErrorBoundary>;
|
||||||
|
|
||||||
|
noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]);
|
||||||
if (!currentNotice) nextNotice();
|
if (!currentNotice) nextNotice();
|
||||||
}
|
}
|
||||||
|
|
@ -104,11 +104,9 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
{richBody ?? <p className="vc-notification-p">{body}</p>}
|
{richBody ?? <p className="vc-notification-p">{body}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{image && <img className="vc-notification-img" src={image} alt="" />}
|
{image && <img className="vc-notification-img" src={image} alt="" />}
|
||||||
{timeout !== 0 && !permanent && (
|
{timeout !== 0 && !permanent && (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings";
|
import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import type { DispatchWithoutAction } from "react";
|
import type { DispatchWithoutAction } from "react";
|
||||||
|
|
||||||
|
|
@ -103,21 +103,9 @@ export function useLogs() {
|
||||||
|
|
||||||
function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
|
function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
|
||||||
const [removing, setRemoving] = useState(false);
|
const [removing, setRemoving] = useState(false);
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const div = ref.current!;
|
|
||||||
|
|
||||||
const setHeight = () => {
|
|
||||||
if (div.clientHeight === 0) return requestAnimationFrame(setHeight);
|
|
||||||
div.style.height = `${div.clientHeight}px`;
|
|
||||||
};
|
|
||||||
|
|
||||||
setHeight();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cl("wrapper", { removing })} ref={ref}>
|
<div className={cl("wrapper", { removing })}>
|
||||||
<NotificationComponent
|
<NotificationComponent
|
||||||
{...data}
|
{...data}
|
||||||
permanent={true}
|
permanent={true}
|
||||||
|
|
@ -129,13 +117,13 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
|
||||||
setTimeout(() => deleteNotification(data.timestamp), 200);
|
setTimeout(() => deleteNotification(data.timestamp), 200);
|
||||||
}}
|
}}
|
||||||
richBody={
|
richBody={
|
||||||
<div className={cl("body")}>
|
<div className={cl("body-wrapper")}>
|
||||||
{data.body}
|
<div className={cl("body")}>{data.body}</div>
|
||||||
<Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
|
<Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,9 +139,14 @@ export function NotificationLog({ log, pending }: { log: PersistentNotificationD
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cl("container")}>
|
<ListScrollerThin
|
||||||
{log.map(n => <NotificationEntry data={n} key={n.id} />)}
|
className={cl("container")}
|
||||||
</div>
|
sections={[log.length]}
|
||||||
|
sectionHeight={0}
|
||||||
|
rowHeight={120}
|
||||||
|
renderSection={() => null}
|
||||||
|
renderRow={item => <NotificationEntry data={log[item.row]} key={log[item.row].id} />}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,15 +154,15 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
|
||||||
const [log, pending] = useLogs();
|
const [log, pending] = useLogs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
|
<ModalRoot {...modalProps} size={ModalSize.LARGE} className={cl("modal")}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Log</Text>
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Log</Text>
|
||||||
<ModalCloseButton onClick={close} />
|
<ModalCloseButton onClick={close} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalContent>
|
<div style={{ width: "100%" }}>
|
||||||
<NotificationLog log={log} pending={pending} />
|
<NotificationLog log={log} pending={pending} />
|
||||||
</ModalContent>
|
</div>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Flex>
|
<Flex>
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: var(--text-default);
|
color: var(--text-default);
|
||||||
background-color: var(--background-base-lower-alt);
|
background-color: var(--background-base-low);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-notification-root {
|
|
||||||
background-color: var(--background-base-low);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
|
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2147483647;
|
z-index: 2147483647;
|
||||||
|
|
@ -32,6 +28,7 @@
|
||||||
|
|
||||||
.vc-notification-content {
|
.vc-notification-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-notification-header {
|
.vc-notification-header {
|
||||||
|
|
@ -81,6 +78,11 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-notification-log-modal {
|
||||||
|
max-width: 962px;
|
||||||
|
width: clamp(var(--modal-width-large, 800px), 962px, 85vw);
|
||||||
|
}
|
||||||
|
|
||||||
.vc-notification-log-empty {
|
.vc-notification-log-empty {
|
||||||
height: 218px;
|
height: 218px;
|
||||||
background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat;
|
background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat;
|
||||||
|
|
@ -88,19 +90,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-notification-log-container {
|
.vc-notification-log-container {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
overflow: hidden;
|
max-height: min(750px, 75vh);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-notification-log-wrapper {
|
.vc-notification-log-wrapper {
|
||||||
|
height: 120px;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
transition: 200ms ease;
|
transition: 200ms ease;
|
||||||
transition-property: height, opacity;
|
transition-property: height, opacity;
|
||||||
}
|
|
||||||
|
|
||||||
.vc-notification-log-wrapper:not(:last-child) {
|
/* stylelint-disable-next-line no-descending-specificity */
|
||||||
margin-bottom: 1em;
|
.vc-notification-root {
|
||||||
|
height: 104px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-notification-log-removing {
|
.vc-notification-log-removing {
|
||||||
|
|
@ -109,9 +115,18 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-notification-log-body {
|
.vc-notification-log-body-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-notification-log-body {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-notification-log-timestamp {
|
.vc-notification-log-timestamp {
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ type ResolveUseSettings<T extends object> = {
|
||||||
[Key in keyof T]:
|
[Key in keyof T]:
|
||||||
Key extends string
|
Key extends string
|
||||||
? T[Key] extends Record<string, unknown>
|
? T[Key] extends Record<string, unknown>
|
||||||
// @ts-ignore "Type instantiation is excessively deep and possibly infinite"
|
// @ts-expect-error "Type instantiation is excessively deep and possibly infinite"
|
||||||
? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never
|
? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never
|
||||||
: Key
|
: Key
|
||||||
: never;
|
: never;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
import { React, TextInput } from "@webpack/common";
|
import { React, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
// TODO: Refactor settings to use this as well
|
|
||||||
interface TextInputProps {
|
interface TextInputProps {
|
||||||
/**
|
/**
|
||||||
* WARNING: Changing this between renders will have no effect!
|
* WARNING: Changing this between renders will have no effect!
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ interface BaseIconProps extends IconProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IconProps = JSX.IntrinsicElements["svg"];
|
type IconProps = JSX.IntrinsicElements["svg"];
|
||||||
type ImageProps = JSX.IntrinsicElements["img"];
|
|
||||||
|
|
||||||
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
|
||||||
import { PluginOptionBoolean } from "@utils/types";
|
|
||||||
import { Forms, React, Switch } from "@webpack/common";
|
|
||||||
|
|
||||||
import { ISettingElementProps } from ".";
|
|
||||||
|
|
||||||
export function SettingBooleanComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionBoolean>) {
|
|
||||||
const def = pluginSettings[id] ?? option.default;
|
|
||||||
|
|
||||||
const [state, setState] = React.useState(def ?? false);
|
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
onError(error !== null);
|
|
||||||
}, [error]);
|
|
||||||
|
|
||||||
function handleChange(newValue: boolean): void {
|
|
||||||
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
|
|
||||||
if (typeof isValid === "string") setError(isValid);
|
|
||||||
else if (!isValid) setError("Invalid input provided.");
|
|
||||||
else {
|
|
||||||
setError(null);
|
|
||||||
setState(newValue);
|
|
||||||
onChange(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Forms.FormSection>
|
|
||||||
<Switch
|
|
||||||
value={state}
|
|
||||||
onChange={handleChange}
|
|
||||||
note={option.description}
|
|
||||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
|
||||||
{...option.componentProps}
|
|
||||||
hideBorder
|
|
||||||
style={{ marginBottom: "0.5em" }}
|
|
||||||
>
|
|
||||||
{wordsToTitle(wordsFromCamel(id))}
|
|
||||||
</Switch>
|
|
||||||
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
|
||||||
</Forms.FormSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
|
||||||
|
|
||||||
interface ISettingElementPropsBase<T> {
|
|
||||||
option: T;
|
|
||||||
onChange(newValue: any): void;
|
|
||||||
pluginSettings: {
|
|
||||||
[setting: string]: any;
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
onError(hasError: boolean): void;
|
|
||||||
definedSettings?: DefinedSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
|
|
||||||
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
|
|
||||||
|
|
||||||
export * from "../../Badge";
|
|
||||||
export * from "./SettingBooleanComponent";
|
|
||||||
export * from "./SettingCustomComponent";
|
|
||||||
export * from "./SettingNumericComponent";
|
|
||||||
export * from "./SettingSelectComponent";
|
|
||||||
export * from "./SettingSliderComponent";
|
|
||||||
export * from "./SettingTextComponent";
|
|
||||||
|
|
||||||
|
|
@ -1,393 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { CodeBlock } from "@components/CodeBlock";
|
|
||||||
import { debounce } from "@shared/debounce";
|
|
||||||
import { copyToClipboard } from "@utils/clipboard";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
|
||||||
import { makeCodeblock } from "@utils/text";
|
|
||||||
import { Patch, ReplaceFn } from "@utils/types";
|
|
||||||
import { search } from "@webpack";
|
|
||||||
import { Button, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
|
||||||
|
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
|
||||||
|
|
||||||
// Do not include diff in non dev builds (side effects import)
|
|
||||||
if (IS_DEV) {
|
|
||||||
var differ = require("diff") as typeof import("diff");
|
|
||||||
}
|
|
||||||
|
|
||||||
const findCandidates = debounce(function ({ find, setModule, setError }) {
|
|
||||||
const candidates = search(find);
|
|
||||||
const keys = Object.keys(candidates);
|
|
||||||
const len = keys.length;
|
|
||||||
if (len === 0)
|
|
||||||
setError("No match. Perhaps that module is lazy loaded?");
|
|
||||||
else if (len !== 1)
|
|
||||||
setError("Multiple matches. Please refine your filter");
|
|
||||||
else
|
|
||||||
setModule([keys[0], candidates[keys[0]]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
interface ReplacementComponentProps {
|
|
||||||
module: [id: number, factory: Function];
|
|
||||||
match: string;
|
|
||||||
replacement: string | ReplaceFn;
|
|
||||||
setReplacementError(error: any): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReplacementComponent({ module, match, replacement, setReplacementError }: ReplacementComponentProps) {
|
|
||||||
const [id, fact] = module;
|
|
||||||
const [compileResult, setCompileResult] = React.useState<[boolean, string]>();
|
|
||||||
|
|
||||||
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
|
||||||
const src: string = fact.toString().replaceAll("\n", "");
|
|
||||||
|
|
||||||
try {
|
|
||||||
new RegExp(match);
|
|
||||||
} catch (e) {
|
|
||||||
return ["", [], []];
|
|
||||||
}
|
|
||||||
const canonicalMatch = canonicalizeMatch(new RegExp(match));
|
|
||||||
try {
|
|
||||||
const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]');
|
|
||||||
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
|
||||||
setReplacementError(void 0);
|
|
||||||
} catch (e) {
|
|
||||||
setReplacementError((e as Error).message);
|
|
||||||
return ["", [], []];
|
|
||||||
}
|
|
||||||
const m = src.match(canonicalMatch);
|
|
||||||
return [patched, m, makeDiff(src, patched, m)];
|
|
||||||
}, [id, match, replacement]);
|
|
||||||
|
|
||||||
function makeDiff(original: string, patched: string, match: RegExpMatchArray | null) {
|
|
||||||
if (!match || original === patched) return null;
|
|
||||||
|
|
||||||
const changeSize = patched.length - original.length;
|
|
||||||
|
|
||||||
// Use 200 surrounding characters of context
|
|
||||||
const start = Math.max(0, match.index! - 200);
|
|
||||||
const end = Math.min(original.length, match.index! + match[0].length + 200);
|
|
||||||
// (changeSize may be negative)
|
|
||||||
const endPatched = end + changeSize;
|
|
||||||
|
|
||||||
const context = original.slice(start, end);
|
|
||||||
const patchedContext = patched.slice(start, endPatched);
|
|
||||||
|
|
||||||
return differ.diffWordsWithSpace(context, patchedContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMatch() {
|
|
||||||
if (!matchResult)
|
|
||||||
return <Forms.FormText>Regex doesn't match!</Forms.FormText>;
|
|
||||||
|
|
||||||
const fullMatch = matchResult[0] ? makeCodeblock(matchResult[0], "js") : "";
|
|
||||||
const groups = matchResult.length > 1
|
|
||||||
? makeCodeblock(matchResult.slice(1).map((g, i) => `Group ${i + 1}: ${g}`).join("\n"), "yml")
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{ userSelect: "text" }}>{Parser.parse(fullMatch)}</div>
|
|
||||||
<div style={{ userSelect: "text" }}>{Parser.parse(groups)}</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDiff() {
|
|
||||||
return diff?.map((p, idx) => {
|
|
||||||
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
|
||||||
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle>Module {id}</Forms.FormTitle>
|
|
||||||
|
|
||||||
{!!matchResult?.[0]?.length && (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle>Match</Forms.FormTitle>
|
|
||||||
{renderMatch()}
|
|
||||||
</>)
|
|
||||||
}
|
|
||||||
|
|
||||||
{!!diff?.length && (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle>Diff</Forms.FormTitle>
|
|
||||||
{renderDiff()}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!diff?.length && (
|
|
||||||
<Button className={Margins.top20} onClick={() => {
|
|
||||||
try {
|
|
||||||
Function(patchedCode.replace(/^function\(/, "function patchedModule("));
|
|
||||||
setCompileResult([true, "Compiled successfully"]);
|
|
||||||
} catch (err) {
|
|
||||||
setCompileResult([false, (err as Error).message]);
|
|
||||||
}
|
|
||||||
}}>Compile</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{compileResult &&
|
|
||||||
<Forms.FormText style={{ color: compileResult[0] ? "var(--status-positive)" : "var(--text-danger)" }}>
|
|
||||||
{compileResult[1]}
|
|
||||||
</Forms.FormText>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
|
||||||
const [isFunc, setIsFunc] = React.useState(false);
|
|
||||||
const [error, setError] = React.useState<string>();
|
|
||||||
|
|
||||||
function onChange(v: string) {
|
|
||||||
setError(void 0);
|
|
||||||
|
|
||||||
if (isFunc) {
|
|
||||||
try {
|
|
||||||
const func = (0, eval)(v);
|
|
||||||
if (typeof func === "function")
|
|
||||||
setReplacement(() => func);
|
|
||||||
else
|
|
||||||
setError("Replacement must be a function");
|
|
||||||
} catch (e) {
|
|
||||||
setReplacement(v);
|
|
||||||
setError((e as Error).message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setReplacement(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(
|
|
||||||
() => void (isFunc ? onChange(replacement) : setError(void 0)),
|
|
||||||
[isFunc]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* FormTitle adds a class if className is not set, so we set it to an empty string to prevent that */}
|
|
||||||
<Forms.FormTitle className="">replacement</Forms.FormTitle>
|
|
||||||
<TextInput
|
|
||||||
value={replacement?.toString()}
|
|
||||||
onChange={onChange}
|
|
||||||
error={error ?? replacementError}
|
|
||||||
/>
|
|
||||||
{!isFunc && (
|
|
||||||
<div className="vc-text-selectable">
|
|
||||||
<Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle>
|
|
||||||
{Object.entries({
|
|
||||||
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
|
|
||||||
"$$": "Insert a $",
|
|
||||||
"$&": "Insert the entire match",
|
|
||||||
"$`\u200b": "Insert the substring before the match",
|
|
||||||
"$'": "Insert the substring after the match",
|
|
||||||
"$n": "Insert the nth capturing group ($1, $2...)",
|
|
||||||
"$self": "Insert the plugin instance",
|
|
||||||
}).map(([placeholder, desc]) => (
|
|
||||||
<Forms.FormText key={placeholder}>
|
|
||||||
{Parser.parse("`" + placeholder + "`")}: {desc}
|
|
||||||
</Forms.FormText>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
className={Margins.top8}
|
|
||||||
value={isFunc}
|
|
||||||
onChange={setIsFunc}
|
|
||||||
note="'replacement' will be evaled if this is toggled"
|
|
||||||
hideBorder={true}
|
|
||||||
>
|
|
||||||
Treat as Function
|
|
||||||
</Switch>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FullPatchInputProps {
|
|
||||||
setFind(v: string): void;
|
|
||||||
setParsedFind(v: string | RegExp): void;
|
|
||||||
setMatch(v: string): void;
|
|
||||||
setReplacement(v: string | ReplaceFn): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) {
|
|
||||||
const [fullPatch, setFullPatch] = React.useState<string>("");
|
|
||||||
const [fullPatchError, setFullPatchError] = React.useState<string>("");
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
if (fullPatch === "") {
|
|
||||||
setFullPatchError("");
|
|
||||||
|
|
||||||
setFind("");
|
|
||||||
setParsedFind("");
|
|
||||||
setMatch("");
|
|
||||||
setReplacement("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
|
|
||||||
|
|
||||||
if (!parsed.find) throw new Error("No 'find' field");
|
|
||||||
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
|
||||||
|
|
||||||
if (parsed.replacement instanceof Array) {
|
|
||||||
if (parsed.replacement.length === 0) throw new Error("Invalid replacement");
|
|
||||||
|
|
||||||
parsed.replacement = {
|
|
||||||
match: parsed.replacement[0].match,
|
|
||||||
replace: parsed.replacement[0].replace
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parsed.replacement.match) throw new Error("No 'replacement.match' field");
|
|
||||||
if (!parsed.replacement.replace) throw new Error("No 'replacement.replace' field");
|
|
||||||
|
|
||||||
setFind(parsed.find instanceof RegExp ? parsed.find.toString() : parsed.find);
|
|
||||||
setParsedFind(parsed.find);
|
|
||||||
setMatch(parsed.replacement.match instanceof RegExp ? parsed.replacement.match.source : parsed.replacement.match);
|
|
||||||
setReplacement(parsed.replacement.replace);
|
|
||||||
setFullPatchError("");
|
|
||||||
} catch (e) {
|
|
||||||
setFullPatchError((e as Error).message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<Forms.FormText className={Margins.bottom8}>Paste your full JSON patch here to fill out the fields</Forms.FormText>
|
|
||||||
<TextArea value={fullPatch} onChange={setFullPatch} onBlur={update} />
|
|
||||||
{fullPatchError !== "" && <Forms.FormText style={{ color: "var(--text-danger)" }}>{fullPatchError}</Forms.FormText>}
|
|
||||||
</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function PatchHelper() {
|
|
||||||
const [find, setFind] = React.useState<string>("");
|
|
||||||
const [parsedFind, setParsedFind] = React.useState<string | RegExp>("");
|
|
||||||
const [match, setMatch] = React.useState<string>("");
|
|
||||||
const [replacement, setReplacement] = React.useState<string | ReplaceFn>("");
|
|
||||||
|
|
||||||
const [replacementError, setReplacementError] = React.useState<string>();
|
|
||||||
|
|
||||||
const [module, setModule] = React.useState<[number, Function]>();
|
|
||||||
const [findError, setFindError] = React.useState<string>();
|
|
||||||
const [matchError, setMatchError] = React.useState<string>();
|
|
||||||
|
|
||||||
const code = React.useMemo(() => {
|
|
||||||
return `
|
|
||||||
{
|
|
||||||
find: ${parsedFind instanceof RegExp ? parsedFind.toString() : JSON.stringify(parsedFind)},
|
|
||||||
replacement: {
|
|
||||||
match: /${match.replace(/(?<!\\)\//g, "\\/")}/,
|
|
||||||
replace: ${typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`.trim();
|
|
||||||
}, [parsedFind, match, replacement]);
|
|
||||||
|
|
||||||
function onFindChange(v: string) {
|
|
||||||
setFind(v);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let parsedFind = v as string | RegExp;
|
|
||||||
if (/^\/.+?\/$/.test(v)) parsedFind = new RegExp(v.slice(1, -1));
|
|
||||||
|
|
||||||
setFindError(void 0);
|
|
||||||
setParsedFind(parsedFind);
|
|
||||||
|
|
||||||
if (v.length) {
|
|
||||||
findCandidates({ find: parsedFind, setModule, setError: setFindError });
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
setFindError((e as Error).message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMatchChange(v: string) {
|
|
||||||
setMatch(v);
|
|
||||||
|
|
||||||
try {
|
|
||||||
new RegExp(v);
|
|
||||||
setMatchError(void 0);
|
|
||||||
} catch (e: any) {
|
|
||||||
setMatchError((e as Error).message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsTab title="Patch Helper">
|
|
||||||
<Forms.FormTitle>full patch</Forms.FormTitle>
|
|
||||||
<FullPatchInput
|
|
||||||
setFind={onFindChange}
|
|
||||||
setParsedFind={setParsedFind}
|
|
||||||
setMatch={onMatchChange}
|
|
||||||
setReplacement={setReplacement}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top8}>find</Forms.FormTitle>
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
value={find}
|
|
||||||
onChange={onFindChange}
|
|
||||||
error={findError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle>
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
value={match}
|
|
||||||
onChange={onMatchChange}
|
|
||||||
error={matchError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={Margins.top8} />
|
|
||||||
<ReplacementInput
|
|
||||||
replacement={replacement}
|
|
||||||
setReplacement={setReplacement}
|
|
||||||
replacementError={replacementError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormDivider />
|
|
||||||
{module && (
|
|
||||||
<ReplacementComponent
|
|
||||||
module={module}
|
|
||||||
match={match}
|
|
||||||
replacement={replacement}
|
|
||||||
setReplacementError={setReplacementError}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!(find && match && replacement) && (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
|
||||||
<CodeBlock lang="js" content={code} />
|
|
||||||
<Button onClick={() => copyToClipboard(code)}>Copy to Clipboard</Button>
|
|
||||||
<Button className={Margins.top8} onClick={() => copyToClipboard("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SettingsTab>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IS_DEV ? wrapTab(PatchHelper, "PatchHelper") : null;
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import PluginSettings from "@components/PluginSettings";
|
|
||||||
|
|
||||||
import { wrapTab } from "./shared";
|
|
||||||
|
|
||||||
export default wrapTab(PluginSettings, "Plugins");
|
|
||||||
|
|
@ -1,453 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
|
||||||
import { Link } from "@components/Link";
|
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
|
||||||
import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations";
|
|
||||||
import { openInviteModal } from "@utils/discord";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { relaunch } from "@utils/native";
|
|
||||||
import { useAwaiter, useForceUpdater } from "@utils/react";
|
|
||||||
import { getStylusWebStoreUrl } from "@utils/web";
|
|
||||||
import { findLazy } from "@webpack";
|
|
||||||
import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
|
||||||
|
|
||||||
import { AddonCard } from "./AddonCard";
|
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
|
||||||
|
|
||||||
type FileInput = ComponentType<{
|
|
||||||
ref: Ref<HTMLInputElement>;
|
|
||||||
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
|
|
||||||
multiple?: boolean;
|
|
||||||
filters?: { name?: string; extensions: string[]; }[];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
|
||||||
|
|
||||||
function Validator({ link }: { link: string; }) {
|
|
||||||
const [res, err, pending] = useAwaiter(() => fetch(link).then(res => {
|
|
||||||
if (res.status > 300) throw `${res.status} ${res.statusText}`;
|
|
||||||
const contentType = res.headers.get("Content-Type");
|
|
||||||
if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain"))
|
|
||||||
throw "Not a CSS file. Remember to use the raw link!";
|
|
||||||
|
|
||||||
return "Okay!";
|
|
||||||
}));
|
|
||||||
|
|
||||||
const text = pending
|
|
||||||
? "Checking..."
|
|
||||||
: err
|
|
||||||
? `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
||||||
: "Valid!";
|
|
||||||
|
|
||||||
return <Forms.FormText style={{
|
|
||||||
color: pending ? "var(--text-muted)" : err ? "var(--text-danger)" : "var(--status-positive)"
|
|
||||||
}}>{text}</Forms.FormText>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
|
||||||
if (!themeLinks.length) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
|
||||||
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
|
||||||
<div>
|
|
||||||
{themeLinks.map(rawLink => {
|
|
||||||
const { label, link } = (() => {
|
|
||||||
const match = /^@(light|dark) (.*)/.exec(rawLink);
|
|
||||||
if (!match) return { label: rawLink, link: rawLink };
|
|
||||||
|
|
||||||
const [, mode, link] = match;
|
|
||||||
return { label: `[${mode} mode only] ${link}`, link };
|
|
||||||
})();
|
|
||||||
|
|
||||||
return <Card style={{
|
|
||||||
padding: ".5em",
|
|
||||||
marginBottom: ".5em",
|
|
||||||
marginTop: ".5em"
|
|
||||||
}} key={link}>
|
|
||||||
<Forms.FormTitle tag="h5" style={{
|
|
||||||
overflowWrap: "break-word"
|
|
||||||
}}>
|
|
||||||
{label}
|
|
||||||
</Forms.FormTitle>
|
|
||||||
<Validator link={link} />
|
|
||||||
</Card>;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ThemeCardProps {
|
|
||||||
theme: UserThemeHeader;
|
|
||||||
enabled: boolean;
|
|
||||||
onChange: (enabled: boolean) => void;
|
|
||||||
onDelete: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
|
|
||||||
return (
|
|
||||||
<AddonCard
|
|
||||||
name={theme.name}
|
|
||||||
description={theme.description}
|
|
||||||
author={theme.author}
|
|
||||||
enabled={enabled}
|
|
||||||
setEnabled={onChange}
|
|
||||||
infoButton={
|
|
||||||
IS_WEB && (
|
|
||||||
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
footer={
|
|
||||||
<Flex flexDirection="row" style={{ gap: "0.2em" }}>
|
|
||||||
{!!theme.website && <Link href={theme.website}>Website</Link>}
|
|
||||||
{!!(theme.website && theme.invite) && " • "}
|
|
||||||
{!!theme.invite && (
|
|
||||||
<Link
|
|
||||||
href={`https://discord.gg/${theme.invite}`}
|
|
||||||
onClick={async e => {
|
|
||||||
e.preventDefault();
|
|
||||||
theme.invite != null && openInviteModal(theme.invite).catch(() => showToast("Invalid or expired invite"));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Discord Server
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ThemeTab {
|
|
||||||
LOCAL,
|
|
||||||
ONLINE
|
|
||||||
}
|
|
||||||
|
|
||||||
function ThemesTab() {
|
|
||||||
const settings = useSettings(["themeLinks", "enabledThemes"]);
|
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
|
|
||||||
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
|
|
||||||
const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refreshLocalThemes();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function refreshLocalThemes() {
|
|
||||||
const themes = await VencordNative.themes.getThemesList();
|
|
||||||
setUserThemes(themes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When a local theme is enabled/disabled, update the settings
|
|
||||||
function onLocalThemeChange(fileName: string, value: boolean) {
|
|
||||||
if (value) {
|
|
||||||
if (settings.enabledThemes.includes(fileName)) return;
|
|
||||||
settings.enabledThemes = [...settings.enabledThemes, fileName];
|
|
||||||
} else {
|
|
||||||
settings.enabledThemes = settings.enabledThemes.filter(f => f !== fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onFileUpload(e: SyntheticEvent<HTMLInputElement>) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
if (!e.currentTarget?.files?.length) return;
|
|
||||||
const { files } = e.currentTarget;
|
|
||||||
|
|
||||||
const uploads = Array.from(files, file => {
|
|
||||||
const { name } = file;
|
|
||||||
if (!name.endsWith(".css")) return;
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => {
|
|
||||||
VencordNative.themes.uploadTheme(name, reader.result as string)
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(uploads);
|
|
||||||
refreshLocalThemes();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderLocalThemes() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Card className="vc-settings-card">
|
|
||||||
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
|
||||||
<div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}>
|
|
||||||
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
|
||||||
BetterDiscord Themes
|
|
||||||
</Link>
|
|
||||||
<Link href="https://github.com/search?q=discord+theme">GitHub</Link>
|
|
||||||
</div>
|
|
||||||
<Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="vc-settings-card">
|
|
||||||
<Forms.FormTitle tag="h5">External Resources</Forms.FormTitle>
|
|
||||||
<Forms.FormText>For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.</Forms.FormText>
|
|
||||||
<Forms.FormText>Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</Forms.FormText>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Forms.FormSection title="Local Themes">
|
|
||||||
<QuickActionCard>
|
|
||||||
<>
|
|
||||||
{IS_WEB ?
|
|
||||||
(
|
|
||||||
<QuickAction
|
|
||||||
text={
|
|
||||||
<span style={{ position: "relative" }}>
|
|
||||||
Upload Theme
|
|
||||||
<FileInput
|
|
||||||
ref={fileInputRef}
|
|
||||||
onChange={onFileUpload}
|
|
||||||
multiple={true}
|
|
||||||
filters={[{ extensions: ["css"] }]}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
Icon={PlusIcon}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<QuickAction
|
|
||||||
text="Open Themes Folder"
|
|
||||||
action={() => VencordNative.themes.openFolder()}
|
|
||||||
Icon={FolderIcon}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<QuickAction
|
|
||||||
text="Load missing Themes"
|
|
||||||
action={refreshLocalThemes}
|
|
||||||
Icon={RestartIcon}
|
|
||||||
/>
|
|
||||||
<QuickAction
|
|
||||||
text="Edit QuickCSS"
|
|
||||||
action={() => VencordNative.quickCss.openEditor()}
|
|
||||||
Icon={PaintbrushIcon}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{Settings.plugins.ClientTheme.enabled && (
|
|
||||||
<QuickAction
|
|
||||||
text="Edit ClientTheme"
|
|
||||||
action={() => openPluginModal(Plugins.ClientTheme)}
|
|
||||||
Icon={PencilIcon}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</QuickActionCard>
|
|
||||||
|
|
||||||
<div className={cl("grid")}>
|
|
||||||
{userThemes?.map(theme => (
|
|
||||||
<ThemeCard
|
|
||||||
key={theme.fileName}
|
|
||||||
enabled={settings.enabledThemes.includes(theme.fileName)}
|
|
||||||
onChange={enabled => onLocalThemeChange(theme.fileName, enabled)}
|
|
||||||
onDelete={async () => {
|
|
||||||
onLocalThemeChange(theme.fileName, false);
|
|
||||||
await VencordNative.themes.deleteTheme(theme.fileName);
|
|
||||||
refreshLocalThemes();
|
|
||||||
}}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Forms.FormSection>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the user leaves the online theme textbox, update the settings
|
|
||||||
function onBlur() {
|
|
||||||
settings.themeLinks = [...new Set(
|
|
||||||
themeText
|
|
||||||
.trim()
|
|
||||||
.split(/\n+/)
|
|
||||||
.map(s => s.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderOnlineThemes() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Card className="vc-settings-card vc-text-selectable">
|
|
||||||
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
|
||||||
<Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
|
|
||||||
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Forms.FormSection title="Online Themes" tag="h5">
|
|
||||||
<TextArea
|
|
||||||
value={themeText}
|
|
||||||
onChange={setThemeText}
|
|
||||||
className={"vc-settings-theme-links"}
|
|
||||||
placeholder="Theme Links"
|
|
||||||
spellCheck={false}
|
|
||||||
onBlur={onBlur}
|
|
||||||
rows={10}
|
|
||||||
/>
|
|
||||||
<Validators themeLinks={settings.themeLinks} />
|
|
||||||
</Forms.FormSection>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsTab title="Themes">
|
|
||||||
<TabBar
|
|
||||||
type="top"
|
|
||||||
look="brand"
|
|
||||||
className="vc-settings-tab-bar"
|
|
||||||
selectedItem={currentTab}
|
|
||||||
onItemSelect={setCurrentTab}
|
|
||||||
>
|
|
||||||
<TabBar.Item
|
|
||||||
className="vc-settings-tab-bar-item"
|
|
||||||
id={ThemeTab.LOCAL}
|
|
||||||
>
|
|
||||||
Local Themes
|
|
||||||
</TabBar.Item>
|
|
||||||
<TabBar.Item
|
|
||||||
className="vc-settings-tab-bar-item"
|
|
||||||
id={ThemeTab.ONLINE}
|
|
||||||
>
|
|
||||||
Online Themes
|
|
||||||
</TabBar.Item>
|
|
||||||
</TabBar>
|
|
||||||
|
|
||||||
<CspErrorCard />
|
|
||||||
{currentTab === ThemeTab.LOCAL && renderLocalThemes()}
|
|
||||||
{currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
|
|
||||||
</SettingsTab>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CspErrorCard() {
|
|
||||||
if (IS_WEB) return null;
|
|
||||||
|
|
||||||
const errors = useCspErrors();
|
|
||||||
const forceUpdate = useForceUpdater();
|
|
||||||
|
|
||||||
if (!errors.length) return null;
|
|
||||||
|
|
||||||
const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/");
|
|
||||||
|
|
||||||
const allowUrl = async (url: string) => {
|
|
||||||
const { origin: baseUrl, host } = new URL(url);
|
|
||||||
|
|
||||||
const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes");
|
|
||||||
if (result !== "ok") return;
|
|
||||||
|
|
||||||
CspBlockedUrls.forEach(url => {
|
|
||||||
if (new URL(url).host === host) {
|
|
||||||
CspBlockedUrls.delete(url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
forceUpdate();
|
|
||||||
|
|
||||||
Alerts.show({
|
|
||||||
title: "Restart Required",
|
|
||||||
body: "A restart is required to apply this change",
|
|
||||||
confirmText: "Restart now",
|
|
||||||
cancelText: "Later!",
|
|
||||||
onConfirm: relaunch
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorCard className="vc-settings-card">
|
|
||||||
<Forms.FormTitle tag="h5">Blocked Resources</Forms.FormTitle>
|
|
||||||
<Forms.FormText>Some images, styles, or fonts were blocked because they come from disallowed domains.</Forms.FormText>
|
|
||||||
<Forms.FormText>It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them.</Forms.FormText>
|
|
||||||
<Forms.FormText>
|
|
||||||
After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change.
|
|
||||||
</Forms.FormText>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.top16, Margins.bottom8)}>Blocked URLs</Forms.FormTitle>
|
|
||||||
<div className="vc-settings-csp-list">
|
|
||||||
{errors.map((url, i) => (
|
|
||||||
<div key={url}>
|
|
||||||
{i !== 0 && <Forms.FormDivider className={Margins.bottom8} />}
|
|
||||||
<div className="vc-settings-csp-row">
|
|
||||||
<Link href={url}>{url}</Link>
|
|
||||||
<Button color={Button.Colors.PRIMARY} onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}>
|
|
||||||
Allow
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasImgurHtmlDomain && (
|
|
||||||
<>
|
|
||||||
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom16)} />
|
|
||||||
<Forms.FormText>
|
|
||||||
Imgur links should be direct links in the form of <code>https://i.imgur.com/...</code>
|
|
||||||
</Forms.FormText>
|
|
||||||
<Forms.FormText>To obtain a direct link, right-click the image and select "Copy image address".</Forms.FormText>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ErrorCard>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function UserscriptThemesTab() {
|
|
||||||
return (
|
|
||||||
<SettingsTab title="Themes">
|
|
||||||
<Card className="vc-settings-card">
|
|
||||||
<Forms.FormTitle tag="h5">Themes are not supported on the Userscript!</Forms.FormTitle>
|
|
||||||
|
|
||||||
<Forms.FormText>
|
|
||||||
You can instead install themes with the <Link href={getStylusWebStoreUrl()}>Stylus extension</Link>!
|
|
||||||
</Forms.FormText>
|
|
||||||
</Card>
|
|
||||||
</SettingsTab>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IS_USERSCRIPT
|
|
||||||
? wrapTab(UserscriptThemesTab, "Themes")
|
|
||||||
: wrapTab(ThemesTab, "Themes");
|
|
||||||
|
|
@ -1,268 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useSettings } from "@api/Settings";
|
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { Link } from "@components/Link";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
|
||||||
import { relaunch } from "@utils/native";
|
|
||||||
import { useAwaiter } from "@utils/react";
|
|
||||||
import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
|
|
||||||
import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
|
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
|
||||||
|
|
||||||
import { handleSettingsTabError, SettingsTab, wrapTab } from "./shared";
|
|
||||||
|
|
||||||
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
|
|
||||||
return async () => {
|
|
||||||
dispatcher(true);
|
|
||||||
try {
|
|
||||||
await action();
|
|
||||||
} catch (e: any) {
|
|
||||||
UpdateLogger.error("Failed to update", e);
|
|
||||||
|
|
||||||
let err: string;
|
|
||||||
if (!e) {
|
|
||||||
err = "An unknown error occurred (error is undefined).\nPlease try again.";
|
|
||||||
} else if (e.code && e.cmd) {
|
|
||||||
const { code, path, cmd, stderr } = e;
|
|
||||||
|
|
||||||
if (code === "ENOENT")
|
|
||||||
err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
|
||||||
else {
|
|
||||||
err = `An error occurred while running \`${cmd}\`:\n`;
|
|
||||||
err += stderr || `Code \`${code}\`. See the console for more info`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
err = "An unknown error occurred. See the console for more info.";
|
|
||||||
}
|
|
||||||
|
|
||||||
Alerts.show({
|
|
||||||
title: "Oops!",
|
|
||||||
body: (
|
|
||||||
<ErrorCard>
|
|
||||||
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
|
|
||||||
</ErrorCard>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
dispatcher(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CommonProps {
|
|
||||||
repo: string;
|
|
||||||
repoPending: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) {
|
|
||||||
return <Link href={`${repo}/commit/${hash}`} disabled={disabled}>
|
|
||||||
{hash}
|
|
||||||
</Link>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
|
|
||||||
return (
|
|
||||||
<Card style={{ padding: "0 0.5em" }}>
|
|
||||||
{updates.map(({ hash, author, message }) => (
|
|
||||||
<div key={hash} style={{
|
|
||||||
marginTop: "0.5em",
|
|
||||||
marginBottom: "0.5em"
|
|
||||||
}}>
|
|
||||||
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
|
|
||||||
<span style={{
|
|
||||||
marginLeft: "0.5em",
|
|
||||||
color: "var(--text-default)"
|
|
||||||
}}>{message} - {author}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Updatable(props: CommonProps) {
|
|
||||||
const [updates, setUpdates] = React.useState(changes);
|
|
||||||
const [isChecking, setIsChecking] = React.useState(false);
|
|
||||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
|
||||||
|
|
||||||
const isOutdated = (updates?.length ?? 0) > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!updates && updateError ? (
|
|
||||||
<>
|
|
||||||
<Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText>
|
|
||||||
<ErrorCard style={{ padding: "1em" }}>
|
|
||||||
<p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p>
|
|
||||||
</ErrorCard>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
|
||||||
{isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
|
|
||||||
</Forms.FormText>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isOutdated && <Changes updates={updates} {...props} />}
|
|
||||||
|
|
||||||
<Flex className={classes(Margins.bottom8, Margins.top8)}>
|
|
||||||
{isOutdated && <Button
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
disabled={isUpdating || isChecking}
|
|
||||||
onClick={withDispatcher(setIsUpdating, async () => {
|
|
||||||
if (await update()) {
|
|
||||||
setUpdates([]);
|
|
||||||
await new Promise<void>(r => {
|
|
||||||
Alerts.show({
|
|
||||||
title: "Update Success!",
|
|
||||||
body: "Successfully updated. Restart now to apply the changes?",
|
|
||||||
confirmText: "Restart",
|
|
||||||
cancelText: "Not now!",
|
|
||||||
onConfirm() {
|
|
||||||
relaunch();
|
|
||||||
r();
|
|
||||||
},
|
|
||||||
onCancel: r
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Update Now
|
|
||||||
</Button>}
|
|
||||||
<Button
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
disabled={isUpdating || isChecking}
|
|
||||||
onClick={withDispatcher(setIsChecking, async () => {
|
|
||||||
const outdated = await checkForUpdates();
|
|
||||||
if (outdated) {
|
|
||||||
setUpdates(changes);
|
|
||||||
} else {
|
|
||||||
setUpdates([]);
|
|
||||||
Toasts.show({
|
|
||||||
message: "No updates found!",
|
|
||||||
id: Toasts.genId(),
|
|
||||||
type: Toasts.Type.MESSAGE,
|
|
||||||
options: {
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Check for Updates
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Newer(props: CommonProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
|
||||||
Your local copy has more recent commits. Please stash or reset them.
|
|
||||||
</Forms.FormText>
|
|
||||||
<Changes {...props} updates={changes} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Updater() {
|
|
||||||
const settings = useSettings(["autoUpdate", "autoUpdateNotification"]);
|
|
||||||
|
|
||||||
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (err)
|
|
||||||
UpdateLogger.error("Failed to retrieve repo", err);
|
|
||||||
}, [err]);
|
|
||||||
|
|
||||||
const commonProps: CommonProps = {
|
|
||||||
repo,
|
|
||||||
repoPending
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsTab title="Vencord Updater">
|
|
||||||
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
|
|
||||||
<Switch
|
|
||||||
value={settings.autoUpdate}
|
|
||||||
onChange={(v: boolean) => settings.autoUpdate = v}
|
|
||||||
note="Automatically update Vencord without confirmation prompt"
|
|
||||||
>
|
|
||||||
Automatically update
|
|
||||||
</Switch>
|
|
||||||
<Switch
|
|
||||||
value={settings.autoUpdateNotification}
|
|
||||||
onChange={(v: boolean) => settings.autoUpdateNotification = v}
|
|
||||||
note="Shows a notification when Vencord automatically updates"
|
|
||||||
disabled={!settings.autoUpdate}
|
|
||||||
>
|
|
||||||
Get notified when an automatic update completes
|
|
||||||
</Switch>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
|
||||||
|
|
||||||
<Forms.FormText className="vc-text-selectable">
|
|
||||||
{repoPending
|
|
||||||
? repo
|
|
||||||
: err
|
|
||||||
? "Failed to retrieve - check console"
|
|
||||||
: (
|
|
||||||
<Link href={repo}>
|
|
||||||
{repo.split("/").slice(-2).join("/")}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{" "}(<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)
|
|
||||||
</Forms.FormText>
|
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
|
||||||
|
|
||||||
{isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />}
|
|
||||||
</SettingsTab>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");
|
|
||||||
|
|
||||||
export const openUpdaterModal = IS_UPDATER_DISABLED ? null : function () {
|
|
||||||
const UpdaterTab = wrapTab(Updater, "Updater");
|
|
||||||
|
|
||||||
try {
|
|
||||||
openModal(wrapTab((modalProps: ModalProps) => (
|
|
||||||
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
|
||||||
<ModalContent className="vc-updater-modal">
|
|
||||||
<ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" />
|
|
||||||
<UpdaterTab />
|
|
||||||
</ModalContent>
|
|
||||||
</ModalRoot>
|
|
||||||
), "UpdaterModal"));
|
|
||||||
} catch {
|
|
||||||
handleSettingsTabError();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,301 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
|
||||||
import { useSettings } from "@api/Settings";
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import DonateButton from "@components/DonateButton";
|
|
||||||
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
|
||||||
import { gitRemote } from "@shared/vencordUserAgent";
|
|
||||||
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { identity, isPluginDev } from "@utils/misc";
|
|
||||||
import { relaunch } from "@utils/native";
|
|
||||||
import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import BadgeAPI from "../../plugins/_api/badges";
|
|
||||||
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
|
||||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
|
||||||
import { SpecialCard } from "./SpecialCard";
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
|
||||||
|
|
||||||
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
|
|
||||||
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png";
|
|
||||||
|
|
||||||
const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png";
|
|
||||||
const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png";
|
|
||||||
|
|
||||||
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
|
|
||||||
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
|
|
||||||
|
|
||||||
type KeysOfType<Object, Type> = {
|
|
||||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
|
||||||
}[keyof Object];
|
|
||||||
|
|
||||||
function VencordSettings() {
|
|
||||||
const settings = useSettings();
|
|
||||||
|
|
||||||
const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);
|
|
||||||
|
|
||||||
const isWindows = navigator.platform.toLowerCase().startsWith("win");
|
|
||||||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
|
||||||
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
|
||||||
|
|
||||||
const user = UserStore.getCurrentUser();
|
|
||||||
|
|
||||||
const Switches: Array<false | {
|
|
||||||
key: KeysOfType<typeof settings, boolean>;
|
|
||||||
title: string;
|
|
||||||
note: string;
|
|
||||||
}> =
|
|
||||||
[
|
|
||||||
{
|
|
||||||
key: "useQuickCss",
|
|
||||||
title: "Enable Custom CSS",
|
|
||||||
note: "Loads your Custom CSS"
|
|
||||||
},
|
|
||||||
!IS_WEB && {
|
|
||||||
key: "enableReactDevtools",
|
|
||||||
title: "Enable React Developer Tools",
|
|
||||||
note: "Requires a full restart"
|
|
||||||
},
|
|
||||||
!IS_WEB && (!IS_DISCORD_DESKTOP || !isWindows ? {
|
|
||||||
key: "frameless",
|
|
||||||
title: "Disable the window frame",
|
|
||||||
note: "Requires a full restart"
|
|
||||||
} : {
|
|
||||||
key: "winNativeTitleBar",
|
|
||||||
title: "Use Windows' native title bar instead of Discord's custom one",
|
|
||||||
note: "Requires a full restart"
|
|
||||||
}),
|
|
||||||
!IS_WEB && {
|
|
||||||
key: "transparent",
|
|
||||||
title: "Enable window transparency.",
|
|
||||||
note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart"
|
|
||||||
},
|
|
||||||
!IS_WEB && isWindows && {
|
|
||||||
key: "winCtrlQ",
|
|
||||||
title: "Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)",
|
|
||||||
note: "Requires a full restart"
|
|
||||||
},
|
|
||||||
IS_DISCORD_DESKTOP && {
|
|
||||||
key: "disableMinSize",
|
|
||||||
title: "Disable minimum window size",
|
|
||||||
note: "Requires a full restart"
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsTab title="Vencord Settings">
|
|
||||||
{isDonor(user?.id)
|
|
||||||
? (
|
|
||||||
<SpecialCard
|
|
||||||
title="Donations"
|
|
||||||
subtitle="Thank you for donating!"
|
|
||||||
description="You can manage your perks at any time by messaging @vending.machine."
|
|
||||||
cardImage={VENNIE_DONATOR_IMAGE}
|
|
||||||
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
|
||||||
backgroundColor="#ED87A9"
|
|
||||||
>
|
|
||||||
<DonateButtonComponent />
|
|
||||||
</SpecialCard>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<SpecialCard
|
|
||||||
title="Support the Project"
|
|
||||||
description="Please consider supporting the development of Vencord by donating!"
|
|
||||||
cardImage={donateImage}
|
|
||||||
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
|
||||||
backgroundColor="#c3a3ce"
|
|
||||||
>
|
|
||||||
<DonateButtonComponent />
|
|
||||||
</SpecialCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{isPluginDev(user?.id) && (
|
|
||||||
<SpecialCard
|
|
||||||
title="Contributions"
|
|
||||||
subtitle="Thank you for contributing!"
|
|
||||||
description="Since you've contributed to Vencord you now have a cool new badge!"
|
|
||||||
cardImage={COZY_CONTRIB_IMAGE}
|
|
||||||
backgroundImage={CONTRIB_BACKGROUND_IMAGE}
|
|
||||||
backgroundColor="#EDCC87"
|
|
||||||
buttonTitle="See what you've contributed to"
|
|
||||||
buttonOnClick={() => openContributorModal(user)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Forms.FormSection title="Quick Actions">
|
|
||||||
<QuickActionCard>
|
|
||||||
<QuickAction
|
|
||||||
Icon={LogIcon}
|
|
||||||
text="Notification Log"
|
|
||||||
action={openNotificationLogModal}
|
|
||||||
/>
|
|
||||||
<QuickAction
|
|
||||||
Icon={PaintbrushIcon}
|
|
||||||
text="Edit QuickCSS"
|
|
||||||
action={() => VencordNative.quickCss.openEditor()}
|
|
||||||
/>
|
|
||||||
{!IS_WEB && (
|
|
||||||
<QuickAction
|
|
||||||
Icon={RestartIcon}
|
|
||||||
text="Relaunch Discord"
|
|
||||||
action={relaunch}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!IS_WEB && (
|
|
||||||
<QuickAction
|
|
||||||
Icon={FolderIcon}
|
|
||||||
text="Open Settings Folder"
|
|
||||||
action={() => VencordNative.settings.openFolder()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<QuickAction
|
|
||||||
Icon={GithubIcon}
|
|
||||||
text="View Source Code"
|
|
||||||
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
|
|
||||||
/>
|
|
||||||
</QuickActionCard>
|
|
||||||
</Forms.FormSection>
|
|
||||||
|
|
||||||
<Forms.FormDivider />
|
|
||||||
|
|
||||||
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
|
||||||
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
|
|
||||||
Hint: You can change the position of this settings section in the
|
|
||||||
{" "}<Button
|
|
||||||
look={Button.Looks.BLANK}
|
|
||||||
style={{ color: "var(--text-link)", display: "inline-block" }}
|
|
||||||
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
|
|
||||||
>
|
|
||||||
settings of the Settings plugin
|
|
||||||
</Button>!
|
|
||||||
</Forms.FormText>
|
|
||||||
|
|
||||||
{Switches.map(s => s && (
|
|
||||||
<Switch
|
|
||||||
key={s.key}
|
|
||||||
value={settings[s.key]}
|
|
||||||
onChange={v => settings[s.key] = v}
|
|
||||||
note={s.note}
|
|
||||||
>
|
|
||||||
{s.title}
|
|
||||||
</Switch>
|
|
||||||
))}
|
|
||||||
</Forms.FormSection>
|
|
||||||
|
|
||||||
|
|
||||||
{needsVibrancySettings && <>
|
|
||||||
<Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle>
|
|
||||||
<Select
|
|
||||||
className={Margins.bottom20}
|
|
||||||
placeholder="Window vibrancy style"
|
|
||||||
options={[
|
|
||||||
// Sorted from most opaque to most transparent
|
|
||||||
{
|
|
||||||
label: "No vibrancy", value: undefined
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Under Page (window tinting)",
|
|
||||||
value: "under-page"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Content",
|
|
||||||
value: "content"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Window",
|
|
||||||
value: "window"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Selection",
|
|
||||||
value: "selection"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Titlebar",
|
|
||||||
value: "titlebar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Header",
|
|
||||||
value: "header"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Sidebar",
|
|
||||||
value: "sidebar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Tooltip",
|
|
||||||
value: "tooltip"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Menu",
|
|
||||||
value: "menu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Popover",
|
|
||||||
value: "popover"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Fullscreen UI (transparent but slightly muted)",
|
|
||||||
value: "fullscreen-ui"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "HUD (Most transparent)",
|
|
||||||
value: "hud"
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
select={v => settings.macosVibrancyStyle = v}
|
|
||||||
isSelected={v => settings.macosVibrancyStyle === v}
|
|
||||||
serialize={identity} />
|
|
||||||
</>}
|
|
||||||
|
|
||||||
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
|
|
||||||
<Flex>
|
|
||||||
<Button onClick={openNotificationSettingsModal}>
|
|
||||||
Notification Settings
|
|
||||||
</Button>
|
|
||||||
<Button onClick={openNotificationLogModal}>
|
|
||||||
View Notification Log
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Forms.FormSection>
|
|
||||||
</SettingsTab>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DonateButtonComponent() {
|
|
||||||
return (
|
|
||||||
<DonateButton
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
color={Button.Colors.WHITE}
|
|
||||||
style={{ marginTop: "1em" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDonor(userId: string): boolean {
|
|
||||||
const donorBadges = BadgeAPI.getDonorBadges(userId);
|
|
||||||
return GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default wrapTab(VencordSettings, "Vencord Settings");
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./Badge";
|
|
||||||
export * from "./CheckedTextInput";
|
export * from "./CheckedTextInput";
|
||||||
export * from "./CodeBlock";
|
export * from "./CodeBlock";
|
||||||
export * from "./DonateButton";
|
|
||||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||||
export * from "./ErrorCard";
|
export * from "./ErrorCard";
|
||||||
export * from "./Flex";
|
export * from "./Flex";
|
||||||
|
export * from "./Grid";
|
||||||
export * from "./Heart";
|
export * from "./Heart";
|
||||||
export * from "./Icons";
|
export * from "./Icons";
|
||||||
export * from "./Link";
|
export * from "./Link";
|
||||||
export * from "./Switch";
|
export * from "./settings";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
.vc-addon-card {
|
.vc-addon-card {
|
||||||
background-color: var(--background-base-lower-alt);
|
background-color: var(--card-primary-bg);
|
||||||
color: var(--interactive-active);
|
color: var(--interactive-active);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -11,26 +12,15 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-addon-card {
|
|
||||||
background-color: var(--card-primary-bg);
|
|
||||||
border: 1px solid var(--border-subtle);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-addon-card-disabled {
|
.vc-addon-card-disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-addon-card:hover {
|
.vc-addon-card:hover {
|
||||||
background-color: var(--background-tertiary);
|
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: var(--elevation-high);
|
box-shadow: var(--elevation-high);
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-addon-card:hover {
|
|
||||||
/* same as non-hover, here to overwrite the non-refresh hover background */
|
|
||||||
background-color: var(--card-primary-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-addon-header {
|
.vc-addon-header {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./addonCard.css";
|
import "./AddonCard.css";
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Badge } from "@components/Badge";
|
import { AddonBadge } from "@components/settings/PluginBadge";
|
||||||
import { Switch } from "@components/Switch";
|
import { Switch } from "@components/settings/Switch";
|
||||||
import { Text, useRef } from "@webpack/common";
|
import { Text, useRef } from "@webpack/common";
|
||||||
import type { MouseEventHandler, ReactNode } from "react";
|
import type { MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
|
@ -44,6 +44,7 @@ interface Props {
|
||||||
export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) {
|
export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) {
|
||||||
const titleRef = useRef<HTMLDivElement>(null);
|
const titleRef = useRef<HTMLDivElement>(null);
|
||||||
const titleContainerRef = useRef<HTMLDivElement>(null);
|
const titleContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cl("card", { "card-disabled": disabled })}
|
className={cl("card", { "card-disabled": disabled })}
|
||||||
|
|
@ -67,8 +68,10 @@ export function AddonCard({ disabled, isNew, name, infoButton, footer, author, e
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
</div>{isNew && <Badge text="NEW" color="#ED4245" />}
|
</div>
|
||||||
|
{isNew && <AddonBadge text="NEW" color="#ED4245" />}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{!!author && (
|
{!!author && (
|
||||||
<Text variant="text-md/normal" className={cl("author")}>
|
<Text variant="text-md/normal" className={cl("author")}>
|
||||||
{author}
|
{author}
|
||||||
|
|
@ -16,10 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Heart } from "@components/Heart";
|
||||||
|
import { ButtonProps } from "@vencord/discord-types";
|
||||||
import { Button } from "@webpack/common";
|
import { Button } from "@webpack/common";
|
||||||
import { ButtonProps } from "@webpack/types";
|
|
||||||
|
|
||||||
import { Heart } from "./Heart";
|
|
||||||
|
|
||||||
export default function DonateButton({
|
export default function DonateButton({
|
||||||
look = Button.Looks.LINK,
|
look = Button.Looks.LINK,
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Badge({ text, color }) {
|
export function AddonBadge({ text, color }) {
|
||||||
return (
|
return (
|
||||||
<div className="vc-plugins-badge" style={{
|
<div className="vc-addon-badge" style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
justifySelf: "flex-end",
|
justifySelf: "flex-end",
|
||||||
marginLeft: "auto"
|
marginLeft: "auto"
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
.vc-settings-quickActions-pill {
|
.vc-settings-quickActions-pill {
|
||||||
all: unset;
|
all: unset;
|
||||||
background: var(--background-base-lower);
|
background: var(--button-secondary-background);
|
||||||
color: var(--header-secondary);
|
color: var(--header-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:hover {
|
.vc-settings-quickActions-pill:hover {
|
||||||
background: var(--background-base-lower-alt);
|
background: var(--button-secondary-background-hover);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: var(--elevation-high);
|
box-shadow: var(--elevation-high);
|
||||||
}
|
}
|
||||||
|
|
@ -36,14 +36,6 @@
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-settings-quickActions-pill {
|
|
||||||
background: var(--button-secondary-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.visual-refresh .vc-settings-quickActions-pill:hover {
|
|
||||||
background: var(--button-secondary-background-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-quickActions-img {
|
.vc-settings-quickActions-img {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./quickActions.css";
|
import "./QuickAction.css";
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Card } from "@webpack/common";
|
import { Card } from "@webpack/common";
|
||||||
|
|
@ -12,11 +12,6 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-card {
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-special-card-special {
|
.vc-special-card-special {
|
||||||
padding: 1em 1.5em;
|
padding: 1em 1.5em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./specialCard.css";
|
import "./SpecialCard.css";
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Card, Clickable, Forms, React } from "@webpack/common";
|
import { Card, Clickable, Forms, React } from "@webpack/common";
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue