Compare commits
No commits in common. "hive" and "latest" have entirely different histories.
238 changed files with 1516 additions and 3088 deletions
20
README.md
20
README.md
|
|
@ -1,30 +1,26 @@
|
||||||
# A fork. A fork. We're a fork.
|
# A fork. A fork. We're a fork.
|
||||||
|
|
||||||
Installing is the same as the Vencord devs laid out: [(Install)](https://docs.vencord.dev/installing/)
|
|
||||||
* Beyond that, be sure to clone *this* repository instead of their upstream one.
|
|
||||||
* `git clone https://git.dorkbutt.lol/dorkbutt/vencord`
|
|
||||||
* After completing the steps, go to Settings > Vencord > Plugins and search for
|
|
||||||
"FORKED - usrbg" and enable it. Be sure to tweak its settings!
|
|
||||||
|
|
||||||
|
|
||||||
# Vencord
|
# Vencord
|
||||||
|
|
||||||

|
|
||||||
[](https://codeberg.org/Vee/cord)
|
[](https://codeberg.org/Vee/cord)
|
||||||
|
|
||||||
The cutest Discord client mod
|
The cutest Discord client mod
|
||||||
|
|
||||||

|
|  |
|
||||||
|
| :--------------------------------------------------------------------------------------------------: |
|
||||||
|
| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Easy to install
|
- Super easy to install (Download Installer, open, click install button, done)
|
||||||
- [100+ built in plugins](https://vencord.dev/plugins)
|
- 100+ plugins built in: [See a list](https://vencord.dev/plugins)
|
||||||
|
- Some highlights: SpotifyControls, MessageLogger, Experiments, GameActivityToggle, Translate, NoTrack, QuickReply, Free Emotes/Stickers, PermissionsViewer, CustomCommands, ShowHiddenChannels, PronounDB
|
||||||
- Fairly lightweight despite the many inbuilt plugins
|
- Fairly lightweight despite the many inbuilt plugins
|
||||||
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
|
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
|
||||||
- Works on any Discord branch: Stable, Canary or PTB all work
|
- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!)
|
||||||
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
|
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
|
||||||
- Privacy friendly: blocks Discord analytics & crash reporting out of the box and has no telemetry
|
- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry
|
||||||
- Maintained very actively, broken plugins are usually fixed within 12 hours
|
- Maintained very actively, broken plugins are usually fixed within 12 hours
|
||||||
- Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional)
|
- Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,16 @@
|
||||||
/// <reference path="../src/globals.d.ts" />
|
/// <reference path="../src/globals.d.ts" />
|
||||||
|
|
||||||
import monacoHtmlLocal from "file://monacoWin.html?minify";
|
import monacoHtmlLocal from "file://monacoWin.html?minify";
|
||||||
|
import monacoHtmlCdn from "file://../src/main/monacoWin.html?minify";
|
||||||
import * as DataStore from "../src/api/DataStore";
|
import * as DataStore from "../src/api/DataStore";
|
||||||
import { debounce, localStorage } from "../src/utils";
|
import { debounce } from "../src/utils";
|
||||||
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
|
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
|
||||||
import { getTheme, Theme } from "../src/utils/discord";
|
import { getTheme, Theme } from "../src/utils/discord";
|
||||||
import { getThemeInfo } from "../src/main/themes";
|
import { getThemeInfo } from "../src/main/themes";
|
||||||
import { Settings } from "../src/Vencord";
|
import { Settings } from "../src/Vencord";
|
||||||
import { getStylusWebStoreUrl } from "@utils/web";
|
|
||||||
|
// Discord deletes this so need to store in variable
|
||||||
|
const { localStorage } = window;
|
||||||
|
|
||||||
// listeners for ipc.on
|
// listeners for ipc.on
|
||||||
const cssListeners = new Set<(css: string) => void>();
|
const cssListeners = new Set<(css: string) => void>();
|
||||||
|
|
@ -42,13 +45,12 @@ window.VencordNative = {
|
||||||
themes: {
|
themes: {
|
||||||
uploadTheme: (fileName: string, fileData: string) => DataStore.set(fileName, fileData, themeStore),
|
uploadTheme: (fileName: string, fileData: string) => DataStore.set(fileName, fileData, themeStore),
|
||||||
deleteTheme: (fileName: string) => DataStore.del(fileName, themeStore),
|
deleteTheme: (fileName: string) => DataStore.del(fileName, themeStore),
|
||||||
|
getThemesDir: async () => "",
|
||||||
getThemesList: () => DataStore.entries(themeStore).then(entries =>
|
getThemesList: () => DataStore.entries(themeStore).then(entries =>
|
||||||
entries.map(([name, css]) => getThemeInfo(css, name.toString()))
|
entries.map(([name, css]) => getThemeInfo(css, name.toString()))
|
||||||
),
|
),
|
||||||
getThemeData: (fileName: string) => DataStore.get(fileName, themeStore),
|
getThemeData: (fileName: string) => DataStore.get(fileName, themeStore),
|
||||||
getSystemValues: async () => ({}),
|
getSystemValues: async () => ({}),
|
||||||
|
|
||||||
openFolder: async () => Promise.reject("themes:openFolder is not supported on web"),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
native: {
|
native: {
|
||||||
|
|
@ -75,14 +77,6 @@ window.VencordNative = {
|
||||||
addThemeChangeListener: NOOP,
|
addThemeChangeListener: NOOP,
|
||||||
openFile: NOOP_ASYNC,
|
openFile: NOOP_ASYNC,
|
||||||
async openEditor() {
|
async openEditor() {
|
||||||
if (IS_USERSCRIPT) {
|
|
||||||
const shouldOpenWebStore = confirm("QuickCSS is not supported on the Userscript. You can instead use the Stylus extension.\n\nDo you want to open the Stylus web store page?");
|
|
||||||
if (shouldOpenWebStore) {
|
|
||||||
window.open(getStylusWebStoreUrl(), "_blank");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
|
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
|
||||||
const win = open("about:blank", "VencordQuickCss", features);
|
const win = open("about:blank", "VencordQuickCss", features);
|
||||||
if (!win) {
|
if (!win) {
|
||||||
|
|
@ -98,7 +92,7 @@ window.VencordNative = {
|
||||||
? "vs-light"
|
? "vs-light"
|
||||||
: "vs-dark";
|
: "vs-dark";
|
||||||
|
|
||||||
win.document.write(monacoHtmlLocal);
|
win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -112,9 +106,8 @@ window.VencordNative = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)),
|
set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)),
|
||||||
openFolder: async () => Promise.reject("settings:openFolder is not supported on web"),
|
getSettingsDir: async () => "LocalStorage"
|
||||||
},
|
},
|
||||||
|
|
||||||
pluginHelpers: {} as any,
|
pluginHelpers: {} as any,
|
||||||
csp: {} as any,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.12.6",
|
"version": "1.12.2",
|
||||||
"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,165 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from "./commands";
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
packages/discord-types/src/classes.d.ts
vendored
21
packages/discord-types/src/classes.d.ts
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
export interface ImageModalClasses {
|
|
||||||
image: string,
|
|
||||||
modal: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ButtonWrapperClasses {
|
|
||||||
hoverScale: string;
|
|
||||||
buttonWrapper: string;
|
|
||||||
button: string;
|
|
||||||
iconMask: string;
|
|
||||||
buttonContent: string;
|
|
||||||
icon: string;
|
|
||||||
pulseIcon: string;
|
|
||||||
pulseButton: string;
|
|
||||||
notificationDot: string;
|
|
||||||
sparkleContainer: string;
|
|
||||||
sparkleStar: string;
|
|
||||||
sparklePlus: string;
|
|
||||||
sparkle: string;
|
|
||||||
active: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
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
83
packages/discord-types/src/common/Channel.d.ts
vendored
|
|
@ -1,83 +0,0 @@
|
||||||
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
64
packages/discord-types/src/common/Guild.d.ts
vendored
|
|
@ -1,64 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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
12
packages/discord-types/src/common/Record.d.ts
vendored
|
|
@ -1,12 +0,0 @@
|
||||||
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
33
packages/discord-types/src/common/Role.d.ts
vendored
|
|
@ -1,33 +0,0 @@
|
||||||
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
65
packages/discord-types/src/common/User.d.ts
vendored
|
|
@ -1,65 +0,0 @@
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
8
packages/discord-types/src/common/index.d.ts
vendored
8
packages/discord-types/src/common/index.d.ts
vendored
|
|
@ -1,8 +0,0 @@
|
||||||
export * from "./Application";
|
|
||||||
export * from "./Channel";
|
|
||||||
export * from "./Guild";
|
|
||||||
export * from "./GuildMember";
|
|
||||||
export * from "./messages";
|
|
||||||
export * from "./Role";
|
|
||||||
export * from "./User";
|
|
||||||
export * from "./Record";
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,191 +0,0 @@
|
||||||
import { CommandOption } from './Commands';
|
|
||||||
import { User, UserJSON } from '../User';
|
|
||||||
import { Embed, EmbedJSON } from './Embed';
|
|
||||||
import { DiscordRecord } from "../Record";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: looks like discord has moved over to Date instead of Moment;
|
|
||||||
*/
|
|
||||||
export class Message extends DiscordRecord {
|
|
||||||
constructor(message: object);
|
|
||||||
activity: unknown;
|
|
||||||
application: unknown;
|
|
||||||
applicationId: string | unknown;
|
|
||||||
attachments: MessageAttachment[];
|
|
||||||
author: User;
|
|
||||||
blocked: boolean;
|
|
||||||
bot: boolean;
|
|
||||||
call: {
|
|
||||||
duration: moment.Duration;
|
|
||||||
endedTimestamp: moment.Moment;
|
|
||||||
participants: string[];
|
|
||||||
};
|
|
||||||
channel_id: string;
|
|
||||||
/**
|
|
||||||
* NOTE: not fully typed
|
|
||||||
*/
|
|
||||||
codedLinks: {
|
|
||||||
code?: string;
|
|
||||||
type: string;
|
|
||||||
}[];
|
|
||||||
colorString: unknown;
|
|
||||||
components: unknown[];
|
|
||||||
content: string;
|
|
||||||
customRenderedContent: unknown;
|
|
||||||
editedTimestamp: Date;
|
|
||||||
embeds: Embed[];
|
|
||||||
flags: number;
|
|
||||||
giftCodes: string[];
|
|
||||||
id: string;
|
|
||||||
interaction: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: number;
|
|
||||||
user: User;
|
|
||||||
}[] | undefined;
|
|
||||||
interactionData: {
|
|
||||||
application_command: {
|
|
||||||
application_id: string;
|
|
||||||
default_member_permissions: unknown;
|
|
||||||
default_permission: boolean;
|
|
||||||
description: string;
|
|
||||||
dm_permission: unknown;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
options: CommandOption[];
|
|
||||||
permissions: unknown[];
|
|
||||||
type: number;
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
attachments: MessageAttachment[];
|
|
||||||
guild_id: string | undefined;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
options: {
|
|
||||||
focused: unknown;
|
|
||||||
name: string;
|
|
||||||
type: number;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
type: number;
|
|
||||||
version: string;
|
|
||||||
}[];
|
|
||||||
interactionError: unknown[];
|
|
||||||
isSearchHit: boolean;
|
|
||||||
loggingName: unknown;
|
|
||||||
mentionChannels: string[];
|
|
||||||
mentionEveryone: boolean;
|
|
||||||
mentionRoles: string[];
|
|
||||||
mentioned: boolean;
|
|
||||||
mentions: string[];
|
|
||||||
messageReference: {
|
|
||||||
guild_id?: string;
|
|
||||||
channel_id: string;
|
|
||||||
message_id: string;
|
|
||||||
} | undefined;
|
|
||||||
nick: unknown; // probably a string
|
|
||||||
nonce: string | undefined;
|
|
||||||
pinned: boolean;
|
|
||||||
reactions: MessageReaction[];
|
|
||||||
state: string;
|
|
||||||
stickerItems: {
|
|
||||||
format_type: number;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}[];
|
|
||||||
stickers: unknown[];
|
|
||||||
timestamp: moment.Moment;
|
|
||||||
tts: boolean;
|
|
||||||
type: number;
|
|
||||||
webhookId: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Doesn't actually update the original message; it just returns a new message instance with the added reaction.
|
|
||||||
*/
|
|
||||||
addReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message;
|
|
||||||
/**
|
|
||||||
* Searches each reaction and if the provided string has an index above -1 it'll return the reaction object.
|
|
||||||
*/
|
|
||||||
getReaction(name: string): MessageReaction;
|
|
||||||
/**
|
|
||||||
* Doesn't actually update the original message; it just returns the message instance without the reaction searched with the provided emoji object.
|
|
||||||
*/
|
|
||||||
removeReactionsForEmoji(emoji: ReactionEmoji): Message;
|
|
||||||
/**
|
|
||||||
* Doesn't actually update the original message; it just returns the message instance without the reaction.
|
|
||||||
*/
|
|
||||||
removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message;
|
|
||||||
|
|
||||||
getChannelId(): string;
|
|
||||||
hasFlag(flag: number): boolean;
|
|
||||||
isCommandType(): boolean;
|
|
||||||
isEdited(): boolean;
|
|
||||||
isSystemDM(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A smaller Message object found in FluxDispatcher and elsewhere. */
|
|
||||||
export interface MessageJSON {
|
|
||||||
attachments: MessageAttachment[];
|
|
||||||
author: UserJSON;
|
|
||||||
channel_id: string;
|
|
||||||
components: unknown[];
|
|
||||||
content: string;
|
|
||||||
edited_timestamp: string;
|
|
||||||
embeds: EmbedJSON[];
|
|
||||||
flags: number;
|
|
||||||
guild_id: string | undefined;
|
|
||||||
id: string;
|
|
||||||
loggingName: unknown;
|
|
||||||
member: {
|
|
||||||
avatar: string | undefined;
|
|
||||||
communication_disabled_until: string | undefined;
|
|
||||||
deaf: boolean;
|
|
||||||
hoisted_role: string | undefined;
|
|
||||||
is_pending: boolean;
|
|
||||||
joined_at: string;
|
|
||||||
mute: boolean;
|
|
||||||
nick: string | boolean;
|
|
||||||
pending: boolean;
|
|
||||||
premium_since: string | undefined;
|
|
||||||
roles: string[];
|
|
||||||
} | undefined;
|
|
||||||
mention_everyone: boolean;
|
|
||||||
mention_roles: string[];
|
|
||||||
mentions: UserJSON[];
|
|
||||||
message_reference: {
|
|
||||||
guild_id?: string;
|
|
||||||
channel_id: string;
|
|
||||||
message_id: string;
|
|
||||||
} | undefined;
|
|
||||||
nonce: string | undefined;
|
|
||||||
pinned: boolean;
|
|
||||||
referenced_message: MessageJSON | undefined;
|
|
||||||
state: string;
|
|
||||||
timestamp: string;
|
|
||||||
tts: boolean;
|
|
||||||
type: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageAttachment {
|
|
||||||
filename: string;
|
|
||||||
id: string;
|
|
||||||
proxy_url: string;
|
|
||||||
size: number;
|
|
||||||
spoiler: boolean;
|
|
||||||
url: string;
|
|
||||||
content_type?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReactionEmoji {
|
|
||||||
id: string | undefined;
|
|
||||||
name: string;
|
|
||||||
animated: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageReaction {
|
|
||||||
count: number;
|
|
||||||
emoji: ReactionEmoji;
|
|
||||||
me: boolean;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export * from "./Commands";
|
|
||||||
export * from "./Message";
|
|
||||||
export * from "./Embed";
|
|
||||||
export * from "./Emoji";
|
|
||||||
30
packages/discord-types/src/flux.d.ts
vendored
30
packages/discord-types/src/flux.d.ts
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
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
22
packages/discord-types/src/fluxEvents.d.ts
vendored
File diff suppressed because one or more lines are too long
9
packages/discord-types/src/index.d.ts
vendored
9
packages/discord-types/src/index.d.ts
vendored
|
|
@ -1,9 +0,0 @@
|
||||||
export * from "./common";
|
|
||||||
export * from "./classes";
|
|
||||||
export * from "./components";
|
|
||||||
export * from "./flux";
|
|
||||||
export * from "./fluxEvents";
|
|
||||||
export * from "./menu";
|
|
||||||
export * from "./stores";
|
|
||||||
export * from "./utils";
|
|
||||||
export * as Webpack from "../webpack";
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
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
44
packages/discord-types/src/stores/FluxStore.d.ts
vendored
|
|
@ -1,44 +0,0 @@
|
||||||
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[];
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { FluxStore, Role } from "..";
|
|
||||||
|
|
||||||
export class GuildRoleStore extends FluxStore {
|
|
||||||
getRole(guildId: string, roleId: string): Role;
|
|
||||||
getRoles(guildId: string): Record<string, Role>;
|
|
||||||
getAllGuildRoles(): Record<string, Record<string, Role>>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { Guild, FluxStore } from "..";
|
|
||||||
|
|
||||||
export class GuildStore extends FluxStore {
|
|
||||||
getGuild(guildId: string): Guild;
|
|
||||||
getGuildCount(): number;
|
|
||||||
getGuilds(): Record<string, Guild>;
|
|
||||||
getGuildIds(): string[];
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { FluxStore } from "..";
|
|
||||||
|
|
||||||
export class RelationshipStore extends FluxStore {
|
|
||||||
getFriendIDs(): string[];
|
|
||||||
getIgnoredIDs(): string[];
|
|
||||||
getBlockedIDs(): string[];
|
|
||||||
|
|
||||||
getPendingCount(): number;
|
|
||||||
getRelationshipCount(): number;
|
|
||||||
|
|
||||||
/** Related to friend nicknames. */
|
|
||||||
getNickname(userId: string): string;
|
|
||||||
/** @returns Enum value from constants.RelationshipTypes */
|
|
||||||
getRelationshipType(userId: string): number;
|
|
||||||
isFriend(userId: string): boolean;
|
|
||||||
isBlocked(userId: string): boolean;
|
|
||||||
isIgnored(userId: string): boolean;
|
|
||||||
getSince(userId: string): string;
|
|
||||||
|
|
||||||
getMutableRelationships(): Map<string, number>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
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
10
packages/discord-types/src/stores/UserStore.d.ts
vendored
|
|
@ -1,10 +0,0 @@
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import { FluxStore } from "..";
|
|
||||||
|
|
||||||
export class WindowStore extends FluxStore {
|
|
||||||
isElementFullScreen(): boolean;
|
|
||||||
isFocused(): boolean;
|
|
||||||
windowSize(): Record<"width" | "height", number>;
|
|
||||||
}
|
|
||||||
33
packages/discord-types/src/stores/index.d.ts
vendored
33
packages/discord-types/src/stores/index.d.ts
vendored
|
|
@ -1,33 +0,0 @@
|
||||||
// please keep in alphabetical order
|
|
||||||
export * from "./ChannelStore";
|
|
||||||
export * from "./DraftStore";
|
|
||||||
export * from "./EmojiStore";
|
|
||||||
export * from "./FluxStore";
|
|
||||||
export * from "./GuildMemberStore";
|
|
||||||
export * from "./GuildRoleStore";
|
|
||||||
export * from "./GuildStore";
|
|
||||||
export * from "./MessageStore";
|
|
||||||
export * from "./RelationshipStore";
|
|
||||||
export * from "./SelectedChannelStore";
|
|
||||||
export * from "./SelectedGuildStore";
|
|
||||||
export * from "./ThemeStore";
|
|
||||||
export * from "./UserProfileStore";
|
|
||||||
export * from "./UserStore";
|
|
||||||
export * from "./WindowStore";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React hook that returns stateful data for one or more stores
|
|
||||||
* You might need a custom comparator (4th argument) if your store data is an object
|
|
||||||
* @param stores The stores to listen to
|
|
||||||
* @param mapper A function that returns the data you need
|
|
||||||
* @param dependencies An array of reactive values which the hook depends on. Use this if your mapper or equality function depends on the value of another hook
|
|
||||||
* @param isEqual A custom comparator for the data returned by mapper
|
|
||||||
*
|
|
||||||
* @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id);
|
|
||||||
*/
|
|
||||||
export type useStateFromStores = <T>(
|
|
||||||
stores: any[],
|
|
||||||
mapper: () => T,
|
|
||||||
dependencies?: any,
|
|
||||||
isEqual?: (old: T, newer: T) => boolean
|
|
||||||
) => T;
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"@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,18 +141,6 @@ 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':
|
||||||
|
|
@ -167,6 +155,9 @@ 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
|
||||||
|
|
@ -531,6 +522,9 @@ 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==}
|
||||||
|
|
||||||
|
|
@ -962,6 +956,9 @@ 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'}
|
||||||
|
|
@ -2331,10 +2328,6 @@ 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'}
|
||||||
|
|
@ -2771,6 +2764,11 @@ 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
|
||||||
|
|
@ -3257,6 +3255,11 @@ 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
|
||||||
|
|
@ -4920,8 +4923,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ const defines = stringifyValues({
|
||||||
IS_UPDATER_DISABLED,
|
IS_UPDATER_DISABLED,
|
||||||
IS_WEB: false,
|
IS_WEB: false,
|
||||||
IS_EXTENSION: false,
|
IS_EXTENSION: false,
|
||||||
IS_USERSCRIPT: false,
|
|
||||||
VERSION,
|
VERSION,
|
||||||
BUILD_TIMESTAMP
|
BUILD_TIMESTAMP
|
||||||
});
|
});
|
||||||
|
|
@ -51,7 +50,7 @@ const nodeCommonOpts = {
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
// @ts-expect-error this is never undefined
|
// @ts-ignore this is never undefined
|
||||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ const commonOptions = {
|
||||||
define: stringifyValues({
|
define: stringifyValues({
|
||||||
IS_WEB: true,
|
IS_WEB: true,
|
||||||
IS_EXTENSION: false,
|
IS_EXTENSION: false,
|
||||||
IS_USERSCRIPT: false,
|
|
||||||
IS_STANDALONE: true,
|
IS_STANDALONE: true,
|
||||||
IS_DEV,
|
IS_DEV,
|
||||||
IS_REPORTER,
|
IS_REPORTER,
|
||||||
|
|
@ -99,7 +98,6 @@ const buildConfigs = [
|
||||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||||
define: {
|
define: {
|
||||||
...commonOptions.define,
|
...commonOptions.define,
|
||||||
IS_USERSCRIPT: "true",
|
|
||||||
window: "unsafeWindow",
|
window: "unsafeWindow",
|
||||||
},
|
},
|
||||||
outfile: "dist/Vencord.user.js",
|
outfile: "dist/Vencord.user.js",
|
||||||
|
|
|
||||||
|
|
@ -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-expect-error this is never undefined
|
// @ts-ignore this is never undefined
|
||||||
...commonOpts.plugins
|
...commonOpts.plugins
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -134,11 +134,7 @@ async function init() {
|
||||||
|
|
||||||
if (!IS_WEB && !IS_UPDATER_DISABLED) {
|
if (!IS_WEB && !IS_UPDATER_DISABLED) {
|
||||||
runUpdateCheck();
|
runUpdateCheck();
|
||||||
|
setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes
|
||||||
// this tends to get really annoying, so only do this if the user has auto-update without notification enabled
|
|
||||||
if (Settings.autoUpdate && !Settings.autoUpdateNotification) {
|
|
||||||
setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Settings } from "@api/Settings";
|
import type { Settings } from "@api/Settings";
|
||||||
import { CspRequestResult } from "@main/csp/manager";
|
|
||||||
import { PluginIpcMappings } from "@main/ipcPlugins";
|
import { PluginIpcMappings } from "@main/ipcPlugins";
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { IpcEvents } from "@shared/IpcEvents";
|
import { IpcEvents } from "@shared/IpcEvents";
|
||||||
|
|
@ -34,11 +33,10 @@ export default {
|
||||||
themes: {
|
themes: {
|
||||||
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
|
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
|
||||||
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
|
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
|
||||||
|
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
|
||||||
getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
|
getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
|
||||||
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
|
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
|
||||||
getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES),
|
getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES),
|
||||||
|
|
||||||
openFolder: () => invoke<void>(IpcEvents.OPEN_THEMES_FOLDER),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updater: {
|
updater: {
|
||||||
|
|
@ -51,8 +49,7 @@ export default {
|
||||||
settings: {
|
settings: {
|
||||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||||
set: (settings: Settings, pathToNotify?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, pathToNotify),
|
set: (settings: Settings, pathToNotify?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, pathToNotify),
|
||||||
|
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
|
||||||
openFolder: () => invoke<void>(IpcEvents.OPEN_SETTINGS_FOLDER),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
quickCss: {
|
quickCss: {
|
||||||
|
|
@ -76,17 +73,5 @@ export default {
|
||||||
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
||||||
},
|
},
|
||||||
|
|
||||||
csp: {
|
|
||||||
/**
|
|
||||||
* Note: Only supports full explicit matches, not wildcards.
|
|
||||||
*
|
|
||||||
* If `*.example.com` is allowed, `isDomainAllowed("https://sub.example.com")` will return false.
|
|
||||||
*/
|
|
||||||
isDomainAllowed: (url: string, directives: string[]) => invoke<boolean>(IpcEvents.CSP_IS_DOMAIN_ALLOWED, url, directives),
|
|
||||||
removeOverride: (url: string) => invoke<boolean>(IpcEvents.CSP_REMOVE_OVERRIDE, url),
|
|
||||||
requestAddOverride: (url: string, directives: string[], callerName: string) =>
|
|
||||||
invoke<CspRequestResult>(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName),
|
|
||||||
},
|
|
||||||
|
|
||||||
pluginHelpers: PluginHelpers
|
pluginHelpers: PluginHelpers
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,11 +17,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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() {
|
||||||
|
|
@ -49,8 +51,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: CommandArgument[], name: string): T & {} | undefined;
|
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
|
||||||
export function findOption<T>(args: CommandArgument[], name: string, fallbackValue: T): T & {};
|
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
||||||
export function findOption(args: CommandArgument[], name: string, fallbackValue?: any) {
|
export function findOption(args: Argument[], 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,39 +18,38 @@
|
||||||
|
|
||||||
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, VencordCommand } from "./types";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";
|
||||||
|
|
||||||
export * from "./commandHelpers";
|
export * from "./commandHelpers";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
|
||||||
export let BUILT_IN: VencordCommand[];
|
export let BUILT_IN: Command[];
|
||||||
export const commands = {} as Record<string, VencordCommand>;
|
export const commands = {} as Record<string, Command>;
|
||||||
|
|
||||||
// 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 CommandOption;
|
const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option;
|
||||||
const ReqPlaceholder = Symbol("RequiredMessageOption") as any as CommandOption;
|
const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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: CommandOption = OptPlaceholder;
|
export let OptionalMessageOption: Option = 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: CommandOption = ReqPlaceholder;
|
export let RequiredMessageOption: Option = 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: VencordCommand[]) {
|
export const _init = function (cmds: Command[]) {
|
||||||
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];
|
||||||
|
|
@ -62,7 +61,7 @@ export const _init = function (cmds: VencordCommand[]) {
|
||||||
return cmds;
|
return cmds;
|
||||||
} as never;
|
} as never;
|
||||||
|
|
||||||
export const _handleCommand = function (cmd: VencordCommand, args: CommandArgument[], ctx: CommandContext) {
|
export const _handleCommand = function (cmd: Command, args: Argument[], ctx: CommandContext) {
|
||||||
if (!cmd.isVencordCommand)
|
if (!cmd.isVencordCommand)
|
||||||
return cmd.execute(args, ctx);
|
return cmd.execute(args, ctx);
|
||||||
|
|
||||||
|
|
@ -93,7 +92,7 @@ export const _handleCommand = function (cmd: VencordCommand, args: CommandArgume
|
||||||
* 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 CommandOption | VencordCommand>(opt: O): O {
|
export function prepareOption<O extends Option | Command>(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) => {
|
||||||
|
|
@ -110,7 +109,7 @@ export function prepareOption<O extends CommandOption | VencordCommand>(opt: 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: VencordCommand, plugin: string) {
|
function registerSubCommands(cmd: Command, 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.");
|
||||||
|
|
@ -133,7 +132,7 @@ function registerSubCommands(cmd: VencordCommand, plugin: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerCommand<C extends VencordCommand>(command: C, plugin: string) {
|
export function registerCommand<C extends Command>(command: C, plugin: string) {
|
||||||
if (!BUILT_IN) {
|
if (!BUILT_IN) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[CommandsAPI]",
|
"[CommandsAPI]",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,106 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a Discord client mod
|
* Vencord, a modification for Discord's desktop app
|
||||||
* Copyright (c) 2025 Vendicated and contributors
|
* Copyright (c) 2022 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 { Command } from "@vencord/discord-types";
|
import { Channel, Guild } from "discord-types/general";
|
||||||
export { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@vencord/discord-types/enums";
|
import { Promisable } from "type-fest";
|
||||||
|
|
||||||
export interface VencordCommand extends Command {
|
export interface CommandContext {
|
||||||
isVencordCommand?: boolean;
|
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;
|
||||||
|
|
||||||
|
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-expect-error - file size hacks
|
// @ts-ignore - file size hacks
|
||||||
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
||||||
// @ts-expect-error - file size hacks
|
// @ts-ignore - 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 "@vencord/discord-types";
|
import { Channel, User } from "discord-types/general/index.js";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export function _modifyAccessories(
|
||||||
) {
|
) {
|
||||||
for (const [key, accessory] of accessories.entries()) {
|
for (const [key, accessory] of accessories.entries()) {
|
||||||
const res = (
|
const res = (
|
||||||
<ErrorBoundary noop message={`Failed to render ${key} Message Accessory`} key={key}>
|
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||||
<accessory.render {...props} />
|
<accessory.render {...props} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, Message } from "@vencord/discord-types";
|
import { Channel, Message } from "discord-types/general/index.js";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
export interface MessageDecorationProps {
|
export interface MessageDecorationProps {
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import type { Channel, 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");
|
||||||
|
|
|
||||||
|
|
@ -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 "@vencord/discord-types";
|
import { Channel, Message } from "discord-types/general";
|
||||||
import type { ComponentType, MouseEventHandler } from "react";
|
import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FluxStore, 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
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
all: unset;
|
all: unset;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: var(--text-default);
|
color: var(--text-normal);
|
||||||
background-color: var(--background-base-lower-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-notification-root {
|
.visual-refresh .vc-notification-root {
|
||||||
background-color: var(--background-base-low);
|
background-color: var(--bg-overlay-floating, 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) {
|
||||||
|
|
|
||||||
|
|
@ -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-expect-error "Type instantiation is excessively deep and possibly infinite"
|
// @ts-ignore "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;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
* 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 { ButtonProps } from "@vencord/discord-types";
|
|
||||||
import { Button } from "@webpack/common";
|
import { Button } from "@webpack/common";
|
||||||
|
import { ButtonProps } from "@webpack/types";
|
||||||
|
|
||||||
import { Heart } from "./Heart";
|
import { Heart } from "./Heart";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,15 +75,10 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isNoop() {
|
|
||||||
if (IS_DEV) return false;
|
|
||||||
return this.props.noop;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.error === NO_ERROR) return this.props.children;
|
if (this.state.error === NO_ERROR) return this.props.children;
|
||||||
|
|
||||||
if (this.isNoop) return null;
|
if (this.props.noop) return null;
|
||||||
|
|
||||||
if (this.props.fallback)
|
if (this.props.fallback)
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,5 @@
|
||||||
background-color: #e7828430;
|
background-color: #e7828430;
|
||||||
border: 1px solid #e78284;
|
border: 1px solid #e78284;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: var(--text-default, white);
|
color: var(--text-normal, white);
|
||||||
|
|
||||||
& a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,6 @@ export function Link(props: React.PropsWithChildren<Props>) {
|
||||||
props.style.pointerEvents = "none";
|
props.style.pointerEvents = "none";
|
||||||
props["aria-disabled"] = true;
|
props["aria-disabled"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
props.rel ??= "noreferrer";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a role="link" target="_blank" {...props}>
|
<a role="link" target="_blank" {...props}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import { DevsById } from "@utils/constants";
|
||||||
import { fetchUserProfile } from "@utils/discord";
|
import { fetchUserProfile } from "@utils/discord";
|
||||||
import { classes, pluralise } from "@utils/misc";
|
import { classes, pluralise } from "@utils/misc";
|
||||||
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
||||||
import { User } from "@vencord/discord-types";
|
|
||||||
import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@ import { Flex } from "@components/Flex";
|
||||||
import { gitRemote } from "@shared/vencordUserAgent";
|
import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { User } from "@vencord/discord-types";
|
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
import { Constructor } from "type-fest";
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
import { PluginMeta } from "~plugins";
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
@ -212,7 +212,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
const pluginMeta = PluginMeta[plugin.name];
|
const pluginMeta = PluginMeta[plugin.name];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
|
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
|
||||||
<ModalHeader separator={false}>
|
<ModalHeader separator={false}>
|
||||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
||||||
|
|
||||||
|
|
@ -268,9 +268,9 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
</div>
|
</div>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
{!!plugin.settingsAboutComponent && (
|
{!!plugin.settingsAboutComponent && (
|
||||||
<div className={Margins.bottom8}>
|
<div className={classes(Margins.bottom8, "vc-text-selectable")}>
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component">
|
<ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
|
||||||
<plugin.settingsAboutComponent tempSettings={tempSettings} />
|
<plugin.settingsAboutComponent tempSettings={tempSettings} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() =>
|
||||||
const cl = classNameFactory("vc-plugins-");
|
const cl = classNameFactory("vc-plugins-");
|
||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error");
|
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
||||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ function showErrorToast(message: string) {
|
||||||
|
|
||||||
function ReloadRequiredCard({ required }: { required: boolean; }) {
|
function ReloadRequiredCard({ required }: { required: boolean; }) {
|
||||||
return (
|
return (
|
||||||
<Card className={classes(cl("info-card"), required && "vc-warning-card")}>
|
<Card className={cl("info-card", { "restart-card": required })}>
|
||||||
{required ? (
|
{required ? (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle>
|
||||||
|
|
@ -349,7 +349,7 @@ export default function PluginSettings() {
|
||||||
select={onStatusChange}
|
select={onStatusChange}
|
||||||
isSelected={v => v === searchValue.status}
|
isSelected={v => v === searchValue.status}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
className={InputStyles.input}
|
className={InputStyles.inputDefault}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,13 @@
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-plugins-restart-card {
|
||||||
|
padding: 1em;
|
||||||
|
background: var(--info-warning-background);
|
||||||
|
border: 1px solid var(--info-warning-foreground);
|
||||||
|
color: var(--info-warning-text);
|
||||||
|
}
|
||||||
|
|
||||||
.vc-plugins-restart-button {
|
.vc-plugins-restart-button {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
background: var(--info-warning-foreground) !important;
|
background: var(--info-warning-foreground) !important;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { Settings, useSettings } from "@api/Settings";
|
||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
import { Grid } from "@components/Grid";
|
import { Grid } from "@components/Grid";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
|
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
|
||||||
import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common";
|
import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common";
|
||||||
|
|
@ -38,8 +38,6 @@ function validateUrl(url: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function eraseAllData() {
|
async function eraseAllData() {
|
||||||
if (!await checkCloudUrlCsp()) return;
|
|
||||||
|
|
||||||
const res = await fetch(new URL("/v1/", getCloudUrl()), {
|
const res = await fetch(new URL("/v1/", getCloudUrl()), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: { Authorization: await getCloudAuth() }
|
headers: { Authorization: await getCloudAuth() }
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{compileResult &&
|
{compileResult &&
|
||||||
<Forms.FormText style={{ color: compileResult[0] ? "var(--status-positive)" : "var(--text-danger)" }}>
|
<Forms.FormText style={{ color: compileResult[0] ? "var(--text-positive)" : "var(--text-danger)" }}>
|
||||||
{compileResult[1]}
|
{compileResult[1]}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +194,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
||||||
error={error ?? replacementError}
|
error={error ?? replacementError}
|
||||||
/>
|
/>
|
||||||
{!isFunc && (
|
{!isFunc && (
|
||||||
<div>
|
<div className="vc-text-selectable">
|
||||||
<Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle>
|
||||||
{Object.entries({
|
{Object.entries({
|
||||||
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
|
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,17 @@
|
||||||
|
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations";
|
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { relaunch } from "@utils/native";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { useForceUpdater } from "@utils/react";
|
|
||||||
import { getStylusWebStoreUrl } from "@utils/web";
|
|
||||||
import { findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
@ -52,6 +48,62 @@ const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue &
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
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(--text-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 {
|
interface ThemeCardProps {
|
||||||
theme: UserThemeHeader;
|
theme: UserThemeHeader;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
|
@ -107,6 +159,7 @@ function ThemesTab() {
|
||||||
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
|
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
|
||||||
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
|
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
|
||||||
const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null);
|
const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null);
|
||||||
|
const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshLocalThemes();
|
refreshLocalThemes();
|
||||||
|
|
@ -166,12 +219,6 @@ function ThemesTab() {
|
||||||
<Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText>
|
<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>
|
||||||
|
|
||||||
<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">
|
<Forms.FormSection title="Local Themes">
|
||||||
<QuickActionCard>
|
<QuickActionCard>
|
||||||
<>
|
<>
|
||||||
|
|
@ -194,7 +241,8 @@ function ThemesTab() {
|
||||||
) : (
|
) : (
|
||||||
<QuickAction
|
<QuickAction
|
||||||
text="Open Themes Folder"
|
text="Open Themes Folder"
|
||||||
action={() => VencordNative.themes.openFolder()}
|
action={() => showItemInFolder(themeDir!)}
|
||||||
|
disabled={themeDirPending}
|
||||||
Icon={FolderIcon}
|
Icon={FolderIcon}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -253,13 +301,7 @@ function ThemesTab() {
|
||||||
function renderOnlineThemes() {
|
function renderOnlineThemes() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className={classes("vc-warning-card", Margins.bottom16)}>
|
<Card className="vc-settings-card vc-text-selectable">
|
||||||
<Forms.FormText>
|
|
||||||
This section is for advanced users. If you are having difficulties using it, use the
|
|
||||||
Local Themes tab instead.
|
|
||||||
</Forms.FormText>
|
|
||||||
</Card>
|
|
||||||
<Card className="vc-settings-card">
|
|
||||||
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
<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>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
|
||||||
|
|
@ -271,11 +313,12 @@ function ThemesTab() {
|
||||||
value={themeText}
|
value={themeText}
|
||||||
onChange={setThemeText}
|
onChange={setThemeText}
|
||||||
className={"vc-settings-theme-links"}
|
className={"vc-settings-theme-links"}
|
||||||
placeholder="Enter Theme Links..."
|
placeholder="Theme Links"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
rows={10}
|
rows={10}
|
||||||
/>
|
/>
|
||||||
|
<Validators themeLinks={settings.themeLinks} />
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -304,99 +347,10 @@ function ThemesTab() {
|
||||||
</TabBar.Item>
|
</TabBar.Item>
|
||||||
</TabBar>
|
</TabBar>
|
||||||
|
|
||||||
<CspErrorCard />
|
|
||||||
{currentTab === ThemeTab.LOCAL && renderLocalThemes()}
|
{currentTab === ThemeTab.LOCAL && renderLocalThemes()}
|
||||||
{currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
|
{currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CspErrorCard() {
|
export default wrapTab(ThemesTab, "Themes");
|
||||||
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");
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
|
||||||
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
|
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
|
||||||
<span style={{
|
<span style={{
|
||||||
marginLeft: "0.5em",
|
marginLeft: "0.5em",
|
||||||
color: "var(--text-default)"
|
color: "var(--text-normal)"
|
||||||
}}>{message} - {author}</span>
|
}}>{message} - {author}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -225,7 +225,7 @@ function Updater() {
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
||||||
|
|
||||||
<Forms.FormText>
|
<Forms.FormText className="vc-text-selectable">
|
||||||
{repoPending
|
{repoPending
|
||||||
? repo
|
? repo
|
||||||
: err
|
: err
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity, isPluginDev } from "@utils/misc";
|
import { identity, isPluginDev } from "@utils/misc";
|
||||||
import { relaunch } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
|
import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import BadgeAPI from "../../plugins/_api/badges";
|
import BadgeAPI from "../../plugins/_api/badges";
|
||||||
|
|
@ -52,6 +53,9 @@ type KeysOfType<Object, Type> = {
|
||||||
}[keyof Object];
|
}[keyof Object];
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
|
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||||
|
fallbackValue: "Loading..."
|
||||||
|
});
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);
|
const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);
|
||||||
|
|
@ -167,7 +171,7 @@ function VencordSettings() {
|
||||||
<QuickAction
|
<QuickAction
|
||||||
Icon={FolderIcon}
|
Icon={FolderIcon}
|
||||||
text="Open Settings Folder"
|
text="Open Settings Folder"
|
||||||
action={() => VencordNative.settings.openFolder()}
|
action={() => showItemInFolder(settingsDir)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<QuickAction
|
<QuickAction
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.vc-addon-card {
|
.vc-addon-card {
|
||||||
background-color: var(--background-base-lower-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
color: var(--interactive-active);
|
color: var(--interactive-active);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
.vc-settings-quickActions-pill {
|
.vc-settings-quickActions-pill {
|
||||||
all: unset;
|
all: unset;
|
||||||
background: var(--background-base-lower);
|
background: var(--background-secondary);
|
||||||
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(--background-secondary-alt);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: var(--elevation-high);
|
box-shadow: var(--elevation-high);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
.vc-settings-tab-bar {
|
.vc-settings-tab-bar {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border-bottom: 1px solid var(--border-subtle);
|
border-bottom: 2px solid var(--background-modifier-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-tab-bar-item {
|
.vc-settings-tab-bar-item {
|
||||||
|
|
@ -20,37 +20,29 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-warning-card {
|
|
||||||
padding: 1em;
|
|
||||||
background: var(--info-warning-background);
|
|
||||||
border: 1px solid var(--info-warning-foreground);
|
|
||||||
color: var(--info-warning-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-backup-restore-card {
|
.vc-backup-restore-card {
|
||||||
background-color: var(--info-warning-background);
|
background-color: var(--info-warning-background);
|
||||||
border-color: var(--info-warning-foreground);
|
border-color: var(--info-warning-foreground);
|
||||||
color: var(--info-warning-foreground);
|
color: var(--info-warning-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-theme-links {
|
.vc-settings-theme-links {
|
||||||
/* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */
|
/* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */
|
||||||
display: inline-block !important;
|
display: inline-block !important;
|
||||||
color: var(--text-default) !important;
|
color: var(--text-normal) !important;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em;
|
||||||
border: 1px solid var(--input-border);
|
border: 1px solid var(--background-modifier-accent);
|
||||||
max-height: unset;
|
max-height: unset;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
resize: none;
|
resize: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 1em;
|
|
||||||
line-height: 2em;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-theme-links::placeholder {
|
.vc-settings-theme-links::placeholder {
|
||||||
color: var(--text-muted) !important;
|
color: var(--header-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-theme-links:focus {
|
.vc-settings-theme-links:focus {
|
||||||
|
|
@ -68,6 +60,15 @@
|
||||||
background-color: var(--button-danger-background);
|
background-color: var(--button-danger-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-text-selectable,
|
||||||
|
.vc-text-selectable :where([class*="text" i], [class*="title" i]) {
|
||||||
|
/* make text selectable, silly discord makes the entirety of settings not selectable */
|
||||||
|
user-select: text;
|
||||||
|
|
||||||
|
/* discord also sets cursor: default which prevents the cursor from showing as text */
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.vc-updater-modal {
|
.vc-updater-modal {
|
||||||
padding: 1.5em !important;
|
padding: 1.5em !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
.vc-settings-theme-card {
|
.vc-settings-theme-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: var(--background-base-lower-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
color: var(--interactive-active);
|
color: var(--interactive-active);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
@ -27,26 +27,3 @@
|
||||||
.vc-settings-theme-author::before {
|
.vc-settings-theme-author::before {
|
||||||
content: "by ";
|
content: "by ";
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-csp-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-csp-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
|
|
||||||
& a {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
--custom-button-button-md-height: 26px;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-owner-crown-icon {
|
.vc-owner-crown-icon {
|
||||||
color: var(--status-warning);
|
color: var(--text-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-heart-icon {
|
.vc-heart-icon {
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,9 @@
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import { ModuleFactory } from "@vencord/discord-types/webpack";
|
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { wreq } from "@webpack";
|
import { wreq } from "@webpack";
|
||||||
import { AnyModuleFactory } from "webpack";
|
import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d";
|
||||||
|
|
||||||
export async function loadLazyChunks() {
|
export async function loadLazyChunks() {
|
||||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
}, "Vencord Reporter");
|
}, "Vencord Reporter");
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-ignore
|
||||||
Vencord.Webpack._initReporter = function () {
|
Vencord.Webpack._initReporter = function () {
|
||||||
// initReporter is called in the patched entry point of Discord
|
// initReporter is called in the patched entry point of Discord
|
||||||
// setImmediate to only start searching for lazy chunks after Discord initialized the app
|
// setImmediate to only start searching for lazy chunks after Discord initialized the app
|
||||||
|
|
@ -83,6 +83,7 @@ async function runReporter() {
|
||||||
result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports);
|
result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports);
|
||||||
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
||||||
} else {
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
result = Webpack[method](...args);
|
result = Webpack[method](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3
src/globals.d.ts
vendored
3
src/globals.d.ts
vendored
|
|
@ -29,12 +29,11 @@ declare global {
|
||||||
* replace: "IS_WEB?foo:bar"
|
* replace: "IS_WEB?foo:bar"
|
||||||
* // GOOD
|
* // GOOD
|
||||||
* replace: IS_WEB ? "foo" : "bar"
|
* replace: IS_WEB ? "foo" : "bar"
|
||||||
* // also okay
|
* // also good
|
||||||
* replace: `${IS_WEB}?foo:bar`
|
* replace: `${IS_WEB}?foo:bar`
|
||||||
*/
|
*/
|
||||||
export var IS_WEB: boolean;
|
export var IS_WEB: boolean;
|
||||||
export var IS_EXTENSION: boolean;
|
export var IS_EXTENSION: boolean;
|
||||||
export var IS_USERSCRIPT: boolean;
|
|
||||||
export var IS_STANDALONE: boolean;
|
export var IS_STANDALONE: boolean;
|
||||||
export var IS_UPDATER_DISABLED: boolean;
|
export var IS_UPDATER_DISABLED: boolean;
|
||||||
export var IS_DEV: boolean;
|
export var IS_DEV: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2025 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NativeSettings } from "@main/settings";
|
|
||||||
import { session } from "electron";
|
|
||||||
|
|
||||||
type PolicyMap = Record<string, string[]>;
|
|
||||||
|
|
||||||
export const ConnectSrc = ["connect-src"];
|
|
||||||
export const ImageSrc = [...ConnectSrc, "img-src"];
|
|
||||||
export const CssSrc = ["style-src", "font-src"];
|
|
||||||
export const ImageAndCssSrc = [...ImageSrc, ...CssSrc];
|
|
||||||
export const ImageScriptsAndCssSrc = [...ImageAndCssSrc, "script-src", "worker-src"];
|
|
||||||
|
|
||||||
// Plugins can whitelist their own domains by importing this object in their native.ts
|
|
||||||
// script and just adding to it. But generally, you should just edit this file instead
|
|
||||||
|
|
||||||
export const CspPolicies: PolicyMap = {
|
|
||||||
"http://localhost:*": ImageAndCssSrc,
|
|
||||||
"http://127.0.0.1:*": ImageAndCssSrc,
|
|
||||||
"localhost:*": ImageAndCssSrc,
|
|
||||||
"127.0.0.1:*": ImageAndCssSrc,
|
|
||||||
|
|
||||||
"*.github.io": ImageAndCssSrc, // GitHub pages, used by most themes
|
|
||||||
"github.com": ImageAndCssSrc, // GitHub content (stuff uploaded to markdown forms), used by most themes
|
|
||||||
"raw.githubusercontent.com": ImageAndCssSrc, // GitHub raw, used by some themes
|
|
||||||
"*.gitlab.io": ImageAndCssSrc, // GitLab pages, used by some themes
|
|
||||||
"gitlab.com": ImageAndCssSrc, // GitLab raw, used by some themes
|
|
||||||
"*.codeberg.page": ImageAndCssSrc, // Codeberg pages, used by some themes
|
|
||||||
"codeberg.org": ImageAndCssSrc, // Codeberg raw, used by some themes
|
|
||||||
|
|
||||||
"*.githack.com": ImageAndCssSrc, // githack (namely raw.githack.com), used by some themes
|
|
||||||
"jsdelivr.net": ImageAndCssSrc, // jsDelivr, used by very few themes
|
|
||||||
|
|
||||||
"fonts.googleapis.com": CssSrc, // Google Fonts, used by many themes
|
|
||||||
|
|
||||||
"i.imgur.com": ImageSrc, // Imgur, used by some themes
|
|
||||||
"i.ibb.co": ImageSrc, // ImgBB, used by some themes
|
|
||||||
"i.pinimg.com": ImageSrc, // Pinterest, used by some themes
|
|
||||||
"*.tenor.com": ImageSrc, // Tenor, used by some themes
|
|
||||||
"files.catbox.moe": ImageAndCssSrc, // Catbox, used by some themes
|
|
||||||
|
|
||||||
"cdn.discordapp.com": ImageAndCssSrc, // Discord CDN, used by Vencord and some themes to load media
|
|
||||||
"media.discordapp.net": ImageSrc, // Discord media CDN, possible alternative to Discord CDN
|
|
||||||
|
|
||||||
// CDNs used for some things by Vencord.
|
|
||||||
// FIXME: we really should not be using CDNs anymore
|
|
||||||
"cdnjs.cloudflare.com": ImageScriptsAndCssSrc,
|
|
||||||
"cdn.jsdelivr.net": ImageScriptsAndCssSrc,
|
|
||||||
|
|
||||||
// Function Specific
|
|
||||||
"api.github.com": ConnectSrc, // used for updating Vencord itself
|
|
||||||
"ws.audioscrobbler.com": ConnectSrc, // Last.fm API
|
|
||||||
"translate-pa.googleapis.com": ConnectSrc, // Google Translate API
|
|
||||||
"*.vencord.dev": ImageSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev)
|
|
||||||
"manti.vendicated.dev": ImageSrc, // ReviewDB API
|
|
||||||
"decor.fieryflames.dev": ConnectSrc, // Decor API
|
|
||||||
"ugc.decor.fieryflames.dev": ImageSrc, // Decor CDN
|
|
||||||
"sponsor.ajay.app": ConnectSrc, // Dearrow API
|
|
||||||
"dearrow-thumb.ajay.app": ImageSrc, // Dearrow Thumbnail CDN
|
|
||||||
"usrbg.is-hardly.online": ImageSrc, // USRBG API
|
|
||||||
"icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search)
|
|
||||||
|
|
||||||
// forked addition
|
|
||||||
"*.dorkbutt.lol": ImageAndCssSrc,
|
|
||||||
};
|
|
||||||
|
|
||||||
const findHeader = (headers: PolicyMap, headerName: Lowercase<string>) => {
|
|
||||||
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const parsePolicy = (policy: string): PolicyMap => {
|
|
||||||
const result: PolicyMap = {};
|
|
||||||
policy.split(";").forEach(directive => {
|
|
||||||
const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
|
|
||||||
if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
|
|
||||||
result[directiveKey] = directiveValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stringifyPolicy = (policy: PolicyMap): string =>
|
|
||||||
Object.entries(policy)
|
|
||||||
.filter(([, values]) => values?.length)
|
|
||||||
.map(directive => directive.flat().join(" "))
|
|
||||||
.join("; ");
|
|
||||||
|
|
||||||
|
|
||||||
const patchCsp = (headers: PolicyMap) => {
|
|
||||||
const reportOnlyHeader = findHeader(headers, "content-security-policy-report-only");
|
|
||||||
if (reportOnlyHeader)
|
|
||||||
delete headers[reportOnlyHeader];
|
|
||||||
|
|
||||||
const header = findHeader(headers, "content-security-policy");
|
|
||||||
|
|
||||||
if (header) {
|
|
||||||
const csp = parsePolicy(headers[header][0]);
|
|
||||||
|
|
||||||
const pushDirective = (directive: string, ...values: string[]) => {
|
|
||||||
csp[directive] ??= [...(csp["default-src"] ?? [])];
|
|
||||||
csp[directive].push(...values);
|
|
||||||
};
|
|
||||||
|
|
||||||
pushDirective("style-src", "'unsafe-inline'");
|
|
||||||
// we could make unsafe-inline safe by using strict-dynamic with a random nonce on our Vencord loader script https://content-security-policy.com/strict-dynamic/
|
|
||||||
// HOWEVER, at the time of writing (24 Jan 2025), Discord is INSANE and also uses unsafe-inline
|
|
||||||
// Once they stop using it, we also should
|
|
||||||
pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'");
|
|
||||||
|
|
||||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
|
||||||
pushDirective(directive, "blob:", "data:", "vencord:");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [host, directives] of Object.entries(NativeSettings.store.customCspRules)) {
|
|
||||||
for (const directive of directives) {
|
|
||||||
pushDirective(directive, host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [host, directives] of Object.entries(CspPolicies)) {
|
|
||||||
for (const directive of directives) {
|
|
||||||
pushDirective(directive, host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headers[header] = [stringifyPolicy(csp)];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function initCsp() {
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
|
||||||
if (responseHeaders) {
|
|
||||||
if (resourceType === "mainFrame")
|
|
||||||
patchCsp(responseHeaders);
|
|
||||||
|
|
||||||
// Fix hosts that don't properly set the css content type, such as
|
|
||||||
// raw.githubusercontent.com
|
|
||||||
if (resourceType === "stylesheet") {
|
|
||||||
const header = findHeader(responseHeaders, "content-type");
|
|
||||||
if (header)
|
|
||||||
responseHeaders[header] = ["text/css"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cb({ cancel: false, responseHeaders });
|
|
||||||
});
|
|
||||||
|
|
||||||
// assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones.
|
|
||||||
// For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it
|
|
||||||
// impossible to load css from github raw despite our fix above
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived = () => { };
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2025 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NativeSettings } from "@main/settings";
|
|
||||||
import { IpcEvents } from "@shared/IpcEvents";
|
|
||||||
import { dialog, ipcMain, IpcMainInvokeEvent } from "electron";
|
|
||||||
|
|
||||||
import { CspPolicies, ImageAndCssSrc } from ".";
|
|
||||||
|
|
||||||
export type CspRequestResult = "invalid" | "cancelled" | "unchecked" | "ok" | "conflict";
|
|
||||||
|
|
||||||
export function registerCspIpcHandlers() {
|
|
||||||
ipcMain.handle(IpcEvents.CSP_REMOVE_OVERRIDE, removeCspRule);
|
|
||||||
ipcMain.handle(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, addCspRule);
|
|
||||||
ipcMain.handle(IpcEvents.CSP_IS_DOMAIN_ALLOWED, isDomainAllowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(url: string, directives: string[]) {
|
|
||||||
try {
|
|
||||||
const { host } = new URL(url);
|
|
||||||
|
|
||||||
if (/[;'"\\]/.test(host)) return false;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (directives.length === 0) return false;
|
|
||||||
if (directives.some(d => !ImageAndCssSrc.includes(d))) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMessage(url: string, directives: string[], callerName: string) {
|
|
||||||
const domain = new URL(url).host;
|
|
||||||
|
|
||||||
const message = `${callerName} wants to allow connections to ${domain}`;
|
|
||||||
|
|
||||||
let detail =
|
|
||||||
`Unless you recognise and fully trust ${domain}, you should cancel this request!\n\n` +
|
|
||||||
`You will have to fully close and restart ${IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} for the changes to take effect.`;
|
|
||||||
|
|
||||||
if (directives.length === 1 && directives[0] === "connect-src") {
|
|
||||||
return { message, detail };
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentTypes = directives
|
|
||||||
.filter(type => type !== "connect-src")
|
|
||||||
.map(type => {
|
|
||||||
switch (type) {
|
|
||||||
case "img-src":
|
|
||||||
return "Images";
|
|
||||||
case "style-src":
|
|
||||||
return "CSS & Themes";
|
|
||||||
case "font-src":
|
|
||||||
return "Fonts";
|
|
||||||
default:
|
|
||||||
throw new Error(`Illegal CSP directive: ${type}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sort()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
detail = `The following types of content will be allowed to load from ${domain}:\n${contentTypes}\n\n${detail}`;
|
|
||||||
|
|
||||||
return { message, detail };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addCspRule(_: IpcMainInvokeEvent, url: string, directives: string[], callerName: string): Promise<CspRequestResult> {
|
|
||||||
if (!validate(url, directives)) {
|
|
||||||
return "invalid";
|
|
||||||
}
|
|
||||||
|
|
||||||
const domain = new URL(url).host;
|
|
||||||
|
|
||||||
if (domain in NativeSettings.store.customCspRules) {
|
|
||||||
return "conflict";
|
|
||||||
}
|
|
||||||
|
|
||||||
const { checkboxChecked, response } = await dialog.showMessageBox({
|
|
||||||
...getMessage(url, directives, callerName),
|
|
||||||
type: callerName ? "info" : "warning",
|
|
||||||
title: "Vencord Host Permissions",
|
|
||||||
buttons: ["Cancel", "Allow"],
|
|
||||||
defaultId: 0,
|
|
||||||
cancelId: 0,
|
|
||||||
checkboxLabel: `I fully trust ${domain} and understand the risks of allowing connections to it.`,
|
|
||||||
checkboxChecked: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response !== 1) {
|
|
||||||
return "cancelled";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkboxChecked) {
|
|
||||||
return "unchecked";
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeSettings.store.customCspRules[domain] = directives;
|
|
||||||
return "ok";
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeCspRule(_: IpcMainInvokeEvent, domain: string) {
|
|
||||||
if (domain in NativeSettings.store.customCspRules) {
|
|
||||||
delete NativeSettings.store.customCspRules[domain];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDomainAllowed(_: IpcMainInvokeEvent, url: string, directives: string[]) {
|
|
||||||
try {
|
|
||||||
const domain = new URL(url).host;
|
|
||||||
|
|
||||||
const ruleForDomain = CspPolicies[domain] ?? NativeSettings.store.customCspRules[domain];
|
|
||||||
if (!ruleForDomain) return false;
|
|
||||||
|
|
||||||
return directives.every(d => ruleForDomain.includes(d));
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,11 +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 { app, net, protocol } from "electron";
|
import { app, protocol, session } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { pathToFileURL } from "url";
|
|
||||||
|
|
||||||
import { initCsp } from "./csp";
|
|
||||||
import { ensureSafePath } from "./ipcMain";
|
import { ensureSafePath } from "./ipcMain";
|
||||||
import { RendererSettings } from "./settings";
|
import { RendererSettings } from "./settings";
|
||||||
import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
|
import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
|
||||||
|
|
@ -28,27 +26,21 @@ import { installExt } from "./utils/extensions";
|
||||||
|
|
||||||
if (IS_VESKTOP || !IS_VANILLA) {
|
if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
protocol.handle("vencord", ({ url: unsafeUrl }) => {
|
// Source Maps! Maybe there's a better way but since the renderer is executed
|
||||||
let url = decodeURI(unsafeUrl).slice("vencord://".length).replace(/\?v=\d+$/, "");
|
// from a string I don't think any other form of sourcemaps would work
|
||||||
|
protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => {
|
||||||
|
let url = unsafeUrl.slice("vencord://".length);
|
||||||
if (url.endsWith("/")) url = url.slice(0, -1);
|
if (url.endsWith("/")) url = url.slice(0, -1);
|
||||||
|
|
||||||
if (url.startsWith("/themes/")) {
|
if (url.startsWith("/themes/")) {
|
||||||
const theme = url.slice("/themes/".length);
|
const theme = url.slice("/themes/".length);
|
||||||
|
|
||||||
const safeUrl = ensureSafePath(THEMES_DIR, theme);
|
const safeUrl = ensureSafePath(THEMES_DIR, theme);
|
||||||
if (!safeUrl) {
|
if (!safeUrl) {
|
||||||
return new Response(null, {
|
cb({ statusCode: 403 });
|
||||||
status: 404
|
return;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
cb(safeUrl.replace(/\?v=\d+$/, ""));
|
||||||
return net.fetch(pathToFileURL(safeUrl).toString());
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source Maps! Maybe there's a better way but since the renderer is executed
|
|
||||||
// from a string I don't think any other form of sourcemaps would work
|
|
||||||
|
|
||||||
switch (url) {
|
switch (url) {
|
||||||
case "renderer.js.map":
|
case "renderer.js.map":
|
||||||
case "vencordDesktopRenderer.js.map":
|
case "vencordDesktopRenderer.js.map":
|
||||||
|
|
@ -56,11 +48,10 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
case "vencordDesktopPreload.js.map":
|
case "vencordDesktopPreload.js.map":
|
||||||
case "patcher.js.map":
|
case "patcher.js.map":
|
||||||
case "vencordDesktopMain.js.map":
|
case "vencordDesktopMain.js.map":
|
||||||
return net.fetch(pathToFileURL(join(__dirname, url)).toString());
|
cb(join(__dirname, url));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return new Response(null, {
|
cb({ statusCode: 403 });
|
||||||
status: 404
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -72,7 +63,70 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
|
|
||||||
initCsp();
|
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
|
||||||
|
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove CSP
|
||||||
|
type PolicyResult = Record<string, string[]>;
|
||||||
|
|
||||||
|
const parsePolicy = (policy: string): PolicyResult => {
|
||||||
|
const result: PolicyResult = {};
|
||||||
|
policy.split(";").forEach(directive => {
|
||||||
|
const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
|
||||||
|
if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
|
||||||
|
result[directiveKey] = directiveValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||||
|
Object.entries(policy)
|
||||||
|
.filter(([, values]) => values?.length)
|
||||||
|
.map(directive => directive.flat().join(" "))
|
||||||
|
.join("; ");
|
||||||
|
|
||||||
|
const patchCsp = (headers: Record<string, string[]>) => {
|
||||||
|
const header = findHeader(headers, "content-security-policy");
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
const csp = parsePolicy(headers[header][0]);
|
||||||
|
|
||||||
|
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||||
|
csp[directive] ??= [];
|
||||||
|
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Restrict this to only imported packages with fixed version.
|
||||||
|
// Perhaps auto generate with esbuild
|
||||||
|
csp["script-src"] ??= [];
|
||||||
|
csp["script-src"].push("'unsafe-eval'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com");
|
||||||
|
headers[header] = [stringifyPolicy(csp)];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||||
|
if (responseHeaders) {
|
||||||
|
if (resourceType === "mainFrame")
|
||||||
|
patchCsp(responseHeaders);
|
||||||
|
|
||||||
|
// Fix hosts that don't properly set the css content type, such as
|
||||||
|
// raw.githubusercontent.com
|
||||||
|
if (resourceType === "stylesheet") {
|
||||||
|
const header = findHeader(responseHeaders, "content-type");
|
||||||
|
if (header)
|
||||||
|
responseHeaders[header] = ["text/css"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb({ cancel: false, responseHeaders });
|
||||||
|
});
|
||||||
|
|
||||||
|
// assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones.
|
||||||
|
// For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it
|
||||||
|
// impossible to load css from github raw despite our fix above
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived = () => { };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,14 @@ import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs";
|
||||||
import { open, readdir, readFile } from "fs/promises";
|
import { open, readdir, readFile } from "fs/promises";
|
||||||
import { join, normalize } from "path";
|
import { join, normalize } from "path";
|
||||||
|
|
||||||
import { registerCspIpcHandlers } from "./csp/manager";
|
|
||||||
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
|
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
|
||||||
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, THEMES_DIR } from "./utils/constants";
|
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, THEMES_DIR } from "./utils/constants";
|
||||||
import { makeLinksOpenExternally } from "./utils/externalLinks";
|
import { makeLinksOpenExternally } from "./utils/externalLinks";
|
||||||
|
|
||||||
mkdirSync(THEMES_DIR, { recursive: true });
|
mkdirSync(THEMES_DIR, { recursive: true });
|
||||||
|
|
||||||
registerCspIpcHandlers();
|
|
||||||
|
|
||||||
export function ensureSafePath(basePath: string, path: string) {
|
export function ensureSafePath(basePath: string, path: string) {
|
||||||
const normalizedBasePath = normalize(basePath + "/");
|
const normalizedBasePath = normalize(basePath);
|
||||||
const newPath = join(basePath, path);
|
const newPath = join(basePath, path);
|
||||||
const normalizedPath = normalize(newPath);
|
const normalizedPath = normalize(newPath);
|
||||||
return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null;
|
return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null;
|
||||||
|
|
@ -92,6 +89,7 @@ ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
|
||||||
writeFileSync(QUICKCSS_PATH, css)
|
writeFileSync(QUICKCSS_PATH, css)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
|
||||||
ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes());
|
ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes());
|
||||||
ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName));
|
ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName));
|
||||||
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
|
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
|
||||||
|
|
@ -99,8 +97,6 @@ ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
|
||||||
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
|
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_THEMES_FOLDER, () => shell.openPath(THEMES_DIR));
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_SETTINGS_FOLDER, () => shell.openPath(SETTINGS_DIR));
|
|
||||||
|
|
||||||
export function initIpc(mainWindow: BrowserWindow) {
|
export function initIpc(mainWindow: BrowserWindow) {
|
||||||
let quickCssWatcher: FSWatcher | undefined;
|
let quickCssWatcher: FSWatcher | undefined;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ const asarPath = join(dirname(injectorPath), "..", asarName);
|
||||||
const discordPkg = require(join(asarPath, "package.json"));
|
const discordPkg = require(join(asarPath, "package.json"));
|
||||||
require.main!.filename = join(asarPath, discordPkg.main);
|
require.main!.filename = join(asarPath, discordPkg.main);
|
||||||
|
|
||||||
// @ts-expect-error Untyped method? Dies from cringe
|
// @ts-ignore Untyped method? Dies from cringe
|
||||||
app.setAppPath(asarPath);
|
app.setAppPath(asarPath);
|
||||||
|
|
||||||
if (!IS_VANILLA) {
|
if (!IS_VANILLA) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ RendererSettings.addGlobalChangeListener(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||||
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = RendererSettings.plain);
|
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = RendererSettings.plain);
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string) => {
|
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string) => {
|
||||||
|
|
@ -48,18 +49,16 @@ export interface NativeSettings {
|
||||||
[setting: string]: any;
|
[setting: string]: any;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
customCspRules: Record<string, string[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultNativeSettings: NativeSettings = {
|
const DefaultNativeSettings: NativeSettings = {
|
||||||
plugins: {},
|
plugins: {}
|
||||||
customCspRules: {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const nativeSettings = readSettings<NativeSettings>("native", NATIVE_SETTINGS_FILE);
|
const nativeSettings = readSettings<NativeSettings>("native", NATIVE_SETTINGS_FILE);
|
||||||
mergeDefaults(nativeSettings, DefaultNativeSettings);
|
mergeDefaults(nativeSettings, DefaultNativeSettings);
|
||||||
|
|
||||||
export const NativeSettings = new SettingsStore(nativeSettings as NativeSettings);
|
export const NativeSettings = new SettingsStore(nativeSettings);
|
||||||
|
|
||||||
NativeSettings.addGlobalChangeListener(() => {
|
NativeSettings.addGlobalChangeListener(() => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,7 @@ export function serializeErrors(func: (...args: any[]) => any) {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: e instanceof Error ? {
|
error: e instanceof Error ? {
|
||||||
// prototypes get lost, so turn error into plain object
|
// prototypes get lost, so turn error into plain object
|
||||||
...e,
|
...e
|
||||||
message: e.message,
|
|
||||||
name: e.name,
|
|
||||||
stack: e.stack
|
|
||||||
} : e
|
} : e
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { fetchBuffer, fetchJson } from "@main/utils/http";
|
import { get } from "@main/utils/simpleGet";
|
||||||
import { IpcEvents } from "@shared/IpcEvents";
|
import { IpcEvents } from "@shared/IpcEvents";
|
||||||
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
|
@ -31,8 +31,8 @@ import { serializeErrors, VENCORD_FILES } from "./common";
|
||||||
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
||||||
let PendingUpdates = [] as [string, string][];
|
let PendingUpdates = [] as [string, string][];
|
||||||
|
|
||||||
async function githubGet<T = any>(endpoint: string) {
|
async function githubGet(endpoint: string) {
|
||||||
return fetchJson<T>(API_BASE + endpoint, {
|
return get(API_BASE + endpoint, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
// "All API requests MUST include a valid User-Agent header.
|
// "All API requests MUST include a valid User-Agent header.
|
||||||
|
|
@ -46,8 +46,9 @@ async function calculateGitChanges() {
|
||||||
const isOutdated = await fetchUpdates();
|
const isOutdated = await fetchUpdates();
|
||||||
if (!isOutdated) return [];
|
if (!isOutdated) return [];
|
||||||
|
|
||||||
const data = await githubGet(`/compare/${gitHash}...HEAD`);
|
const res = await githubGet(`/compare/${gitHash}...HEAD`);
|
||||||
|
|
||||||
|
const data = JSON.parse(res.toString("utf-8"));
|
||||||
return data.commits.map((c: any) => ({
|
return data.commits.map((c: any) => ({
|
||||||
// github api only sends the long sha
|
// github api only sends the long sha
|
||||||
hash: c.sha.slice(0, 7),
|
hash: c.sha.slice(0, 7),
|
||||||
|
|
@ -57,8 +58,9 @@ async function calculateGitChanges() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUpdates() {
|
async function fetchUpdates() {
|
||||||
const data = await githubGet("/releases/latest");
|
const release = await githubGet("/releases/latest");
|
||||||
|
|
||||||
|
const data = JSON.parse(release.toString());
|
||||||
const hash = data.name.slice(data.name.lastIndexOf(" ") + 1);
|
const hash = data.name.slice(data.name.lastIndexOf(" ") + 1);
|
||||||
if (hash === gitHash)
|
if (hash === gitHash)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -68,20 +70,16 @@ async function fetchUpdates() {
|
||||||
PendingUpdates.push([name, browser_download_url]);
|
PendingUpdates.push([name, browser_download_url]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyUpdates() {
|
async function applyUpdates() {
|
||||||
const fileContents = await Promise.all(PendingUpdates.map(async ([name, url]) => {
|
await Promise.all(PendingUpdates.map(
|
||||||
const contents = await fetchBuffer(url);
|
async ([name, data]) => writeFile(
|
||||||
return [join(__dirname, name), contents] as const;
|
join(__dirname, name),
|
||||||
}));
|
await get(data)
|
||||||
|
)
|
||||||
await Promise.all(fileContents.map(async ([filename, contents]) =>
|
));
|
||||||
writeFile(filename, contents))
|
|
||||||
);
|
|
||||||
|
|
||||||
PendingUpdates = [];
|
PendingUpdates = [];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { join } from "path";
|
||||||
|
|
||||||
import { DATA_DIR } from "./constants";
|
import { DATA_DIR } from "./constants";
|
||||||
import { crxToZip } from "./crxToZip";
|
import { crxToZip } from "./crxToZip";
|
||||||
import { fetchBuffer } from "./http";
|
import { get } from "./simpleGet";
|
||||||
|
|
||||||
const extensionCacheDir = join(DATA_DIR, "ExtensionCache");
|
const extensionCacheDir = join(DATA_DIR, "ExtensionCache");
|
||||||
|
|
||||||
|
|
@ -69,14 +69,13 @@ export async function installExt(id: string) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
|
const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
|
||||||
|
|
||||||
const buf = await fetchBuffer(url, {
|
const buf = await get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
|
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await extract(crxToZip(buf), extDir)
|
await extract(crxToZip(buf), extDir).catch(console.error);
|
||||||
.catch(err => console.error(`Failed to extract extension ${id}`, err));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session.defaultSession.loadExtension(extDir);
|
session.defaultSession.loadExtension(extDir);
|
||||||
|
|
|
||||||
|
|
@ -1,70 +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 { createWriteStream } from "original-fs";
|
|
||||||
import { Readable } from "stream";
|
|
||||||
import { finished } from "stream/promises";
|
|
||||||
|
|
||||||
type Url = string | URL;
|
|
||||||
|
|
||||||
export async function checkedFetch(url: Url, options?: RequestInit) {
|
|
||||||
try {
|
|
||||||
var res = await fetch(url, options);
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof Error && err.cause) {
|
|
||||||
err = err.cause;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`${options?.method ?? "GET"} ${url} failed: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = `${options?.method ?? "GET"} ${url}: ${res.status} ${res.statusText}`;
|
|
||||||
try {
|
|
||||||
const reason = await res.text();
|
|
||||||
message += `\n${reason}`;
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchJson<T = any>(url: Url, options?: RequestInit) {
|
|
||||||
const res = await checkedFetch(url, options);
|
|
||||||
return res.json() as Promise<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchBuffer(url: Url, options?: RequestInit) {
|
|
||||||
const res = await checkedFetch(url, options);
|
|
||||||
const buf = await res.arrayBuffer();
|
|
||||||
|
|
||||||
return Buffer.from(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function downloadToFile(url: Url, path: string, options?: RequestInit) {
|
|
||||||
const res = await checkedFetch(url, options);
|
|
||||||
if (!res.body) {
|
|
||||||
throw new Error(`Download ${url}: response body is empty`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error weird type conflict
|
|
||||||
const body = Readable.fromWeb(res.body);
|
|
||||||
await finished(body.pipe(createWriteStream(path)));
|
|
||||||
}
|
|
||||||
37
src/main/utils/simpleGet.ts
Normal file
37
src/main/utils/simpleGet.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import https from "https";
|
||||||
|
|
||||||
|
export function get(url: string, options: https.RequestOptions = {}) {
|
||||||
|
return new Promise<Buffer>((resolve, reject) => {
|
||||||
|
https.get(url, options, res => {
|
||||||
|
const { statusCode, statusMessage, headers } = res;
|
||||||
|
if (statusCode! >= 400)
|
||||||
|
return void reject(`${statusCode}: ${statusMessage} - ${url}`);
|
||||||
|
if (statusCode! >= 300)
|
||||||
|
return void resolve(get(headers.location!, options));
|
||||||
|
|
||||||
|
const chunks = [] as Buffer[];
|
||||||
|
res.on("error", reject);
|
||||||
|
|
||||||
|
res.on("data", chunk => chunks.push(chunk));
|
||||||
|
res.once("end", () => resolve(Buffer.concat(chunks)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -30,10 +30,10 @@ import { Margins } from "@utils/margins";
|
||||||
import { shouldShowContributorBadge } from "@utils/misc";
|
import { shouldShowContributorBadge } from "@utils/misc";
|
||||||
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { User } from "@vencord/discord-types";
|
|
||||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64";
|
const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png";
|
||||||
|
|
||||||
const ContributorBadge: ProfileBadge = {
|
const ContributorBadge: ProfileBadge = {
|
||||||
description: "Vencord Contributor",
|
description: "Vencord Contributor",
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||||
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||||
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { WebpackRequire } from "@vencord/discord-types/webpack";
|
import { WebpackRequire } from "@webpack/wreq.d";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
disableAnalytics: {
|
disableAnalytics: {
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ export default definePlugin({
|
||||||
get chromiumVersion() {
|
get chromiumVersion() {
|
||||||
try {
|
try {
|
||||||
return VencordNative.native.getVersions().chrome
|
return VencordNative.native.getVersions().chrome
|
||||||
// @ts-expect-error Typescript will add userAgentData IMMEDIATELY
|
// @ts-ignore Typescript will add userAgentData IMMEDIATELY
|
||||||
|| navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version
|
|| navigator.userAgentData?.brands?.find(b => b.brand === "Chromium" || b.brand === "Google Chrome")?.version
|
||||||
|| null;
|
|| null;
|
||||||
} catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium
|
} catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ import { onlyOnce } from "@utils/onlyOnce";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||||
import { Channel } from "@vencord/discord-types";
|
|
||||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||||
|
import { Channel } from "discord-types/general";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
|
@ -196,6 +196,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore outdated type
|
||||||
const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles;
|
const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles;
|
||||||
if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return;
|
if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return;
|
||||||
|
|
||||||
|
|
@ -318,7 +319,7 @@ export default definePlugin({
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`vc-warning-card ${Margins.top8}`}>
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
Please do not private message Vencord plugin developers for support!
|
Please do not private message Vencord plugin developers for support!
|
||||||
<br />
|
<br />
|
||||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { User } from "@vencord/discord-types";
|
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
|
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
popoutProps: Record<string, any>;
|
popoutProps: Record<string, any>;
|
||||||
|
|
|
||||||
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