Compare commits

...

128 commits

Author SHA1 Message Date
a6a5fff249
owo 2023-09-29 19:46:45 +09:00
Vendicated
62277770a8
bump to v1.5.3 2023-09-28 02:42:55 +02:00
V
4facc3cad7
webContextMenus: support pasting images 2023-09-27 04:13:40 +02:00
TheKodeToad
837d1fc083
web: fix themes tab (#1756) 2023-09-26 21:47:12 +02:00
V
608a67c9ae
fix quick/searchReply & MessageClickactions not working in dms 2023-09-26 03:54:59 +02:00
AutumnVN
f32d25b641
viewRaw, viewIcons, permissionsViewer: fix some edge cases (#1745)
Co-authored-by: V <vendicated@riseup.net>
2023-09-26 01:48:09 +02:00
V
4c7a2ba340
Merge branch 'main' into dev 2023-09-26 01:45:38 +02:00
V
da7f0cfff6
PluginModal: Fix cancel button being white on white in light theme 2023-09-26 01:38:46 +02:00
V
ae6584da7c
add os-accent-color variable (following BetterDiscord) 2023-09-25 18:53:10 +02:00
V
c6b1b9463c
improve webpack find error messages 2023-09-25 18:27:18 +02:00
V
376aaf39ce
fix SearchReply filter 2023-09-25 18:07:43 +02:00
V
5454a41243
update issue templates 2023-09-25 04:27:16 +02:00
Hugo C
044f64e446
Improve permission checks on several plugins (#1746)
Co-authored-by: V <vendicated@riseup.net>
2023-09-24 16:42:53 +02:00
V
0b7fca864a
plugin READMEs: migrate from imgur to github assets 2023-09-24 16:17:33 +02:00
V
30ac256070
migrate all plugins to folders 2023-09-24 16:02:18 +02:00
Sam
d0e2a32471
VcNarrator: Ignore multiple underscores (#1748)
Co-authored-by: V <vendicated@riseup.net>
2023-09-24 15:55:23 +02:00
V
ec026ca34c
Update bug_report.yml 2023-09-24 15:45:22 +02:00
V
4baaa9bd91
Delete .github/ISSUE_TEMPLATE/feature_request.yml 2023-09-24 15:44:02 +02:00
V
d9933c5793
settingsSync: correctly use platform agnostic relaunch 2023-09-24 01:16:50 +02:00
V
2735037a67
[skip ci] bump to v1.5.2 2023-09-24 01:12:00 +02:00
Dziurwa
83bfe28fa4
FriendInvites: Add picking uses (1 or 5) (#1727)
Co-authored-by: V <vendicated@riseup.net>
2023-09-23 03:50:14 +02:00
V
08c5d23636
add max attempts to lazys 2023-09-23 03:25:19 +02:00
V
ac0f834155
Fix Plugin Settings on canary 2023-09-23 03:15:07 +02:00
V
fa16e1b56f
fix updater 2023-09-22 15:58:29 +02:00
V
cfca393f2b
ci(generatePluginList): add filePath 2023-09-22 04:48:54 +02:00
Nuckyz
eacc673bcc
Fix BetterRoleDot making reporter angry 2023-09-21 23:28:40 -03:00
Nuckyz
dba6c4cea6
Fix broken FakeNitro patch (again) 2023-09-21 22:41:25 -03:00
V
6b10947d06
Bump to v1.5.1 2023-09-21 18:58:43 +02:00
V
97b6699afe
Fuck you Mozilla 2023-09-21 18:56:58 +02:00
V
7e91edc757
browser: unhardcode rnnoise 2023-09-21 18:19:59 +02:00
V
a82544e93e
Delete FixInbox: Discord fixed this issue themselves now 2023-09-21 17:57:14 +02:00
AutumnVN
d8c8b74ed7
BetterRoleDot: Setting for copying role color in profile popout (#1698) 2023-09-21 17:40:37 +02:00
Daniel Foster
fadd1598f5
Settings: use nearest-neighbour for shiggy (#1739)
Co-authored-by: V <vendicated@riseup.net>
2023-09-21 17:18:41 +02:00
Manti
e5c0898dd6
[ReviewDB] add emojis, discord markdown & notifications (#1718)
Co-authored-by: V <vendicated@riseup.net>
2023-09-21 17:16:15 +02:00
Nuckyz
9550b74b2a
Fix broken FakeNitro patch 2023-09-20 02:44:31 -03:00
V
6cfb67a52e
fix ci 2023-09-19 04:15:34 +02:00
V
12fe367385
bump to v1.5.0 2023-09-19 04:12:54 +02:00
AutumnVN
fc0417eb80
moreUserTags: fix user tag in member list (#1730) 2023-09-19 04:11:30 +02:00
AutumnVN
1f87d14ab2
textReplace: pad space only in string rules (#1738) 2023-09-19 04:11:30 +02:00
XAlboX
7d8f3508a8
ClearURLs: Add si parameter on youtube.com (#1733)
Co-authored-by: V <vendicated@riseup.net>
2023-09-19 04:11:30 +02:00
V
41f5d71e38
Bundle dependencies with extensions for webstore rule compliance (#1740) 2023-09-19 04:11:27 +02:00
AutumnVN
efb88a4df8
favGifSearch: fix search bar (#1732) 2023-09-19 04:10:15 +02:00
Syncx
a73d09a2f0
PreviewMessage: Add attachments (& misc changes) (#1715) 2023-09-12 23:14:17 +02:00
AutumnVN
cf7c4d63b6
pictureInPicture: don't show PiP button on normal file (#1725) 2023-09-12 23:11:53 +02:00
Archer
a95311ef2c
lastfm: Add setting for artist name and song title only (#1726)
Co-authored-by: V <vendicated@riseup.net>
2023-09-12 23:11:25 +02:00
lovenginx
dd23f9802c
InvisibleChat: fixup decryption modal (#1720) 2023-09-12 23:04:50 +02:00
Nuckyz
f23ddf4cae
oopsies 2023-09-12 05:10:44 -03:00
Nuckyz
4222c7fd9f
Fix broken FakeNitro patch on canary 2023-09-12 05:01:18 -03:00
Nuckyz
09f65b401e
Fix broken MessageLogger patches 2023-09-12 04:28:55 -03:00
V
7364776715
Dearrow: Fix button part 2 2023-09-09 19:51:30 +02:00
V
3a5b70d410
Dearrow: Fix button 2023-09-09 19:49:11 +02:00
V
e08d49edac
New Plugin: Dearrow (#1723) 2023-09-09 19:17:50 +02:00
Mushrrom
17abbd3e3e
LastFM: Add setting for using name + artist as activity name (#1713)
Co-authored-by: V <vendicated@riseup.net>
2023-09-09 04:22:41 +02:00
V
25a1d934c6
bump to v1.4.7 2023-09-09 00:22:24 +02:00
Luna
620319a4bc blurnsfw: fix crash (#31)
Reviewed-on: https://codeberg.org/Ven/cord/pulls/31
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-08 21:55:27 +00:00
Hugo C
4c9996d620
feat(plugin): PictureInPicture (#1697)
Co-authored-by: V <vendicated@riseup.net>
2023-09-08 03:57:44 +02:00
V
885c75fdaa
add custom plugin author popouts (#1712) 2023-09-08 03:42:20 +02:00
Lewis Crichton
f2a22c5e57
feat: crash info in /vencord-debug (#1714)
Co-authored-by: V <vendicated@riseup.net>
2023-09-08 03:41:30 +02:00
V
5e3a485edc
ci: generate plugin readme map 2023-09-08 02:26:47 +02:00
Luna
452bf72e56 rnnoise: fix error on webcam (#29)
errored because there was no audio stream, luckily the patched function had a boolean indicating if it was audio or not so just ignore it if it isnt

Reviewed-on: https://codeberg.org/Ven/cord/pulls/29
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-06 17:10:33 +00:00
Luna
afa47addd7 usrbg: export the data to allow other plugins to access (#30)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Ven <ven@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/Ven/cord/pulls/30
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-06 17:10:04 +00:00
V
8ecee3d09f
fix ViewRaw & VoiceMessages context menu style 2023-09-06 19:08:48 +02:00
luk1337
d2aac5edc5
ClearURLs: Add share_id parameter on reddit.com (#1706) 2023-09-06 18:53:28 +02:00
lovenginx
fc45510053
LoadingQuotes: add a new quote (#1710) 2023-09-06 18:52:14 +02:00
AutumnVN
77bfaf38a5
HideAttachments: fix broken css (#1707) 2023-09-06 18:51:48 +02:00
AutumnVN
a4cadc03d8
viewRaw: dont add context menu to guild folders (#1708) 2023-09-06 18:51:18 +02:00
AutumnVN
315fcf1972
messageLogger: make ignoreChannels also ignore threads (#1709) 2023-09-06 18:50:20 +02:00
Nuckyz
f1b3b3c0a3
Uncanonicalize reported patches 2023-09-06 18:47:54 +02:00
V
4c805d08be
[skip ci] PatchHelper: Fix copy button style 2023-09-05 21:37:39 +02:00
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
258 changed files with 5389 additions and 1037 deletions

View file

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

10
.github/ISSUE_TEMPLATE/blank.yml vendored Normal file
View file

@ -0,0 +1,10 @@
name: Blank Issue
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
body:
- type: textarea
id: content
attributes:
label: Content
validations:
required: true

View file

@ -1,5 +1,5 @@
name: Bug/Crash Report name: Bug/Crash Report
description: Create a bug or crash report for Vencord description: Create a bug or crash report for Vencord. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
labels: [bug] labels: [bug]
title: "[Bug] <title>" title: "[Bug] <title>"

View file

@ -1,4 +1,4 @@
blank_issues_enabled: true blank_issues_enabled: false
contact_links: contact_links:
- name: Vencord Support Server - name: Vencord Support Server
url: https://discord.gg/D9uwnFnqmd url: https://discord.gg/D9uwnFnqmd

View file

@ -1,32 +0,0 @@
name: Feature Request
description: Create a feature request for Vencord. To request new plugins, please use the Discussions tab
labels: [enhancement]
title: "[Feature Request] <title>"
body:
- type: input
id: discord
attributes:
label: Discord Account
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
placeholder: username#0000
validations:
required: false
- type: textarea
id: feature-basic-description
attributes:
label: What is it that you'd like to see?
description: Describe the feature you want added as detailed as possible
placeholder: I think ... would be a cool feature to add. This would be awesome, thanks!
validations:
required: true
- type: checkboxes
id: agreement-check
attributes:
label: Request Agreement
description: DO NOT USE THIS TEMPLATE FOR PLUGIN REQUESTS!!! For plugin requests, **use discussions**
options:
- label: This is not a plugin request
required: true

View file

@ -38,11 +38,11 @@ jobs:
run: pnpm build --standalone run: pnpm build --standalone
- name: Generate plugin list - name: Generate plugin list
run: pnpm generatePluginJson dist/plugins.json run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
- name: Clean up obsolete files - name: Clean up obsolete files
run: | run: |
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
- name: Get some values needed for the release - name: Get some values needed for the release
id: release_values id: release_values

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();`, - Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
`var .{1,2}=([^;]+);` `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 - 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" #### "replace"

View file

@ -4,8 +4,7 @@
The cutest Discord client mod The cutest Discord client mod
![](https://user-images.githubusercontent.com/45497981/235015332-0453d3eb-1da6-4601-963e-ef5e454123a1.png) ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334)
*A screenshot of Vencord featuring the [ClearVision-v6 theme](https://github.com/ClearVision/ClearVision-v6) (Vencord does not come with it pre-installed, it is only an example)*
## Features ## Features

View file

@ -19,9 +19,11 @@
/// <reference path="../src/modules.d.ts" /> /// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" /> /// <reference path="../src/globals.d.ts" />
import monacoHtml from "~fileContent/../src/components/monacoWin.html"; import monacoHtmlLocal from "~fileContent/monacoWin.html";
import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html";
import * as DataStore from "../src/api/DataStore"; import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils"; import { debounce } from "../src/utils";
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
import { getTheme, Theme } from "../src/utils/discord"; import { getTheme, Theme } from "../src/utils/discord";
import { getThemeInfo } from "../src/main/themes"; import { getThemeInfo } from "../src/main/themes";
@ -46,7 +48,8 @@ window.VencordNative = {
getThemesList: () => DataStore.entries(themeStore).then(entries => getThemesList: () => DataStore.entries(themeStore).then(entries =>
entries.map(([name, css]) => getThemeInfo(css, name.toString())) entries.map(([name, css]) => getThemeInfo(css, name.toString()))
), ),
getThemeData: (fileName: string) => DataStore.get(fileName, themeStore) getThemeData: (fileName: string) => DataStore.get(fileName, themeStore),
getSystemValues: async () => ({}),
}, },
native: { native: {
@ -80,6 +83,7 @@ window.VencordNative = {
return; return;
} }
win.baseUrl = EXTENSION_BASE_URL;
win.setCss = setCssDebounced; win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get(); win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () => win.getTheme = () =>
@ -87,7 +91,7 @@ window.VencordNative = {
? "vs-light" ? "vs-light"
: "vs-dark"; : "vs-dark";
win.document.write(monacoHtml); win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn);
}, },
}, },

View file

@ -1,32 +0,0 @@
/**
* @template T
* @param {T[]} arr
* @param {(v: T) => boolean} predicate
*/
function removeFirst(arr, predicate) {
const idx = arr.findIndex(predicate);
if (idx !== -1) arr.splice(idx, 1);
}
chrome.webRequest.onHeadersReceived.addListener(
({ responseHeaders, type, url }) => {
if (!responseHeaders) return;
if (type === "main_frame") {
// In main frame requests, the CSP needs to be removed to enable fetching of custom css
// as desired by the user
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy");
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) {
// Most users will load css from GitHub, but GitHub doesn't set the correct content type,
// so we fix it here
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type");
responseHeaders.push({
name: "Content-Type",
value: "text/css"
});
}
return { responseHeaders };
},
{ urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] },
["blocking", "responseHeaders"]
);

View file

@ -4,6 +4,11 @@ if (typeof browser === "undefined") {
const script = document.createElement("script"); const script = document.createElement("script");
script.src = browser.runtime.getURL("dist/Vencord.js"); script.src = browser.runtime.getURL("dist/Vencord.js");
script.id = "vencord-script";
Object.assign(script.dataset, {
extensionBaseUrl: browser.runtime.getURL(""),
version: browser.runtime.getManifest().version
});
const style = document.createElement("link"); const style = document.createElement("link");
style.type = "text/css"; style.type = "text/css";

View file

@ -28,7 +28,7 @@
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": ["dist/Vencord.js", "dist/Vencord.css"], "resources": ["dist/*", "third-party/*"],
"matches": ["*://*.discord.com/*"] "matches": ["*://*.discord.com/*"]
} }
], ],

View file

@ -26,11 +26,7 @@
} }
], ],
"background": { "web_accessible_resources": ["dist/*", "third-party/*"],
"scripts": ["background.js"]
},
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {

43
browser/monaco.ts Normal file
View file

@ -0,0 +1,43 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./patch-worker";
import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js";
declare global {
const baseUrl: string;
const getCurrentCss: () => Promise<string>;
const setCss: (css: string) => void;
const getTheme: () => string;
}
const BASE = "/dist/monaco/vs";
self.MonacoEnvironment = {
getWorkerUrl(_moduleId: unknown, label: string) {
const path = label === "css" ? "/language/css/css.worker.js" : "/editor/editor.worker.js";
return new URL(BASE + path, baseUrl).toString();
}
};
getCurrentCss().then(css => {
const editor = monaco.editor.create(
document.getElementById("container")!,
{
value: css,
language: "css",
theme: getTheme(),
}
);
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
});

37
browser/monacoWin.html Normal file
View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vencord QuickCSS Editor</title>
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
const script = document.createElement("script");
script.src = new URL("/dist/monaco/index.js", baseUrl);
const style = document.createElement("link");
style.type = "text/css";
style.rel = "stylesheet";
style.href = new URL("/dist/monaco/index.css", baseUrl);
document.body.append(style, script);
</script>
</body>
</html>

135
browser/patch-worker.js Normal file
View file

@ -0,0 +1,135 @@
/*
Copyright 2013 Rob Wu <gwnRob@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Target: Chrome 20+
// W3-compliant Worker proxy.
// This module replaces the global Worker object.
// When invoked, the default Worker object is called.
// If this call fails with SECURITY_ERR, the script is fetched
// using async XHR, and transparently proxies all calls and
// setters/getters to the new Worker object.
// Note: This script does not magically circumvent the Same origin policy.
(function () {
'use strict';
var Worker_ = window.Worker;
var URL = window.URL || window.webkitURL;
// Create dummy worker for the following purposes:
// 1. Don't override the global Worker object if the fallback isn't
// going to work (future API changes?)
// 2. Use it to trigger early validation of postMessage calls
// Note: Blob constructor is supported since Chrome 20, but since
// some of the used Chrome APIs are only supported as of Chrome 20,
// I don't bother adding a BlobBuilder fallback.
var dummyWorker = new Worker_(
URL.createObjectURL(new Blob([], { type: 'text/javascript' })));
window.Worker = function Worker(scriptURL) {
if (arguments.length === 0) {
throw new TypeError('Not enough arguments');
}
try {
return new Worker_(scriptURL);
} catch (e) {
if (e.code === 18/*DOMException.SECURITY_ERR*/) {
return new WorkerXHR(scriptURL);
} else {
throw e;
}
}
};
// Bind events and replay queued messages
function bindWorker(worker, workerURL) {
if (worker._terminated) {
return;
}
worker.Worker = new Worker_(workerURL);
worker.Worker.onerror = worker._onerror;
worker.Worker.onmessage = worker._onmessage;
var o;
while ((o = worker._replayQueue.shift())) {
worker.Worker[o.method].apply(worker.Worker, o.arguments);
}
while ((o = worker._messageQueue.shift())) {
worker.Worker.postMessage.apply(worker.Worker, o);
}
}
function WorkerXHR(scriptURL) {
var worker = this;
var x = new XMLHttpRequest();
x.responseType = 'blob';
x.onload = function () {
// http://stackoverflow.com/a/10372280/938089
var workerURL = URL.createObjectURL(x.response);
bindWorker(worker, workerURL);
};
x.open('GET', scriptURL);
x.send();
worker._replayQueue = [];
worker._messageQueue = [];
}
WorkerXHR.prototype = {
constructor: Worker_,
terminate: function () {
if (!this._terminated) {
this._terminated = true;
if (this.Worker)
this.Worker.terminate();
}
},
postMessage: function (message, transfer) {
if (!(this instanceof WorkerXHR))
throw new TypeError('Illegal invocation');
if (this.Worker) {
this.Worker.postMessage.apply(this.Worker, arguments);
} else {
// Trigger validation:
dummyWorker.postMessage(message);
// Alright, push the valid message to the queue.
this._messageQueue.push(arguments);
}
}
};
// Implement the EventTarget interface
[
'addEventListener',
'removeEventListener',
'dispatchEvent'
].forEach(function (method) {
WorkerXHR.prototype[method] = function () {
if (!(this instanceof WorkerXHR)) {
throw new TypeError('Illegal invocation');
}
if (this.Worker) {
this.Worker[method].apply(this.Worker, arguments);
} else {
this._replayQueue.push({ method: method, arguments: arguments });
}
};
});
Object.defineProperties(WorkerXHR.prototype, {
onmessage: {
get: function () { return this._onmessage || null; },
set: function (func) {
this._onmessage = typeof func === 'function' ? func : null;
}
},
onerror: {
get: function () { return this._onerror || null; },
set: function (func) {
this._onerror = typeof func === 'function' ? func : null;
}
}
});
})();

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. 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: To pull latest changes:

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.4.2", "version": "1.5.3",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -11,7 +11,7 @@
"type": "git", "type": "git",
"url": "git+https://github.com/Vendicated/Vencord.git" "url": "git+https://github.com/Vendicated/Vencord.git"
}, },
"license": "GPL-3.0", "license": "GPL-3.0-or-later",
"author": "Vendicated", "author": "Vendicated",
"directories": { "directories": {
"doc": "docs" "doc": "docs"
@ -31,13 +31,18 @@
"watch": "node scripts/build/build.mjs --watch" "watch": "node scripts/build/build.mjs --watch"
}, },
"dependencies": { "dependencies": {
"@sapphi-red/web-noise-suppressor": "0.3.3",
"@vap/core": "0.0.12", "@vap/core": "0.0.12",
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
"eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4", "fflate": "^0.7.4",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.43.0",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.246",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.14.194",
"@types/node": "^18.16.3", "@types/node": "^18.16.3",
@ -49,9 +54,8 @@
"diff": "^5.1.0", "diff": "^5.1.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.15.18",
"eslint": "^8.28.0", "eslint": "^8.46.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
@ -63,13 +67,14 @@
"stylelint-config-standard": "^33.0.0", "stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.7", "tsx": "^3.12.7",
"type-fest": "^3.9.0", "type-fest": "^3.9.0",
"typescript": "^5.0.4" "typescript": "^5.0.4",
"zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@8.1.1", "packageManager": "pnpm@8.1.1",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch", "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": { "peerDependencyRules": {
"ignoreMissing": [ "ignoreMissing": [

View file

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

View file

@ -1,23 +1,39 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies: patchedDependencies:
eslint-plugin-path-alias@1.0.0: eslint-plugin-path-alias@1.0.0:
hash: m6sma4g6bh67km3q6igf6uxaja hash: m6sma4g6bh67km3q6igf6uxaja
path: patches/eslint-plugin-path-alias@1.0.0.patch path: patches/eslint-plugin-path-alias@1.0.0.patch
eslint@8.28.0: eslint@8.46.0:
hash: 7wc6icvgtg3uswirb5tpsbjnbe hash: xm46kqcmdgzlmm4aifkfpxaho4
path: patches/eslint@8.28.0.patch path: patches/eslint@8.46.0.patch
dependencies: dependencies:
'@sapphi-red/web-noise-suppressor':
specifier: 0.3.3
version: 0.3.3
'@vap/core': '@vap/core':
specifier: 0.0.12 specifier: 0.0.12
version: 0.0.12 version: 0.0.12
'@vap/shiki': '@vap/shiki':
specifier: 0.10.5 specifier: 0.10.5
version: 0.10.5 version: 0.10.5
eslint-plugin-simple-header:
specifier: ^1.0.2
version: 1.0.2
fflate: fflate:
specifier: ^0.7.4 specifier: ^0.7.4
version: 0.7.4 version: 0.7.4
gifenc:
specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3
version: github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3
monaco-editor:
specifier: ^0.43.0
version: 0.43.0
nanoid: nanoid:
specifier: ^4.0.2 specifier: ^4.0.2
version: 4.0.2 version: 4.0.2
@ -26,6 +42,9 @@ dependencies:
version: 1.0.1 version: 1.0.1
devDependencies: devDependencies:
'@types/chrome':
specifier: ^0.0.246
version: 0.0.246
'@types/diff': '@types/diff':
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3 version: 5.0.3
@ -46,10 +65,10 @@ devDependencies:
version: 2.4.2 version: 2.4.2
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^5.59.1 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': '@typescript-eslint/parser':
specifier: ^5.59.1 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: diff:
specifier: ^5.1.0 specifier: ^5.1.0
version: 5.1.0 version: 5.1.0
@ -60,23 +79,20 @@ devDependencies:
specifier: ^0.15.18 specifier: ^0.15.18
version: 0.15.18 version: 0.15.18
eslint: eslint:
specifier: ^8.28.0 specifier: ^8.46.0
version: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) version: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-import-resolver-alias: eslint-import-resolver-alias:
specifier: ^1.1.2 specifier: ^1.1.2
version: 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: eslint-plugin-path-alias:
specifier: ^1.0.0 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: eslint-plugin-simple-import-sort:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.0.0(eslint@8.28.0) version: 10.0.0(eslint@8.46.0)
eslint-plugin-unused-imports: eslint-plugin-unused-imports:
specifier: ^2.0.0 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: highlight.js:
specifier: 10.6.0 specifier: 10.6.0
version: 10.6.0 version: 10.6.0
@ -104,9 +120,17 @@ devDependencies:
typescript: typescript:
specifier: ^5.0.4 specifier: ^5.0.4
version: 5.0.4 version: 5.0.4
zip-local:
specifier: ^0.3.5
version: 0.3.5
packages: 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: /@babel/code-frame@7.21.4:
resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -399,7 +423,7 @@ packages:
dev: true dev: true
optional: 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==} resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -408,8 +432,8 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-visitor-keys: 3.4.0 eslint-visitor-keys: 3.4.2
dev: true dev: true
/@eslint-community/regexpp@4.5.1: /@eslint-community/regexpp@4.5.1:
@ -417,15 +441,20 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true dev: true
/@eslint/eslintrc@1.3.3: /@eslint-community/regexpp@4.6.2:
resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} 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} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.3.4 debug: 4.3.4
espree: 9.4.1 espree: 9.6.1
globals: 13.17.0 globals: 13.20.0
ignore: 5.2.0 ignore: 5.2.4
import-fresh: 3.3.0 import-fresh: 3.3.0
js-yaml: 4.1.0 js-yaml: 4.1.0
minimatch: 3.1.2 minimatch: 3.1.2
@ -434,8 +463,13 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@humanwhocodes/config-array@0.11.7: /@eslint/js@8.46.0:
resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} 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'} engines: {node: '>=10.10.0'}
dependencies: dependencies:
'@humanwhocodes/object-schema': 1.2.1 '@humanwhocodes/object-schema': 1.2.1
@ -498,10 +532,35 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sapphi-red/web-noise-suppressor@0.3.3:
resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==}
dev: false
/@types/chrome@0.0.246:
resolution: {integrity: sha512-MxGxEomGxsJiL9xe/7ZwVgwdn8XVKWbPvxpVQl3nWOjrS0Ce63JsfzxUc4aU3GvRcUPYsfufHmJ17BFyKxeA4g==}
dependencies:
'@types/filesystem': 0.0.33
'@types/har-format': 1.2.13
dev: true
/@types/diff@5.0.3: /@types/diff@5.0.3:
resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==} resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==}
dev: true dev: true
/@types/filesystem@0.0.33:
resolution: {integrity: sha512-2KedRPzwu2K528vFkoXnnWdsG0MtUwPjuA7pRy4vKxlxHEe8qUDZibYHXJKZZr2Cl/ELdCWYqyb/MKwsUuzBWw==}
dependencies:
'@types/filewriter': 0.0.30
dev: true
/@types/filewriter@0.0.30:
resolution: {integrity: sha512-lB98tui0uxc7erbj0serZfJlHKLNJHwBltPnbmO1WRpL5T325GOHRiQfr2E29V2q+S1brDO63Fpdt6vb3bES9Q==}
dev: true
/@types/har-format@1.2.13:
resolution: {integrity: sha512-PwBsCBD3lDODn4xpje3Y1di0aDJp4Ww7aSfMRVw6ysnxD4I7Wmq2mBkSKaDtN403hqH5sp6c9xQUvFYY3+lkBg==}
dev: true
/@types/json-schema@7.0.11: /@types/json-schema@7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
@ -569,7 +628,7 @@ packages:
'@types/node': 18.16.3 '@types/node': 18.16.3
dev: true 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==} resolution: {integrity: sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -583,12 +642,12 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.5.1 '@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/scope-manager': 5.59.1
'@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)
'@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 debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
grapheme-splitter: 1.0.4 grapheme-splitter: 1.0.4
ignore: 5.2.4 ignore: 5.2.4
natural-compare-lite: 1.4.0 natural-compare-lite: 1.4.0
@ -599,7 +658,7 @@ packages:
- supports-color - supports-color
dev: true 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==} resolution: {integrity: sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -615,7 +674,7 @@ packages:
'@typescript-eslint/types': 5.59.1 '@typescript-eslint/types': 5.59.1
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4) '@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
debug: 4.3.4 debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -629,7 +688,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.59.1 '@typescript-eslint/visitor-keys': 5.59.1
dev: true 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==} resolution: {integrity: sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -642,9 +701,9 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4) '@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 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) tsutils: 3.21.0(typescript@5.0.4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
@ -677,7 +736,7 @@ packages:
- supports-color - supports-color
dev: true 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==} resolution: {integrity: sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -686,13 +745,13 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: 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/json-schema': 7.0.11
'@types/semver': 7.3.13 '@types/semver': 7.3.13
'@typescript-eslint/scope-manager': 5.59.1 '@typescript-eslint/scope-manager': 5.59.1
'@typescript-eslint/types': 5.59.1 '@typescript-eslint/types': 5.59.1
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4) '@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 eslint-scope: 5.1.1
semver: 7.5.0 semver: 7.5.0
transitivePeerDependencies: transitivePeerDependencies:
@ -722,16 +781,16 @@ packages:
vscode-textmate: 5.2.0 vscode-textmate: 5.2.0
dev: false 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==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.10.0
dev: true dev: true
/acorn@8.8.0: /acorn@8.10.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
dev: true dev: true
@ -821,6 +880,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/async@1.5.2:
resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==}
dev: true
/atob@2.1.2: /atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'} engines: {node: '>= 4.5.0'}
@ -1446,18 +1509,7 @@ packages:
optional: true optional: true
dev: true dev: true
/eslint-plugin-header@3.1.1(eslint@8.28.0): /eslint-plugin-path-alias@1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.46.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):
resolution: {integrity: sha512-FXus57yC+Zd3sMv46pbloXYwFeNVNHJqlACr9V68FG/IzGFBBokGJpmjDbEjpt8ZCeVSndUubeDWWl2A8sCNVQ==} resolution: {integrity: sha512-FXus57yC+Zd3sMv46pbloXYwFeNVNHJqlACr9V68FG/IzGFBBokGJpmjDbEjpt8ZCeVSndUubeDWWl2A8sCNVQ==}
peerDependencies: peerDependencies:
eslint: ^7 eslint: ^7
@ -1465,14 +1517,18 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
nanomatch: 1.2.13 nanomatch: 1.2.13
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
patched: 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==} resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
peerDependencies: peerDependencies:
eslint: '>=5.0.0' eslint: '>=5.0.0'
@ -1480,10 +1536,10 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
dev: true 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==} resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -1495,8 +1551,8 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
'@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)
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-rule-composer: 0.3.0 eslint-rule-composer: 0.3.0
dev: true dev: true
@ -1513,49 +1569,34 @@ packages:
estraverse: 4.3.0 estraverse: 4.3.0
dev: true dev: true
/eslint-scope@7.1.1: /eslint-scope@7.2.2:
resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
esrecurse: 4.3.0 esrecurse: 4.3.0
estraverse: 5.3.0 estraverse: 5.3.0
dev: true 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: /eslint-visitor-keys@3.4.0:
resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==} resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint@8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe): /eslint-visitor-keys@3.4.2:
resolution: {integrity: sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==} 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} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
'@eslint/eslintrc': 1.3.3 '@eslint-community/eslint-utils': 4.4.0(eslint@8.46.0)
'@humanwhocodes/config-array': 0.11.7 '@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 '@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
ajv: 6.12.6 ajv: 6.12.6
@ -1564,60 +1605,46 @@ packages:
debug: 4.3.4 debug: 4.3.4
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.1.1 eslint-scope: 7.2.2
eslint-utils: 3.0.0(eslint@8.28.0) eslint-visitor-keys: 3.4.2
eslint-visitor-keys: 3.3.0 espree: 9.6.1
espree: 9.4.0 esquery: 1.5.0
esquery: 1.4.0
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1 file-entry-cache: 6.0.1
find-up: 5.0.0 find-up: 5.0.0
glob-parent: 6.0.2 glob-parent: 6.0.2
globals: 13.17.0 globals: 13.20.0
grapheme-splitter: 1.0.4 graphemer: 1.4.0
ignore: 5.2.0 ignore: 5.2.4
import-fresh: 3.3.0
imurmurhash: 0.1.4 imurmurhash: 0.1.4
is-glob: 4.0.3 is-glob: 4.0.3
is-path-inside: 3.0.3 is-path-inside: 3.0.3
js-sdsl: 4.1.5
js-yaml: 4.1.0 js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1 json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1 levn: 0.4.1
lodash.merge: 4.6.2 lodash.merge: 4.6.2
minimatch: 3.1.2 minimatch: 3.1.2
natural-compare: 1.4.0 natural-compare: 1.4.0
optionator: 0.9.1 optionator: 0.9.3
regexpp: 3.2.0
strip-ansi: 6.0.1 strip-ansi: 6.0.1
strip-json-comments: 3.1.1
text-table: 0.2.0 text-table: 0.2.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
patched: true patched: true
/espree@9.4.0: /espree@9.6.1:
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.10.0
acorn-jsx: 5.3.2(acorn@8.8.0) acorn-jsx: 5.3.2(acorn@8.10.0)
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.4.2
dev: true dev: true
/espree@9.4.1: /esquery@1.5.0:
resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
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==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
dependencies: dependencies:
estraverse: 5.3.0 estraverse: 5.3.0
@ -1858,8 +1885,8 @@ packages:
which: 1.3.1 which: 1.3.1
dev: true dev: true
/globals@13.17.0: /globals@13.20.0:
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
type-fest: 0.20.2 type-fest: 0.20.2
@ -1881,10 +1908,18 @@ packages:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
dev: true dev: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: true
/grapheme-splitter@1.0.4: /grapheme-splitter@1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true dev: true
/graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
dev: true
/hard-rejection@2.1.0: /hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1972,11 +2007,6 @@ packages:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: true dev: true
/ignore@5.2.0:
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
engines: {node: '>= 4'}
dev: true
/ignore@5.2.4: /ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -2168,10 +2198,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/js-sdsl@4.1.5:
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
dev: true
/js-tokens@4.0.0: /js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true dev: true
@ -2203,6 +2229,12 @@ packages:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
dev: false dev: false
/jszip@2.7.0:
resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==}
dependencies:
pako: 1.0.11
dev: true
/kind-of@3.2.2: /kind-of@3.2.2:
resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2373,6 +2405,10 @@ packages:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: true dev: true
/monaco-editor@0.43.0:
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
dev: false
/ms@2.0.0: /ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: true dev: true
@ -2485,16 +2521,16 @@ packages:
wrappy: 1.0.2 wrappy: 1.0.2
dev: true dev: true
/optionator@0.9.1: /optionator@0.9.3:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dependencies: dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
deep-is: 0.1.4 deep-is: 0.1.4
fast-levenshtein: 2.0.6 fast-levenshtein: 2.0.6
levn: 0.4.1 levn: 0.4.1
prelude-ls: 1.2.1 prelude-ls: 1.2.1
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.3
dev: true dev: true
/p-limit@2.3.0: /p-limit@2.3.0:
@ -2530,6 +2566,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: true
/parent-module@1.0.1: /parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2681,6 +2721,11 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/q@1.5.1:
resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==}
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
@ -2734,11 +2779,6 @@ packages:
safe-regex: 1.1.0 safe-regex: 1.1.0
dev: true dev: true
/regexpp@3.2.0:
resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
engines: {node: '>=8'}
dev: true
/require-directory@2.1.1: /require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3323,11 +3363,6 @@ packages:
isexe: 2.0.0 isexe: 2.0.0
dev: true 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: /wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -3405,3 +3440,18 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/zip-local@0.3.5:
resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==}
dependencies:
async: 1.5.2
graceful-fs: 4.2.11
jszip: 2.7.0
q: 1.5.1
dev: true
github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3:
resolution: {tarball: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3}
name: gifenc
version: 1.0.3
dev: false

View file

@ -19,13 +19,16 @@
import esbuild from "esbuild"; 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 = { const defines = {
IS_STANDALONE: isStandalone, IS_STANDALONE: isStandalone,
IS_DEV: JSON.stringify(watch), IS_DEV: JSON.stringify(watch),
IS_UPDATER_DISABLED: updaterDisabled,
IS_WEB: false,
IS_EXTENSION: false,
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(), BUILD_TIMESTAMP,
}; };
if (defines.IS_STANDALONE === "false") if (defines.IS_STANDALONE === "false")
// If this is a local build (not standalone), optimise // If this is a local build (not standalone), optimise
@ -40,8 +43,6 @@ const nodeCommonOpts = {
format: "cjs", format: "cjs",
platform: "node", platform: "node",
target: ["esnext"], target: ["esnext"],
minify: true,
bundle: true,
external: ["electron", ...commonOpts.external], external: ["electron", ...commonOpts.external],
define: defines, define: defines,
}; };
@ -50,16 +51,7 @@ const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.j
const sourcemap = watch ? "inline" : "external"; const sourcemap = watch ? "inline" : "external";
await Promise.all([ await Promise.all([
// common preload // Discord Desktop main & renderer & preload
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/preload.js",
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
sourcemap,
}),
// Discord Desktop main & renderer
esbuild.build({ esbuild.build({
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
@ -69,7 +61,7 @@ await Promise.all([
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: true,
IS_VENCORD_DESKTOP: false IS_VESKTOP: false
} }
}), }),
esbuild.build({ esbuild.build({
@ -87,13 +79,24 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: true, 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({ esbuild.build({
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
@ -103,7 +106,7 @@ await Promise.all([
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: false,
IS_VENCORD_DESKTOP: true IS_VESKTOP: true
} }
}), }),
esbuild.build({ esbuild.build({
@ -121,9 +124,20 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: 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 => { ]).catch(err => {

View file

@ -17,14 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import esbuild from "esbuild"; import esbuild from "esbuild";
import { zip } from "fflate";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises"; import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
import Zip from "zip-local";
import { commonOpts, globPlugins, VERSION, watch } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
/** /**
* @type {esbuild.BuildOptions} * @type {esbuild.BuildOptions}
@ -42,28 +41,69 @@ const commonOptions = {
target: ["esnext"], target: ["esnext"],
define: { define: {
IS_WEB: "true", IS_WEB: "true",
IS_EXTENSION: "false",
IS_STANDALONE: "true", IS_STANDALONE: "true",
IS_DEV: JSON.stringify(watch), IS_DEV: JSON.stringify(watch),
IS_DISCORD_DESKTOP: "false", IS_DISCORD_DESKTOP: "false",
IS_VENCORD_DESKTOP: "false", IS_VESKTOP: "false",
IS_UPDATER_DISABLED: "true",
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(), BUILD_TIMESTAMP,
} }
}; };
const MonacoWorkerEntryPoints = [
"vs/language/css/css.worker.js",
"vs/editor/editor.worker.js"
];
const RnNoiseFiles = [
"dist/rnnoise.wasm",
"dist/rnnoise_simd.wasm",
"dist/rnnoise/workletProcessor.js",
"LICENSE"
];
await Promise.all( await Promise.all(
[ [
esbuild.build({
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
bundle: true,
minify: true,
format: "iife",
outbase: "node_modules/monaco-editor/esm/",
outdir: "dist/monaco"
}),
esbuild.build({
entryPoints: ["browser/monaco.ts"],
bundle: true,
minify: true,
format: "iife",
outfile: "dist/monaco/index.js",
loader: {
".ttf": "file"
}
}),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
outfile: "dist/browser.js", outfile: "dist/browser.js",
footer: { js: "//# sourceURL=VencordWeb" }, footer: { js: "//# sourceURL=VencordWeb" },
}), }),
esbuild.build({
...commonOptions,
outfile: "dist/extension.js",
define: {
...commonOptions?.define,
IS_EXTENSION: "true",
},
footer: { js: "//# sourceURL=VencordWeb" },
}),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
define: { define: {
"window": "unsafeWindow", ...(commonOptions?.define),
...(commonOptions?.define) window: "unsafeWindow",
}, },
outfile: "dist/Vencord.user.js", outfile: "dist/Vencord.user.js",
banner: { banner: {
@ -78,12 +118,41 @@ await Promise.all(
); );
/** /**
* @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>} * @type {(dir: string) => Promise<string[]>}
*/ */
async function buildPluginZip(target, files, shouldZip) { async function globDir(dir) {
const files = [];
for (const child of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, child.name);
if (child.isDirectory())
files.push(...await globDir(p));
else
files.push(p);
}
return files;
}
/**
* @type {(dir: string, basePath?: string) => Promise<Record<string, string>>}
*/
async function loadDir(dir, basePath = "") {
const files = await globDir(dir);
return Object.fromEntries(await Promise.all(files.map(async f => [f.slice(basePath.length), await readFile(f)])));
}
/**
* @type {(target: string, files: string[]) => Promise<void>}
*/
async function buildExtension(target, files, noMonaco = false) {
const entries = { const entries = {
"dist/Vencord.js": await readFile("dist/browser.js"), "dist/Vencord.js": await readFile("dist/extension.js"),
"dist/Vencord.css": await readFile("dist/browser.css"), "dist/Vencord.css": await readFile("dist/extension.css"),
...(noMonaco ? {} : await loadDir("dist/monaco")),
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
))),
...Object.fromEntries(await Promise.all(files.map(async f => { ...Object.fromEntries(await Promise.all(files.map(async f => {
let content = await readFile(join("browser", f)); let content = await readFile(join("browser", f));
if (f.startsWith("manifest")) { if (f.startsWith("manifest")) {
@ -99,21 +168,6 @@ async function buildPluginZip(target, files, shouldZip) {
}))), }))),
}; };
if (shouldZip) {
return new Promise((resolve, reject) => {
zip(entries, {}, (err, data) => {
if (err) {
reject(err);
} else {
const out = join("dist", target);
writeFile(out, data).then(() => {
console.info("Extension written to " + out);
resolve();
}).catch(reject);
}
});
});
} else {
await rm(target, { recursive: true, force: true }); await rm(target, { recursive: true, force: true });
await Promise.all(Object.entries(entries).map(async ([file, content]) => { await Promise.all(Object.entries(entries).map(async ([file, content]) => {
const dest = join("dist", target, file); const dest = join("dist", target, file);
@ -123,7 +177,6 @@ async function buildPluginZip(target, files, shouldZip) {
})); }));
console.info("Unpacked Extension written to dist/" + target); console.info("Unpacked Extension written to dist/" + target);
}
} }
const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => {
@ -141,8 +194,9 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
await Promise.all([ await Promise.all([
appendCssRuntime, appendCssRuntime,
buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true), buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildPluginZip("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false), buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true),
buildPluginZip("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"], false),
]); ]);
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip");
console.info("Packed Chromium Extension written to dist/extension.zip");

View file

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

View file

@ -18,9 +18,12 @@
import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs";
import { access, readFile } from "fs/promises"; import { access, readFile } from "fs/promises";
import { join } from "path"; import { join, sep } from "path";
import { normalize as posixNormalize, sep as posixSep } from "path/posix";
import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; 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 { interface Dev {
name: string; name: string;
id: string; id: string;
@ -37,6 +40,7 @@ interface PluginData {
required: boolean; required: boolean;
enabledByDefault: boolean; enabledByDefault: boolean;
target: "discordDesktop" | "vencordDesktop" | "web" | "dev"; target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
filePath: string;
} }
const devs = {} as Record<string, Dev>; const devs = {} as Record<string, Dev>;
@ -157,14 +161,23 @@ async function parseFile(fileName: string) {
if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing"); if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing");
const fileBits = fileName.split("."); const target = getPluginTarget(fileName);
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1)!)) { if (target) {
const mod = fileBits.at(-2)!; if (!["web", "discordDesktop", "vencordDesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`);
if (!["web", "discordDesktop", "vencordDesktop", "dev"].includes(mod)) throw fail(`invalid target ${fileBits.at(-2)}`); data.target = target as any;
data.target = mod as any;
} }
return data; data.filePath = posixNormalize(fileName)
.split(sep)
.join(posixSep)
.replace(/\/index\.([jt]sx?)$/, "")
.replace(/^src\/plugins\//, "");
let readme = "";
try {
readme = readFileSync(join(fileName, "..", "README.md"), "utf-8");
} catch { }
return [data, readme] as const;
} }
throw fail("no default export called 'definePlugin' found"); throw fail("no default export called 'definePlugin' found");
@ -193,18 +206,24 @@ function isPluginFile({ name }: { name: string; }) {
(async () => { (async () => {
parseDevs(); parseDevs();
const plugins = ["src/plugins", "src/plugins/_core"].flatMap(dir => const plugins = [] as PluginData[];
const readmes = {} as Record<string, string>;
await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir =>
readdirSync(dir, { withFileTypes: true }) readdirSync(dir, { withFileTypes: true })
.filter(isPluginFile) .filter(isPluginFile)
.map(async dirent => .map(async dirent => {
parseFile(await getEntryPoint(dir, dirent)) const [data, readme] = await parseFile(await getEntryPoint(dir, dirent));
) plugins.push(data);
); if (readme) readmes[data.name] = readme;
})
));
const data = JSON.stringify(await Promise.all(plugins)); const data = JSON.stringify(plugins);
if (process.argv.length > 2) { if (process.argv.length > 3) {
writeFileSync(process.argv[2], data); writeFileSync(process.argv[2], data);
writeFileSync(process.argv[3], JSON.stringify(readmes));
} else { } else {
console.log(data); console.log(data);
} }

View file

@ -171,7 +171,7 @@ page.on("console", async e => {
plugin, plugin,
type, type,
id, id,
match: regex, match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
error: cause error: cause
}); });
break; break;
@ -263,7 +263,7 @@ function runTime(token: string) {
for (const id in ids) { for (const id in ids) {
const isWasm = await fetch(wreq.p + wreq.u(id)) const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .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) if (!isWasm)
await wreq.e(id as any); 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 * Vencord, a Discord client mod
* Copyright (c) 2022 * Copyright (c) 2023 Vendicated and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* This program is free software: you can redistribute it and/or modify */
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IpcEvents } from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { IpcRes } from "@utils/types"; import { IpcRes } from "@utils/types";
@ -35,7 +23,8 @@ export default {
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName), deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR), getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST), getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName) getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES),
}, },
updater: { updater: {
@ -60,7 +49,7 @@ export default {
}, },
addThemeChangeListener(cb: () => void) { addThemeChangeListener(cb: () => void) {
ipcRenderer.on(IpcEvents.THEME_UPDATE, cb); ipcRenderer.on(IpcEvents.THEME_UPDATE, () => cb());
}, },
openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS), 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 * idb-keyval v6.2.0

View file

@ -0,0 +1,21 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByPropsLazy } from "@webpack";
import { Parser } from "@webpack/common";
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
/**
* Renders code in a Discord codeblock
*/
export function CodeBlock(props: { content?: string, lang: string; }) {
return (
<div className={CodeContainerClasses.markup}>
{Parser.defaultRules.codeBlock.react(props, null, {})}
</div>
);
}

View file

@ -28,8 +28,8 @@ interface BaseIconProps extends IconProps {
interface IconProps extends SVGProps<SVGSVGElement> { interface IconProps extends SVGProps<SVGSVGElement> {
className?: string; className?: string;
height?: number; height?: string | number;
width?: number; width?: string | number;
} }
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) { function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
@ -97,7 +97,7 @@ export function OpenExternalIcon(props: IconProps) {
> >
<polygon <polygon
fill="currentColor" fill="currentColor"
fill-rule="nonzero" fillRule="nonzero"
points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8" points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8"
/> />
</Icon> </Icon>
@ -121,9 +121,13 @@ export function InfoIcon(props: IconProps) {
<Icon <Icon
{...props} {...props}
className={classes(props.className, "vc-info-icon")} className={classes(props.className, "vc-info-icon")}
viewBox="0 0 12 12" viewBox="0 0 24 24"
> >
<path fill="currentColor" d="M6 1C3.243 1 1 3.244 1 6c0 2.758 2.243 5 5 5s5-2.242 5-5c0-2.756-2.243-5-5-5zm0 2.376a.625.625 0 110 1.25.625.625 0 010-1.25zM7.5 8.5h-3v-1h1V6H5V5h1a.5.5 0 01.5.5v2h1v1z" /> <path
fill="currentColor"
transform="translate(2 2)"
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
/>
</Icon> </Icon>
); );
} }
@ -139,8 +143,8 @@ export function OwnerCrownIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="evenodd"
clip-rule="evenodd" clipRule="evenodd"
d="M13.6572 5.42868C13.8879 5.29002 14.1806 5.30402 14.3973 5.46468C14.6133 5.62602 14.7119 5.90068 14.6473 6.16202L13.3139 11.4954C13.2393 11.7927 12.9726 12.0007 12.6666 12.0007H3.33325C3.02725 12.0007 2.76058 11.792 2.68592 11.4954L1.35258 6.16202C1.28792 5.90068 1.38658 5.62602 1.60258 5.46468C1.81992 5.30468 2.11192 5.29068 2.34325 5.42868L5.13192 7.10202L7.44592 3.63068C7.46173 3.60697 7.48377 3.5913 7.50588 3.57559C7.5192 3.56612 7.53255 3.55663 7.54458 3.54535L6.90258 2.90268C6.77325 2.77335 6.77325 2.56068 6.90258 2.43135L7.76458 1.56935C7.89392 1.44002 8.10658 1.44002 8.23592 1.56935L9.09792 2.43135C9.22725 2.56068 9.22725 2.77335 9.09792 2.90268L8.45592 3.54535C8.46794 3.55686 8.48154 3.56651 8.49516 3.57618C8.51703 3.5917 8.53897 3.60727 8.55458 3.63068L10.8686 7.10202L13.6572 5.42868ZM2.66667 12.6673H13.3333V14.0007H2.66667V12.6673Z" d="M13.6572 5.42868C13.8879 5.29002 14.1806 5.30402 14.3973 5.46468C14.6133 5.62602 14.7119 5.90068 14.6473 6.16202L13.3139 11.4954C13.2393 11.7927 12.9726 12.0007 12.6666 12.0007H3.33325C3.02725 12.0007 2.76058 11.792 2.68592 11.4954L1.35258 6.16202C1.28792 5.90068 1.38658 5.62602 1.60258 5.46468C1.81992 5.30468 2.11192 5.29068 2.34325 5.42868L5.13192 7.10202L7.44592 3.63068C7.46173 3.60697 7.48377 3.5913 7.50588 3.57559C7.5192 3.56612 7.53255 3.55663 7.54458 3.54535L6.90258 2.90268C6.77325 2.77335 6.77325 2.56068 6.90258 2.43135L7.76458 1.56935C7.89392 1.44002 8.10658 1.44002 8.23592 1.56935L9.09792 2.43135C9.22725 2.56068 9.22725 2.77335 9.09792 2.90268L8.45592 3.54535C8.46794 3.55686 8.48154 3.56651 8.49516 3.57618C8.51703 3.5917 8.53897 3.60727 8.55458 3.63068L10.8686 7.10202L13.6572 5.42868ZM2.66667 12.6673H13.3333V14.0007H2.66667V12.6673Z"
/> />
</Icon> </Icon>
@ -159,8 +163,6 @@ export function ScreenshareIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z" d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z"
/> />
</Icon> </Icon>
@ -198,8 +200,58 @@ export function Microphone(props: IconProps) {
className={classes(props.className, "vc-microphone")} className={classes(props.className, "vc-microphone")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" /> <path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V22H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1Z" fill="currentColor" /> <path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V22H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1Z" fill="currentColor" />
</Icon > </Icon >
); );
} }
export function CogWheel(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-cog-wheel")}
viewBox="0 0 24 24"
>
<path
clipRule="evenodd"
fill="currentColor"
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
/>
</Icon>
);
}
export function ReplyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-reply-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10 8.26667V4L3 11.4667L10 18.9333V14.56C15 14.56 18.5 16.2667 21 20C20 14.6667 17 9.33333 10 8.26667Z"
/>
</Icon>
);
}
export function DeleteIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-delete-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"
/>
<path
fill="currentColor"
d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"
/>
</Icon>
);
}

