Compare commits

..

11 Commits

Author SHA1 Message Date
Vendicated
79b35d5797 initial custom plugin loading 2023-04-20 01:19:13 +02:00
V
62194674eb Delete highResAvatars.ts 2023-04-20 00:45:24 +02:00
V
04da98498f Restore src/Vencord.ts 2023-04-20 00:44:48 +02:00
Vendicated
6fa0fb017b more additions for external plugins 2023-04-20 00:41:02 +02:00
Vendicated
11ecc45b71 more fixes 2023-04-19 23:12:39 +02:00
Vendicated
82cd8d98f6 convert import aliases 2023-04-19 23:08:07 +02:00
Vendicated
c815f1c5f3 Run autofix to sort these imports! 2023-04-19 22:59:57 +02:00
Vendicated
e248f58d9f stuffs 3 2023-04-19 22:37:58 +02:00
Vendicated
3171b78a36 stuffs 2 2023-04-19 21:49:12 +02:00
Vendicated
525aa3af33 stuffs 2023-04-19 21:36:17 +02:00
Vendicated
b7299ea2cc npm types package 2023-04-19 20:18:42 +02:00
78 changed files with 1097 additions and 981 deletions

View File

@ -2,14 +2,10 @@
The cutest Discord client mod
![](https://user-images.githubusercontent.com/45497981/235015332-0453d3eb-1da6-4601-963e-ef5e454123a1.png)
*A screenshot of Vencord featuring the [ClearVision-v6 theme](https://github.com/ClearVision/ClearVision-v6) (Vencord does not come with it pre-installed, it is only an example)*
## Features
- Super easy to install (Download Installer, open, click install button, done)
- 100+ plugins built in: [See a list](https://vencord.dev/plugins)
- 100+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033)
- Some highlights: SpotifyControls, GameActivityToggle, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
- Fairly lightweight despite the many inbuilt plugins
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
@ -17,14 +13,10 @@ The cutest Discord client mod
- 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
- Maintained very actively, broken plugins are usually fixed within 12 hours
- Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional)
## Installing / Uninstalling
Click the below button to install Vencord to the Discord Desktop app
[![Download and run the Installer](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#vencord-installer)
[![Download and run the Installer ](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#usage)
## Installing on Browser
@ -32,30 +24,25 @@ Click the below button to install Vencord to the Discord Desktop app
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
## Installing our Desktop App
## Building from Source
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app. It is currently in beta and we have yet to implement some features like screensharing, but you can try the beta nonetheless
See the docs folder
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
## Contributing
## Join our Support/Community Server
See [CONTRIBUTING.md](CONTRIBUTING.md) and [Megu's Plugin Guide!](docs/2_PLUGINS.md)
[![Vencord Discord Server](https://invidget.switchblade.xyz/D9uwnFnqmd?theme=dark)](https://discord.gg/D9uwnFnqmd)
[contribute]: CONTRIBUTING.md
[contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute]
## Join
[join]: https://discord.gg/D9uwnFnqmd
[join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join]
## Disclaimer
Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity.
Mention of it does not imply any affiliation with or endorsement by Discord Inc.
<details>
<summary>Using Vencord violates Discord's terms of service</summary>
Client modifications are against Discords Terms of Service.
However, Discord is pretty indifferent about them and there are no known cases of users getting banned for using client mods! So you should generally be fine as long as you dont use any plugins that implement abusive behaviour. But no worries, all inbuilt plugins are safe to use!
Regardless, if your account is very important to you and it getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Vencord), just to be safe
Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it
</details>

View File

@ -59,9 +59,7 @@ async function checkCors(url, method) {
const origin = headers["access-control-allow-origin"];
if (origin !== "*" && origin !== window.location.origin) return false;
const methods = headers["access-control-allow-methods"]?.toLowerCase()
.split(",")
.map(s => s.trim());
const methods = headers["access-control-allow-methods"]?.toLowerCase().split(/,\s/g);
if (methods && !methods.includes(method.toLowerCase())) return false;
return true;

View File

@ -16,7 +16,7 @@ chrome.webRequest.onHeadersReceived.addListener(
// In main frame requests, the CSP needs to be removed to enable fetching of custom css
// as desired by the user
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy");
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) {
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com")) {
// Most users will load css from GitHub, but GitHub doesn't set the correct content type,
// so we fix it here
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type");

View File

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.2.0",
"version": "1.1.9",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -28,7 +28,8 @@
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit",
"uninject": "node scripts/runInstaller.mjs",
"watch": "node scripts/build/build.mjs --watch"
"watch": "node scripts/build/build.mjs --watch",
"buildTypes": "ttsc --emitDeclarationOnly --declaration --outDir packages/vencord-types"
},
"dependencies": {
"@vap/core": "0.0.12",
@ -62,8 +63,10 @@
"stylelint": "^14.16.1",
"stylelint-config-standard": "^29.0.0",
"tsx": "^3.12.6",
"ttypescript": "^1.5.15",
"type-fest": "^3.5.3",
"typescript": "^4.9.4"
"typescript": "^4.9.4",
"typescript-transform-paths": "^3.4.6"
},
"packageManager": "pnpm@8.1.1",
"pnpm": {

7
packages/vencord-types/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*
!.*ignore
!package.json
!README.md
!prepare.ts
!index.d.ts
!globals.d.ts

View File

@ -0,0 +1,3 @@
node_modules
prepare.ts
.gitignore

View File

@ -0,0 +1,11 @@
# Vencord Types
Typings for Vencord's api, published to npm
```sh
npm i @vencord/types
yarn add @vencord/types
pnpm add @vencord/types
```

View File

@ -1,6 +1,6 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
* 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
@ -16,13 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const enum UserType {
Banned = -1,
Normal = 0,
Admin = 1
declare global {
export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord");
}
export interface ReviewDBUser {
lastReviewID: number,
type: UserType;
}
export { };

5
packages/vencord-types/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/* eslint-disable */
/// <reference path="Vencord.d.ts" />
/// <reference path="globals.d.ts" />
/// <reference path="modules.d.ts" />

View File

@ -0,0 +1,26 @@
{
"name": "@vencord/types",
"private": false,
"version": "0.1.3",
"description": "",
"types": "index.d.ts",
"scripts": {
"prepublishOnly": "tsx ./prepare.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Vencord",
"license": "GPL-3.0",
"devDependencies": {
"tsx": "^3.12.6"
},
"dependencies": {
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"discord-types": "^1.3.26",
"standalone-electron-types": "^1.0.0",
"type-fest": "^3.5.3"
}
}

View File

@ -0,0 +1,44 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { cpSync, readdirSync, rmSync } from "fs";
import { join } from "path";
const SRC = join(__dirname, "..", "..", "src");
for (const file of ["preload.d.ts", "userplugins", "main", "debug"]) {
rmSync(join(__dirname, file), { recursive: true, force: true });
}
function copyDtsFiles(from: string, to: string) {
for (const file of readdirSync(from, { withFileTypes: true })) {
// bad
if (from === SRC && file.name === "globals.d.ts") continue;
const fullFrom = join(from, file.name);
const fullTo = join(to, file.name);
if (file.isDirectory()) {
copyDtsFiles(fullFrom, fullTo);
} else if (file.name.endsWith(".d.ts")) {
cpSync(fullFrom, fullTo);
}
}
}
copyDtsFiles(SRC, __dirname);

358
pnpm-lock.yaml generated
View File

@ -8,102 +8,138 @@ patchedDependencies:
hash: 7wc6icvgtg3uswirb5tpsbjnbe
path: patches/eslint@8.28.0.patch
dependencies:
'@vap/core':
specifier: 0.0.12
version: 0.0.12
'@vap/shiki':
specifier: 0.10.3
version: 0.10.3
fflate:
specifier: ^0.7.4
version: 0.7.4
nanoid:
specifier: ^4.0.2
version: 4.0.2
virtual-merge:
specifier: ^1.0.1
version: 1.0.1
importers:
devDependencies:
'@types/diff':
specifier: ^5.0.2
version: 5.0.2
'@types/lodash':
specifier: ^4.14.191
version: 4.14.191
'@types/node':
specifier: ^18.11.18
version: 18.11.18
'@types/react':
specifier: ^18.0.27
version: 18.0.27
'@types/react-dom':
specifier: ^18.0.10
version: 18.0.10
'@types/yazl':
specifier: ^2.4.2
version: 2.4.2
'@typescript-eslint/eslint-plugin':
specifier: ^5.49.0
version: 5.49.0(@typescript-eslint/parser@5.49.0)(eslint@8.28.0)(typescript@4.9.4)
'@typescript-eslint/parser':
specifier: ^5.49.0
version: 5.49.0(eslint@8.28.0)(typescript@4.9.4)
diff:
specifier: ^5.1.0
version: 5.1.0
discord-types:
specifier: ^1.3.26
version: 1.3.26
esbuild:
specifier: ^0.15.18
version: 0.15.18
eslint:
specifier: ^8.28.0
version: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint-import-resolver-alias:
specifier: ^1.1.2
version: 1.1.2
eslint-plugin-header:
specifier: ^3.1.1
version: 3.1.1(eslint@8.28.0)
eslint-plugin-path-alias:
specifier: ^1.0.0
version: 1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.28.0)
eslint-plugin-simple-import-sort:
specifier: ^8.0.0
version: 8.0.0(eslint@8.28.0)
eslint-plugin-unused-imports:
specifier: ^2.0.0
version: 2.0.0(@typescript-eslint/eslint-plugin@5.49.0)(eslint@8.28.0)
highlight.js:
specifier: 10.6.0
version: 10.6.0
moment:
specifier: ^2.29.4
version: 2.29.4
puppeteer-core:
specifier: ^19.6.0
version: 19.6.0
standalone-electron-types:
specifier: ^1.0.0
version: 1.0.0
stylelint:
specifier: ^14.16.1
version: 14.16.1
stylelint-config-standard:
specifier: ^29.0.0
version: 29.0.0(stylelint@14.16.1)
tsx:
specifier: ^3.12.6
version: 3.12.6
type-fest:
specifier: ^3.5.3
version: 3.5.3
typescript:
specifier: ^4.9.4
version: 4.9.4
.:
dependencies:
'@vap/core':
specifier: 0.0.12
version: 0.0.12
'@vap/shiki':
specifier: 0.10.3
version: 0.10.3
fflate:
specifier: ^0.7.4
version: 0.7.4
nanoid:
specifier: ^4.0.2
version: 4.0.2
virtual-merge:
specifier: ^1.0.1
version: 1.0.1
devDependencies:
'@types/diff':
specifier: ^5.0.2
version: 5.0.2
'@types/lodash':
specifier: ^4.14.191
version: 4.14.191
'@types/node':
specifier: ^18.11.18
version: 18.11.18
'@types/react':
specifier: ^18.0.27
version: 18.0.27
'@types/react-dom':
specifier: ^18.0.10
version: 18.0.10
'@types/yazl':
specifier: ^2.4.2
version: 2.4.2
'@typescript-eslint/eslint-plugin':
specifier: ^5.49.0
version: 5.49.0(@typescript-eslint/parser@5.49.0)(eslint@8.28.0)(typescript@4.9.4)
'@typescript-eslint/parser':
specifier: ^5.49.0
version: 5.49.0(eslint@8.28.0)(typescript@4.9.4)
diff:
specifier: ^5.1.0
version: 5.1.0
discord-types:
specifier: ^1.3.26
version: 1.3.26
esbuild:
specifier: ^0.15.18
version: 0.15.18
eslint:
specifier: ^8.28.0
version: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint-import-resolver-alias:
specifier: ^1.1.2
version: 1.1.2
eslint-plugin-header:
specifier: ^3.1.1
version: 3.1.1(eslint@8.28.0)
eslint-plugin-path-alias:
specifier: ^1.0.0
version: 1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.28.0)
eslint-plugin-simple-import-sort:
specifier: ^8.0.0
version: 8.0.0(eslint@8.28.0)
eslint-plugin-unused-imports:
specifier: ^2.0.0
version: 2.0.0(@typescript-eslint/eslint-plugin@5.49.0)(eslint@8.28.0)
highlight.js:
specifier: 10.6.0
version: 10.6.0
moment:
specifier: ^2.29.4
version: 2.29.4
puppeteer-core:
specifier: ^19.6.0
version: 19.6.0
standalone-electron-types:
specifier: ^1.0.0
version: 1.0.0
stylelint:
specifier: ^14.16.1
version: 14.16.1
stylelint-config-standard:
specifier: ^29.0.0
version: 29.0.0(stylelint@14.16.1)
tsx:
specifier: ^3.12.6
version: 3.12.6
ttypescript:
specifier: ^1.5.15
version: 1.5.15(ts-node@10.9.1)(typescript@4.9.4)
type-fest:
specifier: ^3.5.3
version: 3.5.3
typescript:
specifier: ^4.9.4
version: 4.9.4
typescript-transform-paths:
specifier: ^3.4.6
version: 3.4.6(typescript@4.9.4)
packages/vencord-types:
dependencies:
'@types/lodash':
specifier: ^4.14.191
version: 4.14.191
'@types/node':
specifier: ^18.11.18
version: 18.11.18
'@types/react':
specifier: ^18.0.27
version: 18.0.27
'@types/react-dom':
specifier: ^18.0.10
version: 18.0.10
discord-types:
specifier: ^1.3.26
version: 1.3.26
standalone-electron-types:
specifier: ^1.0.0
version: 1.0.0
type-fest:
specifier: ^3.5.3
version: 3.5.3
devDependencies:
tsx:
specifier: ^3.12.6
version: 3.12.6
packages:
@ -128,6 +164,13 @@ packages:
js-tokens: 4.0.0
dev: true
/@cspotcode/source-map-support@0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@csstools/selector-specificity@2.1.1(postcss-selector-parser@6.0.11)(postcss@8.4.21):
resolution: {integrity: sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==}
engines: {node: ^14 || ^16 || >=18}
@ -413,6 +456,22 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
/@jridgewell/resolve-uri@3.1.1:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -434,6 +493,22 @@ packages:
fastq: 1.13.0
dev: true
/@tsconfig/node10@1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12@1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14@1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16@1.0.3:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
/@types/diff@5.0.2:
resolution: {integrity: sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==}
dev: true
@ -444,7 +519,6 @@ packages:
/@types/lodash@4.14.191:
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
dev: true
/@types/minimist@1.2.2:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
@ -452,7 +526,6 @@ packages:
/@types/node@18.11.18:
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
dev: true
/@types/normalize-package-data@2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@ -464,20 +537,17 @@ packages:
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
dev: true
/@types/react-dom@18.0.10:
resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==}
dependencies:
'@types/react': 18.0.27
dev: true
/@types/react@17.0.2:
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
dependencies:
'@types/prop-types': 15.7.5
csstype: 3.1.0
dev: true
/@types/react@18.0.27:
resolution: {integrity: sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==}
@ -485,11 +555,9 @@ packages:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.2
csstype: 3.1.0
dev: true
/@types/scheduler@0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
dev: true
/@types/semver@7.3.13:
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
@ -669,6 +737,11 @@ packages:
acorn: 8.8.0
dev: true
/acorn-walk@8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.8.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
engines: {node: '>=0.4.0'}
@ -721,6 +794,10 @@ packages:
color-convert: 2.0.1
dev: true
/arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
@ -950,6 +1027,10 @@ packages:
yaml: 1.10.2
dev: true
/create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/cross-fetch@3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
dependencies:
@ -980,7 +1061,6 @@ packages:
/csstype@3.1.0:
resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==}
dev: true
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
@ -1053,6 +1133,11 @@ packages:
resolution: {integrity: sha512-RqoZ2GmqaNxyx+99L/RemY5CkwG9D0WEfOKxekwCRXOGrDCep62ngezEJUVMq6rISYQ+085fJnWDQqGHlxVNww==}
dev: true
/diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff@5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
@ -1070,7 +1155,6 @@ packages:
dependencies:
'@types/react': 17.0.2
moment: 2.29.4
dev: true
/doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
@ -2171,6 +2255,10 @@ packages:
yallist: 4.0.0
dev: true
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/map-cache@0.2.2:
resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
engines: {node: '>=0.10.0'}
@ -2262,7 +2350,6 @@ packages:
/moment@2.29.4:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: true
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
@ -2828,7 +2915,6 @@ packages:
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
dependencies:
'@types/node': 18.11.18
dev: true
/static-extend@0.1.2:
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
@ -3043,6 +3129,37 @@ packages:
engines: {node: '>=8'}
dev: true
/ts-node@10.9.1(@types/node@18.11.18)(typescript@4.9.4):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
'@types/node': 18.11.18
acorn: 8.8.0
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.9.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
@ -3068,6 +3185,18 @@ packages:
fsevents: 2.3.2
dev: true
/ttypescript@1.5.15(ts-node@10.9.1)(typescript@4.9.4):
resolution: {integrity: sha512-48ykDNHzFnPMnv4hYX1P8Q84TvCZyL1QlFxeuxsuZ48X2+ameBgPenvmCkHJtoOSxpoWTWi8NcgNrRnVDOmfSg==}
hasBin: true
peerDependencies:
ts-node: '>=8.0.2'
typescript: '>=3.2.2'
dependencies:
resolve: 1.22.1
ts-node: 10.9.1(@types/node@18.11.18)(typescript@4.9.4)
typescript: 4.9.4
dev: true
/type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@ -3098,6 +3227,14 @@ packages:
/type-fest@3.5.3:
resolution: {integrity: sha512-V2+og4j/rWReWvaFrse3s9g2xvUv/K9Azm/xo6CjIuq7oeGqsoimC7+9/A3tfvNcbQf8RPSVj/HV81fB4DJrjA==}
engines: {node: '>=14.16'}
/typescript-transform-paths@3.4.6(typescript@4.9.4):
resolution: {integrity: sha512-qdgpCk9oRHkIBhznxaHAapCFapJt5e4FbFik7Y4qdqtp6VyC3smAIPoDEIkjZ2eiF7x5+QxUPYNwJAtw0thsTw==}
peerDependencies:
typescript: '>=3.6.5'
dependencies:
minimatch: 3.1.2
typescript: 4.9.4
dev: true
/typescript@4.9.4:
@ -3151,6 +3288,10 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/v8-compile-cache@2.3.0:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true
@ -3251,6 +3392,11 @@ packages:
fd-slicer: 1.1.0
dev: true
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- packages/*

View File

@ -76,7 +76,7 @@ await Promise.all([
outfile: "dist/renderer.js",
format: "iife",
target: ["esnext"],
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
footer: { js: "Vencord.Plugins.loadExternalPlugins();\n//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
globalName: "Vencord",
sourcemap,
plugins: [
@ -110,7 +110,7 @@ await Promise.all([
outfile: "dist/vencordDesktopRenderer.js",
format: "iife",
target: ["esnext"],
footer: { js: "//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
footer: { js: "Vencord.Plugins.loadExternalPlugins();\n//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
globalName: "Vencord",
sourcemap,
plugins: [

View File

@ -267,7 +267,7 @@ function runTime(token: string) {
if (!isWasm)
await wreq.e(id as any);
await new Promise(r => setTimeout(r, 150));
await new Promise(r => setTimeout(r, 500));
}
console.error("[PUP_DEBUG]", "Finished loading chunks!");

View File

@ -79,8 +79,8 @@ export function _getBadges(args: BadgeUserArgs) {
: badges.push({ ...badge, ...args });
}
}
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/apiBadges").default).getDonorBadges(args.user.id);
if (donorBadges) badges.unshift(...donorBadges);
const donorBadge = (Plugins.BadgeAPI as any).getDonorBadge(args.user.id);
if (donorBadge) badges.unshift(donorBadge);
return badges;
}

View File

@ -28,6 +28,7 @@ import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList";
import * as $Settings from "./settings";
import * as $SettingsStore from "./SettingsStore";
import * as $Styles from "./Styles";
@ -104,3 +105,9 @@ export const Notifications = $Notifications;
* An api allowing you to patch and add/remove items to/from context menus
*/
export const ContextMenu = $ContextMenu;
/**
* Settings lol
*/
export const Settings = $Settings;
export const settings = $Settings;

View File

@ -150,7 +150,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
);
});
return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>;
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
}
}
@ -180,7 +180,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent>
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
<Forms.FormSection>
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
<Forms.FormText>{plugin.description}</Forms.FormText>

View File

@ -16,6 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export { default as ErrorBoundary } from "./ErrorBoundary";
export * from "./ErrorCard";
export * from "./Flex";
export * from "./Heart";
export * from "./Link";
export { default as PatchHelper } from "./PatchHelper";
export { default as PluginSettings } from "./PluginSettings";
export * from "./Switch";
export { default as VencordSettings } from "./VencordSettings";

View File

@ -1,66 +1,52 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vencord QuickCSS Editor</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/editor/editor.main.min.css"
integrity="sha512-wB3xfL98hWg1bpkVYSyL0js/Jx9s7FsDg9aYO6nOMSJTgPuk/PFqxXQJKKSUjteZjeYrfgo9NFBOA1r9HwDuZw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/loader.min.js"
integrity="sha512-A+6SvPGkIN9Rf0mUXmW4xh7rDvALXf/f0VtOUiHlDUSPknu2kcfz1KzLpOJyL2pO+nZS13hhIjLqVgiQExLJrw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<head>
<meta charset="utf-8">
<title>Vencord QuickCSS Editor</title>
<link rel="stylesheet" data-name="vs/editor/editor.main"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css">
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<script>
require.config({
paths: {
vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs",
},
});
<body>
<div id="container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"></script>
require(["vs/editor/editor.main"], () => {
getCurrentCss().then((css) => {
var editor = monaco.editor.create(
document.getElementById("container"),
{
value: css,
language: "css",
theme: getTheme(),
}
);
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
<script>
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs' } });
require(["vs/editor/editor.main"], () => {
getCurrentCss().then(css => {
var editor = monaco.editor.create(document.getElementById('container'), {
value: css,
language: 'css',
theme: getTheme(),
});
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
});
</script>
</body>
});
</script>
</body>
</html>

View File

@ -22,13 +22,13 @@ import { debounce } from "@utils/debounce";
import IpcEvents from "@utils/IpcEvents";
import { Queue } from "@utils/Queue";
import { BrowserWindow, ipcMain, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { mkdirSync, readdirSync, readFileSync, watch } from "fs";
import { open, readFile, writeFile } from "fs/promises";
import { join } from "path";
import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants";
import { ALLOWED_PROTOCOLS, PLUGINS_DIR, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants";
mkdirSync(SETTINGS_DIR, { recursive: true });
@ -105,3 +105,15 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
});
await win.loadURL(`data:text/html;base64,${monacoHtml}`);
});
ipcMain.on(IpcEvents.GET_PLUGINS, e => {
try {
const files = readdirSync(PLUGINS_DIR).filter(f => f.endsWith(".js"));
console.log(files);
e.returnValue = files.map(f => [f, readFileSync(join(PLUGINS_DIR, f), "utf-8")]);
} catch (err) {
console.error(err);
e.returnValue = [];
}
});

View File

@ -25,6 +25,7 @@ export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? (
: join(app.getPath("userData"), "..", "Vencord")
);
export const SETTINGS_DIR = join(DATA_DIR, "settings");
export const PLUGINS_DIR = join(DATA_DIR, "plugins");
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
export const ALLOWED_PROTOCOLS = [

2
src/modules.d.ts vendored
View File

@ -20,7 +20,7 @@
/// <reference types="standalone-electron-types"/>
declare module "~plugins" {
const plugins: Record<string, import("@utils/types").Plugin>;
const plugins: Record<string, import("./utils/types").Plugin>;
export default plugins;
}

View File

@ -48,7 +48,7 @@ const ContributorBadge: ProfileBadge = {
link: "https://github.com/Vendicated/Vencord"
};
const DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">[]>;
const DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">>;
export default definePlugin({
name: "BadgeAPI",
@ -97,75 +97,78 @@ export default definePlugin({
}
for (const line of lines) {
const [id, description, image] = line.split(",");
(DonorBadges[id] ??= []).push({ image, description });
DonorBadges[id] = { image, description };
}
},
getDonorBadges(userId: string) {
return DonorBadges[userId]?.map(badge => ({
...badge,
position: BadgePosition.START,
props: {
style: {
borderRadius: "50%",
transform: "scale(0.9)" // The image is a bit too big compared to default badges
}
},
onClick() {
const modalKey = openModal(props => (
<ErrorBoundary noop onError={() => {
closeModal(modalKey);
VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/sponsors/Vendicated");
}}>
<Modals.ModalRoot {...props}>
<Modals.ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
<Heart />
Vencord Donor
</Forms.FormTitle>
</Flex>
</Modals.ModalHeader>
<Modals.ModalContent>
<Flex>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533070955872337.png"
alt=""
style={{ margin: "auto" }}
/>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533090627174460.png"
alt=""
style={{ margin: "auto" }}
/>
</Flex>
<div style={{ padding: "1em" }}>
<Forms.FormText>
This Badge is a special perk for Vencord Donors
</Forms.FormText>
<Forms.FormText className={Margins.top20}>
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText>
</div>
</Modals.ModalContent>
<Modals.ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</Modals.ModalFooter>
</Modals.ModalRoot>
</ErrorBoundary>
));
},
}));
getDonorBadge(userId: string) {
const badge = DonorBadges[userId];
if (badge) {
return {
...badge,
position: BadgePosition.START,
props: {
style: {
borderRadius: "50%",
transform: "scale(0.9)" // The image is a bit too big compared to default badges
}
},
onClick() {
const modalKey = openModal(props => (
<ErrorBoundary noop onError={() => {
closeModal(modalKey);
VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/sponsors/Vendicated");
}}>
<Modals.ModalRoot {...props}>
<Modals.ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
<Heart />
Vencord Donor
</Forms.FormTitle>
</Flex>
</Modals.ModalHeader>
<Modals.ModalContent>
<Flex>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533070955872337.png"
alt=""
style={{ margin: "auto" }}
/>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533090627174460.png"
alt=""
style={{ margin: "auto" }}
/>
</Flex>
<div style={{ padding: "1em" }}>
<Forms.FormText>
This Badge is a special perk for Vencord Donors
</Forms.FormText>
<Forms.FormText className={Margins.top20}>
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText>
</div>
</Modals.ModalContent>
<Modals.ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</Modals.ModalFooter>
</Modals.ModalRoot>
</ErrorBoundary>
));
},
};
}
}
});

View File

@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { migratePluginSettings, Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Clipboard, Toasts } from "@webpack/common";
migratePluginSettings("BetterRoleDot", "ClickableRoleDot");
export default definePlugin({
name: "BetterRoleDot",
authors: [Devs.Ven],
@ -29,10 +30,10 @@ export default definePlugin({
patches: [
{
find: ".dotBorderBase",
find: "M0 4C0 1.79086 1.79086 0 4 0H16C18.2091 0 20 1.79086 20 4V16C20 18.2091 18.2091 20 16 20H4C1.79086 20 0 18.2091 0 16V4Z",
replacement: {
match: /,viewBox:"0 0 20 20"/,
replace: "$&,onClick:()=>$self.copyToClipBoard(arguments[0].color),style:{cursor:'pointer'}",
match: /viewBox:"0 0 20 20"/,
replace: "$&,onClick:()=>$self.copyToClipBoard(e.color),style:{cursor:'pointer'}",
},
},
{

View File

@ -23,6 +23,7 @@ import {
removePreEditListener,
removePreSendListener
} from "@api/MessageEvents";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -32,6 +33,7 @@ import { defaultRules } from "./defaultRules";
const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
const reHasRegExpChar = RegExp(reRegExpChar.source);
migratePluginSettings("ClearURLs", "clearURLs");
export default definePlugin({
name: "ClearURLs",
description: "Removes tracking garbage from URLs",

View File

@ -17,7 +17,7 @@
*/
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/settings";
import { definePluginSettings, migratePluginSettings, Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
@ -149,6 +149,8 @@ const settings = definePluginSettings({
}
});
migratePluginSettings("FakeNitro", "NitroBypass");
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],

View File

@ -18,10 +18,9 @@
import { registerCommand, unregisterCommand } from "@api/Commands";
import { Settings } from "@api/settings";
import IpcEvents from "@utils/IpcEvents";
import Logger from "@utils/Logger";
import { Patch, Plugin } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { FluxEvents } from "@webpack/types";
import Plugins from "~plugins";
@ -65,7 +64,7 @@ for (const p of pluginsValues) if (settings[p.name]?.enabled) {
});
}
for (const p of pluginsValues) {
function initPlugin(p: Plugin) {
if (p.settings) {
p.settings.pluginName = p.name;
p.options ??= {};
@ -84,6 +83,9 @@ for (const p of pluginsValues) {
}
}
}
for (const p of pluginsValues) {
initPlugin(p);
}
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins() {
for (const name in Plugins)
@ -113,64 +115,56 @@ export function startDependenciesRecursive(p: Plugin) {
}
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { name, commands, flux } = p;
if (p.start) {
logger.info("Starting plugin", name);
logger.info("Starting plugin", p.name);
if (p.started) {
logger.warn(`${name} already started`);
logger.warn(`${p.name} already started`);
return false;
}
try {
p.start();
p.started = true;
} catch (e) {
logger.error(`Failed to start ${name}\n`, e);
logger.error(`Failed to start ${p.name}\n`, e);
return false;
}
}
if (commands?.length) {
logger.info("Registering commands of plugin", name);
for (const cmd of commands) {
if (p.commands?.length) {
logger.info("Registering commands of plugin", p.name);
for (const cmd of p.commands) {
try {
registerCommand(cmd, name);
registerCommand(cmd, p.name);
} catch (e) {
logger.error(`Failed to register command ${cmd.name}\n`, e);
return false;
}
}
}
if (flux) {
for (const event in flux) {
FluxDispatcher.subscribe(event as FluxEvents, flux[event]);
}
}
return true;
}, p => `startPlugin ${p.name}`);
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { name, commands, flux } = p;
if (p.stop) {
logger.info("Stopping plugin", name);
logger.info("Stopping plugin", p.name);
if (!p.started) {
logger.warn(`${name} already stopped`);
logger.warn(`${p.name} already stopped`);
return false;
}
try {
p.stop();
p.started = false;
} catch (e) {
logger.error(`Failed to stop ${name}\n`, e);
logger.error(`Failed to stop ${p.name}\n`, e);
return false;
}
}
if (commands?.length) {
logger.info("Unregistering commands of plugin", name);
for (const cmd of commands) {
if (p.commands?.length) {
logger.info("Unregistering commands of plugin", p.name);
for (const cmd of p.commands) {
try {
unregisterCommand(cmd.name);
} catch (e) {
@ -180,11 +174,20 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
}
}
if (flux) {
for (const event in flux) {
FluxDispatcher.unsubscribe(event as FluxEvents, flux[event]);
}
}
return true;
}, p => `stopPlugin ${p.name}`);
export function loadExternalPlugins() {
for (const [name, src] of VencordNative.ipc.sendSync(IpcEvents.GET_PLUGINS)) {
try {
const p = Function(src)().default;
if (!p.name || Object.prototype.hasOwnProperty.call(Plugins, p.name))
throw new Error("Invalid plugin or name conflicts with existing plugin");
Plugins[p.name] = p;
initPlugin(p);
} catch (err) {
logger.error(`Failed to load plugin ${name}`, err);
}
}
}

View File

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
import { ChannelStore, FluxDispatcher, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
export interface LogoutEvent {
type: "LOGOUT";
@ -37,54 +37,63 @@ interface PreviousChannel {
channelId: string | null;
}
let isSwitchingAccount = false;
let previousCache: PreviousChannel | undefined;
function attemptToNavigateToChannel(guildId: string | null, channelId: string) {
if (!ChannelStore.hasChannel(channelId)) return;
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}`);
}
export default definePlugin({
name: "KeepCurrentChannel",
description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.",
authors: [Devs.Nuckyz],
flux: {
LOGOUT(e: LogoutEvent) {
({ isSwitchingAccount } = e);
},
isSwitchingAccount: false,
previousCache: {} as PreviousChannel,
CONNECTION_OPEN() {
if (!isSwitchingAccount) return;
isSwitchingAccount = false;
attemptToNavigateToChannel(guildId: string | null, channelId: string) {
if (!ChannelStore.hasChannel(channelId)) return;
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}`);
},
if (previousCache?.channelId)
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId);
},
onLogout(e: LogoutEvent) {
this.isSwitchingAccount = e.isSwitchingAccount;
},
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
if (isSwitchingAccount) return;
onConnectionOpen() {
if (!this.isSwitchingAccount) return;
this.isSwitchingAccount = false;
previousCache = {
guildId,
channelId
};
await DataStore.set("KeepCurrentChannel_previousData", previousCache);
}
if (this.previousCache.channelId) this.attemptToNavigateToChannel(this.previousCache.guildId, this.previousCache.channelId);
},
async onChannelSelect({ guildId, channelId }: ChannelSelectEvent) {
if (this.isSwitchingAccount) return;
this.previousCache = {
guildId,
channelId
};
await DataStore.set("KeepCurrentChannel_previousData", this.previousCache);
},
async start() {
previousCache = await DataStore.get<PreviousChannel>("KeepCurrentChannel_previousData");
if (!previousCache) {
previousCache = {
const previousData = await DataStore.get<PreviousChannel>("KeepCurrentChannel_previousData");
if (previousData) {
this.previousCache = previousData;
if (this.previousCache.channelId) this.attemptToNavigateToChannel(this.previousCache.guildId, this.previousCache.channelId);
} else {
this.previousCache = {
guildId: SelectedGuildStore.getGuildId(),
channelId: SelectedChannelStore.getChannelId() ?? null
};
await DataStore.set("KeepCurrentChannel_previousData", previousCache);
} else if (previousCache.channelId) {
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId);
await DataStore.set("KeepCurrentChannel_previousData", this.previousCache);
}
FluxDispatcher.subscribe("LOGOUT", this.onLogout.bind(this));
FluxDispatcher.subscribe("CONNECTION_OPEN", this.onConnectionOpen.bind(this));
FluxDispatcher.subscribe("CHANNEL_SELECT", this.onChannelSelect.bind(this));
},
stop() {
FluxDispatcher.unsubscribe("LOGOUT", this.onLogout);
FluxDispatcher.unsubscribe("CONNECTION_OPEN", this.onConnectionOpen);
FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onChannelSelect);
}
});

View File

@ -23,7 +23,7 @@ import { getCurrentChannel } from "@utils/discord";
import { useForceUpdater } from "@utils/misc";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { Tooltip } from "@webpack/common";
import { FluxDispatcher, Tooltip } from "@webpack/common";
const counts = {} as Record<string, [number, number]>;
let forceUpdate: () => void;
@ -107,21 +107,27 @@ export default definePlugin({
}
}],
flux: {
GUILD_MEMBER_LIST_UPDATE({ guildId, groups, memberCount, id }) {
// eeeeeh - sometimes it has really wrong counts??? like 10 times less than actual
// but if we only listen to everyone updates, sometimes we never get the count?
// this seems to work but isn't optional
if (id !== "everyone" && counts[guildId]) return;
onGuildMemberListUpdate({ guildId, groups, memberCount, id }) {
// eeeeeh - sometimes it has really wrong counts??? like 10 times less than actual
// but if we only listen to everyone updates, sometimes we never get the count?
// this seems to work but isn't optional
if (id !== "everyone" && counts[guildId]) return;
let count = 0;
for (const group of groups) {
if (group.id !== "offline")
count += group.count;
}
counts[guildId] = [memberCount, count];
forceUpdate?.();
let count = 0;
for (const group of groups) {
if (group.id !== "offline")
count += group.count;
}
counts[guildId] = [memberCount, count];
forceUpdate?.();
},
start() {
FluxDispatcher.subscribe("GUILD_MEMBER_LIST_UPDATE", this.onGuildMemberListUpdate);
},
stop() {
FluxDispatcher.unsubscribe("GUILD_MEMBER_LIST_UPDATE", this.onGuildMemberListUpdate);
},
render: () => (

View File

@ -17,6 +17,7 @@
*/
import { addClickListener, removeClickListener } from "@api/MessageEvents";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -28,6 +29,8 @@ const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed =
const MANAGE_CHANNELS = 1n << 4n;
migratePluginSettings("MessageClickActions", "MessageQuickActions");
export default definePlugin({
name: "MessageClickActions",
description: "Hold Backspace and click to delete, double click to edit",

View File

@ -51,7 +51,7 @@ const SearchResultClasses = findByPropsLazy("message", "searchResult");
let AutoModEmbed: React.ComponentType<any> = () => null;
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
const tenorRegex = /https:\/\/(?:www.)?tenor\.com/;
interface Attachment {
height: number;

View File

@ -51,28 +51,11 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
if (!deleted && !editHistory?.length) return;
toggle: {
if (!deleted) break toggle;
const domElement = document.getElementById(`chat-messages-${channel_id}-${id}`);
if (!domElement) break toggle;
children.push((
<Menu.MenuItem
id={TOGGLE_DELETE_STYLE_ID}
key={TOGGLE_DELETE_STYLE_ID}
label="Toggle Deleted Highlight"
action={() => domElement.classList.toggle("messagelogger-deleted")}
/>
));
}
children.push((
<Menu.MenuItem
id={REMOVE_HISTORY_ID}
key={REMOVE_HISTORY_ID}
label="Remove Message History"
color="danger"
action={() => {
if (deleted) {
FluxDispatcher.dispatch({
@ -87,6 +70,20 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
}}
/>
));
if (!deleted) return;
const domElement = document.getElementById(`chat-messages-${channel_id}-${id}`);
if (!domElement) return;
children.push((
<Menu.MenuItem
id={TOGGLE_DELETE_STYLE_ID}
key={TOGGLE_DELETE_STYLE_ID}
label="Toggle Deleted Highlight"
action={() => domElement.classList.toggle("messagelogger-deleted")}
/>
));
};
export default definePlugin({

View File

@ -17,9 +17,11 @@
*/
import { findOption, OptionalMessageOption } from "@api/Commands";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("MoreKaomoji", "moarKaomojis");
export default definePlugin({
name: "MoreKaomoji",
description: "Adds more Kaomoji to discord. ヽ(´▽`)/",

View File

@ -21,7 +21,7 @@ import { makeRange } from "@components/PluginSettings/components/SettingSliderCo
import { Devs } from "@utils/constants";
import { sleep } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { SelectedChannelStore, UserStore } from "@webpack/common";
import { FluxDispatcher, SelectedChannelStore, UserStore } from "@webpack/common";
import { Message, ReactionEmoji } from "discord-types/general";
interface IMessageCreate {
@ -80,40 +80,50 @@ export default definePlugin({
description: "🗿🗿🗿🗿🗿🗿🗿🗿",
settings,
flux: {
async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) {
if (optimistic || type !== "MESSAGE_CREATE") return;
if (message.state === "SENDING") return;
if (settings.store.ignoreBots && message.author?.bot) return;
if (!message.content) return;
if (channelId !== SelectedChannelStore.getChannelId()) return;
async onMessage(e: IMessageCreate) {
if (e.optimistic || e.type !== "MESSAGE_CREATE") return;
if (e.message.state === "SENDING") return;
if (settings.store.ignoreBots && e.message.author?.bot) return;
if (!e.message.content) return;
if (e.channelId !== SelectedChannelStore.getChannelId()) return;
const moyaiCount = getMoyaiCount(message.content);
for (let i = 0; i < moyaiCount; i++) {
boom();
await sleep(300);
}
},
MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, emoji }: IReactionAdd) {
if (optimistic || type !== "MESSAGE_REACTION_ADD") return;
if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return;
if (channelId !== SelectedChannelStore.getChannelId()) return;
const name = emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
boom();
},
VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) {
if (!emoji?.name) return;
const name = emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
const moyaiCount = getMoyaiCount(e.message.content);
for (let i = 0; i < moyaiCount; i++) {
boom();
await sleep(300);
}
},
onReaction(e: IReactionAdd) {
if (e.optimistic || e.type !== "MESSAGE_REACTION_ADD") return;
if (settings.store.ignoreBots && UserStore.getUser(e.userId)?.bot) return;
if (e.channelId !== SelectedChannelStore.getChannelId()) return;
const name = e.emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
boom();
},
onVoiceChannelEffect(e: IVoiceChannelEffectSendEvent) {
if (!e.emoji?.name) return;
const name = e.emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
boom();
},
start() {
FluxDispatcher.subscribe("MESSAGE_CREATE", this.onMessage);
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD", this.onReaction);
FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", this.onVoiceChannelEffect);
},
stop() {
FluxDispatcher.unsubscribe("MESSAGE_CREATE", this.onMessage);
FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD", this.onReaction);
FluxDispatcher.unsubscribe("VOICE_CHANNEL_EFFECT_SEND", this.onVoiceChannelEffect);
}
});

View File

@ -16,10 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin from "@utils/types";
import { findByProps, findStoreLazy } from "@webpack";
import { Button, Text } from "@webpack/common";
@ -50,28 +49,10 @@ function NoDMNotificationsModal({ modalProps }: { modalProps: ModalProps; }) {
);
}
const settings = definePluginSettings({
guild: {
description: "Mute Guild",
type: OptionType.BOOLEAN,
default: true
},
everyone: {
description: "Suppress @everyone and @here",
type: OptionType.BOOLEAN,
default: true
},
role: {
description: "Suppress All Role @mentions",
type: OptionType.BOOLEAN,
default: true
},
});
export default definePlugin({
name: "MuteNewGuild",
description: "Mutes newly joined guilds",
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
authors: [Devs.Glitch, Devs.Nuckyz],
patches: [
{
find: ",acceptInvite:function",
@ -81,17 +62,10 @@ export default definePlugin({
}
}
],
settings,
handleMute(guildId: string | null) {
if (guildId === "@me" || guildId === "null" || guildId == null) return;
findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(guildId,
{
muted: settings.store.guild,
suppress_everyone: settings.store.everyone,
suppress_roles: settings.store.role
}
);
findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(guildId, { muted: true, suppress_everyone: true, suppress_roles: true });
},
start() {

View File

@ -16,9 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("NoDevtoolsWarning", "STFU");
export default definePlugin({
name: "NoDevtoolsWarning",
description: "Disables the 'HOLD UP' banner in the console. As a side effect, also prevents Discord from hiding your token, which prevents random logouts.",

View File

@ -16,9 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("NoF1", "No F1");
export default definePlugin({
name: "NoF1",
description: "Disables F1 help bind.",

View File

@ -16,9 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("NoRPC", "No RPC");
export default definePlugin({
name: "NoRPC",
description: "Disables Discord's RPC server.",

View File

@ -21,7 +21,7 @@ import definePlugin from "@utils/types";
import { Channel } from "discord-types/general";
import { addContextMenus, removeContextMenus } from "./contextMenus";
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";
import { getPinAt, isPinned, settings, snapshotArray, usePinnedDms } from "./settings";
export default definePlugin({
name: "PinDMs",
@ -44,7 +44,7 @@ export default definePlugin({
},
isPinned,
getSnapshot: sortedSnapshot,
getSnapshot: () => snapshotArray,
getScrollOffset(channelId: string, rowHeight: number, padding: number, preRenderedChildren: number, originalOffset: number) {
if (!isPinned(channelId))

View File

@ -71,7 +71,7 @@ export function togglePin(id: string) {
save([...snapshot]);
}
export function sortedSnapshot() {
function sortedSnapshot() {
requireSnapshot();
if (settings.store.pinOrder === PinOrder.LastMessage)
return PrivateChannelSortStore.getPrivateChannelIds().filter(isPinned);

View File

@ -112,7 +112,7 @@ const PlatformIndicator = ({ user, inline = false, marginLeft = "4px" }: { user:
gap: "4px",
display: inline ? "inline-flex" : "flex",
alignItems: "center",
translate: inline ? "0 3px 0" : undefined
transform: inline ? "translateY(4px)" : undefined
}}
>
{icons}

View File

@ -16,59 +16,66 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack";
import { UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
import { useFormattedPronouns } from "../pronoundbUtils";
import { settings } from "../settings";
import { awaitAndFormatPronouns } from "../pronoundbUtils";
const styles: Record<string, string> = findByPropsLazy("timestampInline");
function shouldShow(message: Message): boolean {
if (!settings.store.showInMessages)
// Respect showInMessages
if (!Settings.plugins.PronounDB.showInMessages)
return false;
// Don't bother fetching bot or system users
if (message.author.bot || message.author.system)
return false;
if (!settings.store.showSelf && message.author.id === UserStore.getCurrentUser().id)
// Respect showSelf options
if (!Settings.plugins.PronounDB.showSelf && message.author.id === UserStore.getCurrentUser().id)
return false;
return true;
}
export function PronounsChatComponentWrapper({ message }: { message: Message; }) {
return shouldShow(message)
? <PronounsChatComponent message={message} />
: null;
if (!shouldShow(message))
return null;
return <PronounsChatComponent message={message} />;
}
export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) {
return shouldShow(message)
? <CompactPronounsChatComponent message={message} />
: null;
if (!shouldShow(message))
return null;
return <CompactPronounsChatComponent message={message} />;
}
function PronounsChatComponent({ message }: { message: Message; }) {
const result = useFormattedPronouns(message.author.id);
return result
? (
const result = awaitAndFormatPronouns(message.author.id);
if (result != null) {
return (
<span
className={classes(styles.timestampInline, styles.timestamp)}
> {result}</span>
)
: null;
);
}
return null;
}
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
const result = useFormattedPronouns(message.author.id);
return result
? (
const result = awaitAndFormatPronouns(message.author.id);
if (result != null) {
return (
<span
className={classes(styles.timestampInline, styles.timestamp, "vc-pronoundb-compact")}
> {result}</span>
)
: null;
);
}
return null;
}

View File

@ -0,0 +1,61 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { UserStore } from "@webpack/common";
import { awaitAndFormatPronouns } from "../pronoundbUtils";
import { UserProfilePronounsProps, UserProfileProps } from "../types";
export default function PronounsProfileWrapper(PronounsComponent: React.ElementType<UserProfilePronounsProps>, props: UserProfilePronounsProps, profileProps: UserProfileProps) {
const user = UserStore.getUser(profileProps.userId) ?? {};
// Respect showInProfile
if (!Settings.plugins.PronounDB.showInProfile)
return null;
// Don't bother fetching bot or system users
if (user.bot || user.system)
return null;
// Respect showSelf options
if (!Settings.plugins.PronounDB.showSelf && user.id === UserStore.getCurrentUser().id)
return null;
return <ProfilePronouns
userId={profileProps.userId}
Component={PronounsComponent}
leProps={props}
/>;
}
function ProfilePronouns(
{ userId, Component, leProps }: {
userId: string;
Component: React.ElementType<UserProfilePronounsProps>;
leProps: UserProfilePronounsProps;
}
) {
const result = awaitAndFormatPronouns(userId);
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
if (result != null) {
// First child is the header, second is a div with the actual text
leProps.currentPronouns ||= result;
return <Component {...leProps} />;
}
return null;
}

View File

@ -19,16 +19,20 @@
import "./styles.css";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import PronounsAboutComponent from "./components/PronounsAboutComponent";
import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } from "./components/PronounsChatComponent";
import { useProfilePronouns } from "./pronoundbUtils";
import { settings } from "./settings";
import PronounsProfileWrapper from "./components/PronounsProfileWrapper";
export enum PronounsFormat {
Lowercase = "LOWERCASE",
Capitalized = "CAPITALIZED"
}
export default definePlugin({
name: "PronounDB",
authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven],
authors: [Devs.Tyman, Devs.TheKodeToad],
description: "Adds pronouns to user messages using pronoundb",
patches: [
// Add next to username (compact mode)
@ -47,30 +51,59 @@ export default definePlugin({
replace: "[$1, $self.PronounsChatComponentWrapper(e)]"
}
},
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
// Hijack the discord pronouns section and add a wrapper around the text section
{
find: ".userTagNoNickname",
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS",
replacement: {
match: /=(\i)\.pronouns/,
replace: "=$self.useProfilePronouns($1.user.id)"
match: /\(0,.\.jsx\)\((?<PronounComponent>\i\..),(?<pronounProps>{currentPronouns.+?:(?<fullProps>\i)\.pronouns.+?})\)/,
replace: "$<fullProps>&&$self.PronounsProfileWrapper($<PronounComponent>,$<pronounProps>,$<fullProps>)"
}
},
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
// Force enable pronouns component ignoring the experiment value
{
find: ".USER_PROFILE_ACTIVITY",
find: ".Messages.USER_POPOUT_PRONOUNS",
replacement: {
match: /\).showPronouns/,
replace: ").showPronouns||true;const vcPronounce=$self.useProfilePronouns(arguments[0].user.id);if(arguments[0].displayProfile)arguments[0].displayProfile.pronouns=vcPronounce"
match: /\.showPronouns/,
replace: ".showPronouns||true"
}
}
],
settings,
options: {
pronounsFormat: {
type: OptionType.SELECT,
description: "The format for pronouns to appear in chat",
options: [
{
label: "Lowercase",
value: PronounsFormat.Lowercase,
default: true
},
{
label: "Capitalized",
value: PronounsFormat.Capitalized
}
]
},
showSelf: {
type: OptionType.BOOLEAN,
description: "Enable or disable showing pronouns for the current user",
default: true
},
showInMessages: {
type: OptionType.BOOLEAN,
description: "Show in messages",
default: true
},
showInProfile: {
type: OptionType.BOOLEAN,
description: "Show in profile",
default: true
}
},
settingsAboutComponent: PronounsAboutComponent,
// Re-export the components on the plugin object so it is easily accessible in patches
PronounsChatComponentWrapper,
CompactPronounsChatComponentWrapper,
useProfilePronouns
PronounsProfileWrapper
});

View File

@ -20,16 +20,10 @@ import { Settings } from "@api/settings";
import { VENCORD_USER_AGENT } from "@utils/constants";
import { debounce } from "@utils/debounce";
import { useAwaiter } from "@utils/misc";
import { UserStore } from "@webpack/common";
import { settings } from "./settings";
import { PronounsFormat } from ".";
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
export const enum PronounsFormat {
Lowercase = "LOWERCASE",
Capitalized = "CAPITALIZED"
}
// A map of cached pronouns so the same request isn't sent twice
const cache: Record<string, PronounCode> = {};
// A map of ids and callbacks that should be triggered on fetch
@ -46,8 +40,8 @@ const bulkFetch = debounce(async () => {
}
});
export function useFormattedPronouns(id: string): string | null {
const [result] = useAwaiter(() => fetchPronouns(id), {
export function awaitAndFormatPronouns(id: string): string | null {
const [result, , isPending] = useAwaiter(() => fetchPronouns(id), {
fallbackValue: getCachedPronouns(id),
onError: e => console.error("Fetching pronouns failed: ", e)
});
@ -59,16 +53,6 @@ export function useFormattedPronouns(id: string): string | null {
return null;
}
export function useProfilePronouns(id: string) {
const pronouns = useFormattedPronouns(id);
if (!settings.store.showInProfile) return null;
if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) return null;
return pronouns;
}
// Gets the cached pronouns, if you're too impatient for a promise!
export function getCachedPronouns(id: string): PronounCode | null {
return cache[id] ?? null;

View File

@ -1,55 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/settings";
import { OptionType } from "@utils/types";
import { PronounsFormat } from "./pronoundbUtils";
export const settings = definePluginSettings({
pronounsFormat: {
type: OptionType.SELECT,
description: "The format for pronouns to appear in chat",
options: [
{
label: "Lowercase",
value: PronounsFormat.Lowercase,
default: true
},
{
label: "Capitalized",
value: PronounsFormat.Capitalized
}
]
},
showSelf: {
type: OptionType.BOOLEAN,
description: "Enable or disable showing pronouns for the current user",
default: true
},
showInMessages: {
type: OptionType.BOOLEAN,
description: "Show in messages",
default: true
},
showInProfile: {
type: OptionType.BOOLEAN,
description: "Show in profile",
default: true
}
});

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings, Settings } from "@api/settings";
import { definePluginSettings, migratePluginSettings, Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -29,6 +29,7 @@ const isMac = navigator.platform.includes("Mac"); // bruh
let replyIdx = -1;
let editIdx = -1;
migratePluginSettings("QuickReply", "InteractionKeybinds");
const enum MentionOptions {
DISABLED,

View File

@ -0,0 +1,41 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { FluxEvents } from "@webpack/types";
import { onChannelDelete, onGuildDelete, onRelationshipRemove } from "./functions";
import { syncAndRunChecks, syncFriends, syncGroups, syncGuilds } from "./utils";
export const FluxHandlers: Partial<Record<FluxEvents, Array<(data: any) => void>>> = {
GUILD_CREATE: [syncGuilds],
GUILD_DELETE: [onGuildDelete],
CHANNEL_CREATE: [syncGroups],
CHANNEL_DELETE: [onChannelDelete],
RELATIONSHIP_ADD: [syncFriends],
RELATIONSHIP_UPDATE: [syncFriends],
RELATIONSHIP_REMOVE: [syncFriends, onRelationshipRemove],
CONNECTION_OPEN: [syncAndRunChecks]
};
export function forEachEvent(fn: (event: FluxEvents, handler: (data: any) => void) => void) {
for (const event in FluxHandlers) {
for (const cb of FluxHandlers[event]) {
fn(event as FluxEvents, cb);
}
}
}

View File

@ -18,10 +18,12 @@
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { onChannelDelete, onGuildDelete, onRelationshipRemove, removeFriend, removeGroup, removeGuild } from "./functions";
import { forEachEvent } from "./events";
import { removeFriend, removeGroup, removeGuild } from "./functions";
import settings from "./settings";
import { syncAndRunChecks, syncFriends, syncGroups, syncGuilds } from "./utils";
import { syncAndRunChecks } from "./utils";
export default definePlugin({
name: "RelationshipNotifier",
@ -53,24 +55,15 @@ export default definePlugin({
}
],
flux: {
GUILD_CREATE: syncGuilds,
GUILD_DELETE: onGuildDelete,
CHANNEL_CREATE: syncGroups,
CHANNEL_DELETE: onChannelDelete,
RELATIONSHIP_ADD: syncFriends,
RELATIONSHIP_UPDATE: syncFriends,
RELATIONSHIP_REMOVE(e) {
onRelationshipRemove(e);
syncFriends();
},
CONNECTION_OPEN: syncAndRunChecks
},
async start() {
setTimeout(() => {
syncAndRunChecks();
}, 5000);
forEachEvent((ev, cb) => FluxDispatcher.subscribe(ev, cb));
},
stop() {
forEachEvent((ev, cb) => FluxDispatcher.unsubscribe(ev, cb));
},
removeFriend,

View File

@ -19,7 +19,6 @@
import { Settings } from "@api/settings";
import { Review } from "../entities/Review";
import { ReviewDBUser } from "../entities/User";
import { authorize, showToast } from "./Utils";
const API_URL = "https://manti.vendicated.dev";
@ -33,12 +32,8 @@ interface Response {
updated: boolean;
}
const WarningFlag = 0b00000010;
export async function getReviews(id: string): Promise<Review[]> {
var flags = 0;
if (!Settings.plugins.ReviewDB.showWarning) flags |= WarningFlag;
const req = await fetch(API_URL + `/api/reviewdb/users/${id}/reviews?flags=${flags}`);
const req = await fetch(API_URL + `/api/reviewdb/users/${id}/reviews`);
const res = (req.status === 200) ? await req.json() as Response : { success: false, message: "An Error occured while fetching reviews. Please try again later.", reviews: [], updated: false };
if (!res.success) {
@ -48,7 +43,6 @@ export async function getReviews(id: string): Promise<Review[]> {
id: 0,
comment: "An Error occured while fetching reviews. Please try again later.",
star: 0,
timestamp: 0,
sender: {
id: 0,
username: "Error",
@ -114,10 +108,8 @@ export async function reportReview(id: number) {
showToast(await res.message);
}
export function getCurrentUserInfo(token: string): Promise<ReviewDBUser> {
return fetch(API_URL + "/api/reviewdb/users", {
body: JSON.stringify({ token }),
method: "POST",
})
.then(r => r.json());
export function getLastReviewID(id: string): Promise<number> {
return fetch(API_URL + "/getLastReviewID?discordid=" + id)
.then(r => r.text())
.then(Number);
}

View File

@ -17,13 +17,13 @@
*/
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import Logger from "@utils/Logger";
import { openModal } from "@utils/modal";
import { findByProps } from "@webpack";
import { FluxDispatcher, React, SelectedChannelStore, Toasts, UserUtils } from "@webpack/common";
import { Review } from "../entities/Review";
import { UserType } from "../entities/User";
export async function openUserProfileModal(userId: string) {
await UserUtils.fetchUser(userId);
@ -86,5 +86,10 @@ export function showToast(text: string) {
export const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
export function canDeleteReview(review: Review, userId: string) {
if (review.sender.discordID === userId || Settings.plugins.ReviewDB.userType === UserType.Admin) return true;
if (review.sender.discordID === userId) return true;
const myId = BigInt(userId);
return myId === Devs.mantikafasi.id ||
myId === Devs.Ven.id ||
myId === Devs.rushii.id;
}

View File

@ -16,10 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { classes, LazyComponent } from "@utils/misc";
import { filters, findBulk } from "@webpack";
import { Alerts, moment, Timestamp, UserStore } from "@webpack/common";
import { Alerts, UserStore } from "@webpack/common";
import { Review } from "../entities/Review";
import { deleteReview, reportReview } from "../Utils/ReviewDBAPI";
@ -33,7 +32,7 @@ export default LazyComponent(() => {
const [
{ cozyMessage, buttons, message, groupStart },
{ container, isHeader },
{ avatar, clickable, username, messageContent, wrapper, cozy, timestampInline, timestamp },
{ avatar, clickable, username, messageContent, wrapper, cozy },
{ contents },
buttonClasses,
{ defaultColor }
@ -103,16 +102,6 @@ export default LazyComponent(() => {
{review.sender.username}
</span>
{review.sender.badges.map(badge => <ReviewBadge {...badge} />)}
{
!Settings.plugins.ReviewDB.hideTimestamps && (
<Timestamp
timestamp={moment(review.timestamp * 1000)}
compact={true}
/>
)
}
<p
className={classes(messageContent, defaultColor)}
style={{ fontSize: 15, marginTop: 4 }}

View File

@ -16,20 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { classes, useAwaiter } from "@utils/misc";
import { findLazy } from "@webpack";
import { Forms, React, Text, UserStore } from "@webpack/common";
import type { KeyboardEvent } from "react";
import { addReview, getReviews } from "../Utils/ReviewDBAPI";
import { authorize, showToast } from "../Utils/Utils";
import { showToast } from "../Utils/Utils";
import ReviewComponent from "./ReviewComponent";
const Classes = findLazy(m => typeof m.textarea === "string");
export default function ReviewsView({ userId }: { userId: string; }) {
const { token } = Settings.plugins.ReviewDB;
const [refetchCount, setRefetchCount] = React.useState(0);
const [reviews, _, isLoading] = useAwaiter(() => getReviews(userId), {
fallbackValue: [],
@ -85,21 +83,8 @@ export default function ReviewsView({ userId }: { userId: string; }) {
<textarea
className={classes(Classes.textarea.replace("textarea", ""), "enter-comment")}
// this produces something like '-_59yqs ...' but since no class exists with that name its fine
placeholder={
token
? (reviews?.some(r => r.sender.discordID === UserStore.getCurrentUser().id)
? `Update review for @${username}`
: `Review @${username}`)
: "You need to authorize to review users!"
}
placeholder={reviews?.some(r => r.sender.discordID === UserStore.getCurrentUser().id) ? `Update review for @${username}` : `Review @${username}`}
onKeyDown={onKeyPress}
onClick={() => {
if (!token) {
showToast("Opening authorization window...");
authorize();
}
}}
style={{
marginTop: "6px",
resize: "none",

View File

@ -31,5 +31,4 @@ export interface Review {
id: number,
star: number,
sender: Sender,
timestamp: number
}

View File

@ -22,11 +22,11 @@ import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Button } from "@webpack/common";
import { Button, UserStore } from "@webpack/common";
import { User } from "discord-types/general";
import ReviewsView from "./components/ReviewsView";
import { getCurrentUserInfo } from "./Utils/ReviewDBAPI";
import { getLastReviewID } from "./Utils/ReviewDBAPI";
import { authorize, showToast } from "./Utils/Utils";
export default definePlugin({
@ -58,31 +58,19 @@ export default definePlugin({
type: OptionType.BOOLEAN,
description: "Notify about new reviews on startup",
default: true,
},
showWarning: {
type: OptionType.BOOLEAN,
description: "Display warning to be respectful at the top of the reviews list",
default: true,
},
hideTimestamps: {
type: OptionType.BOOLEAN,
description: "Hide timestamps on reviews",
default: false,
}
},
async start() {
const settings = Settings.plugins.ReviewDB;
if (!settings.notifyReviews || !settings.token) return;
if (!settings.lastReviewId || !settings.notifyReviews) return;
setTimeout(async () => {
const user = await getCurrentUserInfo(settings.token);
if (settings.lastReviewId < user.lastReviewID) {
settings.lastReviewId = user.lastReviewID;
if (user.lastReviewID !== 0)
showToast("You have new reviews on your profile!");
const id = await getLastReviewID(UserStore.getCurrentUser().id);
if (settings.lastReviewId < id) {
showToast("You have new reviews on your profile!");
settings.lastReviewId = id;
}
settings.userType = user.type;
}, 4000);
},

View File

@ -53,7 +53,7 @@ export default definePlugin({
replacement: [
{
match: /user:(\i),channel:(\i).{0,300}?"@"\.concat\(.+?\)/,
replace: "$&,color:$self.getUserColor($1?.id,{channelId:$2?.id})"
replace: "$&,color:$self.getUserColor($1.id,{channelId:$2?.id})"
}
],
predicate: () => settings.store.chatMentions,
@ -99,12 +99,10 @@ export default definePlugin({
if (!(guildId ??= ChannelStore.getChannel(channelId!)?.guild_id)) return null;
return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null;
},
getUserColor(userId: string, ids: { channelId?: string; guildId?: string; }) {
const colorString = this.getColor(userId, ids);
return colorString && parseInt(colorString.slice(1), 16);
},
roleGroupColor({ id, count, title, guildId }: { id: string; count: number; title: string; guildId: string; }) {
const guild = GuildStore.getGuild(guildId);
const role = guild?.roles[id];
@ -115,7 +113,6 @@ export default definePlugin({
letterSpacing: ".05em"
}}>{title} &mdash; {count}</span>;
},
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
return {
style: {

View File

@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common";
import { FluxDispatcher, GuildStore,PresenceStore, RelationshipStore } from "@webpack/common";
enum IndicatorType {
SERVER = 1 << 0,
@ -71,24 +71,6 @@ function ServersIndicator() {
);
}
function handlePresenceUpdate() {
onlineFriends = 0;
const relations = RelationshipStore.getRelationships();
for (const id of Object.keys(relations)) {
const type = relations[id];
// FRIEND relationship type
if (type === 1 && PresenceStore.getStatus(id) !== "offline") {
onlineFriends += 1;
}
}
forceUpdateFriendCount?.();
}
function handleGuildUpdate() {
guildCount = GuildStore.getGuildCount();
forceUpdateGuildCount?.();
}
export default definePlugin({
name: "ServerListIndicators",
description: "Add online friend count or server count in the server list",
@ -117,21 +99,37 @@ export default definePlugin({
</ErrorBoundary>;
},
flux: {
PRESENCE_UPDATES: handlePresenceUpdate,
GUILD_CREATE: handleGuildUpdate,
GUILD_DELETE: handleGuildUpdate,
handlePresenceUpdate() {
onlineFriends = 0;
const relations = RelationshipStore.getRelationships();
for (const id of Object.keys(relations)) {
const type = relations[id];
// FRIEND relationship type
if (type === 1 && PresenceStore.getStatus(id) !== "offline") {
onlineFriends += 1;
}
}
forceUpdateFriendCount?.();
},
handleGuildUpdate() {
guildCount = GuildStore.getGuildCount();
forceUpdateGuildCount?.();
},
start() {
this.handlePresenceUpdate();
this.handleGuildUpdate();
addServerListElement(ServerListRenderPosition.Above, this.renderIndicator);
handlePresenceUpdate();
handleGuildUpdate();
FluxDispatcher.subscribe("PRESENCE_UPDATES", this.handlePresenceUpdate);
FluxDispatcher.subscribe("GUILD_CREATE", this.handleGuildUpdate);
FluxDispatcher.subscribe("GUILD_DELETE", this.handleGuildUpdate);
},
stop() {
removeServerListElement(ServerListRenderPosition.Above, this.renderIndicator);
FluxDispatcher.unsubscribe("PRESENCE_UPDATES", this.handlePresenceUpdate);
FluxDispatcher.unsubscribe("GUILD_CREATE", this.handleGuildUpdate);
FluxDispatcher.unsubscribe("GUILD_DELETE", this.handleGuildUpdate);
}
});

View File

@ -1,173 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./styles.css";
import { definePluginSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { copyWithToast, LazyComponent } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack";
import { Text, Tooltip } from "@webpack/common";
import { User } from "discord-types/general";
const Section = LazyComponent(() => findByCode("().lastSection"));
const UserProfileStore = findStoreLazy("UserProfileStore");
const ThemeStore = findStoreLazy("ThemeStore");
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
const enum Spacing {
COMPACT,
COZY,
ROOMY
}
const getSpacingPx = (spacing: Spacing | undefined) => (spacing ?? Spacing.COMPACT) * 2 + 4;
const settings = definePluginSettings({
iconSize: {
type: OptionType.NUMBER,
description: "Icon size (px)",
default: 32
},
iconSpacing: {
type: OptionType.SELECT,
description: "Icon margin",
default: Spacing.COZY,
options: [
{ label: "Compact", value: Spacing.COMPACT },
{ label: "Cozy", value: Spacing.COZY }, // US Spelling :/
{ label: "Roomy", value: Spacing.ROOMY }
]
}
});
interface Connection {
type: string;
id: string;
name: string;
verified: boolean;
}
interface ConnectionPlatform {
getPlatformUserUrl(connection: Connection): string;
icon: { lightSVG: string, darkSVG: string; };
}
const profilePopoutComponent = ErrorBoundary.wrap(e =>
<ConnectionsComponent id={e.user.id} theme={getTheme(e.user, e.displayProfile).profileTheme} />
);
const profilePanelComponent = ErrorBoundary.wrap(e =>
<ConnectionsComponent id={e.channel.recipients[0]} theme={ThemeStore.theme} />
);
function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
const profile = UserProfileStore.getUserProfile(id);
if (!profile)
return null;
const connections: Connection[] = profile.connectedAccounts;
if (!connections?.length)
return null;
return (
<Section>
<Text
tag="h2"
variant="eyebrow"
style={{ color: "var(--header-primary)" }}
>
Connections
</Text>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
</Section>
);
}
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
const platform = platforms.get(connection.type);
const url = platform.getPlatformUserUrl?.(connection);
const img = (
<img
aria-label={connection.name}
src={theme === "light" ? platform.icon.lightSVG : platform.icon.darkSVG}
style={{
marginTop: getSpacingPx(settings.store.iconSpacing),
marginRight: getSpacingPx(settings.store.iconSpacing),
width: settings.store.iconSize,
height: settings.store.iconSize
}}
/>
);
return (
<Tooltip
text={`${connection.name}${!connection.verified ? " (unverified)" : ""} (click to ${url ? "open" : "copy"})`}
key={connection.id}
>
{tooltipProps =>
url
? <a
{...tooltipProps}
className="vc-user-connection"
href={url}
target="_blank"
>
{img}
</a>
: <button
{...tooltipProps}
className="vc-user-connection"
onClick={() => copyWithToast(connection.name)}
>
{img}
</button>
}
</Tooltip>
);
}
export default definePlugin({
name: "ShowConnections",
description: "Show connected accounts in user popouts",
authors: [Devs.TheKodeToad],
patches: [
{
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS",
replacement: {
match: /,theme:\i\}\)(?=,.{0,100}setNote:)/,
replace: "$&,$self.profilePopoutComponent(arguments[0])"
}
},
{
find: "\"Profile Panel: user cannot be undefined\"",
replacement: {
// createElement(Divider, {}), createElement(NoteComponent)
match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:/,
replace: "$self.profilePanelComponent(arguments[0]),$&"
}
}
],
settings,
profilePopoutComponent,
profilePanelComponent
});

View File

@ -1,5 +0,0 @@
.vc-user-connection {
all: unset;
display: inline-block;
cursor: pointer;
}

View File

@ -98,8 +98,6 @@ export default definePlugin({
name: "SilentMessageToggle",
authors: [Devs.Nuckyz],
description: "Adds a button to the chat bar to toggle sending a silent message.",
dependencies: ["MessageEventsAPI"],
settings,
patches: [
{

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/settings";
import { definePluginSettings, migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -35,6 +35,7 @@ const settings = definePluginSettings({
}
});
migratePluginSettings("SpotifyCrack", "Ify");
export default definePlugin({
name: "SpotifyCrack",
description: "Free listen along, no auto-pausing in voice chat, and allows activity to continue playing when idling",

View File

@ -17,6 +17,7 @@
*/
import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -73,6 +74,7 @@ function sendMessage(channelId, message) {
});
}
migratePluginSettings("SpotifyShareCommands", "Sendify");
export default definePlugin({
name: "SpotifyShareCommands",
description: "Share your current Spotify track, album or artist via slash command (/track, /album, /artist)",

View File

@ -21,7 +21,7 @@ import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
import { makeCodeblock } from "@utils/misc";
import definePlugin from "@utils/types";
import { isOutdated } from "@utils/updater";
import { Alerts, Forms, UserStore } from "@webpack/common";
import { Alerts, FluxDispatcher, Forms, UserStore } from "@webpack/common";
import gitHash from "~git-hash";
import plugins from "~plugins";
@ -69,16 +69,18 @@ ${makeCodeblock(Object.keys(plugins).filter(Vencord.Plugins.isPluginEnabled).joi
}
}],
flux: {
async CHANNEL_SELECT({ channelId }) {
rememberDismiss() {
DataStore.set(REMEMBER_DISMISS_KEY, gitHash);
},
start() {
FluxDispatcher.subscribe("CHANNEL_SELECT", async ({ channelId }) => {
if (channelId !== SUPPORT_CHANNEL_ID) return;
const myId = BigInt(UserStore.getCurrentUser().id);
if (Object.values(Devs).some(d => d.id === myId)) return;
if (isOutdated && gitHash !== await DataStore.get(REMEMBER_DISMISS_KEY)) {
const rememberDismiss = () => DataStore.set(REMEMBER_DISMISS_KEY, gitHash);
Alerts.show({
title: "Hold on!",
body: <div>
@ -88,10 +90,10 @@ ${makeCodeblock(Object.keys(plugins).filter(Vencord.Plugins.isPluginEnabled).joi
to do so, in case you can't access the Updater page.
</Forms.FormText>
</div>,
onCancel: rememberDismiss,
onConfirm: rememberDismiss
onCancel: this.rememberDismiss,
onConfirm: this.rememberDismiss
});
}
}
});
}
});

View File

@ -24,7 +24,7 @@ import { findByCodeLazy } from "@webpack";
import { GuildMemberStore, React, RelationshipStore, SelectedChannelStore } from "@webpack/common";
import { User } from "discord-types/general";
const Avatar = findByCodeLazy(".typingIndicatorRef", "svg");
const Avatar = findByCodeLazy('"top",spacing:');
const openProfile = findByCodeLazy("friendToken", "USER_PROFILE_MODAL_OPEN");
const settings = definePluginSettings({

View File

@ -4,6 +4,27 @@
z-index: -1;
}
[class*="userPopout"] [class*="NonPremium"] [class*="bannerPremium"] {
top: -30px;
}
[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="bannerSVGWrapper"] {
min-height: 120px !important;
}
[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="bannerSVGWrapper"] foreignObject {
height: 360px;
}
[class*="userPopout"] [class*="NonPremium"]:has([class*="bannerPremium"]) [class*="bannerSVGWrapper"] rect {
height: 120px;
y: -30;
}
[class*="userPopout"] [class*="NonPremium"]:has([class*="bannerPremium"]) [class*="bannerSVGWrapper"] circle {
cy: 86;
}
[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="avatarPositionNormal"],
[class*="PremiumWithoutBanner"]:has([class*="bannerPremium"]) [class*="avatarPositionPremiumNoBanner"] {
top: 76px;

View File

@ -46,17 +46,11 @@ export default definePlugin({
settings,
patches: [
{
find: ".NITRO_BANNER,",
replacement: [
{
match: /(\i)\.premiumType/,
replace: "$self.premiumHook($1)||$&"
},
{
match: /(\i)\.bannerSrc,/,
replace: "$self.useBannerHook($1),"
}
]
find: ".bannerSrc,",
replacement: {
match: /(\i)\.bannerSrc,/,
replace: "$self.useBannerHook($1),"
}
}
],
@ -71,10 +65,6 @@ export default definePlugin({
if (data[user.id]) return data[user.id];
},
premiumHook({ userId }: any) {
if (data[userId]) return 2;
},
async start() {
enableStyle(style);

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore, SelectedChannelStore } from "@webpack/common";
@ -25,6 +26,7 @@ const timers = {} as Record<string, {
i: number;
}>;
migratePluginSettings("VoiceChatDoubleClick", "vcDoubleClick");
export default definePlugin({
name: "VoiceChatDoubleClick",
description: "Join voice chats via double click instead of single click",

View File

@ -24,7 +24,7 @@ import { Margins } from "@utils/margins";
import { wordsToTitle } from "@utils/text";
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Forms, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
import { Button, ChannelStore, FluxDispatcher, Forms, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
interface VoiceState {
userId: string;
@ -137,6 +137,49 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId,
}
*/
function handleVoiceStates({ voiceStates }: { voiceStates: VoiceState[]; }) {
const myChanId = SelectedChannelStore.getVoiceChannelId();
const myId = UserStore.getCurrentUser().id;
for (const state of voiceStates) {
const { userId, channelId, oldChannelId } = state;
const isMe = userId === myId;
if (!isMe) {
if (!myChanId) continue;
if (channelId !== myChanId && oldChannelId !== myChanId) continue;
}
const [type, id] = getTypeAndChannelId(state, isMe);
if (!type) continue;
const template = Settings.plugins.VcNarrator[type + "Message"];
const user = isMe ? "" : UserStore.getUser(userId).username;
const channel = ChannelStore.getChannel(id).name;
speak(formatText(template, user, channel));
// updateStatuses(type, state, isMe);
}
}
function handleToggleSelfMute() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.mute || s.selfMute ? "unmute" : "mute";
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name));
}
function handleToggleSelfDeafen() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name));
}
function playSample(tempSettings: any, type: string) {
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings);
@ -148,51 +191,6 @@ export default definePlugin({
description: "Announces when users join, leave, or move voice channels via narrator",
authors: [Devs.Ven],
flux: {
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
const myChanId = SelectedChannelStore.getVoiceChannelId();
const myId = UserStore.getCurrentUser().id;
for (const state of voiceStates) {
const { userId, channelId, oldChannelId } = state;
const isMe = userId === myId;
if (!isMe) {
if (!myChanId) continue;
if (channelId !== myChanId && oldChannelId !== myChanId) continue;
}
const [type, id] = getTypeAndChannelId(state, isMe);
if (!type) continue;
const template = Settings.plugins.VcNarrator[type + "Message"];
const user = isMe ? "" : UserStore.getUser(userId).username;
const channel = ChannelStore.getChannel(id).name;
speak(formatText(template, user, channel));
// updateStatuses(type, state, isMe);
}
},
AUDIO_TOGGLE_SELF_MUTE() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.mute || s.selfMute ? "unmute" : "mute";
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name));
},
AUDIO_TOGGLE_SELF_DEAF() {
const chanId = SelectedChannelStore.getVoiceChannelId()!;
const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState;
if (!s) return;
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name));
}
},
start() {
if (typeof speechSynthesis === "undefined" || speechSynthesis.getVoices().length === 0) {
new Logger("VcNarrator").warn(
@ -201,6 +199,15 @@ export default definePlugin({
return;
}
FluxDispatcher.subscribe("VOICE_STATE_UPDATES", handleVoiceStates);
FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_MUTE", handleToggleSelfMute);
FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_DEAF", handleToggleSelfDeafen);
},
stop() {
FluxDispatcher.unsubscribe("VOICE_STATE_UPDATES", handleVoiceStates);
FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_MUTE", handleToggleSelfMute);
FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_DEAF", handleToggleSelfDeafen);
},
optionsCache: null as Record<string, PluginOptionsItem> | null,

View File

@ -167,30 +167,26 @@ export default definePlugin({
},
patches: [
// Make pfps clickable
{
find: "onAddFriend:",
replacement: {
match: /\{src:(\i)(?=,avatarDecoration)/,
replace: "{src:$1,onClick:()=>$self.openImage($1)"
// global because Discord has two components that are 99% identical with one small change ._.
match: /\{src:(\i),avatarDecoration/g,
replace: "{src:$1,onClick:()=>$self.openImage($1),avatarDecoration"
}
},
// Make banners clickable
{
find: ".NITRO_BANNER,",
}, {
find: ".popoutNoBannerPremium",
replacement: {
// style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl,
match: /style:\{(?=backgroundImage:(\i&&\i)\?"url\("\.concat\((\i),)/,
match: /style:.{0,10}\{\},(\i)\)/,
replace:
// onClick: () => shouldShowBanner && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0,
'onClick:()=>$1&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
"onClick:$1.backgroundImage&&($1.cursor=\"pointer\"," +
"()=>$self.openImage($1.backgroundImage.replace(\"url(\", \"\"))),$&"
}
},
{
}, {
find: "().avatarWrapperNonUserBot",
replacement: {
match: /(?<=avatarPositionPanel.+?)onClick:(\i\|\|\i)\?void 0(?<=,(\i)=\i\.avatarSrc.+?)/,
replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}"
match: /(avatarPositionPanel.+?)onClick:(\i\|\|\i)\?void 0(?<=,(\i)=\i\.avatarSrc.+?)/,
replace: "$1style:($2)?{cursor:\"pointer\"}:{},onClick:$2?()=>{$self.openImage($3)}"
}
}
]

View File

@ -121,7 +121,7 @@ export default definePlugin({
predicate: () => settings.store.addBack,
replacement: {
// return IS_DESKTOP ? React.createElement(Menu, ...)
match: /return \i\.\i\?/,
match: /return \i\.\i\?(?=\(0,\i\.jsxs?\)\(\i\.Menu)/,
replace: "return true?"
}
},
@ -146,7 +146,7 @@ export default definePlugin({
}
},
{
find: ':"command-suggestions"',
find: 'navId:"textarea-context"',
predicate: () => settings.store.addBack,
replacement: [
{

View File

@ -85,14 +85,10 @@ function makeRenderMoreUsers(users: User[]) {
};
}
function handleClickAvatar(event: React.MouseEvent<HTMLElement, MouseEvent>) {
event.stopPropagation();
}
export default definePlugin({
name: "WhoReacted",
description: "Renders the Avatars of reactors",
authors: [Devs.Ven, Devs.KannaDev],
authors: [Devs.Ven],
patches: [{
find: ",reactionRef:",
@ -136,17 +132,15 @@ export default definePlugin({
<div
style={{ marginLeft: "0.5em", transform: "scale(0.9)" }}
>
<div onClick={handleClickAvatar}>
<UserSummaryItem
users={users}
guildId={ChannelStore.getChannel(message.channel_id)?.guild_id}
renderIcon={false}
max={5}
showDefaultAvatarsForNullUsers
showUserPopout
renderMoreUsers={makeRenderMoreUsers(users)}
/>
</div>
<UserSummaryItem
users={users}
guildId={ChannelStore.getChannel(message.channel_id)?.guild_id}
renderIcon={false}
max={5}
showDefaultAvatarsForNullUsers
showUserPopout
renderMoreUsers={makeRenderMoreUsers(users)}
/>
</div>
);
}

View File

@ -43,4 +43,5 @@ export default strEnum({
UPDATE: "VencordUpdate",
BUILD: "VencordBuild",
OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor",
GET_PLUGINS: "VencordGetPlugins",
} as const);

View File

@ -273,13 +273,5 @@ export const Devs = /* #__PURE__*/ Object.freeze({
FieryFlames: {
name: "Fiery",
id: 890228870559698955n
},
KannaDev: {
name: "Kanna",
id: 317728561106518019n
},
carince: {
name: "carince",
id: 818323528755314698n
}
});

View File

@ -16,17 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./ChangeList";
export * as Constants from "./constants";
export * from "./debounce";
export * as Discord from "./discord";
export * from "./discord";
export * from "./guards";
export { default as IpcEvents } from "./IpcEvents";
export { default as Logger } from "./Logger";
export * from "./margins";
export * from "./misc";
export * as Modals from "./modal";
export * from "./modal";
export * from "./onceDefined";
export * from "./proxyLazy";
export * from "./Queue";
export * from "./react";
export * from "./text";
import * as t from "./types";
export const types = {
// necessary so plugin repo's esbuild doesn't explode
__esModule: true,
...t
};

View File

@ -17,7 +17,6 @@
*/
import { Command } from "@api/Commands";
import { FluxEvents } from "@webpack/types";
import { Promisable } from "type-fest";
// exists to export default definePlugin({...})
@ -102,14 +101,6 @@ export interface PluginDef {
settingsAboutComponent?: React.ComponentType<{
tempSettings?: Record<string, any>;
}>;
/**
* Allows you to subscribe to Flux events
*/
flux?: {
[E in FluxEvents]?: (event: any) => void;
};
tags?: string[];
}
export enum OptionType {
@ -135,7 +126,6 @@ export type PluginSettingDef = (
| PluginSettingSelectDef
| PluginSettingSliderDef
| PluginSettingComponentDef
| PluginSettingBigIntDef
) & PluginSettingCommon;
export interface PluginSettingCommon {

View File

@ -38,7 +38,6 @@ export interface Menu {
label: string;
action?(e: MouseEvent): void;
color?: string;
render?: ComponentType;
onChildrenScroll?: Function;
childRowHeight?: number;

View File

@ -25,7 +25,18 @@
"@webpack/types": ["./webpack/common/types"],
"@webpack/common": ["./webpack/common"],
"@webpack": ["./webpack/webpack"]
}
},
"plugins": [
// Transform paths in output .js files
{ "transform": "typescript-transform-paths" },
// Transform paths in output .d.ts files (Include this line if you output declarations files)
{
"transform": "typescript-transform-paths",
"afterDeclarations": true
}
]
},
"include": ["src/**/*"]
}