Compare commits

...

59 Commits

Author SHA1 Message Date
V
9b987d1e56 Bump to v1.4.6 2023-09-05 21:25:16 +02:00
V
774318d583 ViewRaw: Update description 2023-09-05 21:24:43 +02:00
V
7d954f9ade ViewRaw: Fix ugly copy icon & context menu position 2023-09-05 21:20:13 +02:00
Luna
860d6edc7b HideAttachments: allow hiding stickers (#13)
Self explanatory

Reviewed-on: https://codeberg.org/Ven/cord/pulls/13
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-05 18:56:28 +00:00
Rini
223b0366c6 pinDMs: fix alt+shift+up (#25)
applies the same fix as alt+up/down to the jump to unread one

confirmed non-explosion of shc

Reviewed-on: https://codeberg.org/Ven/cord/pulls/25
Co-authored-by: Rini <rini@rinici.de>
Co-committed-by: Rini <rini@rinici.de>
2023-09-05 18:51:22 +00:00
Luna
69cb7593eb View Raw on more objects (#27)
Shows view raw on guilds, channels, and users

Reviewed-on: https://codeberg.org/Ven/cord/pulls/27
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-05 18:49:57 +00:00
V
8614e17633 Fix some plugins wrongly displaying the settings cog 2023-09-05 20:10:42 +02:00
Ryan Cao
aecd9d8fda feat(memberCount): format count according to user locale (#1679) 2023-09-05 19:46:33 +02:00
Hugo C
faeb4fb585 previewMessage: Hide the button once a message is sent (#1692)
Co-authored-by: V <vendicated@riseup.net>
2023-09-05 19:45:44 +02:00
dolfies
0d18b44ba7 feat(plugin): ShowTimeouts (#1687)
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Co-authored-by: V <vendicated@riseup.net>
2023-09-05 19:42:35 +02:00
omahs
d671bd65ac fix typos (#1703) 2023-09-05 19:40:32 +02:00
AutumnVN
8e952c630b MessageLogger: fix ignore by id doesn't ignore edited (#1705) 2023-09-05 19:39:22 +02:00
V
2c758ccdf8 new plugin: ServerProfile (#1704)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-09-05 18:36:14 +02:00
Nuckyz
c165725297 Fix OpenInApp patch 2023-09-02 03:03:50 -03:00
Nuckyz
29fbe3701a Fix CrashHandler patch 2023-09-02 03:03:31 -03:00
V
0b7c0e9587 new plugin: themeAttributes (#1686) 2023-08-31 01:49:53 +02:00
Cynthia Foxwell
d88524e8cf ShowMeYourName: Fix username being wrong on slash commands (#1690)
Co-authored-by: V <vendicated@riseup.net>
2023-08-31 01:01:29 +02:00
Hugo C
d6efd99849 messageLinkEmbeds: fix AutoModEmbed on compact mode (#1691)
Co-authored-by: V <vendicated@riseup.net>
2023-08-31 01:00:16 +02:00
ruukulada
fe6be987fd gameActivityToggle: Fix disabled SVG look (#1696) 2023-08-31 00:53:05 +02:00
V
d688075c0a Update Screenshot 2023-08-29 01:12:47 +02:00
V
c752be45b2 fix(security - ipc): do not expose sender 2023-08-25 14:57:04 +02:00
V
07c1f5eed1 Fix theme upload on crapOs 2023-08-25 14:37:50 +02:00
V
4df01b1e62 ci: fix generatePluginList 2023-08-25 14:33:33 +02:00
AutumnVN
ebe10d3fad customRPC fixes (#1666)
Co-authored-by: V <vendicated@riseup.net>
2023-08-25 14:30:12 +02:00
Adrian Castro
eca4af829f feat: add copyUserURLs plugin (#1683)
Co-authored-by: V <vendicated@riseup.net>
2023-08-25 14:24:58 +02:00
CanadaHonk
60458cdf1f Win32Updater: use before-quit event instead of patching updater (#1668) 2023-08-25 14:21:27 +02:00
Sefa Eyeoglu
714d87241c build: allow overriding git details & disabling updating (#1677)
Co-authored-by: V <vendicated@riseup.net>
2023-08-25 14:15:02 +02:00
Elliott Tallis
f628aa7a70 ClearURLs: Add si parameter on youtu.be (#1681) 2023-08-25 14:03:58 +02:00
megumin
0f0282551d 🗿 but HD (#1678) 2023-08-25 14:02:10 +02:00
V
0335e1ca59 new plugin FixSpotifyEmbeds: less loud embeds 2023-08-21 16:59:32 +02:00
V
817f0f7473 [skip ci] fix getPluginTarget 2023-08-20 15:14:28 +02:00
V
98a03c8862 bump to 1.4.5 2023-08-19 01:44:11 +02:00
V
72ce7a5ad1 themes: ignore non theme files 2023-08-19 01:33:39 +02:00
Syncx
e699ea63c7 feat(plugin): PreviewMessage (#1607)
Co-authored-by: V <vendicated@riseup.net>
2023-08-19 01:19:10 +02:00
VMGuy23
97e1e9eb7a messageLogger: apply deleted style to ephemeral messages (#1608)
Co-authored-by: V <vendicated@riseup.net>
2023-08-19 01:14:51 +02:00
bb010g
4c4036546a new plugin: NormalizeMessageLinks (#1641) 2023-08-19 01:10:59 +02:00
CanadaHonk
d582e61ec3 Fix patching Win32 updater with OpenAsar (#1667)
This is more generic rewrite allowing for more paths to be added in the future for whatever reason (like a rename in future Discord versions).

(The "OpenAsar" code previously was completely wrong)
2023-08-19 00:54:35 +02:00
V
ccd2ce8baf Bump to 1.4.4 2023-08-16 02:10:06 +02:00
V
ede507e80c chore: update for 'Vencord Desktop' -> 'Vesktop' rebrand 2023-08-16 01:55:56 +02:00
Justice Almanzar
ffdf63563b feat(plugins): Web/Vesktop AI Noise Suppression powered by RNNoise (#1477)
Co-authored-by: V <vendicated@riseup.net>
2023-08-16 01:32:11 +02:00
Sammy
55b755b2df InvisibleChat: improve encryption validator (#1646) 2023-08-16 00:57:56 +02:00
Nuckyz
ca439e9e9a Fix ReviewDB buttons 2023-08-15 19:21:16 -03:00
Nuckyz
e02fcce3dc Fix ServerListAPI 2023-08-15 18:48:50 -03:00
V
3e732646e5 Fix plugins broken by latest canary update (#1637)
* Fix WebContextMenus
* BetterGifAltText: futureproof find
* Fix pronoundb profile patch
* Fix NoScreensharePreview
* Fix FakeNitro emote text patch

---------

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-08-12 05:27:59 +02:00
V
d5b3b51050 bump to v1.4.3 2023-08-12 03:02:55 +02:00
V
725fb27e54 futureproof settings patches 2023-08-12 03:01:53 +02:00
V
243381fc91 Settings: Give vencord sections classes so themes can match them 2023-08-12 02:59:24 +02:00
V
54cbdfdad0 MemberCount: Remove negative margin, fixes issues with some themes 2023-08-12 02:46:38 +02:00
V
fe80b8cc85 AnonymiseFileNames: Properly keep .tar.* extensions 2023-08-12 02:45:11 +02:00
Andrew-DLO
742f5cf556 feat(plugin): SecretRingToneEnabler (#1570)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 02:23:00 +02:00
fawn
3b3da90c44 feat(VencordToolbox): add quickcss toggle (#1604)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 02:14:07 +02:00
TheKodeToad
c79e065d09 ShowMeYourName: Option to use display names instead of usernames (#1634)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 01:53:06 +02:00
V
3b8b43c7e0 ci: Fix reporter
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
2023-08-12 01:36:19 +02:00
AutumnVN
6e7996659f messageLogger: fix ingore guild (#1632) 2023-08-11 06:10:49 +02:00
Arrow
abdf4ebb05 feat(plugin): NoSentryConsole (#1623)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-08-11 00:17:29 +02:00
Justice Almanzar
fa124d8877 build: split preload & renderer.css into D. desktop / vesktop (#1629) 2023-08-11 00:14:50 +02:00
im-h
135da2a5f3 Translate/SendTimestamps: Fix accessibility (#1628)
Co-authored-by: V <vendicated@riseup.net>
2023-08-10 02:31:28 +02:00
Manti
c96a1a9998 [ReviewDB] hide blocked users (#1626)
Co-authored-by: V <vendicated@riseup.net>
2023-08-10 00:28:46 +00:00
Rini
8b6c8bc0ec chore: transition to shorter license headers (#6)
Transition from the GPL jumpscare to a more concise header:

	/*
     * Vencord, a Discord client mod
	 * Copyright (c) 2023 Vendicated and contributors
     *
	 * SPDX-License-Identifier: GPL-3.0
     */

Old files still keep the old headers, while eslint will now autofix the new header

Reviewed-on: https://codeberg.org/Ven/cord/pulls/6
Co-authored-by: Rini <rini@rinici.de>
Co-committed-by: Rini <rini@rinici.de>
2023-08-10 00:17:49 +00:00
99 changed files with 1693 additions and 512 deletions

View File

@ -4,7 +4,7 @@
"ignorePatterns": ["dist", "browser"],
"plugins": [
"@typescript-eslint",
"header",
"simple-header",
"simple-import-sort",
"unused-imports",
"path-alias"
@ -26,35 +26,12 @@
// Since it's only been a month and Vencord has already been stolen
// by random skids who rebranded it to "AlphaCord" and erased all license
// information
"header/header": [
2,
"block",
[
{
"pattern": "!?",
"template": " "
},
" * Vencord, a modification for Discord's desktop app",
{
"pattern": " \\* Copyright \\(c\\) \\d{4}",
"template": " * 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/>.",
""
],
2
"simple-header/header": [
"error",
{
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
"templates": { "author": [".*", "Vendicated and contributors"] }
}
],
"quotes": ["error", "double", { "avoidEscape": true }],
"jsx-quotes": ["error", "prefer-double"],

View File

@ -65,7 +65,7 @@ Also pay attention to the following:
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
`var .{1,2}=([^;]+);`
- If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters
- Additionally, as you might have noticed, all of the appove approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
- Additionally, as you might have noticed, all of the above approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
#### "replace"

View File

@ -4,8 +4,7 @@
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)*
![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334)
## Features

View File

@ -63,7 +63,7 @@ Then fully close Discord from your taskbar or task manager, and restart it. Venc
If you're using Discord already, go into the `Updater` tab in settings.
Sometimes it may be neccessary to manually update if the GUI updater fails.
Sometimes it may be necessary to manually update if the GUI updater fails.
To pull latest changes:

View File

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.4.2",
"version": "1.4.6",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -11,7 +11,7 @@
"type": "git",
"url": "git+https://github.com/Vendicated/Vencord.git"
},
"license": "GPL-3.0",
"license": "GPL-3.0-or-later",
"author": "Vendicated",
"directories": {
"doc": "docs"
@ -31,8 +31,10 @@
"watch": "node scripts/build/build.mjs --watch"
},
"dependencies": {
"@sapphi-red/web-noise-suppressor": "0.3.3",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",
"eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4",
"nanoid": "^4.0.2",
"virtual-merge": "^1.0.1"
@ -49,9 +51,8 @@
"diff": "^5.1.0",
"discord-types": "^1.3.26",
"esbuild": "^0.15.18",
"eslint": "^8.28.0",
"eslint": "^8.46.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
@ -69,7 +70,7 @@
"pnpm": {
"patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
"eslint@8.28.0": "patches/eslint@8.28.0.patch"
"eslint@8.46.0": "patches/eslint@8.46.0.patch"
},
"peerDependencyRules": {
"ignoreMissing": [

View File

@ -1,11 +1,11 @@
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index 2046a148a17fd1d5f3a4bbc9f45f7700259d11fa..f4898c6b57355a4fd72c43a9f32bf1a36a6ccf4a 100644
index 0e0f6f09f2c35f3276173c08f832cde9f2cf56a0..7dc22851715f3574d935f513c1b5e35552985711 100644
--- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js
@@ -97,12 +97,30 @@ module.exports = {
@@ -65,13 +65,31 @@ module.exports = {
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
},
- schema: []
+ schema: [{
+ type: "object",
@ -22,24 +22,28 @@ index 2046a148a17fd1d5f3a4bbc9f45f7700259d11fa..f4898c6b57355a4fd72c43a9f32bf1a3
+ additionalProperties: false
+ }]
},
create(context) {
+ const options = context.options[0] || {};
+ const { extra, extraCharClass } = options || ''
const sourceCode = context.getSourceCode();
+ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra))
+ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass))
+ const { extra, extraCharClass } = options;
const sourceCode = context.sourceCode;
const parser = new RegExpParser();
+ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra));
+ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass));
+
/**
* Reports a node
* @param {ASTNode} node The node to report
@@ -238,7 +256,7 @@ module.exports = {
.filter(charInfo => charInfo.escaped)
// Filter out characters that are valid to escape, based on their position in the regular expression.
- .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text))
+ .filter(charInfo => !(charInfo.inCharClass ? CHARCLASS_ESCAPES : NON_CHARCLASS_ESCAPES).has(charInfo.text))
// Report all the remaining characters.
.forEach(charInfo => report(node, charInfo.index, charInfo.text));
@@ -200,9 +218,9 @@ module.exports = {
let allowedEscapes;
if (characterClassStack.length) {
- allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
+ allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : CHARCLASS_ESCAPES;
} else {
- allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
+ allowedEscapes = NON_CHARCLASS_ESCAPES;
}
if (allowedEscapes.has(escapedChar)) {
return;

255
pnpm-lock.yaml generated
View File

@ -4,17 +4,23 @@ patchedDependencies:
eslint-plugin-path-alias@1.0.0:
hash: m6sma4g6bh67km3q6igf6uxaja
path: patches/eslint-plugin-path-alias@1.0.0.patch
eslint@8.28.0:
hash: 7wc6icvgtg3uswirb5tpsbjnbe
path: patches/eslint@8.28.0.patch
eslint@8.46.0:
hash: xm46kqcmdgzlmm4aifkfpxaho4
path: patches/eslint@8.46.0.patch
dependencies:
'@sapphi-red/web-noise-suppressor':
specifier: 0.3.3
version: 0.3.3
'@vap/core':
specifier: 0.0.12
version: 0.0.12
'@vap/shiki':
specifier: 0.10.5
version: 0.10.5
eslint-plugin-simple-header:
specifier: ^1.0.2
version: 1.0.2
fflate:
specifier: ^0.7.4
version: 0.7.4
@ -46,10 +52,10 @@ devDependencies:
version: 2.4.2
'@typescript-eslint/eslint-plugin':
specifier: ^5.59.1
version: 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.28.0)(typescript@5.0.4)
version: 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.46.0)(typescript@5.0.4)
'@typescript-eslint/parser':
specifier: ^5.59.1
version: 5.59.1(eslint@8.28.0)(typescript@5.0.4)
version: 5.59.1(eslint@8.46.0)(typescript@5.0.4)
diff:
specifier: ^5.1.0
version: 5.1.0
@ -60,23 +66,20 @@ devDependencies:
specifier: ^0.15.18
version: 0.15.18
eslint:
specifier: ^8.28.0
version: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
specifier: ^8.46.0
version: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
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)
version: 1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.46.0)
eslint-plugin-simple-import-sort:
specifier: ^10.0.0
version: 10.0.0(eslint@8.28.0)
version: 10.0.0(eslint@8.46.0)
eslint-plugin-unused-imports:
specifier: ^2.0.0
version: 2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.28.0)
version: 2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.46.0)
highlight.js:
specifier: 10.6.0
version: 10.6.0
@ -107,6 +110,11 @@ devDependencies:
packages:
/@aashutoshrathi/word-wrap@1.2.6:
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
engines: {node: '>=0.10.0'}
dev: true
/@babel/code-frame@7.21.4:
resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
engines: {node: '>=6.9.0'}
@ -399,7 +407,7 @@ packages:
dev: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.28.0):
/@eslint-community/eslint-utils@4.4.0(eslint@8.46.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -408,8 +416,8 @@ packages:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint-visitor-keys: 3.4.0
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-visitor-keys: 3.4.2
dev: true
/@eslint-community/regexpp@4.5.1:
@ -417,15 +425,20 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true
/@eslint/eslintrc@1.3.3:
resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==}
/@eslint-community/regexpp@4.6.2:
resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true
/@eslint/eslintrc@2.1.1:
resolution: {integrity: sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
ajv: 6.12.6
debug: 4.3.4
espree: 9.4.1
globals: 13.17.0
ignore: 5.2.0
espree: 9.6.1
globals: 13.20.0
ignore: 5.2.4
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
@ -434,8 +447,13 @@ packages:
- supports-color
dev: true
/@humanwhocodes/config-array@0.11.7:
resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==}
/@eslint/js@8.46.0:
resolution: {integrity: sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'}
dependencies:
'@humanwhocodes/object-schema': 1.2.1
@ -498,6 +516,10 @@ packages:
- supports-color
dev: true
/@sapphi-red/web-noise-suppressor@0.3.3:
resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==}
dev: false
/@types/diff@5.0.3:
resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==}
dev: true
@ -569,7 +591,7 @@ packages:
'@types/node': 18.16.3
dev: true
/@typescript-eslint/eslint-plugin@5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.28.0)(typescript@5.0.4):
/@typescript-eslint/eslint-plugin@5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -583,12 +605,12 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.5.1
'@typescript-eslint/parser': 5.59.1(eslint@8.28.0)(typescript@5.0.4)
'@typescript-eslint/parser': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
'@typescript-eslint/scope-manager': 5.59.1
'@typescript-eslint/type-utils': 5.59.1(eslint@8.28.0)(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.1(eslint@8.28.0)(typescript@5.0.4)
'@typescript-eslint/type-utils': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
grapheme-splitter: 1.0.4
ignore: 5.2.4
natural-compare-lite: 1.4.0
@ -599,7 +621,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser@5.59.1(eslint@8.28.0)(typescript@5.0.4):
/@typescript-eslint/parser@5.59.1(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -615,7 +637,7 @@ packages:
'@typescript-eslint/types': 5.59.1
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
typescript: 5.0.4
transitivePeerDependencies:
- supports-color
@ -629,7 +651,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.59.1
dev: true
/@typescript-eslint/type-utils@5.59.1(eslint@8.28.0)(typescript@5.0.4):
/@typescript-eslint/type-utils@5.59.1(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -642,9 +664,9 @@ packages:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.1(eslint@8.28.0)(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
tsutils: 3.21.0(typescript@5.0.4)
typescript: 5.0.4
transitivePeerDependencies:
@ -677,7 +699,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/utils@5.59.1(eslint@8.28.0)(typescript@5.0.4):
/@typescript-eslint/utils@5.59.1(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -686,13 +708,13 @@ packages:
eslint:
optional: true
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.28.0)
'@eslint-community/eslint-utils': 4.4.0(eslint@8.46.0)
'@types/json-schema': 7.0.11
'@types/semver': 7.3.13
'@typescript-eslint/scope-manager': 5.59.1
'@typescript-eslint/types': 5.59.1
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-scope: 5.1.1
semver: 7.5.0
transitivePeerDependencies:
@ -722,16 +744,16 @@ packages:
vscode-textmate: 5.2.0
dev: false
/acorn-jsx@5.3.2(acorn@8.8.0):
/acorn-jsx@5.3.2(acorn@8.10.0):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: 8.8.0
acorn: 8.10.0
dev: true
/acorn@8.8.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
/acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
@ -1446,18 +1468,7 @@ packages:
optional: true
dev: true
/eslint-plugin-header@3.1.1(eslint@8.28.0):
resolution: {integrity: sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==}
peerDependencies:
eslint: '>=7.7.0'
peerDependenciesMeta:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
dev: true
/eslint-plugin-path-alias@1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.28.0):
/eslint-plugin-path-alias@1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.46.0):
resolution: {integrity: sha512-FXus57yC+Zd3sMv46pbloXYwFeNVNHJqlACr9V68FG/IzGFBBokGJpmjDbEjpt8ZCeVSndUubeDWWl2A8sCNVQ==}
peerDependencies:
eslint: ^7
@ -1465,14 +1476,18 @@ packages:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
nanomatch: 1.2.13
transitivePeerDependencies:
- supports-color
dev: true
patched: true
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.28.0):
/eslint-plugin-simple-header@1.0.2:
resolution: {integrity: sha512-K1EJ/ueBIjPRA8qR44Ymo+GDmPYYmfoODtainGxVr7PSbX6QiaY+pTuGCrOhO+AtVsYJs8GLSVdGUTXyAxAtOA==}
dev: false
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.46.0):
resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
peerDependencies:
eslint: '>=5.0.0'
@ -1480,10 +1495,10 @@ packages:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
dev: true
/eslint-plugin-unused-imports@2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.28.0):
/eslint-plugin-unused-imports@2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.46.0):
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -1495,8 +1510,8 @@ packages:
eslint:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.28.0)(typescript@5.0.4)
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
'@typescript-eslint/eslint-plugin': 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.46.0)(typescript@5.0.4)
eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-rule-composer: 0.3.0
dev: true
@ -1513,49 +1528,34 @@ packages:
estraverse: 4.3.0
dev: true
/eslint-scope@7.1.1:
resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==}
/eslint-scope@7.2.2:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
dev: true
/eslint-utils@3.0.0(eslint@8.28.0):
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
peerDependenciesMeta:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint-visitor-keys: 2.1.0
dev: true
/eslint-visitor-keys@2.1.0:
resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
engines: {node: '>=10'}
dev: true
/eslint-visitor-keys@3.3.0:
resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint-visitor-keys@3.4.0:
resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint@8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe):
resolution: {integrity: sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==}
/eslint-visitor-keys@3.4.2:
resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4):
resolution: {integrity: sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
'@eslint/eslintrc': 1.3.3
'@humanwhocodes/config-array': 0.11.7
'@eslint-community/eslint-utils': 4.4.0(eslint@8.46.0)
'@eslint-community/regexpp': 4.6.2
'@eslint/eslintrc': 2.1.1
'@eslint/js': 8.46.0
'@humanwhocodes/config-array': 0.11.10
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
ajv: 6.12.6
@ -1564,60 +1564,46 @@ packages:
debug: 4.3.4
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.1.1
eslint-utils: 3.0.0(eslint@8.28.0)
eslint-visitor-keys: 3.3.0
espree: 9.4.0
esquery: 1.4.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.2
espree: 9.6.1
esquery: 1.5.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
find-up: 5.0.0
glob-parent: 6.0.2
globals: 13.17.0
grapheme-splitter: 1.0.4
ignore: 5.2.0
import-fresh: 3.3.0
globals: 13.20.0
graphemer: 1.4.0
ignore: 5.2.4
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-sdsl: 4.1.5
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.1
regexpp: 3.2.0
optionator: 0.9.3
strip-ansi: 6.0.1
strip-json-comments: 3.1.1
text-table: 0.2.0
transitivePeerDependencies:
- supports-color
dev: true
patched: true
/espree@9.4.0:
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
/espree@9.6.1:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
acorn: 8.8.0
acorn-jsx: 5.3.2(acorn@8.8.0)
eslint-visitor-keys: 3.3.0
acorn: 8.10.0
acorn-jsx: 5.3.2(acorn@8.10.0)
eslint-visitor-keys: 3.4.2
dev: true
/espree@9.4.1:
resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
acorn: 8.8.0
acorn-jsx: 5.3.2(acorn@8.8.0)
eslint-visitor-keys: 3.3.0
dev: true
/esquery@1.4.0:
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}
/esquery@1.5.0:
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
engines: {node: '>=0.10'}
dependencies:
estraverse: 5.3.0
@ -1858,8 +1844,8 @@ packages:
which: 1.3.1
dev: true
/globals@13.17.0:
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==}
/globals@13.20.0:
resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==}
engines: {node: '>=8'}
dependencies:
type-fest: 0.20.2
@ -1885,6 +1871,10 @@ packages:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true
/graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
dev: true
/hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
@ -1972,11 +1962,6 @@ packages:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: true
/ignore@5.2.0:
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
engines: {node: '>= 4'}
dev: true
/ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
@ -2168,10 +2153,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/js-sdsl@4.1.5:
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
dev: true
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true
@ -2485,16 +2466,16 @@ packages:
wrappy: 1.0.2
dev: true
/optionator@0.9.1:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
/optionator@0.9.3:
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
engines: {node: '>= 0.8.0'}
dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
deep-is: 0.1.4
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.3
dev: true
/p-limit@2.3.0:
@ -2734,11 +2715,6 @@ packages:
safe-regex: 1.1.0
dev: true
/regexpp@3.2.0:
resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
engines: {node: '>=8'}
dev: true
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@ -3323,11 +3299,6 @@ packages:
isexe: 2.0.0
dev: true
/word-wrap@1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
engines: {node: '>=0.10.0'}
dev: true
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@ -3405,3 +3376,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@ -19,13 +19,14 @@
import esbuild from "esbuild";
import { commonOpts, globPlugins, isStandalone, VERSION, watch } from "./common.mjs";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
const defines = {
IS_STANDALONE: isStandalone,
IS_DEV: JSON.stringify(watch),
IS_UPDATER_DISABLED: updaterDisabled,
VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(),
BUILD_TIMESTAMP,
};
if (defines.IS_STANDALONE === "false")
// If this is a local build (not standalone), optimise
@ -40,8 +41,6 @@ const nodeCommonOpts = {
format: "cjs",
platform: "node",
target: ["esnext"],
minify: true,
bundle: true,
external: ["electron", ...commonOpts.external],
define: defines,
};
@ -50,16 +49,7 @@ const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.j
const sourcemap = watch ? "inline" : "external";
await Promise.all([
// common preload
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/preload.js",
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
sourcemap,
}),
// Discord Desktop main & renderer
// Discord Desktop main & renderer & preload
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/main/index.ts"],
@ -69,7 +59,7 @@ await Promise.all([
define: {
...defines,
IS_DISCORD_DESKTOP: true,
IS_VENCORD_DESKTOP: false
IS_VESKTOP: false
}
}),
esbuild.build({
@ -89,11 +79,23 @@ await Promise.all([
...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: true,
IS_VENCORD_DESKTOP: false
IS_VESKTOP: false
}
}),
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/preload.js",
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false
}
}),
// Vencord Desktop main & renderer
// Vencord Desktop main & renderer & preload
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/main/index.ts"],
@ -103,7 +105,7 @@ await Promise.all([
define: {
...defines,
IS_DISCORD_DESKTOP: false,
IS_VENCORD_DESKTOP: true
IS_VESKTOP: true
}
}),
esbuild.build({
@ -123,7 +125,19 @@ await Promise.all([
...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: false,
IS_VENCORD_DESKTOP: true
IS_VESKTOP: true
}
}),
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/vencordDesktopPreload.js",
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("vencordDesktopPreload") },
sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true
}
}),
]).catch(err => {

View File

@ -24,7 +24,7 @@ import { readFileSync } from "fs";
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path";
import { commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@ -45,9 +45,10 @@ const commonOptions = {
IS_STANDALONE: "true",
IS_DEV: JSON.stringify(watch),
IS_DISCORD_DESKTOP: "false",
IS_VENCORD_DESKTOP: "false",
IS_VESKTOP: "false",
IS_UPDATER_DISABLED: "true",
VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(),
BUILD_TIMESTAMP,
}
};

View File

@ -27,17 +27,21 @@ import { promisify } from "util";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
import { getPluginTarget } from "../utils.mjs";
export const VERSION = PackageJSON.version;
export const BUILD_TIMESTAMP = Date.now();
// https://reproducible-builds.org/docs/source-date-epoch/
export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now();
export const watch = process.argv.includes("--watch");
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater"));
export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
export const banner = {
js: `
// Vencord ${gitHash}
// Standalone: ${isStandalone}
// Platform: ${isStandalone === "false" ? process.platform : "Universal"}
// Updater disabled: ${updaterDisabled}
`.trim()
};
@ -81,14 +85,13 @@ export const globPlugins = kind => ({
if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue;
const fileBits = file.split(".");
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1))) {
const mod = fileBits.at(-2);
if (mod === "dev" && !watch) continue;
if (mod === "web" && kind === "discordDesktop") continue;
if (mod === "desktop" && kind === "web") continue;
if (mod === "discordDesktop" && kind !== "discordDesktop") continue;
if (mod === "vencordDesktop" && kind !== "vencordDesktop") continue;
const target = getPluginTarget(file);
if (target) {
if (target === "dev" && !watch) continue;
if (target === "web" && kind === "discordDesktop") continue;
if (target === "desktop" && kind === "web") continue;
if (target === "discordDesktop" && kind !== "discordDesktop") continue;
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
}
const mod = `p${i}`;
@ -133,11 +136,14 @@ export const gitRemotePlugin = {
namespace: "git-remote", path: args.path
}));
build.onLoad({ filter, namespace: "git-remote" }, async () => {
const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" });
const remote = res.stdout.trim()
.replace("https://github.com/", "")
.replace("git@github.com:", "")
.replace(/.git$/, "");
let remote = process.env.VENCORD_REMOTE;
if (!remote) {
const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" });
remote = res.stdout.trim()
.replace("https://github.com/", "")
.replace("git@github.com:", "")
.replace(/.git$/, "");
}
return { contents: `export default "${remote}"` };
});

View File

@ -21,6 +21,8 @@ import { access, readFile } from "fs/promises";
import { join } from "path";
import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript";
import { getPluginTarget } from "./utils.mjs";
interface Dev {
name: string;
id: string;
@ -157,11 +159,10 @@ async function parseFile(fileName: string) {
if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing");
const fileBits = fileName.split(".");
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1)!)) {
const mod = fileBits.at(-2)!;
if (!["web", "discordDesktop", "vencordDesktop", "dev"].includes(mod)) throw fail(`invalid target ${fileBits.at(-2)}`);
data.target = mod as any;
const target = getPluginTarget(fileName);
if (target) {
if (!["web", "discordDesktop", "vencordDesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`);
data.target = target as any;
}
return data;

View File

@ -263,7 +263,7 @@ function runTime(token: string) {
for (const id in ids) {
const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text())
.then(t => t.includes(".module.wasm"));
.then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
if (!isWasm)
await wreq.e(id as any);

3
scripts/header-new.txt Normal file
View File

@ -0,0 +1,3 @@
Vencord, a Discord client mod
Copyright (c) {year} {author}
SPDX-License-Identifier: GPL-3.0-or-later

17
scripts/header-old.txt Normal file
View File

@ -0,0 +1,17 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) {year} {author}
*
* 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/>.
*/

30
scripts/utils.mjs Normal file
View File

@ -0,0 +1,30 @@
/*
* 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/>.
*/
/**
* @param {string} filePath
* @returns {string | null}
*/
export function getPluginTarget(filePath) {
const pathParts = filePath.split(/[/\\]/);
if (/^index\.tsx?$/.test(pathParts.at(-1))) pathParts.pop();
const identifier = pathParts.at(-1).replace(/\.tsx?$/, "");
const identiferBits = identifier.split(".");
return identiferBits.length === 1 ? null : identiferBits.at(-1);
}

View File

@ -1,20 +1,8 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022
*
* 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/>.
*/
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcEvents } from "@utils/IpcEvents";
import { IpcRes } from "@utils/types";
@ -60,7 +48,7 @@ export default {
},
addThemeChangeListener(cb: () => void) {
ipcRenderer.on(IpcEvents.THEME_UPDATE, cb);
ipcRenderer.on(IpcEvents.THEME_UPDATE, () => cb());
},
openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS),

View File

@ -1,4 +1,4 @@
/* eslint-disable header/header */
/* eslint-disable simple-header/header */
/*!
* idb-keyval v6.2.0

View File

@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { OptionType, Plugin } from "@utils/types";
@ -89,7 +89,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const canSubmit = () => Object.values(errors).every(e => !e);
const hasSettings = Boolean(pluginSettings && plugin.options);
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
React.useEffect(() => {
enableStyle(hideBotTagStyle);

View File

@ -33,11 +33,10 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
setError(null);
setState(newValue);
onChange(newValue);
}
else setError(null);
setState(newValue);
onChange(newValue);
}
return (

View File

@ -28,7 +28,7 @@ import { SettingsTab } from "@components/VencordSettings/shared";
import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { classes, isObjectEmpty } from "@utils/misc";
import { openModalLazy } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
@ -161,7 +161,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
onMouseLeave={onMouseLeave}
infoButton={
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
{plugin.options
{plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel />
: <InfoIcon width="24" height="24" />}
</button>

View File

@ -231,7 +231,7 @@ function ThemesTab() {
ref={fileInputRef}
onChange={onFileUpload}
multiple={true}
filters={[{ extensions: ["*.css"] }]}
filters={[{ extensions: ["css"] }]}
/>
</Button>
) : (

View File

@ -249,4 +249,4 @@ function Updater() {
);
}
export default IS_WEB ? null : wrapTab(Updater, "Updater");
export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";

7
src/globals.d.ts vendored
View File

@ -35,8 +35,9 @@ declare global {
export var IS_WEB: boolean;
export var IS_DEV: boolean;
export var IS_STANDALONE: boolean;
export var IS_UPDATER_DISABLED: boolean;
export var IS_DISCORD_DESKTOP: boolean;
export var IS_VENCORD_DESKTOP: boolean;
export var IS_VESKTOP: boolean;
export var VERSION: string;
export var BUILD_TIMESTAMP: number;
@ -58,8 +59,8 @@ declare global {
* If you really must use it, mark your plugin as Desktop App only by naming it Foo.desktop.ts(x)
*/
export var DiscordNative: any;
export var VencordDesktop: any;
export var VencordDesktopNative: any;
export var Vesktop: any;
export var VesktopNative: any;
interface Window {
webpackChunkdiscord_app: {

View File

@ -23,7 +23,7 @@ import { ensureSafePath, getSettings } from "./ipcMain";
import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
import { installExt } from "./utils/extensions";
if (IS_VENCORD_DESKTOP || !IS_VANILLA) {
if (IS_VESKTOP || !IS_VANILLA) {
app.whenReady().then(() => {
// Source Maps! Maybe there's a better way but since the renderer is executed
// from a string I don't think any other form of sourcemaps would work
@ -44,6 +44,7 @@ if (IS_VENCORD_DESKTOP || !IS_VANILLA) {
case "renderer.js.map":
case "vencordDesktopRenderer.js.map":
case "preload.js.map":
case "vencordDesktopPreload.js.map":
case "patcher.js.map":
case "vencordDesktopMain.js.map":
cb(join(__dirname, url));

View File

@ -53,10 +53,12 @@ async function listThemes(): Promise<UserThemeHeader[]> {
const themeInfo: UserThemeHeader[] = [];
for (const fileName of files) {
if (!fileName.endsWith(".css")) continue;
const data = await getThemeData(fileName).then(stripBOM).catch(() => null);
if (!data) continue;
const parsed = getThemeInfo(data, fileName);
themeInfo.push(parsed);
if (data == null) continue;
themeInfo.push(getThemeInfo(data, fileName));
}
return themeInfo;
@ -138,7 +140,7 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
autoHideMenuBar: true,
darkTheme: true,
webPreferences: {
preload: join(__dirname, "preload.js"),
preload: join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: false

View File

@ -22,6 +22,28 @@ import { readFile } from "fs/promises";
import { request } from "https";
import { basename, normalize } from "path";
import { getSettings } from "./ipcMain";
// FixSpotifyEmbeds
app.on("browser-window-created", (_, win) => {
win.webContents.on("frame-created", (_, { frame }) => {
frame.once("dom-ready", () => {
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
const settings = getSettings().plugins?.FixSpotifyEmbeds;
if (!settings?.enabled) return;
frame.executeJavaScript(`
const original = Audio.prototype.play;
Audio.prototype.play = function() {
this.volume = ${(settings.volume / 100) || 0.1};
return original.apply(this, arguments);
}
`);
}
});
});
});
// #region OpenInApp
// These links don't support CORS, so this has to be native
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;

View File

@ -16,22 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { app, autoUpdater } from "electron";
import { app } from "electron";
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs";
import { basename, dirname, join } from "path";
const { setAppUserModelId } = app;
// Apparently requiring Discords updater too early leads into issues,
// copied this workaround from powerCord
app.setAppUserModelId = function (id: string) {
app.setAppUserModelId = setAppUserModelId;
setAppUserModelId.call(this, id);
patchUpdater();
};
function isNewer($new: string, old: string) {
const newParts = $new.slice(4).split(".").map(Number);
const oldParts = old.slice(4).split(".").map(Number);
@ -77,23 +65,6 @@ function patchLatest() {
}
}
// Windows Host Updates install to a new folder app-{HOST_VERSION}, so we
// need to reinject
function patchUpdater() {
try {
const autoStartScript = join(require.main!.filename, "..", "autoStart", "win32.js");
const { update } = require(autoStartScript);
require.cache[autoStartScript]!.exports.update = function () {
update.apply(this, arguments);
patchLatest();
};
} catch {
// OpenAsar uses electrons autoUpdater on Windows
const { quitAndInstall } = autoUpdater;
autoUpdater.quitAndInstall = function () {
patchLatest();
quitAndInstall.call(this);
};
}
}
// Try to patch latest on before-quit
// Discord's Win32 updater will call app.quit() on restart and open new version on will-quit
app.on("before-quit", patchLatest);

View File

@ -71,7 +71,7 @@ if (!IS_VANILLA) {
constructor(options: BrowserWindowConstructorOptions) {
if (options?.webPreferences?.preload && options.title) {
const original = options.webPreferences.preload;
options.webPreferences.preload = join(__dirname, "preload.js");
options.webPreferences.preload = join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js");
options.webPreferences.sandbox = false;
if (settings.frameless) {
options.frame = false;

View File

@ -1,4 +1,4 @@
/* eslint-disable header/header */
/* eslint-disable simple-header/header */
/*!
* BetterDiscord addon meta parser

View File

@ -16,12 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const VENCORD_FILES = [
IS_DISCORD_DESKTOP ? "patcher.js" : "vencordDesktopMain.js",
"preload.js",
IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js",
IS_DISCORD_DESKTOP ? "renderer.js" : "vencordDesktopRenderer.js",
"renderer.css"
IS_DISCORD_DESKTOP ? "renderer.css" : "vencordDesktopRenderer.css",
];
export function serializeErrors(func: (...args: any[]) => any) {

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import(IS_STANDALONE ? "./http" : "./git");
if (!IS_UPDATER_DISABLED)
import(IS_STANDALONE ? "./http" : "./git");

View File

@ -1,4 +1,4 @@
/* eslint-disable header/header */
/* eslint-disable simple-header/header */
/*!
* crxToZip

View File

@ -27,8 +27,8 @@ export default definePlugin({
{
find: "Messages.DISCODO_DISABLED",
replacement: {
match: /(Messages\.DISCODO_DISABLED\);return)(.*?homeIcon.*?)(\}function)/,
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))$3"
match: /(Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
}
},
{

View File

@ -22,7 +22,7 @@ import definePlugin from "@utils/types";
export default definePlugin({
name: "NoTrack",
description: "Disable Discord's tracking ('science'), metrics and Sentry crash reporting",
authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz],
authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz, Devs.Arrow],
required: true,
patches: [
{
@ -51,6 +51,13 @@ export default definePlugin({
replace: "return;"
}
]
}
},
{
find: ".installedLogHooks)",
replacement: {
match: /if\(\i\.getDebugLogging\(\)&&!\i\.installedLogHooks\)/,
replace: "if(false)"
}
},
]
});

View File

@ -19,7 +19,6 @@
import { addContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { React, SettingsRouter } from "@webpack/common";
@ -63,19 +62,14 @@ export default definePlugin({
replacement: {
get match() {
switch (Settings.plugins.Settings.settingsLocation) {
case "top": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.APP_SETTINGS\}/;
case "aboveActivity": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.ACTIVITY_SETTINGS\}/;
case "belowActivity": return /(?<=\{section:(.{1,2})\.ID\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(.{1,2})\.ID\.CUSTOM,\s*element:.+?}/;
default: {
new Logger("Settings").error(
new Error("No switch case matched????? Don't mess with the settings, silly")
);
// matches nothing
return /(?!a)a/;
}
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/;
case "aboveActivity":
default:
return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
}
},
replace: "...$self.makeSettingsCategories($1),$&"
@ -88,48 +82,50 @@ export default definePlugin({
return [
{
section: ID.HEADER,
label: "Vencord"
label: "Vencord",
className: "vc-settings-header"
},
{
section: "VencordSettings",
label: "Vencord",
element: require("@components/VencordSettings/VencordTab").default
element: require("@components/VencordSettings/VencordTab").default,
className: "vc-settings"
},
{
section: "VencordPlugins",
label: "Plugins",
element: require("@components/VencordSettings/PluginsTab").default,
className: "vc-plugins"
},
{
section: "VencordThemes",
label: "Themes",
element: require("@components/VencordSettings/ThemesTab").default,
className: "vc-themes"
},
!IS_WEB && {
!IS_UPDATER_DISABLED && {
section: "VencordUpdater",
label: "Updater",
element: require("@components/VencordSettings/UpdaterTab").default,
className: "vc-updater"
},
{
section: "VencordCloud",
label: "Cloud",
element: require("@components/VencordSettings/CloudTab").default,
className: "vc-cloud"
},
{
section: "VencordSettingsSync",
label: "Backup & Restore",
element: require("@components/VencordSettings/BackupAndRestoreTab").default,
className: "vc-backup-restore"
},
IS_DEV && {
section: "VencordPatchHelper",
label: "Patch Helper",
element: require("@components/VencordSettings/PatchHelperTab").default,
},
// TODO: make this use customSections
IS_VENCORD_DESKTOP && {
section: "VencordDesktop",
label: "Desktop Settings",
element: VencordDesktop.Components.Settings,
className: "vc-patch-helper"
},
...this.customSections.map(func => func(ID)),
{
@ -172,7 +168,7 @@ export default definePlugin({
get additionalInfo() {
if (IS_DEV) return " (Dev)";
if (IS_WEB) return " (Web)";
if (IS_VENCORD_DESKTOP) return ` (VencordDesktop v${VencordDesktopNative.app.getVersion()})`;
if (IS_VESKTOP) return ` (Vesktop v${VesktopNative.app.getVersion()})`;
if (IS_STANDALONE) return " (Standalone)";
return "";
},

View File

@ -26,6 +26,8 @@ const enum Methods {
Timestamp,
}
const tarExtMatcher = /\.tar\.\w+$/;
export default definePlugin({
name: "AnonymiseFileNames",
authors: [Devs.obscurity],
@ -67,7 +69,8 @@ export default definePlugin({
anonymise(file: string) {
let name = "image";
const extIdx = file.lastIndexOf(".");
const tarMatch = tarExtMatcher.exec(file);
const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
const ext = extIdx !== -1 ? file.slice(extIdx) : "";
switch (Settings.plugins.AnonymiseFileNames.method) {

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -35,7 +34,7 @@ export default definePlugin({
},
},
{
find: 'preload:"none","aria',
find: ".embedGallerySide",
replacement: {
match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/,
replace:

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { findStoreLazy } from "@webpack";
import * as t from "./types/stores";

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const defaultRules = [
"action_object_map",
"action_type_map",
@ -128,6 +127,7 @@ export const defaultRules = [
"redircnt@yandex.*",
"feature@youtube.com",
"kw@youtube.com",
"si@youtu.be",
"wt_zmc",
"utm_source",
"utm_content",

View File

@ -0,0 +1,55 @@
/*
* 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 { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { LinkIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Clipboard, Menu } from "@webpack/common";
import type { Channel, User } from "discord-types/general";
interface UserContextProps {
channel: Channel;
guildId?: string;
user: User;
}
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
children.push(
<Menu.MenuItem
id="vc-copy-user-url"
label="Copy User URL"
action={() => Clipboard.copy(`<https://discord.com/users/${user.id}>`)}
icon={LinkIcon}
/>
);
};
export default definePlugin({
name: "CopyUserURLs",
authors: [Devs.castdrian],
description: "Adds a 'Copy User URL' option to the user context menu.",
start() {
addContextMenuPatch("user-context", UserContextMenuPatch);
},
stop() {
removeContextMenuPatch("user-context", UserContextMenuPatch);
},
});

View File

@ -23,10 +23,12 @@ import { Logger } from "@utils/Logger";
import { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, NavigationRouter } from "@webpack/common";
import type { ReactElement } from "react";
const CrashHandlerLogger = new Logger("CrashHandler");
const ModalStack = findByPropsLazy("pushLazy", "popAll");
const settings = definePluginSettings({
attemptToPreventCrashes: {
@ -51,8 +53,6 @@ export default definePlugin({
authors: [Devs.Nuckyz],
enabledByDefault: true,
popAllModals: undefined as (() => void) | undefined,
settings,
patches: [
@ -62,13 +62,6 @@ export default definePlugin({
match: /(?=this\.setState\()/,
replace: "$self.handleCrash(this)||"
}
},
{
find: 'dispatch({type:"MODAL_POP_ALL"})',
replacement: {
match: /"MODAL_POP_ALL".+?};(?<=(\i)=function.+?)/,
replace: (m, popAll) => `${m}$self.popAllModals=${popAll};`
}
}
],
@ -128,7 +121,7 @@ export default definePlugin({
CrashHandlerLogger.debug("Failed to close open context menu.", err);
}
try {
this.popAllModals?.();
ModalStack?.popAll();
} catch (err) {
CrashHandlerLogger.debug("Failed to close old modals.", err);
}

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, Settings } from "@api/Settings";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards";
@ -86,8 +86,7 @@ const settings = definePluginSettings({
appID: {
type: OptionType.STRING,
description: "Application ID (required)",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (!value) return "Application ID is required.";
if (value && !/^\d+$/.test(value)) return "Application ID must be a number.";
@ -97,8 +96,7 @@ const settings = definePluginSettings({
appName: {
type: OptionType.STRING,
description: "Application name (required)",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (!value) return "Application name is required.";
if (value.length > 128) return "Application name must be not longer than 128 characters.";
@ -108,8 +106,7 @@ const settings = definePluginSettings({
details: {
type: OptionType.STRING,
description: "Details (line 1)",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (value && value.length > 128) return "Details (line 1) must be not longer than 128 characters.";
return true;
@ -118,8 +115,7 @@ const settings = definePluginSettings({
state: {
type: OptionType.STRING,
description: "State (line 2)",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (value && value.length > 128) return "State (line 2) must be not longer than 128 characters.";
return true;
@ -128,8 +124,7 @@ const settings = definePluginSettings({
type: {
type: OptionType.SELECT,
description: "Activity type",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
options: [
{
label: "Playing",
@ -157,16 +152,14 @@ const settings = definePluginSettings({
streamLink: {
type: OptionType.STRING,
description: "Twitch.tv or Youtube.com link (only for Streaming activity type)",
restartNeeded: true,
onChange: setRpc,
isDisabled: isStreamLinkDisabled,
onChange: onChange,
disabled: isStreamLinkDisabled,
isValid: isStreamLinkValid
},
timestampMode: {
type: OptionType.SELECT,
description: "Timestamp mode",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
options: [
{
label: "None",
@ -190,9 +183,8 @@ const settings = definePluginSettings({
startTime: {
type: OptionType.NUMBER,
description: "Start timestamp (only for custom timestamp mode)",
restartNeeded: true,
onChange: setRpc,
isDisabled: isTimestampDisabled,
onChange: onChange,
disabled: isTimestampDisabled,
isValid: (value: number) => {
if (value && value < 0) return "Start timestamp must be greater than 0.";
return true;
@ -201,9 +193,8 @@ const settings = definePluginSettings({
endTime: {
type: OptionType.NUMBER,
description: "End timestamp (only for custom timestamp mode)",
restartNeeded: true,
onChange: setRpc,
isDisabled: isTimestampDisabled,
onChange: onChange,
disabled: isTimestampDisabled,
isValid: (value: number) => {
if (value && value < 0) return "End timestamp must be greater than 0.";
return true;
@ -211,16 +202,14 @@ const settings = definePluginSettings({
},
imageBig: {
type: OptionType.STRING,
description: "Big image key",
restartNeeded: true,
onChange: setRpc,
description: "Big image key/link",
onChange: onChange,
isValid: isImageKeyValid
},
imageBigTooltip: {
type: OptionType.STRING,
description: "Big image tooltip",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (value && value.length > 128) return "Big image tooltip must be not longer than 128 characters.";
return true;
@ -228,16 +217,14 @@ const settings = definePluginSettings({
},
imageSmall: {
type: OptionType.STRING,
description: "Small image key",
restartNeeded: true,
onChange: setRpc,
description: "Small image key/link",
onChange: onChange,
isValid: isImageKeyValid
},
imageSmallTooltip: {
type: OptionType.STRING,
description: "Small image tooltip",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (value && value.length > 128) return "Small image tooltip must be not longer than 128 characters.";
return true;
@ -246,8 +233,7 @@ const settings = definePluginSettings({
buttonOneText: {
type: OptionType.STRING,
description: "Button 1 text",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (value && value.length > 31) return "Button 1 text must be not longer than 31 characters.";
return true;
@ -256,14 +242,12 @@ const settings = definePluginSettings({
buttonOneURL: {
type: OptionType.STRING,
description: "Button 1 URL",
restartNeeded: true,
onChange: setRpc
onChange: onChange
},
buttonTwoText: {
type: OptionType.STRING,
description: "Button 2 text",
restartNeeded: true,
onChange: setRpc,
onChange: onChange,
isValid: (value: string) => {
if (value && value.length > 31) return "Button 2 text must be not longer than 31 characters.";
return true;
@ -272,26 +256,29 @@ const settings = definePluginSettings({
buttonTwoURL: {
type: OptionType.STRING,
description: "Button 2 URL",
restartNeeded: true,
onChange: setRpc
onChange: onChange
}
});
function isStreamLinkDisabled(): boolean {
function onChange() {
setRpc(true);
if (Settings.plugins.CustomRPC.enabled) setRpc();
}
function isStreamLinkDisabled() {
return settings.store.type !== ActivityType.STREAMING;
}
function isStreamLinkValid(): boolean | string {
if (settings.store.type === ActivityType.STREAMING && settings.store.streamLink && !/(https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+)/.test(settings.store.streamLink)) return "Streaming link must be a valid URL.";
function isStreamLinkValid(value: string) {
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
return true;
}
function isTimestampDisabled(): boolean {
function isTimestampDisabled() {
return settings.store.timestampMode !== TimestampMode.CUSTOM;
}
function isImageKeyValid(value: string) {
if (!/https?:\/\//.test(value)) return true;
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image. (e.g. https://i.imgur.com/...)";
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image. (e.g. https://media.tenor.com/...)";
return true;
@ -342,13 +329,10 @@ async function createActivity(): Promise<Activity | undefined> {
};
break;
case TimestampMode.CUSTOM:
if (startTime) {
activity.timestamps = {
start: startTime,
};
if (endTime) {
activity.timestamps.end = endTime;
}
if (startTime || endTime) {
activity.timestamps = {};
if (startTime) activity.timestamps.start = startTime;
if (endTime) activity.timestamps.end = endTime;
}
break;
case TimestampMode.NONE:

View File

@ -313,7 +313,7 @@ export default definePlugin({
find: ".Messages.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION",
predicate: () => settings.store.transformEmojis,
replacement: {
match: /((\i)=\i\.node,\i=\i\.emojiSourceDiscoverableGuild)(.+?return )(.{0,450}Messages\.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION.+?}\))/,
match: /((\i)=\i\.node,\i=\i\.expressionSourceGuild)(.+?return )(.{0,450}Messages\.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION.+?}\))/,
replace: (_, rest1, node, rest2, reactNode) => `${rest1},fakeNitroNode=${node}${rest2}$self.addFakeNotice("EMOJI",${reactNode},fakeNitroNode.fake)`
}
}

View File

@ -0,0 +1,26 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
// The entire code of this plugin can be found in ipcPlugins
export default definePlugin({
name: "FixSpotifyEmbeds",
description: "Fixes spotify embeds being incredibly loud by letting you customise the volume",
authors: [Devs.Ven],
settings: definePluginSettings({
volume: {
type: OptionType.SLIDER,
description: "The volume % to set for spotify embeds. Anything above 10% is veeeery loud",
markers: makeRange(0, 100, 10),
stickToMarkers: false,
default: 10
}
})
});

View File

@ -32,12 +32,18 @@ function makeIcon(showCurrentGame?: boolean) {
return function () {
return (
<svg
width="24"
height="24"
viewBox="0 96 960 960"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M182 856q-51 0-79-35.5T82 734l42-300q9-60 53.5-99T282 296h396q60 0 104.5 39t53.5 99l42 300q7 51-21 86.5T778 856q-21 0-39-7.5T706 826l-90-90H344l-90 90q-15 15-33 22.5t-39 7.5Zm498-240q17 0 28.5-11.5T720 576q0-17-11.5-28.5T680 536q-17 0-28.5 11.5T640 576q0 17 11.5 28.5T680 616Zm-80-120q17 0 28.5-11.5T640 456q0-17-11.5-28.5T600 416q-17 0-28.5 11.5T560 456q0 17 11.5 28.5T600 496ZM310 616h60v-70h70v-60h-70v-70h-60v70h-70v60h70v70Z" />
{!showCurrentGame && <line x1="920" y1="280" x2="40" y2="880" stroke="var(--status-danger)" stroke-width="80" />}
<path fill="currentColor" mask="url(#gameActivityMask)" d="M3.06 20.4q-1.53 0-2.37-1.065T.06 16.74l1.26-9q.27-1.8 1.605-2.97T6.06 3.6h11.88q1.8 0 3.135 1.17t1.605 2.97l1.26 9q.21 1.53-.63 2.595T20.94 20.4q-.63 0-1.17-.225T18.78 19.5l-2.7-2.7H7.92l-2.7 2.7q-.45.45-.99.675t-1.17.225Zm14.94-7.2q.51 0 .855-.345T19.2 12q0-.51-.345-.855T18 10.8q-.51 0-.855.345T16.8 12q0 .51.345 .855T18 13.2Zm-2.4-3.6q.51 0 .855-.345T16.8 8.4q0-.51-.345-.855T15.6 7.2q-.51 0-.855.345T14.4 8.4q0 .51.345 .855T15.6 9.6ZM6.9 13.2h1.8v-2.1h2.1v-1.8h-2.1v-2.1h-1.8v2.1h-2.1v1.8h2.1v2.1Z" />
{!showCurrentGame && <>
<mask id="gameActivityMask" >
<rect fill="white" x="0" y="0" width="24" height="24" />
<path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z"/>
</mask>
<path fill="var(--status-danger)" d="M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z" />
</>}
</svg>
);
};
@ -60,7 +66,7 @@ function GameActivityToggleButton() {
export default definePlugin({
name: "GameActivityToggle",
description: "Adds a button next to the mic and deafen button to toggle game activity.",
authors: [Devs.Nuckyz],
authors: [Devs.Nuckyz, Devs.RuukuLada],
dependencies: ["SettingsStoreAPI"],
patches: [

View File

@ -49,7 +49,7 @@ export default definePlugin({
await this.buildCss();
addButton("HideAttachments", msg => {
if (!msg.attachments.length && !msg.embeds.length) return null;
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
const isHidden = hiddenMessages.has(msg.id);
@ -72,7 +72,7 @@ export default definePlugin({
async buildCss() {
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
style.textContent = `
:is(${elements}) [class*="embedWrapper"] {
:is(${elements}) :is([class*="embedWrapper", [class*"clickableSticker"]) {
/* important is not necessary, but add it to make sure bad themes won't break it */
display: none !important;
}

View File

@ -35,7 +35,7 @@ function EncModal(props: ModalProps) {
const [password, setPassword] = React.useState("password");
const [noCover, setNoCover] = React.useState(false);
const isValid = secret && (noCover || (cover && /\w \w/.test(cover)));
const isValid = secret && (noCover || (cover && cover.trim().split(" ").length > 1));
return (
<ModalRoot {...props}>

View File

@ -30,6 +30,9 @@ const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
};
const sharedIntlNumberFormat = new Intl.NumberFormat();
const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
function MemberCount() {
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const { groups } = useStateFromStores(
@ -52,13 +55,12 @@ function MemberCount() {
return (
<Flex id="vc-membercount" style={{
marginTop: "1em",
marginBottom: "-.5em",
paddingInline: "1em",
justifyContent: "center",
alignContent: "center",
gap: 0
}}>
<Tooltip text={`${online} Online in this Channel`} position="bottom">
<Tooltip text={`${numberFormat(online)} online in this channel`} position="bottom">
{props => (
<div {...props}>
<span
@ -71,11 +73,11 @@ function MemberCount() {
marginRight: "0.5em"
}}
/>
<span style={{ color: "var(--green-360)" }}>{online}</span>
<span style={{ color: "var(--green-360)" }}>{numberFormat(online)}</span>
</div>
)}
</Tooltip>
<Tooltip text={`${total} Total Server Members`} position="bottom">
<Tooltip text={`${numberFormat(total)} total server members`} position="bottom">
{props => (
<div {...props}>
<span
@ -89,7 +91,7 @@ function MemberCount() {
marginLeft: "1em"
}}
/>
<span style={{ color: "var(--primary-400)" }}>{total}</span>
<span style={{ color: "var(--primary-400)" }}>{numberFormat(total)}</span>
</div>
)}
</Tooltip>

View File

@ -18,6 +18,7 @@
import { addAccessory } from "@api/MessageAccessories";
import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStore";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js";
import { classes } from "@utils/misc";
@ -318,9 +319,10 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe
/>;
}
const compactModeEnabled = getSettingStoreLazy<boolean>("textAndImages", "messageDisplayCompact")!;
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
const { message, channel, guildID } = props;
const isDM = guildID === "@me";
const images = getImages(message);
const { parse } = Parser;
@ -336,7 +338,7 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
</Text>
}
compact={false}
compact={compactModeEnabled.getSetting()}
content={
<>
{message.content || message.attachments.length <= images.length
@ -363,7 +365,7 @@ export default definePlugin({
name: "MessageLinkEmbeds",
description: "Adds a preview to messages that link another message",
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
dependencies: ["MessageAccessoriesAPI"],
dependencies: ["MessageAccessoriesAPI", "SettingsStoreAPI"],
patches: [
{
find: ".embedCard",

View File

@ -3,6 +3,11 @@
color: #f04747 !important;
}
/* Bot "thinking" text highlighting */
.messagelogger-deleted [class*="colorStandard-"] {
color: #f04747 !important;
}
/* Embed highlighting */
.messagelogger-deleted article :is(div, span, h1, h2, h3, p) {
color: #f04747 !important;

View File

@ -26,7 +26,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
import overlayStyle from "./deleteStyleOverlay.css?managed";
import textStyle from "./deleteStyleText.css?managed";
@ -92,7 +92,7 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
export default definePlugin({
name: "MessageLogger",
description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven],
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN],
start() {
addDeleteStyle();
@ -169,21 +169,14 @@ export default definePlugin({
try {
if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
const myId = UserStore.getCurrentUser().id;
function mutate(id: string) {
const mutate = (id: string) => {
const msg = cache.get(id);
if (!msg) return;
const EPHEMERAL = 64;
const shouldIgnore = data.mlDeleted ||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
ignoreBots && msg.author?.bot ||
ignoreSelf && msg.author?.id === myId ||
ignoreUsers.includes(msg.author?.id) ||
ignoreChannels.includes(msg.channel_id) ||
ignoreGuilds.includes(msg.guild_id);
this.shouldIgnore(msg);
if (shouldIgnore) {
cache = cache.remove(id);
@ -192,7 +185,7 @@ export default definePlugin({
.set("deleted", true)
.set("attachments", m.attachments.map(a => (a.deleted = true, a))));
}
}
};
if (isBulk) {
data.ids.forEach(mutate);
@ -205,6 +198,17 @@ export default definePlugin({
return cache;
},
shouldIgnore(message: any) {
const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
const myId = UserStore.getCurrentUser().id;
return ignoreBots && message.author?.bot ||
ignoreSelf && message.author?.id === myId ||
ignoreUsers.includes(message.author?.id) ||
ignoreChannels.includes(message.channel_id) ||
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
},
// Based on canary 9ab8626bcebceaea6da570b9c586172d02b9c996
patches: [
{
@ -237,7 +241,7 @@ export default definePlugin({
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/,
replace: "$1" +
".update($3,m =>" +
" (($2.message.flags & 64) === 64 || (Vencord.Settings.plugins.MessageLogger.ignoreBots && $2.message.author?.bot) || (Vencord.Settings.plugins.MessageLogger.ignoreSelf && $2.message.author?.id === Vencord.Webpack.Common.UserStore.getCurrentUser().id)) ? m :" +
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
" $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" +
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
" m" +

View File

@ -54,6 +54,8 @@ interface IVoiceChannelEffectSendEvent {
const MOYAI = "🗿";
const MOYAI_URL =
"https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai.mp3";
const MOYAI_URL_HD =
"https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai_hd.wav";
const settings = definePluginSettings({
volume: {
@ -63,6 +65,14 @@ const settings = definePluginSettings({
default: 0.5,
stickToMarkers: false
},
quality: {
description: "Quality of the 🗿🗿🗿",
type: OptionType.SELECT,
options: [
{ label: "Normal", value: "Normal", default: true },
{ label: "HD", value: "HD" }
],
},
triggerWhenUnfocused: {
description: "Trigger the 🗿 even when the window is unfocused",
type: OptionType.BOOLEAN,
@ -157,7 +167,11 @@ function getMoyaiCount(message: string) {
function boom() {
if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return;
const audioElement = document.createElement("audio");
audioElement.src = MOYAI_URL;
audioElement.src = settings.store.quality === "HD"
? MOYAI_URL_HD
: MOYAI_URL;
audioElement.volume = settings.store.volume;
audioElement.play();
}

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import { isNonNullish } from "@utils/guards";
import definePlugin from "@utils/types";

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";

View File

@ -27,10 +27,10 @@ export default definePlugin({
{
find: '("ApplicationStreamPreviewUploadManager")',
replacement: [
"\\i\\.default\\.makeChunkedRequest\\(",
"\\i\\.\\i\\.post\\({url:"
String.raw`\i\.\i\.makeChunkedRequest\(`,
String.raw`\i\.\i\.post\({url:`
].map(match => ({
match: new RegExp(`(?=return\\[(\\d),${match}\\i\\.\\i\\.STREAM_PREVIEW.+?}\\)\\];)`),
match: new RegExp(String.raw`(?=return\[(\d),${match}\i\.\i\.STREAM_PREVIEW.+?}\)\];)`),
replace: (_, code) => `return[${code},Promise.resolve({body:"",status:204})];`
}))
}

View File

@ -0,0 +1,26 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NormalizeMessageLinks",
description: "Strip canary/ptb from message links",
authors: [Devs.bb010g],
patches: [
{
find: ".Messages.COPY_MESSAGE_LINK,",
replacement: {
match: /\.concat\(location\.host\)/,
replace: ".concat($self.normalizeHost(location.host))",
},
},
],
normalizeHost(host: string) {
return host.replace(/(^|\b)(canary\.|ptb\.)(discord.com)$/, "$1$3");
},
});

View File

@ -55,8 +55,8 @@ export default definePlugin({
{
find: '"MaskedLinkStore"',
replacement: {
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,200}\.trusted)/,
replace: "return $self.handleLink(...arguments).then(handled => handled || $1)"
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,250}\.trusted)/,
replace: "return $self.handleLink(...arguments).then(handled => handled||$1)"
}
},
// Make Spotify profile activity links open in app on web

View File

@ -178,12 +178,12 @@ export default definePlugin({
start() {
addContextMenuPatch("user-context", this.userContextMenuPatch);
addContextMenuPatch("channel-context", this.channelContextMenuPatch);
addContextMenuPatch("guild-context", this.guildContextMenuPatch);
addContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
},
stop() {
removeContextMenuPatch("user-context", this.userContextMenuPatch);
removeContextMenuPatch("channel-context", this.channelContextMenuPatch);
removeContextMenuPatch("guild-context", this.guildContextMenuPatch);
removeContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
},
});

View File

@ -122,6 +122,14 @@ export default definePlugin({
// ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
replace: ".concat($self.getSnapshot()).concat($2.filter(c=>!$self.isPinned(c)))"
}
}
},
// fix alt+shift+up/down
{
find: '"alt+shift+down"',
replacement: {
match: /(?<=return \i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/,
replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
}
},
]
});

View File

@ -0,0 +1,82 @@
/*
* 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 { sendBotMessage } from "@api/Commands";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
interface Props {
type: {
analyticsName: string;
};
}
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
export function PreviewButton(chatBoxProps: Props) {
const channelId = SelectedChannelStore.getChannelId();
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
if (chatBoxProps.type.analyticsName !== "normal") return null;
if (!draft) return null;
return (
<Tooltip text="Preview Message">
{tooltipProps => (
<Button
{...tooltipProps}
onClick={() =>
sendBotMessage(
channelId,
{
content: getDraft(channelId),
author: UserStore.getCurrentUser()
}
)}
size=""
look={ButtonLooks.BLANK}
innerClassName={ButtonWrapperClasses.button}
style={{ padding: "0 2px", height: "100%" }}
>
<div className={ButtonWrapperClasses.buttonWrapper}>
<img width={24} height={24} src="https://discord.com/assets/4c5a77a89716352686f590a6f014770c.svg" />
</div>
</Button>
)}
</Tooltip>
);
}
export default definePlugin({
name: "PreviewMessage",
description: "Lets you preview your message before sending it",
authors: [Devs.Aria],
patches: [
{
find: ".activeCommandOption",
replacement: {
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
replace: "$&;try{$2||$1.unshift($self.previewIcon(arguments[0]))}catch{}",
}
},
],
previewIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
});

View File

@ -68,7 +68,8 @@ export default definePlugin({
find: ".USER_PROFILE_ACTIVITY",
replacement: [
{
match: /getGlobalName\(\i\);(?<=displayProfile.{0,200})/,
/* FIXME: old name is getGlobalName, new name is getName. Remove optional Global once stable has also migrated */
match: /\.get(?:Global)?Name\(\i\);(?<=displayProfile.{0,200})/,
replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;"
},
PRONOUN_TOOLTIP_PATCH

View File

@ -33,7 +33,7 @@ export default LazyComponent(() => {
// this is terrible, blame mantika
const p = filters.byProps;
const [
{ cozyMessage, buttons, message, groupStart },
{ cozyMessage, buttons, message, buttonsInner, groupStart },
{ container, isHeader },
{ avatar, clickable, username, messageContent, wrapper, cozy },
buttonClasses,
@ -134,7 +134,7 @@ export default LazyComponent(() => {
<div className={classes(container, isHeader, buttons)} style={{
padding: "0px",
}}>
<div className={buttonClasses.wrapper} >
<div className={classes(buttonClasses.wrapper, buttonsInner)} >
<ReportButton onClick={reportRev} />
{canDeleteReview(review, UserStore.getCurrentUser().id) && (

View File

@ -19,7 +19,7 @@
import { classes } from "@utils/misc";
import { useAwaiter, useForceUpdater } from "@utils/react";
import { findByPropsLazy } from "@webpack";
import { Forms, React, UserStore } from "@webpack/common";
import { Forms, React, RelationshipStore, UserStore } from "@webpack/common";
import type { KeyboardEvent } from "react";
import { Review } from "../entities";
@ -60,6 +60,9 @@ export default function ReviewsView({
fallbackValue: null,
deps: [refetchSignal, signal, page],
onSuccess: data => {
if (settings.store.hideBlockedUsers)
data!.reviews = data!.reviews?.filter(r => !RelationshipStore.isBlocked(r.sender.discordID));
scrollToTop?.();
onFetchReviews(data!);
}

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Review, ReviewDBUser } from "./entities";
import { settings } from "./settings";
import { authorize, showToast } from "./utils";

View File

@ -48,6 +48,11 @@ export const settings = definePluginSettings({
description: "Hide timestamps on reviews",
default: false,
},
hideBlockedUsers: {
type: OptionType.BOOLEAN,
description: "Hide reviews from blocked users",
default: true,
},
website: {
type: OptionType.COMPONENT,
description: "ReviewDB website",

View File

@ -0,0 +1,21 @@
/*
* 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/>.
*/
export const SupressionIcon = ({ enabled }: { enabled: boolean; }) => enabled
? <svg aria-hidden="true" role="img" width="20" height="20" viewBox="0 0 24 24"><path d="M10.889 4C10.889 3.44772 11.3367 3 11.889 3H12.1112C12.6635 3 13.1112 3.44772 13.1112 4V20C13.1112 20.5523 12.6635 21 12.1112 21H11.889C11.3367 21 10.889 20.5523 10.889 20V4Z" fill="currentColor"></path><path d="M6.44439 6.25C6.44439 5.69772 6.89211 5.25 7.44439 5.25H7.66661C8.2189 5.25 8.66661 5.69772 8.66661 6.25V17.75C8.66661 18.3023 8.2189 18.75 7.66661 18.75H7.44439C6.89211 18.75 6.44439 18.3023 6.44439 17.75V6.25Z" fill="currentColor"></path><path d="M3.22222 15.375C3.77451 15.375 4.22222 14.9273 4.22222 14.375L4.22222 9.625C4.22222 9.07272 3.77451 8.625 3.22222 8.625H3C2.44772 8.625 2 9.07272 2 9.625V14.375C2 14.9273 2.44772 15.375 3 15.375H3.22222Z" fill="currentColor"></path><path d="M22.0001 13.25C22.0001 13.8023 21.5523 14.25 21.0001 14.25H20.7778C20.2255 14.25 19.7778 13.8023 19.7778 13.25V10.75C19.7778 10.1977 20.2255 9.75 20.7778 9.75H21.0001C21.5523 9.75 22.0001 10.1977 22.0001 10.75V13.25Z" fill="currentColor"></path><path d="M16.3333 7.5C15.781 7.5 15.3333 7.94772 15.3333 8.5V15.5C15.3333 16.0523 15.781 16.5 16.3333 16.5H16.5555C17.1078 16.5 17.5555 16.0523 17.5555 15.5V8.5C17.5555 7.94772 17.1078 7.5 16.5555 7.5H16.3333Z" fill="currentColor"></path></svg>
: <svg aria-hidden="true" role="img" width="20" height="20" viewBox="0 0 48 48"><path d="M30.6666 24.9644L35.1111 20.5199V31C35.1111 32.1046 34.2156 33 33.1111 33H32.6666C31.562 33 30.6666 32.1046 30.6666 31V24.9644Z" fill="currentColor"></path><path d="M26.2224 14.1463V8C26.2224 6.89543 25.327 6 24.2224 6H23.7779C22.6734 6 21.7779 6.89543 21.7779 8V18.5907L26.2224 14.1463Z" fill="currentColor"></path><path d="M21.7779 33.8543L21.9254 33.7056L26.2224 29.4086V40C26.2224 41.1046 25.327 42 24.2224 42H23.7779C22.6734 42 21.7779 41.1046 21.7779 40V33.8543Z" fill="currentColor"></path><path d="M17.3332 23.0354L12.8888 27.4799V12.5C12.8888 11.3954 13.7842 10.5 14.8888 10.5H15.3332C16.4378 10.5 17.3332 11.3954 17.3332 12.5V23.0354Z" fill="currentColor"></path><path d="M8.44445 28.75C8.44445 29.8546 7.54902 30.75 6.44445 30.75H6C4.89543 30.75 4 29.8546 4 28.75V19.25C4 18.1454 4.89543 17.25 6 17.25H6.44445C7.54902 17.25 8.44445 18.1454 8.44445 19.25L8.44445 28.75Z" fill="currentColor"></path><path d="M44.0001 26.5C44.0001 27.6046 43.1047 28.5 42.0001 28.5H41.5557C40.4511 28.5 39.5557 27.6046 39.5557 26.5V21.5C39.5557 20.3954 40.4511 19.5 41.5557 19.5H42.0001C43.1047 19.5 44.0001 20.3954 44.0001 21.5V26.5Z" fill="currentColor"></path><path d="M42 8.54L39.46 6L6 39.46L8.54 42L16.92 33.64L19.38 31.16L22.7 27.84L29.98 20.56L42 8.54Z" fill="currentColor"></path></svg>;

View File

@ -0,0 +1,249 @@
/*
* 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 { classNameFactory } from "@api/Styles";
import { Switch } from "@components/Switch";
import { loadRnnoise, RnnoiseWorkletNode } from "@sapphi-red/web-noise-suppressor";
import { Devs } from "@utils/constants";
import { rnnoiseWasmSrc, rnnoiseWorkletSrc } from "@utils/dependencies";
import { makeLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { LazyComponent } from "@utils/react";
import definePlugin from "@utils/types";
import { findByCode } from "@webpack";
import { FluxDispatcher, Popout, React } from "@webpack/common";
import { MouseEvent, ReactNode } from "react";
import { SupressionIcon } from "./icons";
const RNNOISE_OPTION = "RNNOISE";
interface PanelButtonProps {
tooltipText: string;
icon: () => ReactNode;
onClick: (event: MouseEvent<HTMLElement>) => void;
tooltipClassName?: string;
disabled?: boolean;
shouldShow?: boolean;
}
const PanelButton = LazyComponent<PanelButtonProps>(() => findByCode("Masks.PANEL_BUTTON"));
const enum SpinnerType {
SpinningCircle = "spinningCircle",
ChasingDots = "chasingDots",
LowMotion = "lowMotion",
PulsingEllipsis = "pulsingEllipsis",
WanderingCubes = "wanderingCubes",
}
export interface SpinnerProps {
type: SpinnerType;
animated?: boolean;
className?: string;
itemClassName?: string;
}
const Spinner = LazyComponent<SpinnerProps>(() => findByCode(".spinningCircleInner"));
function createExternalStore<S>(init: () => S) {
const subscribers = new Set<() => void>();
let state = init();
return {
get: () => state,
set: (newStateGetter: (oldState: S) => S) => {
state = newStateGetter(state);
for (const cb of subscribers) cb();
},
use: () => {
return React.useSyncExternalStore<S>(onStoreChange => {
subscribers.add(onStoreChange);
return () => subscribers.delete(onStoreChange);
}, () => state);
},
} as const;
}
const cl = classNameFactory("vc-rnnoise-");
const loadedStore = createExternalStore(() => ({
isLoaded: false,
isLoading: false,
isError: false,
}));
const getRnnoiseWasm = makeLazy(() => {
loadedStore.set(s => ({ ...s, isLoading: true }));
return loadRnnoise({
url: rnnoiseWasmSrc(),
simdUrl: rnnoiseWasmSrc(true),
}).then(buffer => {
// Check WASM magic number cus fetch doesnt throw on 4XX or 5XX
if (new DataView(buffer.slice(0, 4)).getUint32(0) !== 0x0061736D) throw buffer;
loadedStore.set(s => ({ ...s, isLoaded: true }));
return buffer;
}).catch(error => {
if (error instanceof ArrayBuffer) error = new TextDecoder().decode(error);
logger.error("Failed to load RNNoise WASM:", error);
loadedStore.set(s => ({ ...s, isError: true }));
return null;
}).finally(() => {
loadedStore.set(s => ({ ...s, isLoading: false }));
});
});
const logger = new Logger("RNNoise");
const settings = definePluginSettings({}).withPrivateSettings<{ isEnabled: boolean; }>();
const setEnabled = (enabled: boolean) => {
settings.store.isEnabled = enabled;
FluxDispatcher.dispatch({ type: "AUDIO_SET_NOISE_SUPPRESSION", enabled });
};
function NoiseSupressionPopout() {
const { isEnabled } = settings.use();
const { isLoading, isError } = loadedStore.use();
const isWorking = isEnabled && !isError;
return <div className={cl("popout")}>
<div className={cl("popout-heading")}>
<span>Noise Supression</span>
<div style={{ flex: 1 }} />
{isLoading && <Spinner type={SpinnerType.PulsingEllipsis} />}
<Switch checked={isWorking} onChange={setEnabled} disabled={isError} />
</div>
<div className={cl("popout-desc")}>
Enable AI noise suppression! Make some noise&mdash;like becoming an air conditioner, or a vending machine fan&mdash;while speaking. Your friends will hear nothing but your beautiful voice
</div>
</div>;
}
export default definePlugin({
name: "AI Noise Suppression",
description: "Uses an open-source AI model (RNNoise) to remove background noise from your microphone",
authors: [Devs.Vap],
settings,
enabledByDefault: true,
patches: [
{
// Pass microphone stream to RNNoise
find: "window.webkitAudioContext",
replacement: {
match: /(?<=\i\.acquire=function\((\i)\)\{return )navigator\.mediaDevices\.getUserMedia\(\1\)(?=\})/,
replace: m => `${m}.then(stream => $self.connectRnnoise(stream))`
},
},
{
// Noise suppression button in call modal
find: "renderNoiseCancellation()",
replacement: {
match: /(?<=(\i)\.jsxs?.{0,70}children:\[)(?=\i\?\i\.renderNoiseCancellation\(\))/,
replace: (_, react) => `${react}.jsx($self.NoiseSupressionButton, {}),`
},
},
{
// Give noise suppression component a "shouldShow" prop
find: "Masks.PANEL_BUTTON",
replacement: {
match: /(?<==(\i)\.tooltipForceOpen.{0,100})(?=tooltipClassName:)/,
replace: (_, props) => `shouldShow: ${props}.shouldShow,`
}
},
{
// Noise suppression option in voice settings
find: "Messages.USER_SETTINGS_NOISE_CANCELLATION_KRISP",
replacement: [{
match: /(?<=(\i)=\i\?\i\.KRISP:\i.{1,20}?;)/,
replace: (_, option) => `if ($self.isEnabled()) ${option} = ${JSON.stringify(RNNOISE_OPTION)};`,
}, {
match: /(?=\i&&(\i)\.push\(\{name:(?:\i\.){1,2}Messages.USER_SETTINGS_NOISE_CANCELLATION_KRISP)/,
replace: (_, options) => `${options}.push({ name: "AI (RNNoise)", value: "${RNNOISE_OPTION}" });`,
}, {
match: /(?<=onChange:function\((\i)\)\{)(?=(?:\i\.){1,2}setNoiseCancellation)/,
replace: (_, option) => `$self.setEnabled(${option}.value === ${JSON.stringify(RNNOISE_OPTION)});`,
}],
},
],
setEnabled,
isEnabled: () => settings.store.isEnabled,
async connectRnnoise(stream: MediaStream): Promise<MediaStream> {
if (!settings.store.isEnabled) return stream;
const audioCtx = new AudioContext();
await audioCtx.audioWorklet.addModule(rnnoiseWorkletSrc);
const rnnoiseWasm = await getRnnoiseWasm();
if (!rnnoiseWasm) {
logger.warn("Failed to load RNNoise, noise suppression won't work");
return stream;
}
const rnnoise = new RnnoiseWorkletNode(audioCtx, {
wasmBinary: rnnoiseWasm,
maxChannels: 1,
});
const source = audioCtx.createMediaStreamSource(stream);
source.connect(rnnoise);
const dest = audioCtx.createMediaStreamDestination();
rnnoise.connect(dest);
// Cleanup
const onEnded = () => {
rnnoise.disconnect();
source.disconnect();
audioCtx.close();
rnnoise.destroy();
};
stream.addEventListener("inactive", onEnded, { once: true });
return dest.stream;
},
NoiseSupressionButton(): ReactNode {
const { isEnabled } = settings.use();
const { isLoading, isError } = loadedStore.use();
return <Popout
key="rnnoise-popout"
align="center"
animation={Popout.Animation.TRANSLATE}
autoInvert={true}
nudgeAlignIntoViewport={true}
position="top"
renderPopout={() => <NoiseSupressionPopout />}
spacing={8}
>
{(props, { isShown }) => (
<PanelButton
{...props}
tooltipText="Noise Suppression powered by RNNoise"
tooltipClassName={cl("tooltip")}
shouldShow={!isShown}
icon={() => <div style={{
color: isError ? "var(--status-danger)" : "inherit",
opacity: isLoading ? 0.5 : 1,
}}>
<SupressionIcon enabled={isEnabled} />
</div>}
/>
)}
</Popout>;
},
});

View File

@ -0,0 +1,29 @@
.vc-rnnoise-popout {
background: var(--background-floating);
border-radius: 0.25em;
padding: 1em;
width: 16em;
}
.vc-rnnoise-popout-heading {
color: var(--text-normal);
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.1em;
margin-bottom: 1em;
gap: 0.5em;
}
.vc-rnnoise-popout-desc {
color: var(--text-muted);
font-size: 0.9em;
display: flex;
align-items: center;
line-height: 1.5;
}
.vc-rnnoise-tooltip {
text-align: center;
}

View File

@ -0,0 +1,35 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "SecretRingToneEnabler",
description: "Always play the secret version of the discord ringtone",
authors: [Devs.AndrewDLO],
patches: [
{
find: "84a1b4e11d634dbfa1e5dd97a96de3ad",
replacement: {
match: "84a1b4e11d634dbfa1e5dd97a96de3ad.mp3",
replace: "b9411af07f154a6fef543e7e442e4da9.mp3",
},
},
],
});

View File

@ -146,7 +146,7 @@ export default definePlugin({
<div style={{ display: "flex" }}>
<Button
aria-haspopup="dialog"
aria-label=""
aria-label="Insert Timestamp"
size=""
look={ButtonLooks.BLANK}
onMouseEnter={onMouseEnter}

View File

@ -0,0 +1,247 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { classNameFactory } from "@api/Styles";
import { openImageModal, openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react";
import { findByCode, findByPropsLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
import { Guild, User } from "discord-types/general";
const IconUtils = findByPropsLazy("getGuildBannerURL");
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
const UserRow = LazyComponent(() => findByCode(".listDiscriminator"));
const cl = classNameFactory("vc-gp-");
export function openGuildProfileModal(guild: Guild) {
openModal(props =>
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<GuildProfileModal guild={guild} />
</ModalRoot>
);
}
const enum Tabs {
ServerInfo,
Friends,
BlockedUsers
}
interface GuildProps {
guild: Guild;
}
interface RelationshipProps extends GuildProps {
setCount(count: number): void;
}
const fetched = {
friends: false,
blocked: false
};
function renderTimestamp(timestamp: number) {
return (
<Timestamp timestamp={moment(timestamp)} />
);
}
function GuildProfileModal({ guild }: GuildProps) {
const [friendCount, setFriendCount] = useState<number>();
const [blockedCount, setBlockedCount] = useState<number>();
useEffect(() => {
fetched.friends = false;
fetched.blocked = false;
}, []);
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({
id: guild.id,
banner: guild.banner
}, true).replace(/\?size=\d+$/, "?size=1024");
const iconUrl = guild.icon && IconUtils.getGuildIconURL({
id: guild.id,
icon: guild.icon,
canAnimate: true,
size: 512
});
return (
<div className={cl("root")}>
{bannerUrl && currentTab === Tabs.ServerInfo && (
<img
className={cl("banner")}
src={bannerUrl}
alt=""
onClick={() => openImageModal(bannerUrl)}
/>
)}
<div className={cl("header")}>
{guild.icon
? <img
src={iconUrl}
alt=""
onClick={() => openImageModal(iconUrl)}
/>
: <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{guild.acronym}</div>
}
<div className={cl("name-and-description")}>
<Forms.FormTitle tag="h5" className={cl("name")}>{guild.name}</Forms.FormTitle>
{guild.description && <Forms.FormText>{guild.description}</Forms.FormText>}
</div>
</div>
<TabBar
type="top"
look="brand"
className={cl("tab-bar")}
selectedItem={currentTab}
onItemSelect={setCurrentTab}
>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.ServerInfo })}
id={Tabs.ServerInfo}
>
Server Info
</TabBar.Item>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.Friends })}
id={Tabs.Friends}
>
Friends{friendCount !== undefined ? ` (${friendCount})` : ""}
</TabBar.Item>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.BlockedUsers })}
id={Tabs.BlockedUsers}
>
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
</TabBar.Item>
</TabBar>
<div className={cl("tab-content")}>
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
</div>
</div>
);
}
function Owner(guildId: string, owner: User) {
const guildAvatar = GuildMemberStore.getMember(guildId, owner.id)?.avatar;
const ownerAvatarUrl =
guildAvatar
? IconUtils.getGuildMemberAvatarURLSimple({
userId: owner!.id,
avatar: guildAvatar,
guildId,
canAnimate: true
}, true)
: IconUtils.getUserAvatarURL(owner, true);
return (
<div className={cl("owner")}>
<img src={ownerAvatarUrl} alt="" onClick={() => openImageModal(ownerAvatarUrl)} />
{Parser.parse(`<@${owner.id}>`)}
</div>
);
}
function ServerInfoTab({ guild }: GuildProps) {
const [owner] = useAwaiter(() => UserUtils.fetchUser(guild.ownerId), {
deps: [guild.ownerId],
fallbackValue: null
});
const Fields = {
"Server Owner": owner ? Owner(guild.id, owner) : "Loading...",
"Created At": renderTimestamp(SnowflakeUtils.extractTimestamp(guild.id)),
"Joined At": renderTimestamp(guild.joinedAt.getTime()),
"Vanity Link": guild.vanityURLCode ? (<a>{`discord.gg/${guild.vanityURLCode}`}</a>) : "-", // Making the anchor href valid would cause Discord to reload
"Preferred Locale": guild.preferredLocale || "-",
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
"Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`,
"Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category
"Roles": Object.keys(guild.roles).length - 1, // - @everyone
};
return (
<div className={cl("info")}>
{Object.entries(Fields).map(([name, node]) =>
<div className={cl("server-info-pair")} key={name}>
<Forms.FormTitle tag="h5">{name}</Forms.FormTitle>
{typeof node === "string" ? <span>{node}</span> : node}
</div>
)}
</div>
);
}
function FriendsTab({ guild, setCount }: RelationshipProps) {
return UserList("friends", guild, RelationshipStore.getFriendIDs(), setCount);
}
function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
const blockedIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isBlocked(id));
return UserList("blocked", guild, blockedIds, setCount);
}
function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setCount: (count: number) => void) {
const missing = [] as string[];
const members = [] as string[];
for (const id of ids) {
if (GuildMemberStore.isMember(guild.id, id))
members.push(id);
else
missing.push(id);
}
// Used for side effects (rerender on member request success)
useStateFromStores(
[GuildMemberStore],
() => GuildMemberStore.getMemberIds(guild.id),
null,
(old, curr) => old.length === curr.length
);
useEffect(() => {
if (!fetched[type] && missing.length) {
fetched[type] = true;
FluxDispatcher.dispatch({
type: "GUILD_MEMBERS_REQUEST",
guildIds: [guild.id],
userIds: missing
});
}
}, []);
useEffect(() => setCount(members.length), [members.length]);
return (
<ScrollerThin fade className={cl("scroller")}>
{members.map(id =>
<UserRow
user={UserStore.getUser(id)}
status={PresenceStore.getStatus(id) || "offline"}
onSelect={() => openUserProfile(id)}
onContextMenu={() => { }}
/>
)}
</ScrollerThin>
);
}

View File

@ -0,0 +1,7 @@
# ServerProfile
Allows you to view info about servers and see friends and blocked users
![image](https://github.com/Vendicated/Vencord/assets/45497981/a49783b5-e8fc-41d8-968f-58600e9f6580)
![image](https://github.com/Vendicated/Vencord/assets/45497981/5efc158a-e671-4196-a15a-77edf79a2630)
![image](https://github.com/Vendicated/Vencord/assets/45497981/f43be943-6dc4-4232-9709-fbeb382d8e54)

View File

@ -0,0 +1,40 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Menu } from "@webpack/common";
import { Guild } from "discord-types/general";
import { openGuildProfileModal } from "./GuildProfileModal";
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => () => {
const group = findGroupChildrenByChildId("privacy", children);
group?.push(
<Menu.MenuItem
id="vc-server-profile"
label="Server Profile"
action={() => openGuildProfileModal(guild)}
/>
);
};
export default definePlugin({
name: "ServerProfile",
description: "Allows you to view info about a server by right clicking it in the server list",
authors: [Devs.Ven, Devs.Nuckyz],
tags: ["guild", "info"],
start() {
addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
},
stop() {
removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
}
});

View File

@ -0,0 +1,97 @@
.vc-gp-root {
height: 100%;
user-select: text;
}
.vc-gp-banner {
width: 100%;
cursor: pointer;
}
.vc-gp-header {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5em;
margin: 0.5em;
}
.vc-gp-header img {
width: 48px;
height: 48px;
cursor: pointer;
}
.vc-gp-name-and-description {
display: flex;
flex-direction: column;
gap: 0.2em;
}
.vc-gp-name {
margin: 0;
}
.vc-gp-tab-bar {
border-bottom: 2px solid var(--background-modifier-accent);
margin: 20px 12px 0;
display: flex;
gap: 40px;
align-items: stretch;
flex-direction: row;
}
.vc-gp-tab {
border-bottom: 2px solid transparent;
color: var(--interactive-normal);
cursor: pointer;
height: 39px;
line-height: 14px;
}
.vc-gp-tab-content {
margin: 1em;
}
.vc-gp-tab:where(.vc-gp-selected, :hover, :focus) {
border-bottom-color: var(--interactive-active);
}
.vc-gp-info {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1em;
}
.vc-gp-server-info-pair {
color: var(--text-normal);
}
.vc-gp-server-info-pair [class^="timestamp"] {
margin-left: 0;
}
.vc-gp-owner {
display: flex;
align-items: center;
gap: 0.2em;
}
.vc-gp-owner img {
height: 20px;
border-radius: 50%;
cursor: pointer;
}
.vc-gp-scroller {
width: 100%;
max-height: 500px;
}
.vc-gp-scroller [class^="listRow"] {
margin: 1px 0;
}
.vc-gp-scroller [class^="listRow"]:hover {
background-color: var(--background-modifier-hover);
}

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { shikiOnigasmSrc, shikiWorkerSrc } from "@utils/dependencies";
import { WorkerClient } from "@vap/core/ipc";
import type { IShikiTheme, IThemedToken } from "@vap/shiki";

View File

@ -1,4 +1,4 @@
/* eslint-disable header/header */
/* eslint-disable simple-header/header */
import React from "react";
const handleClick = async () =>

View File

@ -21,13 +21,14 @@ import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "discord-types/general";
import { Message, User } from "discord-types/general";
interface UsernameProps {
author: { nick: string; };
message: Message;
withMentionPrefix?: boolean;
isRepliedMessage: boolean;
userOverride?: User;
}
const settings = definePluginSettings({
@ -40,6 +41,11 @@ const settings = definePluginSettings({
{ label: "Username only", value: "user" },
],
},
displayNames: {
type: OptionType.BOOLEAN,
description: "Use display names in place of usernames",
default: false
},
inReplies: {
type: OptionType.BOOLEAN,
default: false,
@ -50,7 +56,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ShowMeYourName",
description: "Display usernames next to nicks, or no nicks at all",
authors: [Devs.dzshn],
authors: [Devs.dzshn, Devs.TheKodeToad],
patches: [
{
find: ".withMentionPrefix",
@ -62,10 +68,13 @@ export default definePlugin({
],
settings,
renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix }: UsernameProps) => {
if (message.interaction) return author?.nick;
renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => {
try {
const { username } = message.author;
const user = userOverride ?? message.author;
let { username } = user;
if (settings.store.displayNames)
username = (user as any).globalName || username;
const { nick } = author;
const prefix = withMentionPrefix ? "@" : "";
if (username === nick || isRepliedMessage && !settings.store.inReplies)

View File

@ -0,0 +1,35 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "ShowTimeouts",
description: "Display member timeout icons in chat regardless of permissions.",
authors: [Devs.Dolfies],
patches: [
{
find: "showCommunicationDisabledStyles",
replacement: {
match: /&&\i\.\i\.canManageUser\(\i\.\i\.MODERATE_MEMBERS,\i\.author,\i\)/,
replace: "",
},
},
],
});

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/react";
import definePlugin from "@utils/types";

View File

@ -53,7 +53,7 @@ export default definePlugin({
const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VENCORD_DESKTOP) return `Vencord Desktop v${VencordDesktopNative.app.getVersion()}`;
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
// @ts-expect-error

View File

@ -0,0 +1,20 @@
# ThemeAttributes
This plugin adds data attributes to various elements inside Discord
This allows themes to more easily theme those elements or even do things that otherwise wouldn't be possible
## Available Attributes
### All Tab Bars (User Settings, Server Settings, etc)
`data-tab-id` contains the id of that tab
![image](https://github.com/Vendicated/Vencord/assets/45497981/1263b782-f673-4f09-820c-4cc366d062ad)
### Chat Messages
- `data-author-id` contains the id of the author
- `data-is-self` is a boolean indicating whether this is the current user's message
![image](https://github.com/Vendicated/Vencord/assets/45497981/34bd5053-3381-402f-82b2-9c812cc7e122)

View File

@ -0,0 +1,45 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
export default definePlugin({
name: "ThemeAttributes",
description: "Adds data attributes to various elements for theming purposes",
authors: [Devs.Ven],
patches: [
// Add data-tab-id to all tab bar items
// This for examples applies to the User and Server settings sidebars
{
find: ".tabBarRef",
replacement: {
match: /style:this\.getStyle\(\),role:"tab"/,
replace: "$&,'data-tab-id':this.props.id"
}
},
// Add data-author-id and data-is-self to all messages
{
find: ".messageListItem",
replacement: {
match: /\.messageListItem(?=,"aria)/,
replace: "$&,...$self.getMessageProps(arguments[0])"
}
}
],
getMessageProps(props: { message: Message; }) {
const authorId = props.message?.author?.id;
return {
"data-author-id": authorId,
"data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id
};
}
});

View File

@ -51,7 +51,7 @@ export function TranslateChatBarIcon({ slateProps }: { slateProps: { type: { ana
<div style={{ display: "flex" }}>
<Button
aria-haspopup="dialog"
aria-label=""
aria-label="Open Translate Modal"
size=""
look={ButtonLooks.BLANK}
onMouseEnter={onMouseEnter}

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
To generate:
- Visit https://translate.google.com/?sl=auto&tl=en&op=translate

View File

@ -19,6 +19,7 @@
import "./index.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/react";
@ -66,6 +67,15 @@ function VencordPopout(onClose: () => void) {
label="Open Notification Log"
action={openNotificationLogModal}
/>
<Menu.MenuCheckboxItem
id="vc-toolbox-quickcss-toggle"
checked={Settings.useQuickCss}
label={"Enable QuickCSS"}
action={() => {
Settings.useQuickCss = !Settings.useQuickCss;
onClose();
}}
/>
<Menu.MenuItem
id="vc-toolbox-quickcss"
label="Open QuickCSS"

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
@ -25,9 +26,11 @@ import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Forms, Menu, Parser, Text } from "@webpack/common";
import { Message } from "discord-types/general";
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
const CopyIcon = () => {
return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="22" height="22">
@ -60,17 +63,13 @@ function cleanMessage(msg: Message) {
function CodeBlock(props: { content: string, lang: string; }) {
return (
// make text selectable
<div style={{ userSelect: "text" }}>
<div className={CodeContainerClasses.markup}>
{Parser.defaultRules.codeBlock.react(props, null, {})}
</div>
);
}
function openViewRawModal(msg: Message) {
msg = cleanMessage(msg);
const msgJson = JSON.stringify(msg, null, 4);
function openViewRawModal(json: string, type: string, msgContent?: string) {
const key = openModal(props => (
<ErrorBoundary>
<ModalRoot {...props} size={ModalSize.LARGE}>
@ -80,26 +79,28 @@ function openViewRawModal(msg: Message) {
</ModalHeader>
<ModalContent>
<div style={{ padding: "16px 0" }}>
{!!msg.content && (
{!!msgContent && (
<>
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
<CodeBlock content={msg.content} lang="" />
<CodeBlock content={msgContent} lang="" />
<Forms.FormDivider className={Margins.bottom20} />
</>
)}
<Forms.FormTitle tag="h5">Message Data</Forms.FormTitle>
<CodeBlock content={msgJson} lang="json" />
<Forms.FormTitle tag="h5">{type} Data</Forms.FormTitle>
<CodeBlock content={json} lang="json" />
</div>
</ModalContent >
<ModalFooter>
<Flex cellSpacing={10}>
<Button onClick={() => copyWithToast(msgJson, "Message data copied to clipboard!")}>
Copy Message JSON
</Button>
<Button onClick={() => copyWithToast(msg.content, "Content copied to clipboard!")}>
Copy Raw Content
<Button onClick={() => copyWithToast(json, `${type} data copied to clipboard!`)}>
Copy {type} JSON
</Button>
{!!msgContent && (
<Button onClick={() => copyWithToast(msgContent, "Content copied to clipboard!")}>
Copy Raw Content
</Button>
)}
</Flex>
</ModalFooter>
</ModalRoot >
@ -107,6 +108,13 @@ function openViewRawModal(msg: Message) {
));
}
function openViewRawModalMessage(msg: Message) {
msg = cleanMessage(msg);
const msgJson = JSON.stringify(msg, null, 4);
return openViewRawModal(msgJson, "Message", msg.content);
}
const settings = definePluginSettings({
clickMethod: {
description: "Change the button to view the raw content/data of any message.",
@ -118,10 +126,33 @@ const settings = definePluginSettings({
}
});
function MakeContextCallback(name: string) {
const callback: NavContextMenuPatchCallback = (children, props) => () => {
const lastChild = children.at(-1);
if (lastChild?.key === "developer-actions") {
const p = lastChild.props;
if (!Array.isArray(p.children))
p.children = [p.children];
({ children } = p);
}
children.splice(-1, 0,
<Menu.MenuItem
id={`vc-view-${name.toLowerCase()}-raw`}
label="View Raw"
action={() => openViewRawModal(JSON.stringify(props[name.toLowerCase()], null, 4), name)}
icon={CopyIcon}
/>
);
};
return callback;
}
export default definePlugin({
name: "ViewRaw",
description: "Copy and view the raw content/data of any message.",
authors: [Devs.KingFish, Devs.Ven, Devs.rad],
description: "Copy and view the raw content/data of any message, channel or guild",
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
dependencies: ["MessagePopoverAPI"],
settings,
@ -131,7 +162,7 @@ export default definePlugin({
if (settings.store.clickMethod === "Right") {
copyWithToast(msg.content);
} else {
openViewRawModal(msg);
openViewRawModalMessage(msg);
}
};
@ -143,7 +174,7 @@ export default definePlugin({
} else {
e.preventDefault();
e.stopPropagation();
openViewRawModal(msg);
openViewRawModalMessage(msg);
}
};
@ -160,9 +191,16 @@ export default definePlugin({
onContextMenu: handleContextMenu
};
});
addContextMenuPatch("guild-context", MakeContextCallback("Guild"));
addContextMenuPatch("channel-context", MakeContextCallback("Channel"));
addContextMenuPatch("user-context", MakeContextCallback("User"));
},
stop() {
removeButton("CopyRawMessage");
removeContextMenuPatch("guild-context", MakeContextCallback("Guild"));
removeContextMenuPatch("channel-context", MakeContextCallback("Channel"));
removeContextMenuPatch("user-context", MakeContextCallback("User"));
}
});

View File

@ -39,8 +39,8 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN,
description: "Add back the Discord context menus for images, links and the chat input bar",
// Web slate menu has proper spellcheck suggestions and image context menu is also pretty good,
// so disable this by default. Vencord Desktop just doesn't, so enable by default
default: IS_VENCORD_DESKTOP,
// so disable this by default. Vesktop just doesn't, so enable by default
default: IS_VESKTOP,
restartNeeded: true
}
});
@ -50,7 +50,7 @@ export default definePlugin({
description: "Re-adds context menus missing in the web version of Discord: Links & Images (Copy/Open Link/Image), Text Area (Copy, Cut, Paste, SpellCheck)",
authors: [Devs.Ven],
enabledByDefault: true,
required: IS_VENCORD_DESKTOP,
required: IS_VESKTOP,
settings,
@ -86,7 +86,7 @@ export default definePlugin({
},
// Fix silly Discord calling the non web support copy
{
match: /\w\.default\.copy/,
match: /\i\.\i\.copy/,
replace: "Vencord.Webpack.Common.Clipboard.copy"
}
]
@ -102,7 +102,7 @@ export default definePlugin({
replace: "if(null=="
},
{
match: /return\s*?\[\i\.default\.canCopyImage\(\)/,
match: /return\s*?\[\i\.\i\.canCopyImage\(\)/,
replace: "return [true"
},
{
@ -158,7 +158,7 @@ export default definePlugin({
},
{
// Change calls to DiscordNative.clipboard to us instead
match: /\b\i\.default\.(copy|cut|paste)/g,
match: /\b\i\.\i\.(copy|cut|paste)/g,
replace: "$self.$1"
}
]

View File

@ -28,7 +28,7 @@ contextBridge.exposeInMainWorld("VencordNative", VencordNative);
// Discord
if (location.protocol !== "data:") {
// #region cssInsert
const rendererCss = join(__dirname, "renderer.css");
const rendererCss = join(__dirname, IS_VESKTOP ? "vencordDesktopRenderer.css" : "renderer.css");
const style = document.createElement("style");
style.id = "vencord-css-core";
@ -51,9 +51,9 @@ if (location.protocol !== "data:") {
}
// #endregion
if (process.env.DISCORD_PRELOAD) {
if (IS_DISCORD_DESKTOP) {
webFrame.executeJavaScript(readFileSync(join(__dirname, "renderer.js"), "utf-8"));
require(process.env.DISCORD_PRELOAD);
require(process.env.DISCORD_PRELOAD!);
}
} // Monaco popout
else {

View File

@ -331,10 +331,38 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "rad",
id: 610945092504780823n
},
AndrewDLO: {
name: "Andrew-DLO",
id: 434135504792059917n
},
HypedDomi: {
name: "HypedDomi",
id: 354191516979429376n
}
},
Rini: {
name: "Rini",
id: 1079479184478441643n
},
castdrian: {
name: "castdrian",
id: 224617799434108928n
},
Arrow: {
name: "arrow",
id: 958158495302176778n
},
bb010g: {
name: "bb010g",
id: 72791153467990016n,
},
Dolfies: {
name: "Dolfies",
id: 852892297661906993n,
},
RuukuLada: {
name: "RuukuLada",
id: 119705748346241027n,
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly

View File

@ -79,5 +79,9 @@ const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist";
export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`;
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm";
export const rnnoiseDist = "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist";
export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`;
export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`;
// @ts-expect-error SHUT UP
export const getStegCloak = makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js"));

View File

@ -74,6 +74,16 @@ export function isObject(obj: unknown): obj is object {
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
}
/**
* Check if an object is empty or in other words has no own properties
*/
export function isObjectEmpty(obj: object) {
for (const k in obj)
if (Object.hasOwn(obj, k)) return false;
return true;
}
/**
* Returns null if value is not a URL, otherwise return URL object.
* Avoids having to wrap url checks in a try/catch

View File

@ -20,12 +20,12 @@ export function relaunch() {
if (IS_DISCORD_DESKTOP)
window.DiscordNative.app.relaunch();
else
window.VencordDesktopNative.app.relaunch();
window.VesktopNative.app.relaunch();
}
export function showItemInFolder(path: string) {
if (IS_DISCORD_DESKTOP)
window.DiscordNative.fileManager.showItemInFolder(path);
else
window.VencordDesktopNative.fileManager.showItemInFolder(path);
window.VesktopNative.fileManager.showItemInFolder(path);
}

View File

@ -63,7 +63,7 @@ export async function update() {
export const getRepo = () => Unwrap(VencordNative.updater.getRepo());
export async function maybePromptToUpdate(confirmMessage: string, checkForDev = false) {
if (IS_WEB) return;
if (IS_WEB || IS_UPDATER_DISABLED) return;
if (checkForDev && IS_DEV) return;
try {

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// eslint-disable-next-line path-alias/no-relative
import { filters, mapMangledModuleLazy, waitFor } from "../webpack";
import type * as t from "./types/menu";

View File

@ -27,6 +27,13 @@ export const Flux: t.Flux = findByPropsLazy("connectStores");
export type GenericStore = t.FluxStore & Record<string, any>;
export enum DraftType {
ChannelMessage = 0,
ThreadSettings = 1,
FirstThreadMessage = 2,
ApplicationLauncherCommand = 3
}
export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & {
getMessages(chanId: string): any;
};
@ -52,6 +59,7 @@ export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & {
export let EmojiStore: t.EmojiStore;
export let WindowStore: t.WindowStore;
export let DraftStore: t.DraftStore;
export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', {
openUntrustedLink: filters.byCode(".apply(this,arguments)")
@ -76,6 +84,7 @@ export const useStateFromStores: <T>(
) => T
= findByCodeLazy("useStateFromStores");
waitForStore("DraftStore", s => DraftStore = s);
waitForStore("UserStore", s => UserStore = s);
waitForStore("ChannelStore", m => ChannelStore = m);
waitForStore("SelectedChannelStore", m => SelectedChannelStore = m);

View File

@ -17,7 +17,7 @@
*/
import type { Moment } from "moment";
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>;
@ -338,16 +338,16 @@ export type Popout = ComponentType<{
thing: {
"aria-controls": string;
"aria-expanded": boolean;
onClick(event: MouseEvent): void;
onKeyDown(event: KeyboardEvent): void;
onMouseDown(event: MouseEvent): void;
onClick(event: MouseEvent<HTMLElement>): void;
onKeyDown(event: KeyboardEvent<HTMLElement>): void;
onMouseDown(event: MouseEvent<HTMLElement>): void;
},
data: {
isShown: boolean;
position: string;
}
): ReactNode;
shouldShow: boolean;
shouldShow?: boolean;
renderPopout(args: {
closePopout(): void;
isPositioned: boolean;

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { DraftType } from "@webpack/common";
import { Channel } from "discord-types/general";
import { FluxDispatcher, FluxEvents } from "./utils";
@ -148,3 +149,26 @@ export class EmojiStore extends FluxStore {
get favoriteEmojisWithoutFetchingLatest(): Emoji[];
};
}
export interface DraftObject {
channelId: string;
timestamp: number;
draft: string;
}
interface DraftState {
[userId: string]: {
[channelId: string]: {
[key in DraftType]?: Omit<DraftObject, "channelId">;
} | undefined;
} | undefined;
}
export class DraftStore extends FluxStore {
getDraft(channelId: string, type: DraftType): string;
getRecentlyEditedDrafts(type: DraftType): DraftObject[];
getState(): DraftState;
getThreadDraftWithParentMessageId?(arg: any): any;
getThreadSettings(channelId: string): any | null;
}

View File

@ -93,7 +93,7 @@ export const UserUtils = {
};
export const Clipboard = mapMangledModuleLazy('document.queryCommandEnabled("copy")||document.queryCommandSupported("copy")', {
copy: filters.byCode(".default.copy("),
copy: filters.byCode(".copy("),
SUPPORTS_COPY: x => typeof x === "boolean",
});