View file

@ -0,0 +1,113 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./contributorModal.css";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { DevsById } from "@utils/constants";
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general";
import Plugins from "~plugins";
import { PluginCard } from ".";
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
const cl = classNameFactory("vc-author-modal-");
export function openContributorModal(user: User) {
openModal(modalProps =>
<ModalRoot {...modalProps}>
<ErrorBoundary>
<ModalContent className={cl("root")}>
<ContributorModal user={user} />
</ModalContent>
</ErrorBoundary>
</ModalRoot>
);
}
function GithubIcon() {
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
return <img src={src} alt="GitHub" />;
}
function WebsiteIcon() {
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
return <img src={src} alt="Website" />;
}
function ContributorModal({ user }: { user: User; }) {
useSettings();
const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id));
useEffect(() => {
if (!profile && !user.bot && user.id)
fetchUserProfile(user.id);
}, [user.id]);
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
const plugins = useMemo(() => {
const allPlugins = Object.values(Plugins);
const pluginsByAuthor = DevsById[user.id]
? allPlugins.filter(p => p.authors.includes(DevsById[user.id]))
: allPlugins.filter(p => p.authors.some(a => a.name === user.username));
return pluginsByAuthor
.filter(p => !p.name.endsWith("API"))
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
}, [user.id, user.username]);
return (
<>
<div className={cl("header")}>
<img
className={cl("avatar")}
src={user.getAvatarURL(void 0, 512, true)}
alt=""
/>
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
<div className={cl("links")}>
{website && (
<MaskedLink
href={"https://" + website}
>
<WebsiteIcon />
</MaskedLink>
)}
{githubName && (
<MaskedLink href={`https://github.com/${githubName}`}>
<GithubIcon />
</MaskedLink>
)}
</div>
</div>
<div className={cl("plugins")}>
{plugins.map(p =>
<PluginCard
key={p.name}
plugin={p}
disabled={p.required ?? false}
onRestartNeeded={() => showToast("Restart to apply changes!")}
/>
)}
</div>
</>
);
}

View file

@ -18,17 +18,16 @@
import { generateId } from "@api/Commands"; import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins"; 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 { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { OptionType, Plugin } from "@utils/types"; import { OptionType, Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByCode, findByPropsLazy } from "@webpack";
import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { Constructor } from "type-fest"; import { Constructor } from "type-fest";
@ -41,7 +40,7 @@ import {
SettingSliderComponent, SettingSliderComponent,
SettingTextComponent SettingTextComponent
} from "./components"; } from "./components";
import hideBotTagStyle from "./userPopoutHideBotTag.css?managed"; import { openContributorModal } from "./ContributorModal";
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
@ -89,30 +88,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const canSubmit = () => Object.values(errors).every(e => !e); 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(() => { React.useEffect(() => {
enableStyle(hideBotTagStyle);
let originalUser: User;
(async () => { (async () => {
for (const user of plugin.authors.slice(0, 6)) { for (const user of plugin.authors.slice(0, 6)) {
const author = user.id const author = user.id
? await UserUtils.fetchUser(`${user.id}`) ? await UserUtils.fetchUser(`${user.id}`)
// only show name & pfp and no actions so users cannot harass plugin devs for support (send dms, add as friend, etc)
.then(u => (originalUser = u, makeDummyUser(u)))
.catch(() => makeDummyUser({ username: user.name })) .catch(() => makeDummyUser({ username: user.name }))
: makeDummyUser({ username: user.name }); : makeDummyUser({ username: user.name });
setAuthors(a => [...a, author]); setAuthors(a => [...a, author]);
} }
})(); })();
return () => {
disableStyle(hideBotTagStyle);
if (originalUser)
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: originalUser });
};
}, []); }, []);
async function saveAndClose() { async function saveAndClose() {
@ -214,6 +202,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
showDefaultAvatarsForNullUsers showDefaultAvatarsForNullUsers
showUserPopout showUserPopout
renderMoreUsers={renderMoreUsers} renderMoreUsers={renderMoreUsers}
renderUser={(user: User) => (
<Clickable
className={AvatarStyles.clickableAvatar}
onClick={() => openContributorModal(user)}
>
<img
className={AvatarStyles.avatar}
src={user.getAvatarURL(void 0, 80, true)}
alt={user.username}
title={user.username}
/>
</Clickable>
)}
/> />
</div> </div>
</Forms.FormSection> </Forms.FormSection>
@ -237,7 +238,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Button <Button
onClick={onClose} onClick={onClose}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
color={Button.Colors.WHITE} color={Button.Colors.PRIMARY}
look={Button.Looks.LINK} look={Button.Looks.LINK}
> >
Cancel Cancel

View file

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

View file

@ -0,0 +1,57 @@
.vc-author-modal-root {
padding: 1em;
}
.vc-author-modal-header {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.vc-author-modal-name {
text-transform: none;
flex-grow: 0;
background: var(--background-tertiary);
border-radius: 0 9999px 9999px 0;
padding: 6px 0.8em 6px 0.5em;
font-size: 20px;
height: 20px;
position: relative;
}
.vc-author-modal-name::before {
content: "";
display: block;
position: absolute;
height: 100%;
width: 16px;
background: var(--background-tertiary);
z-index: -1;
left: -16px;
top: 0;
}
.vc-author-modal-avatar {
height: 32px;
width: 32px;
border-radius: 50%;
}
.vc-author-modal-links {
margin-left: auto;
display: flex;
gap: 0.2em;
}
.vc-author-modal-links img {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-tertiary);
box-sizing: border-box
}
.vc-author-modal-plugins {
display: grid;
gap: 0.5em;
}

View file

@ -22,17 +22,18 @@ import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices"; import { showNotice } from "@api/Notices";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { CogWheel, InfoIcon } from "@components/Icons";
import PluginModal from "@components/PluginSettings/PluginModal"; import PluginModal from "@components/PluginSettings/PluginModal";
import { AddonCard } from "@components/VencordSettings/AddonCard"; import { AddonCard } from "@components/VencordSettings/AddonCard";
import { SettingsTab } from "@components/VencordSettings/shared"; import { SettingsTab } from "@components/VencordSettings/shared";
import { ChangeList } from "@utils/ChangeList"; import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes, isObjectEmpty } from "@utils/misc";
import { openModalLazy } from "@utils/modal"; import { openModalLazy } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -46,8 +47,6 @@ const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
function showErrorToast(message: string) { function showErrorToast(message: string) {
Toasts.show({ Toasts.show({
@ -91,7 +90,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
isNew?: boolean; isNew?: boolean;
} }
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name]; const settings = Settings.plugins[plugin.name];
const isEnabled = () => settings.enabled ?? false; const isEnabled = () => settings.enabled ?? false;
@ -161,9 +160,9 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
infoButton={ infoButton={
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}> <button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
{plugin.options {plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel /> ? <CogWheel />
: <InfoIcon width="24" height="24" />} : <InfoIcon />}
</button> </button>
} }
/> />

View file

@ -1,3 +0,0 @@
[class|="userPopoutOuter"] [class*="botTag"] {
display: none;
}

View file

@ -17,6 +17,7 @@
*/ */
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { CodeBlock } from "@components/CodeBlock";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
@ -299,7 +300,7 @@ function PatchHelper() {
{!!(find && match && replacement) && ( {!!(find && match && replacement) && (
<> <>
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div> <CodeBlock lang="js" content={code} />
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button> <Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
</> </>
)} )}

View file

@ -18,8 +18,11 @@
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { IsFirefox } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native"; import { showItemInFolder } from "@utils/native";
@ -40,7 +43,6 @@ type FileInput = ComponentType<{
}>; }>;
const InviteActions = findByPropsLazy("resolveInvite"); const InviteActions = findByPropsLazy("resolveInvite");
const TrashIcon = findByCodeLazy("M5 6.99902V18.999C5 20.101 5.897 20.999");
const FileInput: FileInput = findByCodeLazy("activateUploadDialogue="); const FileInput: FileInput = findByCodeLazy("activateUploadDialogue=");
const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const TextAreaProps = findLazy(m => typeof m.textarea === "string");
@ -112,7 +114,7 @@ function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
infoButton={ infoButton={
IS_WEB && ( IS_WEB && (
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}> <div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
<TrashIcon /> <DeleteIcon />
</div> </div>
) )
} }
@ -231,7 +233,7 @@ function ThemesTab() {
ref={fileInputRef} ref={fileInputRef}
onChange={onFileUpload} onChange={onFileUpload}
multiple={true} multiple={true}
filters={[{ extensions: ["*.css"] }]} filters={[{ extensions: ["css"] }]}
/> />
</Button> </Button>
) : ( ) : (
@ -249,12 +251,14 @@ function ThemesTab() {
> >
Load missing Themes Load missing Themes
</Button> </Button>
{!IsFirefox && (
<Button <Button
onClick={() => VencordNative.quickCss.openEditor()} onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
> >
Edit QuickCSS Edit QuickCSS
</Button> </Button>
)}
</> </>
</Card> </Card>
@ -316,6 +320,15 @@ function ThemesTab() {
return ( return (
<SettingsTab title="Themes"> <SettingsTab title="Themes">
{IsFirefox && (
<ErrorCard>
<Forms.FormTitle tag="h5">Warning</Forms.FormTitle>
<Forms.FormText>
You are using Firefox. Expect the vast majority of themes to not work.
If this is a problem, use a chromium browser or Discord Desktop / Vesktop.
</Forms.FormText>
</ErrorCard>
)}
<TabBar <TabBar
type="top" type="top"
look="brand" look="brand"

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,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { IsFirefox } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity } from "@utils/misc"; import { identity } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native"; import { relaunch, showItemInFolder } from "@utils/native";
@ -110,12 +110,14 @@ function VencordSettings() {
Restart Client Restart Client
</Button> </Button>
)} )}
{!IsFirefox && (
<Button <Button
onClick={() => VencordNative.quickCss.openEditor()} onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}> disabled={settingsDir === "Loading..."}>
Open QuickCSS File Open QuickCSS File
</Button> </Button>
)}
{!IS_WEB && ( {!IS_WEB && (
<Button <Button
onClick={() => showItemInFolder(settingsDir)} onClick={() => showItemInFolder(settingsDir)}
@ -258,7 +260,11 @@ function DonateCard({ image }: DonateCardProps) {
src={image} src={image}
alt="" alt=""
height={128} height={128}
style={{ marginLeft: "auto", transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : "" }} style={{
imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0,
marginLeft: "auto",
transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0
}}
/> />
</Card> </Card>
); );

View file

@ -8,6 +8,7 @@
width: 100%; width: 100%;
transition: 0.1s ease-out; transition: 0.1s ease-out;
transition-property: box-shadow, transform, background, opacity; transition-property: box-shadow, transform, background, opacity;
box-sizing: border-box;
} }
.vc-addon-card-disabled { .vc-addon-card-disabled {

8
src/globals.d.ts vendored
View file

@ -33,10 +33,12 @@ declare global {
* replace: `${IS_WEB}?foo:bar` * replace: `${IS_WEB}?foo:bar`
*/ */
export var IS_WEB: boolean; export var IS_WEB: boolean;
export var IS_EXTENSION: boolean;
export var IS_DEV: boolean; export var IS_DEV: boolean;
export var IS_STANDALONE: boolean; export var IS_STANDALONE: boolean;
export var IS_UPDATER_DISABLED: boolean;
export var IS_DISCORD_DESKTOP: boolean; export var IS_DISCORD_DESKTOP: boolean;
export var IS_VENCORD_DESKTOP: boolean; export var IS_VESKTOP: boolean;
export var VERSION: string; export var VERSION: string;
export var BUILD_TIMESTAMP: number; export var BUILD_TIMESTAMP: number;
@ -58,8 +60,8 @@ declare global {
* If you really must use it, mark your plugin as Desktop App only by naming it Foo.desktop.ts(x) * 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 DiscordNative: any;
export var VencordDesktop: any; export var Vesktop: any;
export var VencordDesktopNative: any; export var VesktopNative: any;
interface Window { interface Window {
webpackChunkdiscord_app: { webpackChunkdiscord_app: {

View file

@ -23,7 +23,7 @@ import { ensureSafePath, getSettings } from "./ipcMain";
import { IS_VANILLA, THEMES_DIR } from "./utils/constants"; import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
import { installExt } from "./utils/extensions"; import { installExt } from "./utils/extensions";
if (IS_VENCORD_DESKTOP || !IS_VANILLA) { if (IS_VESKTOP || !IS_VANILLA) {
app.whenReady().then(() => { app.whenReady().then(() => {
// Source Maps! Maybe there's a better way but since the renderer is executed // 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 // 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 "renderer.js.map":
case "vencordDesktopRenderer.js.map": case "vencordDesktopRenderer.js.map":
case "preload.js.map": case "preload.js.map":
case "vencordDesktopPreload.js.map":
case "patcher.js.map": case "patcher.js.map":
case "vencordDesktopMain.js.map": case "vencordDesktopMain.js.map":
cb(join(__dirname, url)); cb(join(__dirname, url));

View file

@ -22,12 +22,12 @@ import "./ipcPlugins";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { IpcEvents } from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { BrowserWindow, ipcMain, shell } from "electron"; import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
import { mkdirSync, readFileSync, watch } from "fs"; import { mkdirSync, readFileSync, watch } from "fs";
import { open, readdir, readFile, writeFile } from "fs/promises"; import { open, readdir, readFile, writeFile } from "fs/promises";
import { join, normalize } from "path"; import { join, normalize } from "path";
import monacoHtml from "~fileContent/../components/monacoWin.html;base64"; import monacoHtml from "~fileContent/monacoWin.html;base64";
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes"; import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants"; import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants";
@ -53,10 +53,12 @@ async function listThemes(): Promise<UserThemeHeader[]> {
const themeInfo: UserThemeHeader[] = []; const themeInfo: UserThemeHeader[] = [];
for (const fileName of files) { for (const fileName of files) {
if (!fileName.endsWith(".css")) continue;
const data = await getThemeData(fileName).then(stripBOM).catch(() => null); const data = await getThemeData(fileName).then(stripBOM).catch(() => null);
if (!data) continue; if (data == null) continue;
const parsed = getThemeInfo(data, fileName);
themeInfo.push(parsed); themeInfo.push(getThemeInfo(data, fileName));
} }
return themeInfo; return themeInfo;
@ -110,6 +112,10 @@ ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR); ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes()); ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes());
ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName)); ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName));
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
// win & mac only
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
}));
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR); ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings()); ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
@ -138,7 +144,7 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
autoHideMenuBar: true, autoHideMenuBar: true,
darkTheme: true, darkTheme: true,
webPreferences: { webPreferences: {
preload: join(__dirname, "preload.js"), preload: join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js"),
contextIsolation: true, contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
sandbox: false sandbox: false

View file

@ -22,6 +22,28 @@ import { readFile } from "fs/promises";
import { request } from "https"; import { request } from "https";
import { basename, normalize } from "path"; 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 // #region OpenInApp
// These links don't support CORS, so this has to be native // These links don't support CORS, so this has to be native
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/; 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/>. * 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 { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs";
import { basename, dirname, join } from "path"; 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) { function isNewer($new: string, old: string) {
const newParts = $new.slice(4).split(".").map(Number); const newParts = $new.slice(4).split(".").map(Number);
const oldParts = old.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 // Try to patch latest on before-quit
// need to reinject // Discord's Win32 updater will call app.quit() on restart and open new version on will-quit
function patchUpdater() { app.on("before-quit", patchLatest);
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);
};
}
}

View file

@ -71,7 +71,7 @@ if (!IS_VANILLA) {
constructor(options: BrowserWindowConstructorOptions) { constructor(options: BrowserWindowConstructorOptions) {
if (options?.webPreferences?.preload && options.title) { if (options?.webPreferences?.preload && options.title) {
const original = options.webPreferences.preload; 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; options.webPreferences.sandbox = false;
if (settings.frameless) { if (settings.frameless) {
options.frame = false; options.frame = false;

View file

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

View file

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

View file

@ -27,8 +27,8 @@ export default definePlugin({
{ {
find: "Messages.DISCODO_DISABLED", find: "Messages.DISCODO_DISABLED",
replacement: { replacement: {
match: /(Messages\.DISCODO_DISABLED\);return)(.*?homeIcon.*?)(\}function)/, match: /(Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))$3" 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({ export default definePlugin({
name: "NoTrack", name: "NoTrack",
description: "Disable Discord's tracking ('science'), metrics and Sentry crash reporting", 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, required: true,
patches: [ patches: [
{ {
@ -51,6 +51,13 @@ export default definePlugin({
replace: "return;" 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 { addContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { React, SettingsRouter } from "@webpack/common"; import { React, SettingsRouter } from "@webpack/common";
@ -63,19 +62,14 @@ export default definePlugin({
replacement: { replacement: {
get match() { get match() {
switch (Settings.plugins.Settings.settingsLocation) { switch (Settings.plugins.Settings.settingsLocation) {
case "top": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.USER_SETTINGS\}/; case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.BILLING_SETTINGS\}/; case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.APP_SETTINGS\}/; case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "aboveActivity": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.ACTIVITY_SETTINGS\}/; case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/;
case "belowActivity": return /(?<=\{section:(.{1,2})\.ID\.DIVIDER},)\{section:"changelog"/; case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/;
case "bottom": return /\{section:(.{1,2})\.ID\.CUSTOM,\s*element:.+?}/; case "aboveActivity":
default: { default:
new Logger("Settings").error( return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
new Error("No switch case matched????? Don't mess with the settings, silly")
);
// matches nothing
return /(?!a)a/;
}
} }
}, },
replace: "...$self.makeSettingsCategories($1),$&" replace: "...$self.makeSettingsCategories($1),$&"
@ -88,48 +82,50 @@ export default definePlugin({
return [ return [
{ {
section: ID.HEADER, section: ID.HEADER,
label: "Vencord" label: "Vencord",
className: "vc-settings-header"
}, },
{ {
section: "VencordSettings", section: "VencordSettings",
label: "Vencord", label: "Vencord",
element: require("@components/VencordSettings/VencordTab").default element: require("@components/VencordSettings/VencordTab").default,
className: "vc-settings"
}, },
{ {
section: "VencordPlugins", section: "VencordPlugins",
label: "Plugins", label: "Plugins",
element: require("@components/VencordSettings/PluginsTab").default, element: require("@components/VencordSettings/PluginsTab").default,
className: "vc-plugins"
}, },
{ {
section: "VencordThemes", section: "VencordThemes",
label: "Themes", label: "Themes",
element: require("@components/VencordSettings/ThemesTab").default, element: require("@components/VencordSettings/ThemesTab").default,
className: "vc-themes"
}, },
!IS_WEB && { !IS_UPDATER_DISABLED && {
section: "VencordUpdater", section: "VencordUpdater",
label: "Updater", label: "Updater",
element: require("@components/VencordSettings/UpdaterTab").default, element: require("@components/VencordSettings/UpdaterTab").default,
className: "vc-updater"
}, },
{ {
section: "VencordCloud", section: "VencordCloud",
label: "Cloud", label: "Cloud",
element: require("@components/VencordSettings/CloudTab").default, element: require("@components/VencordSettings/CloudTab").default,
className: "vc-cloud"
}, },
{ {
section: "VencordSettingsSync", section: "VencordSettingsSync",
label: "Backup & Restore", label: "Backup & Restore",
element: require("@components/VencordSettings/BackupAndRestoreTab").default, element: require("@components/VencordSettings/BackupAndRestoreTab").default,
className: "vc-backup-restore"
}, },
IS_DEV && { IS_DEV && {
section: "VencordPatchHelper", section: "VencordPatchHelper",
label: "Patch Helper", label: "Patch Helper",
element: require("@components/VencordSettings/PatchHelperTab").default, element: require("@components/VencordSettings/PatchHelperTab").default,
}, className: "vc-patch-helper"
// TODO: make this use customSections
IS_VENCORD_DESKTOP && {
section: "VencordDesktop",
label: "Desktop Settings",
element: VencordDesktop.Components.Settings,
}, },
...this.customSections.map(func => func(ID)), ...this.customSections.map(func => func(ID)),
{ {
@ -172,7 +168,7 @@ export default definePlugin({
get additionalInfo() { get additionalInfo() {
if (IS_DEV) return " (Dev)"; if (IS_DEV) return " (Dev)";
if (IS_WEB) return " (Web)"; 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)"; if (IS_STANDALONE) return " (Standalone)";
return ""; return "";
}, },

View file

@ -17,7 +17,7 @@
*/ */
import { DataStore } from "@api/index"; import { DataStore } from "@api/index";
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants"; import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants";
import { isPluginDev } from "@utils/misc"; import { isPluginDev } from "@utils/misc";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -27,9 +27,10 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import plugins from "~plugins"; import plugins from "~plugins";
import settings from "./_core/settings"; import settings from "./settings";
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss";
const AllowedChannelIds = [ const AllowedChannelIds = [
SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_ID,
@ -48,12 +49,12 @@ export default definePlugin({
name: "vencord-debug", name: "vencord-debug",
description: "Send Vencord Debug info", description: "Send Vencord Debug info",
predicate: ctx => AllowedChannelIds.includes(ctx.channel.id), predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),
execute() { async execute() {
const { RELEASE_CHANNEL } = window.GLOBAL_ENV; const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
const client = (() => { const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`; 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}`; if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
// @ts-expect-error // @ts-expect-error
@ -75,6 +76,10 @@ export default definePlugin({
OpenAsar: "openasar" in window, OpenAsar: "openasar" in window,
}; };
if (IS_DISCORD_DESKTOP) {
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
}
const debugInfo = ` const debugInfo = `
**Vencord Debug Info** **Vencord Debug Info**
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")} >>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
@ -111,6 +116,22 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "
onConfirm: rememberDismiss onConfirm: rememberDismiss
}); });
} }
if (IsFirefox) {
const rememberDismiss = () => DataStore.set(FIREFOX_DISMISS_KEY, true);
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>You are using Firefox.</Forms.FormText>
<Forms.FormText>Due to Firefox's stupid extension guidelines, most themes and many plugins will not function correctly.</Forms.FormText>
<Forms.FormText>Do not report bugs. Do not ask for help with broken plugins.</Forms.FormText>
<Forms.FormText>Instead, use a chromium browser, Discord Desktop, or Vesktop.</Forms.FormText>
</div>,
onCancel: rememberDismiss,
onConfirm: rememberDismiss
});
}
} }
} }
}); });

View file

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

43
src/plugins/badge.ts Normal file
View file

@ -0,0 +1,43 @@
/* eslint-disable header/header */
import { BadgePosition, ProfileBadge } from "@api/Badges";
import { Badges } from "@api/index";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { UserStore } from "@webpack/common";
const SHIGGY_BADGE = "https://cdn.discordapp.com/emojis/1101838344146665502.gif?size=240&quality=lossless";
const BLOBFOXBOX_BADGE = "https://cdn.discordapp.com/emojis/1036216552736952350.webp?size=240&quality=lossless";
const ShiggyBadge: ProfileBadge = {
description: "true shiggy fan",
image: SHIGGY_BADGE,
position: BadgePosition.START,
props: {
style: { transform: "scale(0.9)" }
},
shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
link: "https://ryanccn.dev/"
};
const BlobfoxBoxBadge: ProfileBadge = {
description: "blobfox",
image: BLOBFOXBOX_BADGE,
position: BadgePosition.START,
props: {
style: { transform: "scale(0.9)" }
},
shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
link: "https://ryanccn.dev/"
};
export default definePlugin({
name: "Ryan's Extra Badges",
description: "shiggy",
authors: [Devs.RyanCaoDev],
dependencies: ["BadgeAPI"],
start() {
Badges.addBadge(ShiggyBadge);
Badges.addBadge(BlobfoxBoxBadge);
},
});

View file

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

View file

@ -23,7 +23,7 @@ import { Clipboard, Toasts } from "@webpack/common";
export default definePlugin({ export default definePlugin({
name: "BetterRoleDot", name: "BetterRoleDot",
authors: [Devs.Ven], authors: [Devs.Ven, Devs.AutumnVN],
description: description:
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously", "Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
@ -43,6 +43,23 @@ export default definePlugin({
match: /"(?:username|dot)"===\i(?!\.\i)/g, match: /"(?:username|dot)"===\i(?!\.\i)/g,
replace: "true", replace: "true",
}, },
},
{
find: ".ADD_ROLE_A11Y_LABEL",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
replacement: {
match: /"dot"===\i/,
replace: "true"
}
},
{
find: ".roleVerifiedIcon",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
replacement: {
match: /"dot"===\i/,
replace: "true"
}
} }
], ],
@ -50,7 +67,14 @@ export default definePlugin({
bothStyles: { bothStyles: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show both role dot and coloured names", description: "Show both role dot and coloured names",
restartNeeded: true,
default: false, default: false,
},
copyRoleColorInProfilePopout: {
type: OptionType.BOOLEAN,
description: "Allow click on role dot in profile popout to copy role color",
restartNeeded: true,
default: false
} }
}, },

View file

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

View file

@ -49,7 +49,7 @@ export default definePlugin({
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')" replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
}, { }, {
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g, match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')" replace: "$1,vcProps=$2$3+(vcProps.nsfw?' vc-nsfw-img':'')"
}] }]
} }
], ],

View file

@ -0,0 +1,63 @@
/*
* 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/>.
*/
/*
* This plugin was modified from code licensed under the following license:
*
* MIT License
*
* Copyright (c) 2021-present Sebastian Law
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { findByPropsLazy } from "@webpack";
import { Tooltip } from "@webpack/common";
export default function Indicator({ layers, bottom }: { layers: number; bottom: boolean; }) {
return (
<Tooltip color="black" position="top" text={layers <= 1 ? "🥺" : `Decoded from ${layers} nested bottom messages`}>
{({ onMouseLeave, onMouseEnter }) => (
<span
className={`power-bottom-indicator ${findByPropsLazy("edited").edited}`}
style={{ color: "var(--text-muted)" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{bottom ? "(bottom)" : "(original)"}
</span>
)}
</Tooltip>
);
}

View file

@ -0,0 +1,73 @@
/*
* 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/>.
*/
/*
* This file was adapted from https://github.com/bottom-software-foundation/bottom-js
* Which is, hopefully, licensed under MIT.
*/
const CHARACTER_VALUES: [number, string][] = [
[200, "🫂"],
[50, "💖"],
[10, "✨"],
[5, "🥺"],
[1, ","],
[0, "❤️"],
];
const SECTION_SEPERATOR = "👉👈";
const FINAL_TERMINATOR = new RegExp(`(${SECTION_SEPERATOR})?$`);
function encodeChar(charValue: number): string {
if (charValue === 0) return "";
const [val, currentCase]: [number, string] =
CHARACTER_VALUES.find(([val]) => charValue >= val) || CHARACTER_VALUES[-1];
return `${currentCase}${encodeChar(charValue - val)}`;
}
export function encode(value: string): string {
return Array.from(new TextEncoder().encode(value))
.map((v: number) => encodeChar(v) + SECTION_SEPERATOR)
.join("");
}
export function decode(value: string): string {
return new TextDecoder().decode(Uint8Array.from(
value
.trim()
.replace(FINAL_TERMINATOR, "")
.split(SECTION_SEPERATOR)
.map(letters => {
return Array.from(letters)
.map(character => {
const [value, emoji]: [number, string] = CHARACTER_VALUES.find(
([_, em]) => em === character
) || [-1, ""];
if (!emoji) {
throw new TypeError(`Invalid bottom text: '${character}'`);
}
return value;
})
.reduce((p, c) => p + c);
})
));
}
export default {
encode: encode,
decode: decode
};

View file

@ -0,0 +1,158 @@
/*
* 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/>.
*/
/*
* This plugin was modified from code licensed under the following license:
*
* MIT License
*
* Copyright (c) 2021-present Sebastian Law
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { FluxDispatcher, MessageStore } from "@webpack/common";
import type { Message } from "discord-types/general";
import Bottom from "./encoding";
class BottomHandler {
cache: Record<string, Record<string, { originalContent: string; top?: boolean; layers?: number; }>>;
re: RegExp;
constructor() {
this.cache = {};
this.re = /((?:((?:\uD83E\uDEC2)?(?:💖)*(?:✨)*(?:🥺)*(?:,)*(❤️)?)(?:👉👈|\u200b))+)/gm;
}
isTranslated(message) {
if (
!this.cache[message.channel_id] ||
!this.cache[message.channel_id][message.id]
) { return false; }
return this.cache[message.channel_id][message.id].originalContent !== message.content;
}
translate(text: string, notNested: boolean) {
var original = text;
var translated = text;
var layers = 0;
while (original.match(this.re)) {
translated = original.replace(this.re, (str, p1, offset, s) => Bottom.decode(p1) || p1);
// the regex can sometimes pick up invalid bottom in which case we want to return to avoid an infinite loop
if (translated === original || notNested) break;
else {
original = translated;
layers++;
}
}
return {
translated: translated,
layers: layers,
};
}
translateMessage(message: Message, decodeLayers: boolean) {
if (!message.content || message.content.length === 0) {
return "";
}
// Build cache if it doesn't exist
if (!this.cache[message.channel_id]) {
this.cache[message.channel_id] = {};
}
if (!this.cache[message.channel_id][message.id]) {
this.cache[message.channel_id][message.id] = {
originalContent: message.content,
};
}
const cached = this.cache[message.channel_id][message.id];
if (this.isTranslated(message)) {
// if we're reverting back to original, just set the content back to original
message.content = cached.originalContent;
this.updateMessage(message);
} else {
// the message hasn't been edited, let's try to decode it
const { translated, layers } = this.translate(message.content, !decodeLayers);
if (translated === message.content) {
// we don't want to do anything if there is no bottom
// since the translation fails, mark this message to not show the indicator
cached.top = true;
throw new Error("No Bottom detected 🥺");
} else {
// let the indicator show how many layers of decoding we did
cached.layers = layers;
message.content = translated;
this.updateMessage(message);
}
}
}
updateMessage(message: Message) {
console.log({
bottomTranslation: true,
type: "MESSAGE_UPDATE",
message,
});
FluxDispatcher.dispatch({
bottomTranslation: true,
type: "MESSAGE_UPDATE",
message,
});
}
clearCache() {
for (const channelID in this.cache) {
for (const messageID in this.cache[channelID]) {
this.removeMessage(channelID, messageID);
}
}
this.cache = {};
}
removeMessage(channelID: string, messageID: string, reset = true) {
const message = MessageStore.getMessage(channelID, messageID);
if (reset) {
message.content = this.cache[channelID][messageID].originalContent;
this.updateMessage(message);
}
delete this.cache[channelID][messageID];
}
}
export default BottomHandler;

View file

@ -0,0 +1,262 @@
/*
* 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/>.
*/
/*
* This plugin was modified from code licensed under the following license:
*
* MIT License
*
* Copyright (c) 2021-present Sebastian Law
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { findOption, RequiredMessageOption } from "@api/Commands";
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, Toasts } from "@webpack/common";
import Indicator from "./components/Indicator";
import Bottom from "./encoding";
import BottomHandler from "./handler";
const Handler = new BottomHandler();
const settings = definePluginSettings({
"decode-layers": {
description: "Decode Layers",
type: OptionType.BOOLEAN,
default: true,
},
"auto-encode-send": {
description: "Automatically encode outgoing messages",
type: OptionType.BOOLEAN,
default: false,
},
"encode-send-type": {
description: "Automatic Encode Behavior",
type: OptionType.SELECT,
options:
[
{
label: "All",
default: true,
value: 0,
},
{
label: "Inline (Greedy)",
value: 1,
},
{
label: "Inline (Parsed)",
value: 2,
}
],
},
"inline-bottom-prefix": {
description: "Inline bottom prefix",
type: OptionType.STRING,
default: "👉",
},
"inline-bottom-suffix": {
description: "Inline bottom suffix",
type: OptionType.STRING,
default: "👈",
},
});
const escapeRegex: (string: string) => string = string => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
function count(string: string, subString: string): number {
var n = 0;
var pos = 0;
const step = subString.length;
while (true) {
pos = string.indexOf(subString, pos);
if (pos >= 0) {
n++;
pos += step;
} else break;
}
return n;
}
function inlineEncode(p: string, s: string, text: string): string {
var np = count(text, p);
var ns = count(text, s);
if (np === 0 || ns === 0) return text;
var pl = p.length;
var sl = s.length;
const result: string[] = [];
let idx = 0;
while (true) {
var startIndex = text.indexOf(p, idx);
if (startIndex < 0) {
result.push(text.slice(idx));
break;
}
var endIndex = text.indexOf(s, startIndex + pl);
if (endIndex < 0) {
result.push(text.slice(idx));
break;
}
result.push(text.slice(idx, startIndex));
startIndex += pl;
result.push(Bottom.encode(text.slice(startIndex, endIndex)));
endIndex += sl;
idx = endIndex;
}
return result.join("");
}
export default definePlugin({
name: "Bottom",
description: "The Vencord plugin for bottom 🥺",
authors: [
{
id: 1038096782963507210n,
name: "skyevg",
},
],
dependencies: ["MessagePopoverAPI", "CommandsAPI", "MessageEventsAPI", "MessageAccessoriesAPI"],
settings,
start() {
addButton("bottom", msg => {
return {
label: "Translate Bottom",
icon: () => (
<svg x="0" y="0" aria-hidden="false" width="22" height="22" viewBox="0 0 36 36" fill="currentColor" className="icon">
<circle fill="#FFCC4D" cx="18" cy="18" r="18" />
<path fill="#65471B" d="M20.996 27c-.103 0-.206-.016-.309-.049-1.76-.571-3.615-.571-5.375 0-.524.169-1.089-.117-1.26-.642-.171-.525.117-1.089.643-1.26 2.162-.702 4.447-.702 6.609 0 .525.171.813.735.643 1.26-.137.421-.529.691-.951.691z" />
<path fill="#FFF" d="M30.335 12.068c-.903 2.745-3.485 4.715-6.494 4.715-.144 0-.289-.005-.435-.014-1.477-.093-2.842-.655-3.95-1.584.036.495.076.997.136 1.54.152 1.388.884 2.482 2.116 3.163.82.454 1.8.688 2.813.752 1.734.109 3.57-.28 4.873-.909 1.377-.665 2.272-1.862 2.456-3.285.183-1.415-.354-2.924-1.515-4.378z" />
<path fill="#65471B" d="M21.351 7.583c-1.297.55-1.947 2.301-1.977 5.289l.039.068c.897 1.319 2.373 2.224 4.088 2.332.114.007.228.011.341.011 2.634 0 4.849-1.937 5.253-4.524-.115-.105-.221-.212-.343-.316-3.715-3.17-6.467-3.257-7.401-2.86z" />
<path fill="#F4900C" d="M23.841 16.783c3.009 0 5.591-1.97 6.494-4.715-.354-.443-.771-.88-1.241-1.309-.404 2.587-2.619 4.524-5.253 4.524-.113 0-.227-.004-.341-.011-1.715-.108-3.191-1.013-4.088-2.332l-.039-.068c-.007.701.021 1.473.083 2.313 1.108.929 2.473 1.491 3.95 1.584.146.01.291.014.435.014z" />
<circle fill="#FFF" cx="21.413" cy="10.705" r="1.107" />
<path fill="#FFF" d="M12.159 16.783c-3.009 0-5.591-1.97-6.494-4.715-1.161 1.454-1.697 2.963-1.515 4.377.185 1.423 1.079 2.621 2.456 3.285 1.303.629 3.138 1.018 4.873.909 1.013-.064 1.993-.297 2.813-.752 1.231-.681 1.963-1.775 2.116-3.163.06-.542.1-1.042.136-1.536-1.103.923-2.47 1.487-3.95 1.58-.146.011-.291.015-.435.015z" />
<path fill="#65471B" d="M12.159 15.283c.113 0 .227-.004.341-.011 1.715-.108 3.191-1.013 4.088-2.332l.039-.068c-.031-2.988-.68-4.739-1.977-5.289-.934-.397-3.687-.31-7.401 2.859-.122.104-.227.211-.343.316.404 2.588 2.619 4.525 5.253 4.525z" />
<path fill="#F4900C" d="M16.626 12.872l-.039.068c-.897 1.319-2.373 2.224-4.088 2.332-.114.007-.228.011-.341.011-2.634 0-4.849-1.937-5.253-4.524-.47.429-.887.866-1.241 1.309.903 2.745 3.485 4.715 6.494 4.715.144 0 .289-.005.435-.014 1.48-.093 2.847-.657 3.95-1.58.062-.841.091-1.614.083-2.317z" />
<path fill="#FFF" d="M9.781 11.81c.61-.038 1.074-.564 1.035-1.174-.038-.61-.564-1.074-1.174-1.036-.61.038-1.074.564-1.036 1.174.039.61.565 1.074 1.175 1.036z" />
</svg>
),
message: msg,
channel: ChannelStore.getChannel(msg.channel_id),
onClick: async () => {
try {
Handler.translateMessage(msg, settings.store["decode-layers"]);
} catch (e) {
console.error(e);
Toasts.show(
{
id: Toasts.genId(),
message: e.message,
type: Toasts.Type.MESSAGE
}
);
}
}
};
});
addAccessory("bottom", props => {
try {
if (!Handler.cache[props.message.channel_id][props.message.id].top) {
try {
return (
<ErrorBoundary>
<Indicator layers={Handler.cache[props.message.channel_id][props.message.id].layers ?? 0} bottom={!Handler.isTranslated(props.message)} />
</ErrorBoundary>
);
} catch { }
}
}
catch { }
return null;
});
this.preSend = addPreSendListener((_, msg) => {
if (settings.store["auto-encode-send"]) {
const sendType = settings.store["encode-send-type"];
var { content } = msg;
switch (sendType) {
case 0: // all
content = Bottom.encode(content);
break;
case 1: // inline greedy
var prefix = escapeRegex(settings.store["inline-bottom-prefix"]);
var suffix = escapeRegex(settings.store["inline-bottom-suffix"]);
var reg = new RegExp(`${prefix}(.+)${suffix}`, "gm");
content = content.replace(reg, (str, p1, o, s) => Bottom.encode(p1));
break;
case 2: // inline parsed
var prefix = settings.store["inline-bottom-prefix"];
var suffix = settings.store["inline-bottom-prefix"];
content = inlineEncode(prefix, suffix, content);
break;
}
msg.content = content;
}
});
},
stop() {
removeButton("bottom");
removeAccessory("bottom");
removePreSendListener(this.preSend);
},
commands: [
{
name: "bottom",
description: "Translate and send text as bottom 🥺",
options: [RequiredMessageOption],
execute: opts => ({
content: Bottom.encode(findOption(opts, "message", "")),
})
}
]
});

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
export const defaultRules = [ export const defaultRules = [
"action_object_map", "action_object_map",
"action_type_map", "action_type_map",
@ -128,6 +127,9 @@ export const defaultRules = [
"redircnt@yandex.*", "redircnt@yandex.*",
"feature@youtube.com", "feature@youtube.com",
"kw@youtube.com", "kw@youtube.com",
"si@youtube.com",
"pp@youtube.com",
"si@youtu.be",
"wt_zmc", "wt_zmc",
"utm_source", "utm_source",
"utm_content", "utm_content",
@ -136,4 +138,5 @@ export const defaultRules = [
"utm_term", "utm_term",
"si@open.spotify.com", "si@open.spotify.com",
"igshid", "igshid",
"share_id@reddit.com",
]; ];

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 { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater"; import { maybePromptToUpdate } from "@utils/updater";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, NavigationRouter } from "@webpack/common"; import { FluxDispatcher, NavigationRouter } from "@webpack/common";
import type { ReactElement } from "react"; import type { ReactElement } from "react";
const CrashHandlerLogger = new Logger("CrashHandler"); const CrashHandlerLogger = new Logger("CrashHandler");
const ModalStack = findByPropsLazy("pushLazy", "popAll");
const settings = definePluginSettings({ const settings = definePluginSettings({
attemptToPreventCrashes: { attemptToPreventCrashes: {
@ -51,8 +53,6 @@ export default definePlugin({
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
enabledByDefault: true, enabledByDefault: true,
popAllModals: undefined as (() => void) | undefined,
settings, settings,
patches: [ patches: [
@ -62,13 +62,6 @@ export default definePlugin({
match: /(?=this\.setState\()/, match: /(?=this\.setState\()/,
replace: "$self.handleCrash(this)||" 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); CrashHandlerLogger.debug("Failed to close open context menu.", err);
} }
try { try {
this.popAllModals?.(); ModalStack?.popAll();
} catch (err) { } catch (err) {
CrashHandlerLogger.debug("Failed to close old modals.", 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/>. * 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 { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards"; import { isTruthy } from "@utils/guards";
@ -86,8 +86,7 @@ const settings = definePluginSettings({
appID: { appID: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Application ID (required)", description: "Application ID (required)",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (!value) return "Application ID is required."; if (!value) return "Application ID is required.";
if (value && !/^\d+$/.test(value)) return "Application ID must be a number."; if (value && !/^\d+$/.test(value)) return "Application ID must be a number.";
@ -97,8 +96,7 @@ const settings = definePluginSettings({
appName: { appName: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Application name (required)", description: "Application name (required)",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (!value) return "Application name is required."; if (!value) return "Application name is required.";
if (value.length > 128) return "Application name must be not longer than 128 characters."; if (value.length > 128) return "Application name must be not longer than 128 characters.";
@ -108,8 +106,7 @@ const settings = definePluginSettings({
details: { details: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Details (line 1)", description: "Details (line 1)",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (value && value.length > 128) return "Details (line 1) must be not longer than 128 characters."; if (value && value.length > 128) return "Details (line 1) must be not longer than 128 characters.";
return true; return true;
@ -118,8 +115,7 @@ const settings = definePluginSettings({
state: { state: {
type: OptionType.STRING, type: OptionType.STRING,
description: "State (line 2)", description: "State (line 2)",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (value && value.length > 128) return "State (line 2) must be not longer than 128 characters."; if (value && value.length > 128) return "State (line 2) must be not longer than 128 characters.";
return true; return true;
@ -128,8 +124,7 @@ const settings = definePluginSettings({
type: { type: {
type: OptionType.SELECT, type: OptionType.SELECT,
description: "Activity type", description: "Activity type",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
options: [ options: [
{ {
label: "Playing", label: "Playing",
@ -157,16 +152,14 @@ const settings = definePluginSettings({
streamLink: { streamLink: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Twitch.tv or Youtube.com link (only for Streaming activity type)", description: "Twitch.tv or Youtube.com link (only for Streaming activity type)",
restartNeeded: true, onChange: onChange,
onChange: setRpc, disabled: isStreamLinkDisabled,
isDisabled: isStreamLinkDisabled,
isValid: isStreamLinkValid isValid: isStreamLinkValid
}, },
timestampMode: { timestampMode: {
type: OptionType.SELECT, type: OptionType.SELECT,
description: "Timestamp mode", description: "Timestamp mode",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
options: [ options: [
{ {
label: "None", label: "None",
@ -190,9 +183,8 @@ const settings = definePluginSettings({
startTime: { startTime: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "Start timestamp (only for custom timestamp mode)", description: "Start timestamp (only for custom timestamp mode)",
restartNeeded: true, onChange: onChange,
onChange: setRpc, disabled: isTimestampDisabled,
isDisabled: isTimestampDisabled,
isValid: (value: number) => { isValid: (value: number) => {
if (value && value < 0) return "Start timestamp must be greater than 0."; if (value && value < 0) return "Start timestamp must be greater than 0.";
return true; return true;
@ -201,9 +193,8 @@ const settings = definePluginSettings({
endTime: { endTime: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "End timestamp (only for custom timestamp mode)", description: "End timestamp (only for custom timestamp mode)",
restartNeeded: true, onChange: onChange,
onChange: setRpc, disabled: isTimestampDisabled,
isDisabled: isTimestampDisabled,
isValid: (value: number) => { isValid: (value: number) => {
if (value && value < 0) return "End timestamp must be greater than 0."; if (value && value < 0) return "End timestamp must be greater than 0.";
return true; return true;
@ -211,16 +202,14 @@ const settings = definePluginSettings({
}, },
imageBig: { imageBig: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Big image key", description: "Big image key/link",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: isImageKeyValid isValid: isImageKeyValid
}, },
imageBigTooltip: { imageBigTooltip: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Big image tooltip", description: "Big image tooltip",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (value && value.length > 128) return "Big image tooltip must be not longer than 128 characters."; if (value && value.length > 128) return "Big image tooltip must be not longer than 128 characters.";
return true; return true;
@ -228,16 +217,14 @@ const settings = definePluginSettings({
}, },
imageSmall: { imageSmall: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Small image key", description: "Small image key/link",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: isImageKeyValid isValid: isImageKeyValid
}, },
imageSmallTooltip: { imageSmallTooltip: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Small image tooltip", description: "Small image tooltip",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (value && value.length > 128) return "Small image tooltip must be not longer than 128 characters."; if (value && value.length > 128) return "Small image tooltip must be not longer than 128 characters.";
return true; return true;
@ -246,8 +233,7 @@ const settings = definePluginSettings({
buttonOneText: { buttonOneText: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Button 1 text", description: "Button 1 text",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (value && value.length > 31) return "Button 1 text must be not longer than 31 characters."; if (value && value.length > 31) return "Button 1 text must be not longer than 31 characters.";
return true; return true;
@ -256,14 +242,12 @@ const settings = definePluginSettings({
buttonOneURL: { buttonOneURL: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Button 1 URL", description: "Button 1 URL",
restartNeeded: true, onChange: onChange
onChange: setRpc
}, },
buttonTwoText: { buttonTwoText: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Button 2 text", description: "Button 2 text",
restartNeeded: true, onChange: onChange,
onChange: setRpc,
isValid: (value: string) => { isValid: (value: string) => {
if (value && value.length > 31) return "Button 2 text must be not longer than 31 characters."; if (value && value.length > 31) return "Button 2 text must be not longer than 31 characters.";
return true; return true;
@ -272,26 +256,29 @@ const settings = definePluginSettings({
buttonTwoURL: { buttonTwoURL: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Button 2 URL", description: "Button 2 URL",
restartNeeded: true, onChange: onChange
onChange: setRpc
} }
}); });
function isStreamLinkDisabled(): boolean { function onChange() {
setRpc(true);
if (Settings.plugins.CustomRPC.enabled) setRpc();
}
function isStreamLinkDisabled() {
return settings.store.type !== ActivityType.STREAMING; return settings.store.type !== ActivityType.STREAMING;
} }
function isStreamLinkValid(): boolean | string { function isStreamLinkValid(value: 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."; if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
return true; return true;
} }
function isTimestampDisabled(): boolean { function isTimestampDisabled() {
return settings.store.timestampMode !== TimestampMode.CUSTOM; return settings.store.timestampMode !== TimestampMode.CUSTOM;
} }
function isImageKeyValid(value: string) { 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?:\/\/(?!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/...)"; 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; return true;
@ -342,13 +329,10 @@ async function createActivity(): Promise<Activity | undefined> {
}; };
break; break;
case TimestampMode.CUSTOM: case TimestampMode.CUSTOM:
if (startTime) { if (startTime || endTime) {
activity.timestamps = { activity.timestamps = {};
start: startTime, if (startTime) activity.timestamps.start = startTime;
}; if (endTime) activity.timestamps.end = endTime;
if (endTime) {
activity.timestamps.end = endTime;
}
} }
break; break;
case TimestampMode.NONE: case TimestampMode.NONE:

View file

@ -0,0 +1,5 @@
# Dearrow
Makes YouTube embed titles and thumbnails less sensationalist, powered by [Dearrow](https://dearrow.ajay.app/)
https://github.com/Vendicated/Vencord/assets/45497981/7bf81108-102d-47c5-8ba5-357db4db1283

View file

@ -0,0 +1,161 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import { Tooltip } from "@webpack/common";
import type { Component } from "react";
interface Props {
embed: {
rawTitle: string;
provider?: {
name: string;
};
thumbnail: {
proxyURL: string;
};
video: {
url: string;
};
dearrow: {
enabled: boolean;
oldTitle?: string;
oldThumb?: string;
};
};
}
const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/;
async function embedDidMount(this: Component<Props>) {
try {
const { embed } = this.props;
if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return;
const videoId = embedUrlRe.exec(embed.video.url)?.[1];
if (!videoId) return;
const res = await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`);
if (!res.ok) return;
const { titles, thumbnails } = await res.json();
const hasTitle = titles[0]?.votes >= 0;
const hasThumb = thumbnails[0]?.votes >= 0;
if (!hasTitle && !hasThumb) return;
embed.dearrow = {
enabled: true
};
if (titles[0]?.votes >= 0) {
embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = titles[0].title;
}
if (thumbnails[0]?.votes >= 0) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
}
this.forceUpdate();
} catch (err) {
new Logger("Dearrow").error("Failed to dearrow embed", err);
}
}
function DearrowButton({ component }: { component: Component<Props>; }) {
const { embed } = component.props;
if (!embed?.dearrow) return null;
return (
<Tooltip text={embed.dearrow.enabled ? "This embed has been dearrowed, click to restore" : "Click to dearrow"}>
{({ onMouseEnter, onMouseLeave }) => (
<button
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")}
onClick={() => {
const { enabled, oldThumb, oldTitle } = embed.dearrow;
embed.dearrow.enabled = !enabled;
if (oldTitle) {
embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = oldTitle;
}
if (oldThumb) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
embed.thumbnail.proxyURL = oldThumb;
}
component.forceUpdate();
}}
>
{/* Dearrow Icon, taken from https://dearrow.ajay.app/logo.svg (and optimised) */}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24px"
height="24px"
viewBox="0 0 36 36"
aria-label="Toggle Dearrow"
>
<path
fill="#1213BD"
d="M36 18.302c0 4.981-2.46 9.198-5.655 12.462s-7.323 5.152-12.199 5.152s-9.764-1.112-12.959-4.376S0 23.283 0 18.302s2.574-9.38 5.769-12.644S13.271 0 18.146 0s9.394 2.178 12.589 5.442C33.931 8.706 36 13.322 36 18.302z"
/>
<path
fill="#88c9f9"
d="m 30.394282,18.410186 c 0,3.468849 -1.143025,6.865475 -3.416513,9.137917 -2.273489,2.272442 -5.670115,2.92874 -9.137918,2.92874 -3.467803,0 -6.373515,-1.147212 -8.6470033,-3.419654 -2.2734888,-2.272442 -3.5871299,-5.178154 -3.5871299,-8.647003 0,-3.46885 0.9420533,-6.746149 3.2144954,-9.0196379 2.2724418,-2.2734888 5.5507878,-3.9513905 9.0196378,-3.9513905 3.46885,0 6.492841,1.9322561 8.76633,4.204698 2.273489,2.2724424 3.788101,5.2974804 3.788101,8.7663304 z"
/>
<path
fill="#0a62a5"
d="m 23.95823,17.818306 c 0,3.153748 -2.644888,5.808102 -5.798635,5.808102 -3.153748,0 -5.599825,-2.654354 -5.599825,-5.808102 0,-3.153747 2.446077,-5.721714 5.599825,-5.721714 3.153747,0 5.798635,2.567967 5.798635,5.721714 z"
/>
</svg>
</button>
)}
</Tooltip>
);
}
export default definePlugin({
name: "Dearrow",
description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow",
authors: [Devs.Ven],
embedDidMount,
renderButton(component: Component<Props>) {
return (
<ErrorBoundary noop>
<DearrowButton component={component} />
</ErrorBoundary>
);
},
patches: [{
find: "this.renderInlineMediaEmbed",
replacement: [
// patch componentDidMount to replace embed thumbnail and title
{
match: /(\i).render=function.{0,50}\i\.embed/,
replace: "$1.componentDidMount=$self.embedDidMount,$&"
},
// add dearrow button
{
match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/,
replace: "children:[$self.renderButton(this),"
}
]
}],
});

View file

@ -0,0 +1,12 @@
.vc-dearrow-toggle-off svg {
filter: grayscale(1);
}
.vc-dearrow-toggle-on, .vc-dearrow-toggle-off {
all: unset;
display: inline;
cursor: pointer;
position: absolute;
top: 0.75rem;
right: 0.75rem;
}

View file

@ -24,11 +24,9 @@ import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
const StickersStore = findStoreLazy("StickersStore"); const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS("); const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
@ -120,7 +118,7 @@ function getGuildCandidates(data: Data) {
return Object.values(GuildStore.getGuilds()).filter(g => { return Object.values(GuildStore.getGuilds()).filter(g => {
const canCreate = g.ownerId === meId || const canCreate = g.ownerId === meId ||
BigInt(PermissionStore.getGuildPermissions({ id: g.id }) & MANAGE_EMOJIS_AND_STICKERS) === MANAGE_EMOJIS_AND_STICKERS; (PermissionStore.getGuildPermissions({ id: g.id }) & PermissionsBits.CREATE_GUILD_EXPRESSIONS) === PermissionsBits.CREATE_GUILD_EXPRESSIONS;
if (!canCreate) return false; if (!canCreate) return false;
if (data.t === "Sticker") return true; if (data.t === "Sticker") return true;

View file

@ -19,7 +19,7 @@
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
@ -27,6 +27,7 @@ import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react"; import type { ReactElement, ReactNode } from "react";
const DRAFT_TYPE = 0; const DRAFT_TYPE = 0;
@ -102,6 +103,11 @@ interface StickerPack {
stickers: Sticker[]; stickers: Sticker[];
} }
const enum FakeNoticeType {
Sticker,
Emoji
}
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/; const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
@ -212,15 +218,15 @@ export default definePlugin({
} }
}, },
{ {
find: "canStreamHighQuality:function", find: "canUseHighVideoUploadQuality:function",
predicate: () => settings.store.enableStreamQualityBypass, predicate: () => settings.store.enableStreamQualityBypass,
replacement: [ replacement: [
"canUseHighVideoUploadQuality", "canUseHighVideoUploadQuality",
"canStreamHighQuality", // TODO: Remove the last two when they get removed from stable
"canStreamMidQuality" "(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
].map(func => { ].map(func => {
return { return {
match: new RegExp(`${func}:function\\(\\i\\){`), match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
replace: "$&return true;" replace: "$&return true;"
}; };
}) })
@ -305,16 +311,24 @@ export default definePlugin({
}, },
{ {
match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/, match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/,
replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice("STICKER",${reactNode},!!${props}.renderableSticker?.fake)` replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!${props}.renderableSticker?.fake)`
} }
] ]
}, },
{ {
find: ".Messages.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION", find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,",
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
match: /((\i)=\i\.node,\i=\i\.emojiSourceDiscoverableGuild)(.+?return )(.{0,450}Messages\.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION.+?}\))/, match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<=(\i)=\i\.node.+?)/,
replace: (_, rest1, node, rest2, reactNode) => `${rest1},fakeNitroNode=${node}${rest2}$self.addFakeNotice("EMOJI",${reactNode},fakeNitroNode.fake)` replace: (m, node) => `${m}fakeNitroNode:${node},`
}
},
{
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
predicate: () => settings.store.transformEmojis,
replacement: {
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
} }
} }
], ],
@ -610,18 +624,18 @@ export default definePlugin({
return link.target && fakeNitroEmojiRegex.test(link.target); return link.target && fakeNitroEmojiRegex.test(link.target);
}, },
addFakeNotice(type: "STICKER" | "EMOJI", node: Array<ReactNode>, fake: boolean) { addFakeNotice(type: FakeNoticeType, node: Array<ReactNode>, fake: boolean) {
if (!fake) return node; if (!fake) return node;
node = Array.isArray(node) ? node : [node]; node = Array.isArray(node) ? node : [node];
switch (type) { switch (type) {
case "STICKER": { case FakeNoticeType.Sticker: {
node.push(" This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users."); node.push(" This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users.");
return node; return node;
} }
case "EMOJI": { case FakeNoticeType.Emoji: {
node.push(" This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users."); node.push(" This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users.");
return node; return node;
@ -650,15 +664,11 @@ export default definePlugin({
}, },
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
const [{ parseURL }, { const { parseURL } = importApngJs();
GIFEncoder,
quantize,
applyPalette
}] = await Promise.all([importApngJs(), getGifEncoder()]);
const { frames, width, height } = await parseURL(stickerLink); const { frames, width, height } = await parseURL(stickerLink);
const gif = new GIFEncoder(); const gif = GIFEncoder();
const resolution = Settings.plugins.FakeNitro.stickerSize; const resolution = Settings.plugins.FakeNitro.stickerSize;
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");

View file

@ -0,0 +1,6 @@
# FavoriteEmojiFirst
Puts your favorite emoji first in the emoji autocomplete.
![a screenshot of the favourite emojis section](https://github.com/Vendicated/Vencord/assets/45497981/419c8c16-1afc-46e0-9cc2-20b9c3489711)
![a comparison of the emoji picker before and after enabling this plugin](https://github.com/Vendicated/Vencord/assets/45497981/4f57626d-cfc6-4155-a47c-2eac191231bb)

View file

@ -0,0 +1,5 @@
# FavoriteGifSearch
Adds a search bar to favorite gifs.
![Screenshot](https://github.com/Vendicated/Vencord/assets/45497981/19552adc-d921-4153-976e-e9361dc8fdaf)

View file

@ -60,7 +60,7 @@ interface Instance {
} }
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "gutterSize"); const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput");
export const settings = definePluginSettings({ export const settings = definePluginSettings({
searchOption: { searchOption: {
@ -87,7 +87,7 @@ export const settings = definePluginSettings({
export default definePlugin({ export default definePlugin({
name: "FavoriteGifSearch", name: "FavoriteGifSearch",
authors: [Devs.Aria], authors: [Devs.Aria],
description: "Adds a search bar for favorite gifs", description: "Adds a search bar to favorite gifs.",
patches: [ patches: [
{ {

View file

@ -1,56 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Forms } from "@webpack/common";
export default definePlugin({
name: "FixInbox",
description: "Fixes the Unreads Inbox from crashing Discord when you're in lots of guilds.",
authors: [Devs.Megu],
patches: [{
find: "INBOX_OPEN:function",
replacement: {
// This function normally dispatches a subscribe event to every guild.
// this is badbadbadbadbad so we just get rid of it.
match: /INBOX_OPEN:function.+?\{/,
replace: "$&return true;"
}
}],
settingsAboutComponent() {
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">What's the problem?</Forms.FormTitle>
<Forms.FormText style={{ marginBottom: 8 }}>
By default, Discord emits a GUILD_SUBSCRIPTIONS event for every guild you're in.
When you're in a lot of guilds, this can cause the gateway to ratelimit you.
This causes the client to crash and get stuck in an infinite ratelimit loop as it tries to reconnect.
</Forms.FormText>
<Forms.FormTitle tag="h3">How does it work?</Forms.FormTitle>
<Forms.FormText>
This plugin works by stopping the client from sending GUILD_SUBSCRIPTIONS events to the gateway when you open the unreads inbox.
This means that not all unreads will be shown, instead only already-subscribed guilds' unreads will be shown, but your client won't crash anymore.
</Forms.FormText>
</Forms.FormSection>
);
}
});

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

@ -1,6 +1,6 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors * Copyright (c) 2023 Vendicated and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
@ -35,14 +35,29 @@ export default definePlugin({
name: "create friend invite", name: "create friend invite",
description: "Generates a friend invite link.", description: "Generates a friend invite link.",
inputType: ApplicationCommandInputType.BOT, inputType: ApplicationCommandInputType.BOT,
execute: async (_, ctx) => { options: [{
if (!UserStore.getCurrentUser().phone) name: "Uses",
description: "How many uses?",
choices: [
{ label: "1", name: "1", value: "1" },
{ label: "5", name: "5", value: "5" }
],
required: false,
type: ApplicationCommandOptionType.INTEGER
}],
execute: async (args, ctx) => {
const uses = findOption<number>(args, "Uses", 5);
if (uses === 1 && !UserStore.getCurrentUser().phone)
return sendBotMessage(ctx.channel.id, { return sendBotMessage(ctx.channel.id, {
content: "You need to have a phone number connected to your account to create a friend invite!" content: "You need to have a phone number connected to your account to create a friend invite with 1 use!"
}); });
let invite: any;
if (uses === 1) {
const random = uuid.v4(); const random = uuid.v4();
const invite = await RestAPI.post({ const { body: { invite_suggestions } } = await RestAPI.post({
url: "/friend-finder/find-friends", url: "/friend-finder/find-friends",
body: { body: {
modified_contacts: { modified_contacts: {
@ -50,15 +65,17 @@ export default definePlugin({
}, },
phone_contact_methods_count: 1 phone_contact_methods_count: 1
} }
}).then(res => });
FriendInvites.createFriendInvite({ invite = await FriendInvites.createFriendInvite({
code: res.body.invite_suggestions[0][3], code: invite_suggestions[0][3],
recipient_phone_number_or_email: random, recipient_phone_number_or_email: random,
contact_visibility: 1, contact_visibility: 1,
filter_visibilities: [], filter_visibilities: [],
filtered_invite_suggestions_index: 1 filtered_invite_suggestions_index: 1
}) });
); } else {
invite = await FriendInvites.createFriendInvite();
}
sendBotMessage(ctx.channel.id, { sendBotMessage(ctx.channel.id, {
content: ` content: `
@ -67,7 +84,7 @@ export default definePlugin({
Max uses: \`${invite.max_uses}\` Max uses: \`${invite.max_uses}\`
`.trim().replace(/\s+/g, " ") `.trim().replace(/\s+/g, " ")
}); });
}, }
}, },
{ {
name: "view friend invites", name: "view friend invites",
@ -95,7 +112,7 @@ export default definePlugin({
execute: async (_, ctx) => { execute: async (_, ctx) => {
await FriendInvites.revokeFriendInvites(); await FriendInvites.revokeFriendInvites();
return void sendBotMessage(ctx.channel.id, { sendBotMessage(ctx.channel.id, {
content: "All friend invites have been revoked." content: "All friend invites have been revoked."
}); });
}, },

View file

@ -32,12 +32,18 @@ function makeIcon(showCurrentGame?: boolean) {
return function () { return function () {
return ( return (
<svg <svg
width="24" width="20"
height="24" height="20"
viewBox="0 96 960 960" 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" /> <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 && <line x1="920" y1="280" x2="40" y2="880" stroke="var(--status-danger)" stroke-width="80" />} {!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> </svg>
); );
}; };
@ -60,7 +66,7 @@ function GameActivityToggleButton() {
export default definePlugin({ export default definePlugin({
name: "GameActivityToggle", name: "GameActivityToggle",
description: "Adds a button next to the mic and deafen button to toggle game activity.", 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"], dependencies: ["SettingsStoreAPI"],
patches: [ patches: [

Some files were not shown because too many files have changed in this diff Show more