Compare commits

..

709 commits

Author SHA1 Message Date
17bb28a19b
Merge remote-tracking branch 'upstream/main' 2024-09-27 11:12:29 -04:00
Vendicated
832e874c35
bump to v1.10.3 2024-09-26 18:19:08 +02:00
sadan4
bc59fc41b3
Fix multiple plugins for latest Discord update (#2911)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
2024-09-25 22:25:48 -03:00
Vendicated
1abfb5f0cf
CustomIdle: fix crash on canary 2024-09-25 23:12:08 +02:00
2af1cd19b5
Merge remote-tracking branch 'upstream/main' 2024-09-25 17:06:21 -04:00
Nuckyz
cb2848f186
ContextMenuAPI: Fix findGroupChildrenByChildId 2024-09-24 02:13:44 -03:00
sadan4
c3f2e76b9c
BetterFolders: Fix sidebar in wrong location (#2904) 2024-09-24 02:09:33 -03:00
Nuckyz
eb63a54fa6
UserVoiceShow: Fix incorrect dependencies 2024-09-23 13:23:30 -03:00
lewisakura
5881716c57
RoleColorEverywhere: Fix unneeded restart on setting change (#2899) 2024-09-23 13:23:30 -03:00
sadan4
d7cbe270e5
FakeNitro: Fix wrongfully allowed emojis in voice calls (#2901) 2024-09-23 13:23:30 -03:00
Nuckyz
c29362ca89
FullSearchContext: Re-add Copy Author ID 2024-09-23 13:23:30 -03:00
Nuckyz
409f47bf24
PronounDB: Fix crashing 2024-09-22 14:26:50 -03:00
Vendicated
b0e2f310bc
RoleColorEverywhere: add chat message colour toggle 2024-09-22 18:26:10 +02:00
Nuckyz
65069c673c
UserVoiceShow: Fix showing hidden channels 2024-09-22 13:05:08 -03:00
Nuckyz
b1db18c319
PronounDB: Rework API to avoid rate limits 2024-09-22 12:47:18 -03:00
0779d4d1a8
Merge remote-tracking branch 'upstream/main' 2024-09-22 10:27:44 -04:00
TheGreenPig
db5fe2a394
Fix plugin settings inconsistency regarding setting names (#2884) 2024-09-22 04:48:54 -03:00
sadan4
e4318a887a
ConsoleJanitor: Ignore all loggers with whitelist (#2896) 2024-09-22 04:48:54 -03:00
Kyuuhachi
eaf62d8c1c
RoleColorEverywhere: Add coloring to message contents (#2893) 2024-09-22 07:11:07 +00:00
DokterKaj
22a5b18bfa
CopyFileContents: Add padding to button (#2848) 2024-09-21 16:54:50 -03:00
Drew
1dc2d92493
ReplaceGoogleSearch: Fix DuckDuckGo URL (#2895) 2024-09-21 16:54:49 -03:00
Joona
492b0cff08
OpenInApp: Fix opening in spotify activity cards for web (#2894) 2024-09-21 16:54:49 -03:00
Nuckyz
2d675b4b2e
ReviewDB: Fix in panel profile (again) 2024-09-21 16:54:49 -03:00
Nuckyz
49b0a38c37
UserVoiceShow: Show in messages 2024-09-21 16:54:49 -03:00
Nuckyz
467157539c
Bump to 1.10.2 2024-09-20 09:16:16 -03:00
Nuckyz
e8242f22c9
UserVoiceShow: Better support for DM channels 2024-09-20 09:06:26 -03:00
Vendicated
f7587d9b2e
ShowHiddenThings: fix discovery keyword filter bypass 2024-09-20 02:03:50 +02:00
adryd
755e869db7
clearURLs: Add si@soundcloud.com to rule list (#2890) 2024-09-20 01:31:20 +02:00
Nuckyz
a015cf96f6
UserVoiceShow: Fix setting name 2024-09-19 13:33:32 -03:00
Vendicated
c7e5295da0
SearchReply => FullSearchContext ~ now adds all options back 2024-09-18 21:33:46 +02:00
Vendicated
8afd79dd50
add Icons to webpack commons 2024-09-18 01:36:52 +02:00
Vendicated
65c5897dc3
remove need to depend on CommandsAPI 2024-09-18 01:26:25 +02:00
Nuckyz
6cce8a8bc4
Experiments: Allow clips to be recorded without streaming 2024-09-17 14:30:23 -03:00
Nuckyz
1848b16536
ReviewDB: Fix in panel profile 2024-09-17 14:30:23 -03:00
Kyuuhachi
c572116b97
BetterSettings: Add submenu for plugins (#2858)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-09-17 15:40:11 +00:00
Lumap
e26986f66a
AppleMusicRichPresence: fix formatting when listening to radio (#2869)
Co-authored-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com>
Co-authored-by: v <vendicated@riseup.net>
2024-09-17 17:29:46 +02:00
Nuckyz
f12335a371
UserVoiceShow: Fix for simplified profiles 2024-09-16 15:16:41 -03:00
Nuckyz
640d99dcda
delete NoDefaultHangStatus ~ Removed feature 2024-09-16 07:51:10 -03:00
e7d081dc2a
updated vc-timezones submodule
Signed-off-by: cswimr <seaswimmerthefsh@gmail.com>
2024-09-14 23:43:52 -04:00
91b6df95d6
Merge remote-tracking branch 'upstream/main' 2024-09-14 23:42:02 -04:00
Vendicated
bcfef05a8a
delete TimeBarAllActivites ~ now a stock feature 2024-09-14 17:22:29 +02:00
Cookie
f17b92c2fd
OpenInApp: support Spotify prerelease links (#2870) 2024-09-14 15:03:58 +00:00
Ryan Cao
292f7d71d3
AppleMusicRichPresence: fix metadata fetching (#2864) 2024-09-14 16:59:05 +02:00
sadan4
b822542352
BetterFolders: Fix pending clan applications (#2867) 2024-09-12 07:46:00 -03:00
Nuckyz
f27361f017
TimeBarAllActivities: Fix timestamp component 2024-09-11 08:13:23 -03:00
thororen
a765212cfe
NoTrack: Fix blocking analytics (#2857) 2024-09-11 07:50:55 -03:00
Relitrix
56459bdcad
new plugin AccountPanelServerProfile (#2836) 2024-09-06 12:33:23 -03:00
a0b5e7e745
Merge remote-tracking branch 'upstream/main' 2024-09-06 09:09:02 -04:00
Nuckyz
868b2ea9f0
Fix SortFriendRequests received date 2024-09-06 09:40:10 -03:00
Nuckyz
9e7f8829f2
CustomRPC: Fix activity preview styling 2024-09-06 09:22:52 -03:00
niko
e5a4db6460
TimeBarAllActivities: Fix not working (#2847) 2024-09-06 11:40:29 +00:00
Vendicated
8890c8c6b4
fix Commands, Badges, RoleColorEverywhere 2024-09-05 02:35:40 +02:00
Vendicated
61d3c08f1f
bump to v1.10.1 2024-09-05 02:25:27 +02:00
Vendicated
e6994e1946
MentionAvatars: Fix compatibility with ServerInfo plugin 2024-09-05 02:24:35 +02:00
Nyako
40512d7294
XSOverlay: fix profile images (#2788)
Fixes https://github.com/Vendicated/Vencord/issues/2787

Co-authored-by: v <vendicated@riseup.net>
2024-09-05 00:16:41 +00:00
Vendicated
be02baffaa
MemberCount: fix null safety
and improve types for PopoutPosition

Co-Authored-By: fres621 <126067139+fres621@users.noreply.github.com>
2024-09-05 02:13:40 +02:00
Haruka
99cd423efb
fix crashing on canary when searching slash commands (#2844) 2024-09-04 23:26:08 +02:00
Nuckyz
7333f40db6
Dearrow: Fix thumbnails with default option 2024-09-04 10:16:45 -03:00
ElectricSteve
244cb26c32
Dearrow: Add option to not dearrow by default (#2818) 2024-09-04 09:12:58 -03:00
Nuckyz
df8aec8e3c
Fix BetterFolders and FriendsSince 2024-09-04 08:25:23 -03:00
sadan4
d10e649b63
VolumeBooster: Fix playing sound in wrong output device (#2840) 2024-09-04 08:04:17 -03:00
Ramzi Al Haddad
7f784befc2
SecretRingToneEnabler: Option to always play Snow Halation Theme (#2831) 2024-09-03 10:50:25 +00:00
Maddie
30e4e83158
NoServerEmojis: Fix detecting server emojis (#2835) 2024-09-03 01:53:03 +00:00
Maddie
4c4f2894fb
PronounDB: Fix patched pronoun tooltip (#2832) 2024-09-02 08:37:54 +00:00
June Park
c51d7b8fb4
ReviewDB: Fix wording in server reviews (#2826) 2024-09-02 08:09:45 +00:00
Nuckyz
accfc15125
Ban ts-pattern normal import 2024-09-02 01:54:16 -03:00
Maddie
27e81b20db
Allow online themes to be applied only in dark or light mode (#2701) 2024-09-02 04:51:29 +00:00
Joona
d0ad4e6c1d
MutualGroupDMs: Add Mutual Groups to DM Sidebar (#2817) 2024-09-02 03:50:52 +00:00
vishnyanetchereshnya
0c71d6c3fa
PermissionsViewer: Show RoleIcons & which role grants permission (#2824) 2024-09-02 00:42:52 -03:00
sadan4
74fd85bd3d
VolumeBooster: Fix on Vesktop (#2828)
Also fixed an IgnoreActivities patch and added a README to it
2024-09-01 20:39:19 -03:00
Nuckyz
968e688c10
Bump to 1.9.9 2024-09-01 00:53:46 -03:00
vxray
b595a3e33c
OpenInApp: Add support for localization in Spotify URL regex (#2776) 2024-09-01 03:20:22 +00:00
sadan4
e07a4e19e6
VolumeBooster: Support browser and Vesktop (#2730) 2024-09-01 03:08:33 +00:00
ImBanana
273981deb7
new plugin StickerPaste ~ Insert stickers instead of sending (#2781) 2024-08-31 23:40:03 -03:00
sadan4
81eabc7ee2
PatchHelper: Add Copy as Codeblock button (#2820) 2024-09-01 02:13:22 +00:00
Obsidian
d5eaae9d51
new plugin CopyFileContents ~Easily copy text file attachments contents (#2790) 2024-08-31 23:09:14 -03:00
niko
00276cad7c
TimeBarAllActivities: Support new activity cards (#2813) 2024-09-01 01:45:57 +00:00
Nuckyz
eb0d91fd8e
Add ts-pattern as @webpack/common 2024-08-30 06:12:28 -03:00
Nick Oates
07e629d8d4
SuperReactionTweaks: Allow disabling Super Reactions (#2805) 2024-08-30 05:39:40 +00:00
Cookie
e473a57c3d
IgnoreActivities: Add option for blacklist filter (#2712) 2024-08-30 01:08:48 +00:00
Nuckyz
ad3d936dfd
Fix settings wrapping fallback 2024-08-29 21:41:51 -03:00
Nuckyz
db2f5c9292
ConsoleJanitor: Remove non needed patch
notosans no longer errors
2024-08-29 03:56:21 -03:00
Surge
5bfc608f7d
new plugin AlwaysExpandRoles ~ Alternative to ShowAllRoles (#2809) 2024-08-29 06:43:52 +00:00
Nuckyz
7d8214fc37
Fix PermissionsViewer on user popouts 2024-08-28 02:10:24 -03:00
c7489bfa88
Merge remote-tracking branch 'upstream/main' 2024-08-26 15:06:59 -04:00
SerStars
4b16fbcaa9
MentionAvatars: Also display role icons in role mentions (#2801)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-08-26 10:27:00 +00:00
Nuckyz
7ec842d4b0
MoreUserTags: fix settings ui logic
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-08-23 20:09:58 -03:00
Vendicated
6659f2c413
bump to v1.9.8 2024-08-24 00:19:10 +02:00
Nuckyz
1fb5e8df99
Add missing methods to ExpressionPickerStore 2024-08-23 19:00:26 -03:00
Nuckyz
1e8f59f13d
ReviewDB: Add view review button to other profiles types 2024-08-23 18:50:05 -03:00
Nuckyz
f0e6986835
PronounDB: Fix on user profiles 2024-08-23 18:41:40 -03:00
Supertiger
8afcb8e4dd
new plugin NoMaskedLinkPaste (#2782)
Co-authored-by: v <vendicated@riseup.net>
2024-08-23 23:36:47 +02:00
ImBanana
2569c39ddf
MemberCount: add thread support (#2785)
Co-authored-by: v <vendicated@riseup.net>
2024-08-23 17:54:12 +02:00
Nuckyz
ed9b28febf
Fix ViewIcons and NoProfileThemes 2024-08-23 03:02:04 -03:00
Nuckyz
2e81b9aeba
PermissionsViewer: Fix user popout 2024-08-22 20:29:39 -03:00
v
44c8463496
Remove obsolete patches for old profiles (#2800)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-08-22 17:13:57 +02:00
Vendicated
2bdc2f4e82
delete ShowAllRoles ~ now a stock feature 2024-08-22 17:08:46 +02:00
Nuckyz
66b247b2d3
Fix persisting $$vencordPatchedSource when a module is loaded again 2024-08-20 04:13:21 -03:00
351fa9ff86
Merge remote-tracking branch 'upstream/main' 2024-08-19 18:13:36 -04:00
Nuckyz
3d80cb2d55
Delete AutomodContext ~ Now a stock feature 2024-08-19 17:50:21 -03:00
rini ☔
904022a2f7
[Webkeybinds] Don't override browser keybinds (#2792) 2024-08-18 20:20:03 +02:00
Nuckyz
9cada9ad13
fix(Webpack): Not canonicalizing regex in some places 2024-08-18 00:47:16 -03:00
Nuckyz
051bce89f8
feat(Webpack): Add $$vencordPatchedSource to patched factories 2024-08-18 00:26:40 -03:00
HAHALOSAH
eaca14bb5a
Fix Online Themes tab (#2786) 2024-08-17 04:30:34 +00:00
Nuckyz
d388aa4347
Decor: Fix on current user area 2024-08-14 19:34:59 -03:00
b1559dd519
Merge remote-tracking branch 'upstream/main' 2024-08-14 14:30:30 -04:00
Nuckyz
4301ed889d
Fix ShowHiddenThings feature & PauseInvitesForever 2024-08-13 01:51:18 -03:00
Joona
1a712e75a6
fix(SpotifyControls): sync with external shuffle toggles (#2767)
Co-authored-by: v <vendicated@riseup.net>
2024-08-10 16:35:21 +00:00
Vendicated
87e6fa8647
build: improve errors when breaking module boundaries 2024-08-10 17:25:47 +02:00
Lumap
003e4a08d5
fix PictureInPicture (#2773)
Co-authored-by: v <vendicated@riseup.net>
2024-08-10 16:39:33 +02:00
Vendicated
4e3c178043
fix MutualGroupDMs 2024-08-10 16:35:11 +02:00
d1111fc488
Merge remote-tracking branch 'upstream/main' 2024-08-05 00:41:44 -04:00
Vendicated
5160f906f4
delete MaskedLinkPaste ~ now a stock Discord feature 2024-08-03 19:02:48 +02:00
Vendicated
021d9bf179
fix message hover buttons... again 2024-08-03 17:54:25 +02:00
Vendicated
d1996034b7
fix YoutubeAdblock not working for some users 2024-08-03 15:18:35 +02:00
Vendicated
8274b67597
fix message hover buttons 2024-08-03 11:04:03 +02:00
Nuckyz
e437498c8f
Fix broken patches 2024-08-02 21:27:02 -03:00
Nyako
09c6c16cf9
XSOverlay: Return old API for compatibility (#2753) 2024-08-02 18:19:15 +00:00
Aiden
e99eec50bc
Migrate to eslint flat config; update dependencies (#2627)
Co-authored-by: vee <vendicated@riseup.net>
2024-08-02 12:12:59 +02:00
52c63bf13c
Merge remote-tracking branch 'upstream/main' 2024-08-01 22:34:17 -04:00
Vendicated
d919cd6bf1
bump to v1.9.7 2024-08-01 15:08:08 +02:00
Sqaaakoi
c185f47f4d
ShowHiddenThings: Fix ModView highest role fix (#2747) 2024-08-01 13:04:52 +00:00
ingobeans
83d90f03ee
FakeProfileThemes: fix crash when encountering invalid colours (#2714) 2024-08-01 15:01:48 +02:00
Vendicated
0f8d21a846
new plugin YoutubeAdblock: blocks ads in embeds (formerly WatchTogetherAdblock) 2024-08-01 14:50:09 +02:00
Sqaaakoi
2658459a98
fix: Add ViewRaw & MessageLogger context menu options on threads (#2750) 2024-08-01 12:12:44 +00:00
Ashton
f8b01c1a31
Translate: Add DeepL support (#2721)
Co-authored-by: v <vendicated@riseup.net>
2024-08-01 14:10:27 +02:00
jenku
2382294e8b
Decor: add copy preset id button (#2737)
Co-authored-by: v <vendicated@riseup.net>
2024-08-01 08:57:00 +00:00
Luna
d47be6c017
MentionAvatars: Add option to hide @ symbol(#2725)
Co-authored-by: v <vendicated@riseup.net>
2024-08-01 07:20:00 +00:00
Nick Oates
6c12a33aa6
fix(BetterFolders): Close folders when switching accounts (#2748) 2024-08-01 09:12:02 +02:00
Scab
9cc42bf457
MutualGroupDms: make display consistent with Mutual Servers (#2727)
Co-authored-by: Cookie <52550063+Covkie@users.noreply.github.com>
2024-08-01 09:10:30 +02:00
Vendicated
1bfdcf2697
fix BetterUploadButton on canary 2024-07-31 03:08:57 +02:00
Vendicated
3013c669c0
Fix ShowConnections
Co-Authored-By: Masterjoona <69722179+Masterjoona@users.noreply.github.com>
2024-07-31 03:02:10 +02:00
fres621
e460b5efb6
Fix MessagePopoverAPI patch (#2746)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-07-30 20:26:58 -03:00
Nyako
902a86c3b2
XSOverlay: Update to new API (#2736)
Co-authored-by: v <vendicated@riseup.net>
2024-07-30 21:48:11 +00:00
Surge
51ae019cd5
feat(plugins/openInApp) Refactor code and add Apple Music support (#2744)
Co-authored-by: v <vendicated@riseup.net>
Co-authored-by: Shiggy <136832773+shiggybot@users.noreply.github.com>
2024-07-30 23:42:57 +02:00
Sqaaakoi
0f5cf37ef9
fix(ShowHiddenThings): always render highest role in ModView (#2709) 2024-07-30 21:18:42 +00:00
thororen
5c88284ed3
feat(showHiddenChannels): Fix Broken Patch (#2726) 2024-07-26 12:55:32 -03:00
Vendicated
5e9a9fe836
bump to v1.9.6 2024-07-25 13:49:01 +02:00
Masterjoona
bc801853e2
Fix ImageZoom, SpotifyControls and ViewIcons (#2723)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-07-25 13:46:00 +02:00
Vendicated
2044264729
fix PictureInPicture on canary 2024-07-23 02:56:24 +02:00
41702851c7
updated submodules
Signed-off-by: Seaswimmer <seaswimmerthefsh@gmail.com>
2024-07-20 15:42:16 -04:00
9ad552a5dc
Merge remote-tracking branch 'upstream/main' 2024-07-20 15:41:44 -04:00
Nuckyz
3704c71ae1
Fix loading scientific notation chunks 2024-07-19 00:50:39 -03:00
Vendicated
80b493d7a8
WhoReacted: fix errors 2024-07-17 21:30:10 +02:00
Vendicated
67632ecc11
MentionAvatars: fix mentions being ultra wide in topics 2024-07-17 02:36:19 +02:00
Vendicated
c3852cb892
fix badges on canary 2024-07-17 02:34:05 +02:00
Vendicated
5bd10c8608
MessageLogger: fix edit modal close button 2024-07-15 05:09:13 +02:00
Kyuuhachi
ea746f6633
MessageLogger: add compact display of history & edit modal (#2299)
Co-authored-by: vee <vendicated@riseup.net>
2024-07-15 04:51:42 +02:00
Vendicated
2b273d9dbd
fix(MessageLogger edits): render with proper guild context 2024-07-15 03:54:01 +02:00
Jono99
71977f070a
fix(ShowMeYourName) - proper case insensitive name comparison (#2633)
Co-authored-by: vee <vendicated@riseup.net>
2024-07-14 22:24:55 +02:00
Vendicated
bbf43c3073
fix(FakeNitro): do not convert applicable twitch sub emotes 2024-07-13 20:34:16 +02:00
Hen
0e7570ad71
InvisibleChat: fix embeds missing decrypted content (#2655) 2024-07-13 17:17:00 +00:00
vee
92ae62602b
new plugin MentionAvatars: Shows user avatars inside mentions (#2691) 2024-07-13 19:14:22 +02:00
Nuckyz
0057ab42e8
Fix broken experiments patch 2024-07-12 16:40:26 -03:00
Ulysses Zhan
993304f96c
CtrlEnterSend: fix for new Discord update (#2689)
Co-authored-by: vee <vendicated@riseup.net>
2024-07-12 19:27:28 +00:00
Nuckyz
04dce64bfd
Fix broken patches/finds 2024-07-11 16:56:02 -03:00
c6ae30965c
Updated DontLeak and VencordPlugins (submodules)
Signed-off-by: Seaswimmer <seaswimmerthefsh@gmail.com>
2024-07-11 13:22:52 -04:00
0e701a06d0
Merge remote-tracking branch 'upstream/main' 2024-07-11 13:21:04 -04:00
Nuckyz
9f8c749421
Fix broken patches 2024-07-10 04:33:03 -03:00
Vendicated
02092a985c
BetterNotes: fix crashing 2024-07-06 18:43:13 +02:00
Vendicated
9f79cc05a6
bump to v1.9.4 2024-07-06 18:31:43 +02:00
Vendicated
319a99c293
fix(MessageLogger): don't break attachment edits 2024-07-05 18:43:51 +02:00
Vendicated
c3f7950f2e
MessageLogger: fix not blurring deleted spoilers 2024-07-05 17:12:46 +02:00
Vendicated
93dc880bc0
Delete UrbanDictionary ~ better done via user app
https://discord.com/application-directory/search?q=urban
2024-07-05 16:58:11 +02:00
Vendicated
78d2713151
Delete WikiSearch ~ better done via user app
https://discord.com/application-directory/search?q=wiki
2024-07-05 16:56:32 +02:00
Nuckyz
bec4c76d9a
Fix ShowAllRoles 2024-07-04 17:04:16 -03:00
Nuckyz
256a85c95c
feat(plugins): ConsoleJanitor (#2659) 2024-07-03 00:10:08 -03:00
Vendicated
99b41dba19
improve settings ui (again) 2024-07-02 21:49:09 -03:00
Ulysses Zhan
77492061f5
ctrlEnterSend: fix for new Discord update (#2647) 2024-07-01 00:59:36 +02:00
605f0fd9f3
updated submodules 2024-06-30 14:08:22 -04:00
5061de8d0b
Merge remote-tracking branch 'upstream/main' 2024-06-30 04:14:33 -04:00
Vendicated
5c05443f45
improve settings ui 2024-06-29 20:27:00 +02:00
Masterjoona
8a7c0d7e61
WebContextMenus: implement context menu for text selection (#2577)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-29 16:42:27 +00:00
Antti Ellilä
2d570a524b
friendsSince: add support for new profiles (#2623)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-29 16:39:09 +00:00
Luna
086c31c890
new plugin ShowAllRoles: show all roles in simplified profiles (#2624)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-29 15:43:25 +00:00
Vendicated
169edcb5b7
improve support helper 2024-06-29 17:34:13 +02:00
Vendicated
bda0e1844b
browser: increase minimum browser versions 2024-06-29 11:21:13 +02:00
Nuckyz
2fa56b80ab
Harder conditions for Sentry patching 2024-06-29 01:33:16 -03:00
Nuckyz
5c1c786cf9
Fix ReviewDB patch 2024-06-28 18:34:23 -03:00
Nuckyz
62485e8694
Obliterate Sentry 2024-06-28 18:34:23 -03:00
Vendicated
e4bf71784e
TextReplace: support /v regex flag 2024-06-28 16:04:51 +02:00
Vendicated
484d70fb15
bump to v1.9.3 2024-06-28 13:20:24 +02:00
Nuckyz
df6ffd90e3
Fix broken patches 2024-06-28 00:53:09 -03:00
Nuckyz
96873ccef7
Temp fix for disabling Sentry 2024-06-27 23:58:01 -03:00
Nuckyz
cbc7f7230a
Fix acquiring WebpackRequire 2024-06-27 23:37:10 -03:00
Vendicated
e37a0cfec9
bump to v1.9.2 2024-06-27 16:16:29 +02:00
Nuckyz
f81cd5d9a4
Fix broken patches 2024-06-27 16:15:42 +02:00
Vendicated
7c923b9962
fix enabled by default logic 2024-06-27 16:14:32 +02:00
Vendicated
14e11973ef
fix ShowConnections & FakeProfileThemes 2024-06-26 18:42:05 +02:00
Vendicated
bc0d4a80ff
BadgeAPI: fix bugs in new profiles 2024-06-26 18:09:21 +02:00
Vendicated
e0d99e2f6c
add ReviewDB in new profiles 2024-06-26 17:37:21 +02:00
Vendicated
6d4c9339dc
PermissionsViewer: add to simplified profiles 2024-06-26 17:36:00 +02:00
Vendicated
705da29df5
add Mutual Groups to new profiles 2024-06-26 17:10:48 +02:00
Vendicated
7f1ccef383
fix ShowConnections 2024-06-26 13:27:39 +02:00
Scyye
3ad76b7f0f
NewGuildSettings: Add "Apply NewGuildSettings" button in menu (#2556)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-26 11:10:39 +00:00
Lorenzo Rizzotti
4008c93069
ShowHiddenThings: fix discovery filter bypass (#2626)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-26 13:03:23 +02:00
programminglaboratorys
cd205b1386
ReviewDB: add to context menu in new profile ui (#2622)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-26 11:02:20 +00:00
Bloofield
3688c7e4c9
ShowMeYourName: compare username & nick case insensitive (#2630)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-26 10:59:24 +00:00
Oliver Anderson
32c2128c5b
ReplaceGoogleSearch: improve search engine selection (#2620)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-26 10:57:18 +00:00
Vendicated
d61a930b99
Developer Documentation has moved to https://docs.vencord.dev 2024-06-26 01:12:19 +02:00
Jakup
df32e8d305
fix missing space in MoreKaomoji (#2625) 2024-06-25 21:40:25 +02:00
Nuckyz
62afad3e65
IgnoredActivities: Fix not ignoring in tracked activities (used in Content Feed) 2024-06-22 20:22:43 -03:00
Nuckyz
87d3e30ebf
web extension: fix vencord sometimes breaking after reloading
Co-Authored-By: vee <vendicated+git@riseup.net>
2024-06-22 08:05:18 +02:00
7f29a10ecb
Merge remote-tracking branch 'upstream/main' 2024-06-22 01:08:12 -04:00
Vendicated
3d46f19025
bump to v1.9.1 2024-06-22 03:25:40 +02:00
Vendicated
6ce7fde19c
upgrade monaco (QuickCss Editor) to 0.50.0; fixes some syntax 2024-06-22 01:10:08 +02:00
Vendicated
18df66a4b4
fix SpotifyControls 2024-06-22 00:31:28 +02:00
Nuckyz
e16c9ca70f
GameActivityToggle: Fix moving settings button outside 2024-06-21 18:50:21 -03:00
Vendicated
495da11347
fix Summaries 2024-06-21 22:49:55 +02:00
Vendicated
0b033aa51b
PluginManager: catch errors during plugin flux handlers 2024-06-21 22:42:25 +02:00
Vendicated
b9392c3be2
Improve SupportHelper
- improve update check when entering support channel
- add "Run Snippet" button to venbot messages with codeblock
- add "Send /vencord-debug" button to messages that contain /vencord-debug
- add "Update Now" button to messages by venbot and in #known-issues that contain "update"
- add some common issues like RPC disabled / NoRPC enabled to /vencord-debug
- split plugin list into separate /vencord-plugins command to reduce size & avoid >2000 chars errors
2024-06-21 19:58:01 +02:00
Nuckyz
7dc1d4c498
ReverseImageSearch: Fix duplicate find 2024-06-21 04:15:45 -03:00
Vendicated
c7e4bec940
Plugin Page: add indicator for excluded plugins 2024-06-21 04:15:45 -03:00
Nuckyz
db1481711b
Reporter: Test mapMangledModule 2024-06-21 04:15:44 -03:00
Nuckyz
d4ed747434
Clean-up related additions to mangled exports 2024-06-21 04:15:44 -03:00
Seaswimmer
1a6a97f6c2 Merge remote-tracking branch 'upstream/main' 2024-06-20 12:07:28 -04:00
Nuckyz
d07042236d
Add wrapSettingsHook back; Fix FakeNitro subscription emoji bypass 2024-06-19 23:50:03 -03:00
Vendicated
3ce241021f
PluginModals: add plugin website & source code links 2024-06-20 04:40:07 +02:00
Masterjoona
76f6912511
VoiceMessages: properly respect user's microphone choice (#2602) 2024-06-19 20:04:39 +02:00
Vendicated
e9e789be70
fix NewGuildSettings 2024-06-19 16:59:54 +02:00
ryan-0324
920f326053
fix MoreUserTags (#2599) 2024-06-19 16:56:01 +02:00
Vendicated
d12624ac4b
Fix ShowHiddenThings 2024-06-19 16:51:04 +02:00
Vendicated
28ddadf732
Fix CustomIdle 2024-06-19 16:24:24 +02:00
Seaswimmer
7c713dd7aa Merge remote-tracking branch 'upstream/main' 2024-06-19 03:02:08 -04:00
Nuckyz
d04ead7d35
FakeNitro: Fix theme bypass 2024-06-19 02:39:15 -03:00
Vendicated
7b6259215a
Fix VoiceMessages 2024-06-19 07:38:21 +02:00
vee
3505adad6d
final batch of fixes ~ we are SO BACK!! (#2598)
* Fix ImplicitRelationships

* performance fixes

* fix false pos

* fix super reaction tweaks

* Fix PermissionFreeWill

* Fix AlwaysTrust

* clean ups

* Fix ImageLink

* Fix ValidReply

* Fix ShowHiddenChannels partially and race conditions related to exports

* fix bucnh of webpack finds

* Fix FriendsSince, RevealAllSpoilers

* finish show hidden channels

* read if cute

* doomsday fix: ClientTheme (#2597)

* fix friendinvites

* fix extractAndLoadChunks

* bleh

* fix FakeNitro

* fake nitro part 2

* and part 3

* bump to v1.9.0

* remove dead settings patch

* fix ForceOwnerCrown

* fix decor lazy load

---------

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: rushii <33725716+rushiiMachine@users.noreply.github.com>
2024-06-19 07:16:29 +02:00
Seaswimmer
482cfb8140 Merge remote-tracking branch 'upstream/main' 2024-06-18 23:11:44 -04:00
Seaswimmer
8d84f72616 fixed a naming issue preventing discordColorways from being loaded 2024-06-18 23:08:13 -04:00
vee
d19b0aeb5b
second batch of fixes (#2596)
Co-authored-by: programminglaboratorys <107296738+programminglaboratorys@users.noreply.github.com>
Co-authored-by: Haruka <personal@shiroko.me>
Co-authored-by: Amia <9750071+aamiaa@users.noreply.github.com>
Co-authored-by: AutumnVN <autumnvnchino@gmail.com>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-06-19 04:36:21 +02:00
vee
d6f1209438
fix first set of plugins (#2591)
* Add back mangled webpack searching

* Make window non enumerable in all cases

* fix some webpack commons

* oops

* fix more webpack commons

* fix some finds

* fix more webpack commons

* fix common names

* fix reporter

* fix Constants common

* more fix

* fix SettingsStores (return of old SettingsStoreAPI)

* doomsday fix: MutualGroupDMs (#2585)

* fix SettingsStoreAPI

* fix MessageLinkEmbeds

* fix checking uninitialised settings

* doomsday fix: BetterSessions (#2587)

* doomsday fix: ReviewDB and Summaries (#2586)

Co-authored-by: vee <vendicated@riseup.net>

* fix various things that use default/other names

* fix settings

* wbctxmenus

* fix BetterSettings

* wouldnt it be funny if discord reverted again once we're done

* fix ViewIcons

* fix showconnections

* fix FriendsSince

* FakeNitro: fix app icons

* doomsday fix: NoPendingCount (#2590)

---------

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Amia <9750071+aamiaa@users.noreply.github.com>
Co-authored-by: Manti <67705577+mantikafasi@users.noreply.github.com>
2024-06-19 03:04:15 +02:00
Nuckyz
db6b1f5aaf
Fix plugins on stable 2024-06-18 17:13:57 -03:00
Vendicated
098124175f
Fix crashes & settings on canary 2024-06-17 23:00:25 +02:00
SuperStormer
c1593e1806
Dearrow: fix ">" handling (#2582) 2024-06-17 19:13:27 +02:00
Vendicated
e79430ca84
RelationShipNotifier: try to fix false positives for unavailable guilds 2024-06-14 23:04:43 +02:00
Vendicated
ca810250d1
ConsoleShortcuts: add Stores map with all stores 2024-06-14 22:56:21 +02:00
Nuckyz
2f4e346e26
FixCodeblockGap: Fix broken patch 2024-06-13 23:28:38 -03:00
Nuckyz
e0e35058fd
Discord code blocks can't have indentation 2024-06-12 19:15:26 -03:00
Nuckyz
dc74d28b86
Reporter: Fix summary code blocks 2024-06-12 18:30:11 -03:00
Nuckyz
0561bd1951
fix: ShowConnections patch; chore: Remove dead code 2024-06-12 16:39:04 -03:00
Vendicated
40c5ade82d
MessageLinkEmbeds: fix display when using compact mode 2024-06-12 04:48:42 +02:00
Nuckyz
008227cdfc
Bump to 1.8.9 2024-06-11 23:31:42 -03:00
Nuckyz
fd2311db3b
Fix broken patches 2024-06-11 23:10:47 -03:00
Vendicated
9de18ac8c7
Experiments: add toggle for toolbar dev button
Co-Authored-By: F53 <fseusb@gmail.com>
2024-06-12 03:45:29 +02:00
Vendicated
64025bc523
MessageLogger: fix bugs with embeds & edits back to prev state 2024-06-12 02:32:42 +02:00
Nuckyz
26f5e829fe
o7 ResurrectHome - Home feature was removed from Discord
Discord deleted pretty much all the client side code for the legacy home. While it's still possible for the client side code to be reconstructed, it won't be an easy task, so the plugin is getting deleted for now (in case someone ever implements the home again).
2024-06-11 17:45:35 -03:00
Vendicated
aaba22f577
ShowConnections: improve look in simplified prof; fix tooltip overflow 2024-06-09 04:09:08 +02:00
Vendicated
4bf28f4634
BadgeAPI: fix our badges not showing if there are 0 discord badges 2024-06-09 04:09:08 +02:00
programminglaboratorys
50c4513737
RoleColorEverywhere: show role colors in the reactor list (#2490)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-09 04:09:07 +02:00
Masterjoona
62830464af
fix showconnections in new profiles (#2567)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-06-09 04:09:07 +02:00
Vendicated
4ec01d0f40
disable UseEcoQoSForBackgroundProcess 2024-06-08 05:54:36 +02:00
Vendicated
65970618d8
fix badges in new profiles 2024-06-08 05:40:09 +02:00
AutumnVN
810ff894dc
ui(RestartCard): fix yellow button now being blue (#2550) 2024-06-08 04:21:27 +02:00
AutumnVN
914b211a91
betterRoleDot: fix click to copy role color in profile (#2551)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-08 02:21:01 +00:00
Vendicated
2c5079b49f
MessageLogger: fix potential error 2024-06-08 04:15:29 +02:00
vee
239da032ec
README: add shiggy background on github 2024-06-08 00:49:11 +02:00
AutumnVN
5996e67c7d
fix USRBG & ViewIcons in new profiles (#2557)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-06-07 23:42:12 +02:00
Nickyux
29c65948b4
MessageLogger: add context menu option to clear channel history (#2008)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-07 21:28:17 +00:00
Elvyra
1bc9a800a6
fix moreUserTags (#2563) 2024-06-07 23:05:14 +02:00
Lumap
40db2f5078
AppleMusicRichPresence: add option to disable large/small image (#2562) 2024-06-07 23:04:40 +02:00
NuclideK
c54650b29a
customRPC: fix typos in settings descriptions (#2559) 2024-06-07 20:24:49 +02:00
57719c5e4e Merge branch 'main' of https://www.coastalcommits.com/SeaswimmerTheFsh/Vencord; branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-06-06 15:58:52 -04:00
Nuckyz
43b6933fe6
Reporter: Include page errors; load wasm chunks 2024-06-06 00:48:54 -03:00
AutumnVN
0b611a2911
USRBG: fix in simplified profile (#2549) 2024-06-06 03:07:20 +00:00
AutumnVN
5976d52cbc
viewIcons: support new simplified profile (#2535)
Co-authored-by: Sqaaakoi <sqaaakoi-git@sqaaakoi.xyz>
2024-06-06 05:05:53 +02:00
notsu
9cafe8084c
SpotifyControls: fix no artists on local files (#2543)
Co-authored-by: vee <vendicated@riseup.net>
2024-06-06 02:17:47 +00:00
nekohaxx
67b709a796
new plugin NoOnboardingDelay: skip long onboarding animations (#2533)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-06-06 02:01:44 +00:00
nyx
0dac08c17d
PlatformIndicators: fix embedded (console) devices (#2546)
Co-authored-by: Vendicated <vendicated@riseup.net>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-06-06 03:40:31 +02:00
Vendicated
b88be8014e
experiments: change toolbar help button -> dev menu 2024-06-06 02:55:18 +02:00
vishnyanetchereshnya
e5e8b9ba01
new plugin CopyEmojiMarkdown ~ more easily copy emoji formatting (#2266)
Co-authored-by: Happy enderman <66224387+happyendermangit@users.noreply.github.com>
Co-authored-by: vee <vendicated@riseup.net>
2024-06-06 01:40:02 +02:00
Ryan Cao
0aa7bef9fa
new plugin AppleMusicRichPresence (#2455)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-06-06 01:19:53 +02:00
Vendicated
9ab7b8b9c9
experiments: remove obsolete isStaff patch; rename ServerProfile -> ServerInfo 2024-06-05 23:46:53 +02:00
Nuckyz
23584393a9
NoPendingCount: Fix for message requests 2024-06-05 23:46:53 +02:00
Nuckyz
ed5ae2ba5c
Add shortcut for lazy loading chunks 2024-06-05 23:46:52 +02:00
Vendicated
8fd5d068da
fix(css): brand-experiment is now brand-500 2024-06-05 23:46:52 +02:00
ca48faa89d Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-06-04 11:23:26 -04:00
lewisakura
06824c273f
chore: security advisory link for blank issues [skip ci] (#2542) 2024-06-03 12:01:34 +00:00
Nuckyz
a66138f157
Bump to 1.8.8 2024-06-01 00:30:18 -03:00
Nuckyz
aa7eb77050
ShowHiddenChannels: Fix patch 2024-05-31 23:41:38 -03:00
Nuckyz
d07e4c71b5
Make Reporter runnable in desktop 2024-05-31 23:28:58 -03:00
c045a02598 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-05-31 19:50:44 -04:00
Nuckyz
7ccd073506
Fix ShowConnections & FriendsSince patches 2024-05-31 00:16:56 -03:00
Nuckyz
2b565fed25
Make vencord-debug usable everywhere if user is pluginDev 2024-05-30 18:34:16 -03:00
Nuckyz
05a40445c8
refactor: improve build scripts & automatic testing
- Fix reporter breaking because of ConsoleShortcuts
- Fix extractAndLoadChunks issue with 2 match groups; Improve testing of lazy extractAndLoadChunks
- Reporter: Properly implement reporter build of Vencord; Test more plugins; Fix running in wrong pages
- Fix wrong external files and clean up build script; Remove non used stuff
2024-05-30 23:30:44 +02:00
Nuckyz
537fc5e33d
feat(API): updateMessage API for forcing re-renders 2024-05-29 04:57:18 -03:00
Vendicated
9b9a5322c9
webpack: make window exports non enumerable 2024-05-29 06:32:49 +02:00
Vendicated
da01237c05
Summaries: update README 2024-05-29 05:21:05 +02:00
Vendicated
86aabe73eb
add more flags for preventing background unloading 2024-05-29 04:24:48 +02:00
vee
a78dba321d
ConsoleShortcuts: Fix autocomplete on lazies, add more utils (#2519) 2024-05-28 17:31:58 -03:00
Lexi
b9e83d9d28
new plugin DontRoundMyTimestamps: round 7.6y -> 7y instead of 8y (#2060)
Co-authored-by: V <vendicated@riseup.net>
2024-05-28 02:22:42 +00:00
AutumnVN
8131ca8f15
USRBG: support new simplified profile (#2501)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-28 02:15:48 +00:00
Vendicated
5b35d7c644
fix occasional errors in Dearrow & ImageZoom 2024-05-28 02:35:40 +02:00
sunnie
c2f8837602
new plugin: MaskedLinkPaste (#2514)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-28 02:23:30 +02:00
nin0dev
c431b7d2ab
fix(MessageLogger): correctly mark markdown headers red (#2511)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-28 02:21:12 +02:00
Ulysses Zhan
8bda3a1e6a
LoadingQuotes: more customization & custom quotes support (#1795)
Co-authored-by: lewisakura <lewi@lewisakura.moe>
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-05-27 18:36:09 +02:00
Nuckyz
6b4899804a
Summaries: Fix start error if no summaries-data exist 2024-05-26 21:16:12 -03:00
Luna
41c5bbd952
new plugin WatchTogetherAdblock: block ads in youtube activity (#2021)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-26 19:44:04 +00:00
Vendicated
9ec671819d
build: improve fileInclude plugin 2024-05-26 19:15:51 +02:00
Vendicated
c836270320
fix minor bugs in various plugins
- FriendsSince: Don't show for friend requests
- FakeNitro: Fix attempting to bypass unicode emojis #2503
- MessageLatency: ignore bots #2504
- CtrlEnterSend: use cmd+enter on macOS #2502
- ReplaceGoogleSearch: trim LF character #2488

Co-authored-by: AutumnVN <autumnvnchino@gmail.com>
Co-authored-by: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com>
Co-authored-by: Lumap <lumap@duck.com>
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
2024-05-26 18:24:02 +02:00
Manti
4f2c2b8e4a
new plugin Summaries: show Discords AI-generated convo summaries (#2481)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-05-26 18:14:29 +02:00
Nuckyz
a8e18f17e2
ConsoleShortcuts: Start at Init 2024-05-26 01:11:36 -03:00
Nuckyz
8f59cd8a1a
Optimize slowest patches 2024-05-23 21:48:12 -03:00
Vendicated
1866e4d379
Bump to v1.8.6 2024-05-24 01:07:07 +02:00
Vendicated
349169e67a
discord why tf would u roll back to 10 days old build??? 2024-05-24 01:05:17 +02:00
Nuckyz
775877281e
Revert removal of DevTools context menu fix 2024-05-23 06:04:50 -03:00
Vendicated
a0778f6a2e
work around discord unloading in background 2024-05-23 03:35:02 +02:00
Vendicated
869e71112e
fix AnonymiseFilenames 2024-05-23 03:26:23 +02:00
Vendicated
b335df7fe2
MessageLogger: fix edit logging 2024-05-23 03:25:02 +02:00
Nuckyz
f686cba398
Fix not setting property on originalOnChunksLoaded 2024-05-22 05:11:09 -03:00
Nuckyz
f469060ccf
Fix reporter false positive and DefaultExtractAndLoadChunksRegex not catching all cases 2024-05-22 00:47:12 -03:00
Nuckyz
afd56820db
Revert "MessageLinkEmbeds: No longer need to reset global regex"
It is still needed for messageLinkRegex.test
2024-05-21 23:58:37 -03:00
PWall
0751722add
QuickReply: skip blocked messages if NoBlockedMessages enabled (#2476) 2024-05-21 02:52:43 +02:00
k26pl
44d708129b
fix(MessageLogger): correctly blur spoilered images (#2433)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-21 00:44:29 +00:00
dolfies
08d7de06b2
ShowHiddenThings: more effectively explode Algolia filters (#2484)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-21 02:28:06 +02:00
goodbee
9c092b9c29
feat(BetterRoleContext): Add option to view role icons (#2482)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-21 00:24:00 +00:00
Alyxia Sother
f384fe6aa5
fakeProfileThemes: settings UI improvements (#966)
Co-authored-by: V <vendicated@riseup.net>
2024-05-21 00:13:24 +00:00
638adf747c
update submodule 2024-05-20 16:29:50 -04:00
d3096d6f27 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-05-20 16:20:00 -04:00
Vendicated
dac2d7520d
bump to v1.8.5 2024-05-20 18:19:45 +02:00
Nuckyz
025508f18d
StartupTimings: Fix patch 2024-05-19 23:32:15 -03:00
Nuckyz
0a595120b9
Fix: Ignore bundled lib webpack on web 2024-05-19 23:08:33 -03:00
Nuckyz
5f8b96dced
Change duplicate find for SHC and VCDoubleClick 2024-05-19 22:11:42 -03:00
Nuckyz
a94b88cd56
MessageLinkEmbeds: No longer need to reset global regex 2024-05-19 04:20:27 -03:00
Nuckyz
b33b5bdc9f
MessageLinkEmbeds: Add limit for nested links 2024-05-19 03:54:49 -03:00
Nuckyz
bc8b465753
chore: Make package manager version not strict 2024-05-19 03:40:38 -03:00
Nuckyz
eac8a026a6
fix(PatchHelper): Make find and match more responsive 2024-05-18 21:53:38 -03:00
Eric
d43731833a
Fix: PatchHelper not auto filling match field (#2338) 2024-05-19 00:45:05 +00:00
Noxillio
caed7cd92c
MoreUserTags: If server owner tag is disabled, do not give other tags (#2219) 2024-05-19 00:22:45 +00:00
Eric
54e1bac6c6
new plugin CustomIdle (#2342) 2024-05-18 23:41:58 +00:00
Nuckyz
04a86490a5
FriendsSince: Show in user profile modal 2024-05-18 00:36:50 -03:00
Nuckyz
4e92612aa8
ResurrectHome: Likely fix breaking latest messages in chat 2024-05-17 19:41:12 -03:00
Nuckyz
8b0e7030ad
ViewIcons: Fix Group Icons being clickable in channel list 2024-05-17 18:42:28 -03:00
vee
c3757a2ae6
add package for publishing types to npm (#2473)
https://www.npmjs.com/package/@vencord/types
2024-05-17 23:01:07 +02:00
a57ee6d726 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-05-17 15:25:07 -04:00
flag
54817ab506
lastfmRPC: add setting to toggle "View Song" button (#2292)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-17 16:36:35 +00:00
Nico
5fc6ba86d1
fix(replaceGoogleSearch): correct GitHub casing (#2471) 2024-05-17 17:40:01 +02:00
Nuckyz
84e477f678
Add missing README to new plugins 2024-05-17 05:43:40 -03:00
Nico
0b4b6031c5
new plugin NoDefaultHangStatus (#2468) 2024-05-17 08:21:12 +00:00
Nuckyz
60f8225b96
chore: Fix non standard plugin names 2024-05-17 04:51:59 -03:00
Nuckyz
6547cc10f7
FakeNitro: Fix attempting to bypass unicode emojis
Closes #2470
2024-05-17 04:34:50 -03:00
Moxxie
ffe1d7cc4d
new plugin ReplaceGoogleSearch (#2450) 2024-05-17 04:17:14 -03:00
Tuur Martens
03d83e1ff7
new plugin AutomodContext (#2290) 2024-05-17 04:11:41 -03:00
Nuckyz
0c50e153ef
FakeNitro: Fix & rewrite emoji bypass patches 2024-05-16 23:02:50 -03:00
Nuckyz
c5e554e48c
ViewIcon: Replace regex find with string find 2024-05-16 02:37:24 -03:00
nyx
cddc811c02
feat(ViewIcons): Group & User DMs icons support (#2464) 2024-05-16 05:26:40 +00:00
DShadow
fb19642d8d
fix(readAllNotificationsButton): Mark threads as read (#2437) 2024-05-16 01:07:14 -03:00
Sqaaakoi
4281b7a94a
ShowTimeoutDuration: Simplify tooltip style, allow changing style without reload (#2441) 2024-05-16 00:21:52 -03:00
Nuckyz
09f894468a
MessageLatency: Fix wrong constant & false positive 2024-05-15 23:38:36 -03:00
rozbrajaczpoziomow
7b4ecff67e
feat(MessageLatency): Show milliseconds option (#2454) 2024-05-15 23:22:45 -03:00
Nuckyz
c0c897fc23
extractAndLoadChunksLazy: Cache result to avoid searching factories everytime 2024-05-15 23:00:21 -03:00
Eric
0460374af0
Fix: Plugins without start/stop function failing to stop/start (#2463) 2024-05-16 01:46:09 +00:00
Nuckyz
54f58cd7c9
Fix: Canonicalize regex finds 2024-05-15 00:38:18 -03:00
Nuckyz
f74da73086
feat: Allow finds to use regex (#2452) 2024-05-14 23:57:43 -03:00
ScattrdBlade
4da8b9aad7
PetPet: Fix Upload Image Option (#2461) 2024-05-15 02:44:47 +00:00
Aztup
f4d6461690
feat(plugins/openInApp) Add tidal support (#2404)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-15 04:35:00 +02:00
Eric
4d572670f1
new plugin ValidReply ~ fix "Message could not be loaded" (#2337)
Co-authored-by: V <vendicated@riseup.net>
2024-05-15 02:10:29 +00:00
Nuckyz
1fea842093
BetterFolders: Fix scrolling 2024-05-14 22:47:35 -03:00
mcpower
46801de21f
chore: tidy up suggested vscode extensions list (#2221)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-05-15 01:42:09 +00:00
c1bac12a1a Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-05-14 21:27:05 -04:00
Vendicated
0e4724ec0d
Settings: remove obsolete patch; add redundancy & more useful dbg copy 2024-05-15 03:14:02 +02:00
Nuckyz
840d571ce2
Fix BetterSettings & StartupTimings patch 2024-05-14 21:34:34 -03:00
! Sleepy
97dd56ccda
MoreUserTags: Add chat moderator tag (#2424)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-05-15 02:17:19 +02:00
Ulysses Zhan
81d3f5df1a
new plugin CtrlEnterSend (#1794)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-15 01:21:00 +02:00
Ulysses Zhan
5232a85319
new plugin NoServerEmoji ~ hides server emojis from autocomplete (#1787)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-15 01:18:30 +02:00
Board
d8b3869b81
ThemeAttributes: add larger avatar url variables to avatars (#2449)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-14 23:07:33 +00:00
Vendicated
719c6140f3
fix Vencord Settings section being added multiple times 2024-05-14 21:18:43 +02:00
Vendicated
a54b55edad
bump to v1.8.4 2024-05-14 18:54:00 +02:00
Vendicated
12376c622e
fix settings ui on canary 2024-05-14 18:52:35 +02:00
Nuckyz
d4ebfc233f
Make all RestAPI calls use Endpoints object 2024-05-13 23:00:11 -03:00
Nuckyz
9dc8e4e244
Properly ErrorBoundary recent changes 2024-05-13 23:00:11 -03:00
Anubis
892167420a
MessageLogger: use discord variables instead of hardcoded colors (#2428) 2024-05-14 01:57:20 +00:00
Haruka
5d049534a7
FakeNitro: allow using subscription-locked emojis (#2456)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-05-14 03:39:05 +02:00
Amia
bd6f9e6f32
fix(MutualGroupDMs): properly pass props (#2457)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-14 01:22:49 +00:00
axiand
9621dc7bb3
EmoteCloner: allow cloning from reactions (#2458) 2024-05-14 01:16:49 +00:00
dolfies
59ee9c501d
feat(ShowHiddenThings): Remove Discovery banned/NSFW filters (#2453) 2024-05-13 05:09:19 +00:00
Vendicated
f6765818d2
ValidUser: fix rendering old mentions when message is edited
Fixes https://github.com/Vendicated/Vencord/issues/2451
2024-05-13 03:54:15 +02:00
Vendicated
1f1c80c5f3
ValidUser: fix crashing when viewing a valid-userd staff's profile 2024-05-13 03:30:24 +02:00
Nuckyz
902b6bcdf2
PinDMs: ErrorBoundary renderChannel 2024-05-12 20:58:26 -03:00
dolfies
fd7dafb153
fix(MessageLatency): Adjust for Discord kotlin clients (#2443) 2024-05-12 23:07:12 +00:00
nyan
5c7fa5578c
XSOverlay: Adjust message length timeout (#2445) 2024-05-12 19:54:08 -03:00
Vendicated
bbec51fd19
but here's the bumper (v1.8.3) 2024-05-12 03:23:00 +02:00
Cats
a99354503f
feat(Translate): add toggle for chat bar icon (#2418) 2024-05-12 02:44:06 +02:00
HAHALOSAH
d6507947f5
Plugin Settings: fix text overflow for long plugin names (#2383)
Co-authored-by: V <vendicated@riseup.net>
2024-05-12 00:32:44 +00:00
Claire
f21db5cb01
add Native settings implementation (#2346)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-12 02:08:17 +02:00
Vendicated
0f9acba59e
settingsSync: include date in filename for better sorting
Co-authored-by: cd CreepArghhh_ <65649991+cd-CreepArghhh@users.noreply.github.com>
2024-05-12 02:00:29 +02:00
Elvyra
b22bfc80fd
pronounDB: Update to API v2 (#2355)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-12 01:23:51 +02:00
Fafa
cc5e39c9a9
Dearrow: allow configuring which elements get dearrowd (#2414)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-05-11 21:50:29 +00:00
Nuckyz
c55b0de30c
FakeNitro: Update description 2024-05-11 18:34:54 -03:00
nin0dev
207fe84636
CustomRPC: show warning when game activity is disabled (#2245)
Co-authored-by: V <vendicated@riseup.net>
2024-05-11 23:29:31 +02:00
Overcast Warmth System
9b328da4ce
ThemeAttributes: add data-author-username to messages (#2422) 2024-05-11 22:57:48 +02:00
Im_Banana
2eb8ba1841
SilentTyping: add chat input context menu option to toggle (#2386)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-11 22:49:00 +02:00
Han Seung Min - 한승민
6b88eaccbb
messageLatency: fix grammar & add aliucord/kotlin client tooltip (#2426) 2024-05-11 16:09:48 +00:00
nyan
fbaa4ad5bc
XSOverlay: add settings for different notification types (#2055)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-11 18:05:22 +02:00
DShadow
395b0007bf
permissionsViewer: add role & user context menus to copy id (#2436)
Co-authored-by: V <vendicated@riseup.net>
2024-05-11 17:43:34 +02:00
Nuckyz
1a3a378fb1
ErrorBoundary some more components 2024-05-09 03:14:20 -03:00
Vendicated
14e68d9a24
im the dumbest dumdum 2024-05-09 03:19:11 +02:00
Vendicated
251ee32e01
new plugin ShowTimeoutDuration ~ shows how much longer a user's timeout will last 2024-05-09 03:10:15 +02:00
Vendicated
a2acce55c3
BetterSettings: fix error handling crashing in some niche cases 2024-05-09 03:10:15 +02:00
f6d3312244
added SelfShopPreview 2024-05-08 20:40:52 -04:00
89ded967ce
added ModViewBypass 2024-05-08 20:40:46 -04:00
c5a3472c46
added FriendshipRanks 2024-05-08 20:40:32 -04:00
Nuckyz
840a8f1fdd
CrashHandler: Increment timeout for trying to recover 2024-05-08 18:53:55 -03:00
Vendicated
6bd0898efe
fix(SupportHelper): dont flag vencord web as externally updated 2024-05-08 23:52:28 +02:00
Vendicated
025193533d
VoiceDownload: fix doing nothing on discord desktop app 2024-05-08 23:49:47 +02:00
Vendicated
b1cc67a860
fix(BetterSettings): do not catch errors of other ui 2024-05-08 23:42:04 +02:00
Nuckyz
6ad17ff7e7
BetterFolders: Fix component erroring 2024-05-08 17:12:13 -03:00
0d23fdeb41 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-05-08 13:56:31 -04:00
Vendicated
449f95500a
bump to v1.8.2 2024-05-08 03:56:25 +02:00
KK2-5
dd3b7e5346
LastfmRichPresence: Add option to use album name as status name (#2400)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-08 03:55:32 +02:00
dolfies
5c787145e3
showHiddenThings: also show ModView & hidden discovery servers (#2415)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-08 03:50:26 +02:00
puv
1317222c35
feat(plugin): VoiceDownload (#2280)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-08 03:44:57 +02:00
Kyuuhachi
d3acd7edc7
new plugin ReplyTimestamp: show timestamps of replied messages (#2296)
Co-authored-by: vee <vendicated@riseup.net>
2024-05-08 03:39:20 +02:00
Han Seung Min - 한승민
efca196ded
new plugin MessageLatency: indicator for other people's latency (#2353) 2024-05-08 03:25:32 +02:00
kaitlynkitty
e2dc9e75d1
new plugin WebScreenShareFixes: remove low stream bitrate limit (#2405) 2024-05-08 03:17:42 +02:00
Vendicated
21d2019e60
improve SupportHelper 2024-05-08 03:14:41 +02:00
Nuckyz
53dda32fb0
BetterFolders: Fix broken patch 2024-05-07 21:39:34 -03:00
Vendicated
799b903da9
Revert "messageLogger: fix niche bug ignoring edits when content is same (#2403)"
This reverts commit 85d6d74a3e.

As suspected, this code was actually necessary
2024-05-07 22:40:14 +02:00
Vendicated
97acffafcc
fix useStateFromStores JSDoc
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-05-07 16:32:19 +02:00
Nuckyz
75847147d1
FakeNitro: Add custom notifications sound bypass 2024-05-07 03:20:56 -03:00
Nuckyz
0e66c4a1f5
Fix subscribing to plugin flux events twice 2024-05-07 02:47:08 -03:00
Vendicated
72b17761bb
upgrade nodejs & pnpm to latest versions 2024-05-07 04:54:25 +02:00
Nuckyz
ecf6af5884
FakeNitro: Make disableEmbedPermissionCheck setting not private 2024-05-06 05:07:46 -03:00
Sqaaakoi
61235ce994
ImageLink: Fix embed showing in gifs (#2417) 2024-05-05 17:53:51 -03:00
Fafa
45c1e42ce4
ReviewDB: Fix context menus being added to folders (#2416) 2024-05-05 17:53:51 -03:00
Nuckyz
a090872d8f
ImplicitRelationships: Properly test find 2024-05-05 17:53:51 -03:00
Nuckyz
60e6fdacfa
Resolve PluginSettings circular deps better 2024-05-05 17:53:51 -03:00
Vendicated
ce18000c4e
fix webpack patch on client using discordapp.com part 2 2024-05-05 18:58:23 +02:00
Vendicated
066b872219
fix webpack patch on client using discordapp.com 2024-05-05 18:56:49 +02:00
Vendicated
a525cd0113
bump to v1.8.1 2024-05-05 04:00:52 +02:00
Nuckyz
80b738ff3e
Future proof FakeNitro and Experiments to not brick Discord startup 2024-05-04 21:15:14 -03:00
Vendicated
dfb06e47d0
fix overflow when having two rows of badges 2024-05-04 01:39:38 +02:00
Gabriel Ruiz Pérez
04d7cb8797
NewGuildSettings: add push notifs, highlights & events (#2413)
Co-authored-by: V <vendicated@riseup.net>
2024-05-04 01:15:18 +02:00
dolfies
a6c09bc909
feat(ValidUser): also display badges & banner (#2235)
Co-authored-by: V <vendicated@riseup.net>
2024-05-04 00:21:02 +02:00
Nuckyz
a98f12bd1e
SecretRingToneEnabler: Fix patch 2024-05-03 19:07:43 -03:00
Nuckyz
54bb7b96e9
Test Patches: Remove pnpm add puppeteer as it's already in deps 2024-05-03 18:58:56 -03:00
Nuckyz
1ef87361f2
Test Patches: Faster chromium setup; Update action versions (#2412) 2024-05-03 21:47:15 +00:00
Kyuuhachi
0350db7690
feat(plugin): ImageLink (#2297) 2024-05-03 22:42:14 +02:00
Vendicated
520e915168
fix badges with custom component 2024-05-03 22:18:31 +02:00
sunnie
78183eb226
MsgClickActions: control ping via shift & NoReplyMention plugin (#2390)
Co-authored-by: V <vendicated@riseup.net>
2024-05-03 22:00:42 +02:00
katlyn
1af44b25f3
feat(USRBG): update to new API (#2388) 2024-05-03 14:51:53 +00:00
Sqaaakoi
315f4f4e58
ReviewDB: add more context menu shortcuts to view reviews (#2382)
Co-authored-by: V <vendicated@riseup.net>
2024-05-03 15:17:12 +02:00
dolfies
03d7e0fb93
fix sort conflict of ImplicitRelationships & SortFriendRequests (#2408) 2024-05-03 13:09:54 +00:00
Nuckyz
84c53b4a27
MoreUserTags: Remove old workaround; MessageClickActions: Move finds outside of start 2024-05-03 04:52:07 -03:00
dolfies
86b53b24a6
feat(plugin): PauseInvitesForever (#2372)
Co-authored-by: V <vendicated@riseup.net>
2024-05-02 23:09:53 +00:00
HAHALOSAH
85d6d74a3e
messageLogger: fix niche bug ignoring edits when content is same (#2403) 2024-05-02 23:00:00 +00:00
Nuckyz
a055b1d47b
refactor(Webpack): more reliable patching (#2237) 2024-05-02 23:52:41 +02:00
Vendicated
0a598ae966
fix FriendsSince 2024-05-02 15:38:53 +02:00
Vendicated
f54dcb74d7
improve contributor modal & badge 2024-05-02 15:24:00 +02:00
Nuckyz
5bc20ba162
NoTrack: Option to keep analytics, improve patches 2024-05-02 00:36:37 -03:00
Nuckyz
7af733c7c8
RoleColorEverywhere: Actually fix patch 2024-05-01 17:19:42 -03:00
Nuckyz
0f7e60b208
ResurrectHome: Fix patch 2024-05-01 17:09:45 -03:00
Nuckyz
761e6b39ed
RoleColorEverywhere: Fix patch 2024-05-01 16:35:48 -03:00
Nuckyz
51729c828e
Bump to 1.8.0 2024-04-30 20:49:27 -03:00
HAHALOSAH
6d01093eec
fix(fakeNitro): Use getEmojiURL as emoji.url was removed (#2401) 2024-04-30 20:30:01 -03:00
Nuckyz
97886e5728
BetterFolders: Fix patch; MessageLogger: Ignore Venbot; ReviewDB: Clean migration code 2024-04-30 17:34:45 -03:00
dolfies
840c775ed8
fix(ImplicitRelationships): Use new Flux event (#2392) 2024-04-27 17:29:29 -03:00
cba15d56d3
updated DiscordColorways to v5.6.4.2 2024-04-27 13:51:49 -04:00
101e25329e Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-04-27 13:49:02 -04:00
Nuckyz
d2941281a4
Bump to 1.7.9 2024-04-27 01:04:47 -03:00
Nuckyz
304bc96660
RoleColorEverywhere: Fix patch again 2024-04-27 01:03:07 -03:00
Nuckyz
fafd46d202
FriendsSince: Remove workaround for stable compatibility 2024-04-26 18:32:03 -03:00
Nuckyz
c10466f607
MoreUserTags: Fix patches 2024-04-26 18:31:57 -03:00
Nuckyz
36327ebd70
PronounDB: Fix patch 2024-04-26 15:56:06 -03:00
0758ecd31c
updated submodule 2024-04-25 17:15:26 -04:00
01b19b94fc Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-04-25 17:14:39 -04:00
Elvyra
4fce88fa8f
Fix OnePingPerDM and RoleColorEverywhere patches (#2387) 2024-04-24 18:05:02 -03:00
7977e917a1
updated DiscordColorways 2024-04-24 14:44:47 -04:00
2dd955f57c Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-04-24 14:42:34 -04:00
Koda!!
9e0aa4b23c
feat(plugin): StreamerModeOnStream (#2320)
Co-authored-by: V <vendicated@riseup.net>
2024-04-24 05:51:49 +02:00
dolfies
d55205c55a
feat(plugin): ImplicitRelationships (#947)
Co-authored-by: Angelos Bouklis <53124886+ArjixWasTaken@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2024-04-24 05:41:53 +02:00
My-Name-Is-Jeff
6a69701b54
fix ValidUser (#2381) 2024-04-24 03:30:23 +00:00
Vendicated
d5f70070ef
fix badges 2024-04-24 05:27:14 +02:00
Vendicated
7f0e7dd02b
fix FakeProfileThemes 2024-04-24 05:23:50 +02:00
Amia
5cf014cb06
New Plugin: BetterSessions (#1324)
Co-authored-by: V <vendicated@riseup.net>
2024-04-23 23:06:19 +02:00
523dae7fa6
added QuickReactFrequents and DiscordColorways 2024-04-23 11:08:56 -04:00
fa99ee4093
updated submodules 2024-04-23 11:03:21 -04:00
74bdc96840 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-04-23 11:00:22 -04:00
dolfies
af67ddefa1
ShowTimeouts->ShowHiddenThings ~show invite-disabled tooltip too (#2375) 2024-04-23 01:46:11 +02:00
Vendicated
8f73b9fd5f
Make Updater slightly more future proof
- Removes the option to disable update notifications. Users really should not be outdated, so this option was never good. To disable notifications, turn on auto update
- Enables auto update by default. Users keep complaining about issues while being outdated, so this should help
- Update Notification now opens Updater in a modal to remove dependency on Settings patch. This makes it slightly more failsafe, it's unlikely that both modals and our settings patch break
2024-04-20 14:51:33 +02:00
Vendicated
0bebc85b0d
fix FriendsSince on canary 2024-04-20 11:52:58 +02:00
Vendicated
74df53e7c8
ValidUser: fix not working for @unknown-user mentions 2024-04-20 11:37:22 +02:00
Vendicated
87ef214810
RoleColorEverywhere: fix chat mentions 2024-04-20 11:29:15 +02:00
Elvyra
5aa19bbf49
fix ValidUser (#2369) 2024-04-20 11:18:03 +02:00
Vendicated
97ce410f57
bump to v1.7.8 2024-04-18 00:53:28 +02:00
Vendicated
82d914e62f
CustomRPC: fix preview styles 2024-04-18 00:40:09 +02:00
Vendicated
0c6ddf80e8
ShowConnections: fix icon theme logic 2024-04-18 00:26:09 +02:00
Vendicated
89c82e2cd1
fix settings patch 2024-04-17 23:42:56 +02:00
e26f0a0cc0 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-04-17 11:23:15 -04:00
Vendicated
538b87062a
bump to v1.7.7 2024-04-17 04:44:56 +02:00
Vendicated
23b0841cc7
fix ShowConnections 2024-04-17 04:44:42 +02:00
Elvyra
3a79e41d67
fix MessageLogger (#2358) 2024-04-16 20:11:25 +00:00
AutumnVN
356a2c290d
fix: RoleColorEverywhere, PictureInPicture, NoMosaic (#2356) 2024-04-16 20:10:15 +00:00
Vendicated
52f8a85ab9
fix(FavoriteEmojiFirst): don't lower suggestion count on hover 2024-04-14 14:26:36 +02:00
Vendicated
0f5a75aa4b
bump to v1.7.6 2024-04-14 02:39:04 +02:00
Vendicated
99f523b87c
fix extractAndLoadChunks for when there are no chunks to be loaded 2024-04-14 02:22:13 +02:00
Vendicated
00427c53d8
fix pronoundb 2024-04-14 01:50:18 +02:00
AutumnVN
59fc922aee
viewIcons: fix for banners (#2351) 2024-04-13 23:40:27 +00:00
Elvyra
af7d1b9df2
fix ShowMeYourName (#2354) 2024-04-13 23:36:56 +00:00
Vendicated
e5bd5534db
Fix SpotifyControls 2024-04-14 01:35:04 +02:00
Vendicated
89dc74d5d7
partially revert "Array support for find + ResurrectHome: View Server Home Button on Server Guide (#2283)"
This reverts commit 5636f9d979.

It breaks Vesktop which is not acceptable. Need to resolve this conflict and add this back later
2024-04-14 00:59:04 +02:00
35a15effc8 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-04-11 21:48:28 -04:00
Nuckyz
5636f9d979
Array support for find + ResurrectHome: View Server Home Button on Server Guide (#2283) 2024-04-09 17:09:23 -03:00
Vendicated
dc4c678aa3
bump to v1.7.5 2024-04-09 04:19:35 +02:00
AutumnVN
7fa1259821
NoMosaic: make loading image work with responsive layout (#2095) 2024-04-09 04:04:09 +02:00
Haruka
34c74b43bd
new plugin UnlockedAvatarZoom: allows crop zooming in further (#2287)
Co-authored-by: V <vendicated@riseup.net>
2024-04-09 03:56:28 +02:00
Vendicated
38ffdd7d94
FriendsSince: Add icon to be consistent with "member since"
Co-authored-by: Trey <47907719+trwy7@users.noreply.github.com>
2024-04-09 02:52:21 +02:00
Sqaaakoi
26f3618c2c
TypingIndicator: Add an option to show user avatars (#2319)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-04-09 02:41:48 +02:00
byron
ae01e88e13
EmoteCloner: fix low quality; don't count managed emojis (#2321)
Co-authored-by: Nam Anh <phamnamanh25@gmail.com>
Co-authored-by: ryan-0324 <77452312+ryan-0324@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2024-04-09 02:33:18 +02:00
AutumnVN
3ecd2deae5
plugin settings: fix filter dropdown having no padding (#2332) 2024-04-09 02:23:55 +02:00
Eric
cba611c1cc
Better error for primitives on proxyLazy + fix StartupTimings (#2339) 2024-04-08 04:33:35 +00:00
Vendicated
e0becc1ba0
ShowHiddenChannels: Fix incompatibility with favorite channels experiment 2024-04-07 21:46:32 +02:00
Jade ・:*・。*・
c311155d7c
PinDMs: Fix unexpected behaviours when using last message sort (#2324) 2024-04-05 16:29:08 -03:00
Nuckyz
778d79cd35
Fix MutualGroupDMs and UserVoiceShow patch 2024-04-05 16:09:04 -03:00
Kyuuhachi
18d4780635
fix(MessageLinkEmbeds): Actually disable when disabled (#2323) 2024-04-03 12:51:28 +00:00
Nuckyz
88f353e7f6
ResurrectHome: Fix Force Server Home patch 2024-04-02 13:39:28 -03:00
9bc090b6a8
update submodules 2024-03-31 05:14:42 -04:00
0fe5b70f8d
remove serverdownload (the github repository was taken down) 2024-03-31 05:12:23 -04:00
1929f1eb55 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-03-31 05:10:03 -04:00
Koda!!
c623e44786
ReviewDB: Fix website url (#2318)
Co-authored-by: V <vendicated@riseup.net>
2024-03-31 04:33:03 +02:00
Vendicated
b158cecd4b
FakeNitro: Fix sending unavailable emotes of the current server 2024-03-31 01:26:57 +01:00
Vendicated
69b349da77
fix minor updater bugs 2024-03-30 00:28:15 +01:00
8da37f0b24 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-03-29 10:16:48 -04:00
Syncx
650f4050e1
fix(PinDMs): display properly when there are 0 unpinned dms (#2281)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2024-03-28 21:40:47 +01:00
Nuckyz
60f6678107
Decor: Fix find targetting wrong module in some cases 2024-03-28 12:57:28 -03:00
Nuckyz
bdef47eb8a
Bump to 1.7.4 2024-03-28 11:44:38 -03:00
Nuckyz
7fe718a018
FakeNitro: Make Soundboard sounds not require boost level 2024-03-28 11:30:04 -03:00
Nuckyz
126023f8f2
ShowHiddenChannels: Fix patches 2024-03-28 10:46:28 -03:00
Nuckyz
762684a138
Fix more patches 2024-03-28 10:30:29 -03:00
Nuckyz
74c38146d5
Fix BetterSettings patches (#2307) 2024-03-28 10:17:05 -03:00
AutumnVN
2f07dc230a
fix BetterGifPicker, Experiments 2024-03-28 10:02:09 -03:00
TheKodeToad
344f8c9f03
Fix DisableCallIdle 2024-03-28 09:58:57 -03:00
Nuckyz
ec34412100
Fix store finds 2024-03-28 09:49:59 -03:00
AutumnVN
de9122b05b
fix ImageZoom, LoadingQuote, NoRPC, RevealAllSpoilers, SecretRingTone (#2306) 2024-03-28 06:43:05 +01:00
Vendicated
82ab3ad1b9
i may be stupid :3 2024-03-28 05:18:00 +01:00
Vendicated
e71fcc3010
Fix FakeNitro 2024-03-28 04:21:52 +01:00
Nuckyz
c997ff7ada
Fix lazy chunk force loading 2024-03-28 04:05:11 +01:00
Vendicated
6c711e2781
Fix PinDMs 2024-03-28 03:58:11 +01:00
Vendicated
b1009baf7a
Fix MessageLogger 2024-03-28 03:47:00 +01:00
Vendicated
74b6ceee78
Fix WhoReacted 2024-03-28 03:43:37 +01:00
Vendicated
8ab56f5bcf
ReadAllNotificationButton: make button less ugly 2024-03-27 21:44:54 +01:00
Vendicated
0d22ff0091
webpack: fix infinite recursion when using ConsoleShortcuts plugin 2024-03-27 21:27:56 +01:00
Nuckyz
31c21594e6
Reporter: Ignore another useless error 2024-03-27 10:39:58 -03:00
Nuckyz
0983a038f1
Fix broken patches 2024-03-27 10:30:34 -03:00
3291483d39 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-03-22 16:03:24 -04:00
Nuckyz
e1f8b3cb30
UserVoiceShow: Fix UserPopout patch stealing predicates from another component 2024-03-22 09:45:28 -03:00
Nuckyz
8d35cc6112
BetterNotes: Fix patches 2024-03-22 09:45:28 -03:00
Vendicated
ca18b6e044
Fix CloudSync 2024-03-22 04:08:08 +01:00
Vendicated
caa14ece0d
bump to v1.7.3 2024-03-22 03:14:57 +01:00
mcpower
ec66c35d41
MessageLinkEmbeds: Load embeds from newest to oldest (#2230)
Co-authored-by: V <vendicated@riseup.net>
2024-03-22 01:27:09 +00:00
Inbestigator
1cb295b1b9
new plugin: OverrideForumDefaults (#2272)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-03-22 02:16:24 +01:00
MrDiamondDog
5646fe402a
feat(PatchHelper): Paste Full Patch (#1982) 2024-03-21 23:59:25 +00:00
Syncx
2ce3487477
PinDMs: add category support & fix bugs (#2203)
Co-authored-by: V <vendicated@riseup.net>
2024-03-22 00:55:37 +01:00
Trey
55901ba35e
AlwaysTrust: Add options for each popup (#2234)
Co-authored-by: V <vendicated@riseup.net>
2024-03-21 23:44:37 +00:00
Masterjoona
04d5423b08
fix(AnonymiseFileNames): anonymise files in forum posts (#2270) 2024-03-21 23:42:41 +00:00
Eric Liu
021948c919
WhoReacted: fix scroll jumping when rendering users (#2271)
Co-authored-by: V <vendicated@riseup.net>
2024-03-22 00:39:26 +01:00
Vendicated
3e332a6062
fix RestAPI find 2024-03-21 23:27:08 +01:00
Vendicated
0d5f492891
fix ReviewDB 2024-03-21 21:17:47 +01:00
thororen
ae9435ac55
MemberCount: Fix error when not in a channel (#2277) 2024-03-19 12:06:09 +01:00
AutumnVN
90ee07fd98
emoteCloner: fix cloning gif stickers (#2268) 2024-03-16 16:43:04 +01:00
Hyper
356d8d8e5e
feat(MessageLogger): Add delete & edit toggle (#2032)
Co-authored-by: V <vendicated@riseup.net>
2024-03-16 02:27:59 +00:00
Vendicated
23aeb21272
fix ViewIcons & Decor patches 2024-03-16 02:30:36 +01:00
Kyuuhachi
6140b95814
new plugin: BetterSettings ~ improves Discord's settings (#2222)
- makes opening settings much faster
- removes the scuffed transition animation
- organises the settings cog context menu into categories

Co-authored-by: Vendicated <vendicated@riseup.net>
2024-03-16 02:19:26 +01:00
stupid cat
f3ee43fe66
favGifSearch: don't error on favourited non-urls (#2260) 2024-03-13 22:23:04 +01:00
Vendicated
afdcf0edb9
refactor shared utils to more obviously separate contexts 2024-03-13 21:59:09 +01:00
V
9aa205b5ec
rewrite settings api to use SettingsStore class (#2257)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-03-13 21:45:45 +01:00
Vendicated
7190437e92
rename Devs.obscurity => Devs.fawn :3 2024-03-13 00:36:42 +01:00
Nuckyz
bf9a225038
HTTP Updater: Only include first commit line 2024-03-12 20:18:46 -03:00
Nuckyz
6a7657de3f
Remove getGuildRoles 2024-03-12 20:18:44 -03:00
9e89449383 Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-03-11 21:57:25 -04:00
Nuckyz
6e363814d1
ResurrectHome: Fix README image 2024-03-11 12:37:59 -03:00
Vendicated
2730eada8d
Bump to v1.7.2 2024-03-11 16:35:21 +01:00
Elvyra
f9924d555a
new Plugin: FriendsSince ~ shows friend date in profiles (#2240)
* feat: Create friendsSince plugin

* chore: add devs entry

* fix text

* Update src/plugins/friendsSince/index.tsx

Co-authored-by: V <vendicated@riseup.net>

* refactor: Put element into its own section

* feat: add section to user profile in DMs

* escape {

* fixes

* Add README

* Wrap in ErrorBoundary

---------

Co-authored-by: V <vendicated@riseup.net>
2024-03-11 15:31:36 +00:00
Haruka
1fbc4f7ce1
fix(ServerProfile): crop banner to prevent overflow (#2250) 2024-03-11 16:19:35 +01:00
Vendicated
34390e0365
Add workaround for guild role api changes on canary/ptb
fixes RoleColorEverywhere, ServerInfo, PermissionViewer, BetterRoleContext
2024-03-11 16:15:49 +01:00
Jack
497f0de9a1
chore(Decor): Change URL formula for cost savings (#2247) 2024-03-11 15:52:11 +01:00
Nuckyz
992533245b
RoleColorEverywhere: Wrap roleGroupColor in ErrorBoundary 2024-03-08 19:05:30 -03:00
Nuckyz
b0d37c981e
Bump to 1.7.1 2024-03-08 00:29:06 -03:00
Nuckyz
cf7830e747
ShowHiddenChannels: Fix patches 2024-03-08 00:24:23 -03:00
Nuckyz
10f33b3dec
Make more finds use filters.componentByCode 2024-03-08 00:24:21 -03:00
Amia
688ff255d2
fix(MutualGroupDMs): update regex (#2242) 2024-03-08 03:18:18 +00:00
Vendicated
2e90d4c03d
New plugin: BetterRoleContext ~ edit/copy colour shortcuts in profile 2024-03-07 20:23:07 +01:00
Nuckyz
1c1d82f9a8
VencordToolbox: don't subscribe to all settings
Also remove one indirection from useSettings
2024-03-07 13:06:08 -03:00
Nuckyz
102842d528
Close Ipc FS watchers if window is closed 2024-03-07 11:33:00 -03:00
Nuckyz
19799767ad
Fix trying to check for updates if origin doesn't have same branch 2024-03-07 10:27:50 -03:00
Sam
f70114238c
MemberCount: Add options to choose where the member count will be displayed (#2224) 2024-03-07 10:27:50 -03:00
AutumnVN
a59c14f9aa
CustomRPC: Change timestamp to milisecond (#2231) 2024-03-07 10:27:50 -03:00
Nuckyz
980206d315
Fix waitFor initial finds traces getting logged to the console even though they always fail 2024-03-07 10:27:50 -03:00
Kyuuhachi
42a9fa2d47
Refactor ContextMenuAPI (#2236) 2024-03-07 10:06:24 +00:00
Nuckyz
612fdf8952
FakeNitro: Fix trimming wrong content 2024-03-06 07:12:09 -03:00
dolfies
4f0c0a12dc
feat(plugin): ResurrectHome (#2232) 2024-03-05 22:38:49 +00:00
Nuckyz
23ff82fa62
FakeNitro: Remove extra space in modal 2024-03-03 17:49:21 +01:00
Nuckyz
553a48b6ce
FakeNitro: Fix hyperlink text setting for stickers 2024-03-03 17:48:50 +01:00
Nuckyz
806960f1c6
ClientTheme: do not use lodash on start method 2024-03-03 17:47:26 +01:00
Nuckyz
1a1156e1ed
Add more settings to IgnoreActivities (#2153) 2024-03-01 04:09:55 +00:00
[object Object]
9179f55bf2
fix Vencloud not working on UserScript (#2213)
Co-authored-by: V <vendicated@riseup.net>
2024-03-01 01:26:32 +01:00
Vendicated
3ebde1aae8
fix some minor bugs 2024-03-01 00:18:09 +01:00
Vendicated
da50c7a19b
MemberCount: Also add to server tooltip; refactor code 2024-02-28 20:25:08 +01:00
Nuckyz
7de54a294f
Fix NotificationVolume 2024-02-27 23:43:03 -03:00
Nuckyz
e0166ef1e6
Fix FakeNitro patch and message content patch error 2024-02-27 23:42:57 -03:00
b23bbc958f Merge branch 'main' of https://www.coastalcommits.com/SeaswimmerTheFsh/Vencord 2024-02-27 18:26:51 -05:00
9f52e0a5b1 updated message logger enhanced 2024-02-27 18:26:36 -05:00
4709c87afe updated some userplugins 2024-02-27 18:26:36 -05:00
1510e8f7a8 added ServerDownload 2024-02-27 18:26:36 -05:00
6a5660d3ba added vc-timezones 2024-02-27 18:26:35 -05:00
44350f02cd added a bunch of custom plugins 2024-02-27 18:26:35 -05:00
86d42200d5 Added DontLeak 2024-02-27 18:26:35 -05:00
61329466bf removed the userplugins readme because it causes a build error 2024-02-27 18:26:35 -05:00
28c36f9c0d added submodules 2024-02-27 18:26:35 -05:00
17120a9771 removed userplugins 2024-02-27 18:26:35 -05:00
8c8dcf789e Revert "changing to coastalcommits mirrors"
This reverts commit 09c3025e81.
2024-02-27 18:26:35 -05:00
428749001f changing to coastalcommits mirrors 2024-02-27 18:26:34 -05:00
e77a7b142d changed gitignore 2024-02-27 18:26:34 -05:00
Vendicated
8ccd731aee
bump to v1.7.0 2024-02-27 13:41:55 +01:00
Andrei Neacsu
1afa185f57
LastfmRichPresence: Add an option to hide the Last.fm logo (#2189)
Co-authored-by: V <vendicated@riseup.net>
2024-02-27 12:30:27 +00:00
Lualt
76de8c424e
feat(plugin) FakeNitro: Allow customising hyperlink text (#2192)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-02-27 13:27:37 +01:00
Nuckyz
ed5e1be7a4
Add permissions checks for FakeNitro actions (#2160)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-02-27 13:19:05 +01:00
WackyModer
27696ed62a
whoReacted: fix reaction count being off by one (#2209)
Co-authored-by: V <vendicated@riseup.net>
2024-02-27 11:31:51 +00:00
Vendicated
b9d0a1c563
SpotifyControls: fix seekbar grabber alignment 2024-02-27 12:01:15 +01:00
Av32000
5e7b4e9c92
SpotifyControls: export album cover as CSS variable for themes (#2197)
--vc-spotify-track-image
2024-02-27 10:27:34 +00:00
Nuckyz
9958f5a2ea
Fix ReviewDB 2024-02-27 05:04:54 -03:00
Sqaaakoi
414184ef25
ImageZoom: negate the border offsetting the lens (#2117)
Co-authored-by: Lewis Crichton <lewi@lewisakura.moe>
2024-02-25 23:51:09 +00:00
Vendicated
2d8715adf0
SuperReactionTweaks: only super react by default if user has nitro 2024-02-23 13:06:03 +01:00
Nuckyz
e3fd954512
Fix Decor patch 2024-02-22 21:31:15 -03:00
Manti
f922f0bc0d
fix reviewdb auth not working on userscript (#2194) 2024-02-20 18:13:25 +00:00
36e623059d
updated message logger enhanced 2024-02-19 13:34:16 -05:00
5c03ef46d0 Merge branch 'main' of coastalcommits.com:CoastalCommitsArchival/github.com-Vendicated-Vencord 2024-02-18 23:22:25 -05:00
c27663ced4
updated some userplugins 2024-02-18 23:21:56 -05:00
Vendicated
604f4c49af
VoiceMessages: Add warning if audio file is not OggOpus 2024-02-18 18:22:48 +01:00
Margot
c3030bbad0
MuteNewGuild -> NewGuildSettings; add 'show all channels' option (#2065)
Co-authored-by: MopigamesYT <mopigames@proton.me>
Co-authored-by: V <vendicated@riseup.net>
2024-02-18 17:42:26 +01:00
Syncx
7b96071643
fix ImageZoom patch (#2181) 2024-02-16 06:52:09 +00:00
Vendicated
a501da692f
MessageLinkEmbeds: fix group dm support, improve ui 2024-02-15 11:08:43 +01:00
Vendicated
bc0a55053d
MessageLinkEmbeds: fix erroring on some invalid message links 2024-02-15 10:12:33 +01:00
Vendicated
89367e3b2a
WebContextMenus: fix copying images
it broke because of the new url expiry parameters
2024-02-15 09:25:51 +01:00
Nuckyz
f1bdf385eb
Bump to 1.6.9 2024-02-14 15:25:10 -03:00
Nuckyz
93b2095d71
Fix FakeNitro hyperlinks not working sometimes
It is caused by emoji names with conflict with default emojis, but have a letter capitalized
2024-02-14 15:16:31 -03:00
Nuckyz
46ee193cd0
Fix MessageLogger edit Timestamp component 2024-02-14 15:07:51 -03:00
Nuckyz
48822bdc58
Attempt to fix CrashHandler odd issues 2024-02-14 15:00:29 -03:00
Vendicated
0c9d2a6a21
Fix plugins using the Timestamp component 2024-02-13 09:05:19 +01:00
Vendicated
8b6a40311b
Bump to v1.6.8 2024-02-13 09:04:28 +01:00
Nuckyz
38beb93e5f
Fix CrashHandler failing to recover and showing gray screen instead 2024-02-10 13:42:31 -03:00
Nuckyz
cc0d9a90bc
Fix UserVoiceShow patch 2024-02-09 21:27:34 -03:00
Nuckyz
d3bbd2c02e
FakeNitro: option to use hyperlinks or not 2024-02-09 21:00:15 -03:00
Nuckyz
558c2a0b2e
Make PermissionsViewer and ShowHiddenChannels default dropdown change when clicked 2024-02-09 19:47:43 -03:00
Nuckyz
237f080471
DisableDMCallIdle -> DisableCallIdle; add more functionality 2024-02-09 19:33:15 -03:00
sappho
b2c047390e
FakeNitro: Send fake links using Discord hyperlink markdown feature (#2158) 2024-02-09 19:33:14 -03:00
Klen_list
f1f0da4a9d
fixYoutubeEmbeds: fix when youtube decides to use http without s (#2155)
Co-authored-by: V <vendicated@riseup.net>
2024-02-08 18:55:48 +00:00
Nuckyz
935d0a0a03
Fix broken patches and finds 2024-02-08 15:33:49 -03:00
Nuckyz
a108448255
Improve DisableDMCallIdle 2024-02-07 20:39:02 -03:00
Nuckyz
3325a8de40
Fix FakeNitro patch 2024-02-06 21:30:07 -03:00
Vendicated
377def4a33
Fix crash when toggling chat button plugins without restart 2024-02-06 18:12:09 +01:00
Vendicated
de570a4800
inject: suppress ugly node error 2024-02-06 17:59:44 +01:00
Vendicated
685b6f2fa7
Improve PreviewMessage icon; make button padding/margin more consistent
Co-authored-by: Andrew Grant <andrew.grant@mail.rit.edu>
2024-02-06 17:52:05 +01:00
Nuckyz
7592b5c172
Fix devtools context menus on canary 2024-02-06 13:31:18 -03:00
Kuba (kb)
8b3189f1ed
fixYoutubeEmbeds - Support locales other than english (#2128)
Co-authored-by: V <vendicated@riseup.net>
2024-02-06 16:18:49 +00:00
ssranked
115da04724
clearURLs: add music.youtube.com (#2118)
Co-authored-by: ssranked <ssrankedghoul@gmail.com>
2024-02-06 16:13:43 +00:00
sunnie
ec53b0230f
readAllNotificationsButton: also mark voice channels as read (#2140) 2024-02-06 16:12:26 +00:00
Vendicated
64fa2f8652
ChatButtonApi: do not add buttons if you can't send messages 2024-02-06 17:06:26 +01:00
Vendicated
e58aa5dbab
Translate: Add auto translate enable alert
somehow people accidently enable it then get confused why vencord has "auto correct". this should hopefully solve the issue
2024-02-06 17:04:01 +01:00
Vendicated
2c198e547c
Fix PreviewMessage icon being offcentre 2024-02-06 16:50:21 +01:00
Vendicated
bf977e0047
Add chat bar button api ~ fixes buttons for russian users 2024-02-06 16:29:47 +01:00
EdVraz
8938f4a3cf
fix moreUserTags (#2146) 2024-02-03 21:50:51 -03:00
Vendicated
cc885b5bb3
remove lumap 2024-02-03 02:11:59 +01:00
0d425e0293
added ServerDownload 2024-02-02 18:00:17 -05:00
742d862f03
added vc-timezones 2024-02-02 17:59:51 -05:00
cc6740c32e
added a bunch of custom plugins 2024-02-02 17:58:20 -05:00
0c1e984377
Added DontLeak 2024-02-02 17:54:49 -05:00
5afa1ac81c
removed the userplugins readme because it causes a build error 2024-01-30 23:35:48 -05:00
d010e9d609
added submodules 2024-01-30 23:29:47 -05:00
271036887b
removed userplugins 2024-01-30 23:27:47 -05:00
074d239d62
Revert "changing to coastalcommits mirrors"
This reverts commit 09c3025e81.
2024-01-27 17:49:33 -05:00
09c3025e81
changing to coastalcommits mirrors 2024-01-27 17:48:58 -05:00
a771a24082
changed gitignore 2024-01-27 17:39:18 -05:00
V
620c127b58
[skip ci] fix pnpm inject 2024-01-25 07:22:34 +01:00
EdVraz
4bb0db5066
fix anonymiseFileNames (#2125) 2024-01-22 00:21:10 +00:00
Manti
e707538b73
[ReviewDB] update for new api changes; some fixes (#2120)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-01-22 00:18:48 +00:00
~coolelectronics
1670733458
feat(plugin): FixYoutubeEmbeds - fix UMG blocked embeds (#2116)
Co-authored-by: V <vendicated@riseup.net>
2024-01-19 01:07:35 +00:00
Vendicated
988435714e
Add back transparency option 2024-01-19 01:08:25 +01:00
Vendicated
74300e0a69
WebContextMenus: only force png on copy image, not save image 2024-01-18 00:58:40 +01:00
Vendicated
3d64f3da41
WebContextMenus: fix copying images 2024-01-18 00:54:52 +01:00
Vendicated
8a168bd185
bump to v1.6.7 2024-01-16 04:12:15 +01:00
Bloofield
e8e09c17e9
ClearURLs: Add igsh (#2103) 2024-01-16 04:07:05 +01:00
AutumnVN
11d3165009
roleColorEverywhere: thread role color (again) (#2098)
Co-authored-by: V <vendicated@riseup.net>
2024-01-16 04:01:59 +01:00
Sam
60bc823eab
new plugin: BetterGifPicker (#2108)
Co-authored-by: V <vendicated@riseup.net>
2024-01-16 03:00:41 +00:00
CodeF53
f14001b531
ClientTheme light mode support, shortcut buttons (#2010)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2024-01-16 02:53:27 +00:00
rini
1a982ae9aa
feat(anonymisefilenames): add icon to toggle anonymising (#2087)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: fawn <fawn@rinici.de>
2024-01-16 02:51:12 +00:00
Vendicated
a171b35e97
ReviewDB fixes 2024-01-15 19:58:33 +01:00
Manti
8bd54173db
Bring back ReviewDB (#2097)
Changes from old version:
- You can now delete reviews on your own profile
- You can now block up to 50 users. This will prevent them from leaving reviews on your profile

Co-authored-by: V <vendicated@riseup.net>
2024-01-14 22:46:24 +01:00
Vendicated
2ab1c50c73
QuickCss: reopen existing window instead of new one 2024-01-13 19:27:41 +01:00
Vendicated
69a4d2734e
fix(git updater): correctly persist isDev switch 2024-01-13 19:08:11 +01:00
Vendicated
cb7045c00b
WebContextMenus: use vesktop native clipboard - fixes some permission issues 2024-01-13 19:05:01 +01:00
Vendicated
8c89002867
forgor not everyone uses enlgihs 2024-01-09 17:44:18 +01:00
Vendicated
ba6d23a31f
fix adding View Raw to undesired channel context menus 2024-01-09 17:39:38 +01:00
Grzesiek11
ba2695b499
Add FixCodeblockGap plugin (#2064) 2024-01-06 02:05:23 +00:00
Vendicated
d73790efb3
bump to v1.6.6 2024-01-06 01:22:04 +01:00
effct • Lia Aaron Thoma
0d6bb35075
TextReplace: allow replacing with nothing (#1933) 2024-01-06 00:02:09 +00:00
Hari Rana
d5c58ab2b3
updater: Add top and bottom margins between commits (#2042)
Co-authored-by: V <vendicated@riseup.net>
2024-01-05 23:55:14 +00:00
Grzesiek11
be9ec3b7ac
SendTimestamps: add setting to disable message replacement (#2076) 2024-01-05 23:23:09 +00:00
Moxie
6530526fb2
Updater: Fix grammar (1 Updates => 1 Update) (#2084)
Co-authored-by: V <vendicated@riseup.net>
2024-01-06 00:10:16 +01:00
Vendicated
13961a4ba5
ReverseImageSearch: add support for image modal 2024-01-05 23:52:22 +01:00
Vendicated
4d198e46bc
fix wrongly patching 'Events' context menu 2024-01-05 23:31:34 +01:00
Vendicated
3eada99ad6
fix(ToolBox): don't add entries of disabled plugins 2024-01-05 22:15:54 +01:00
sunnie
88fc15752d
webContextMenus: add Show My Camera item (#2086) 2024-01-05 22:12:13 +01:00
Vendicated
9e810e5511
fix cobu sync 2024-01-05 22:07:00 +01:00
Nuckyz
4a1c85c8ad
Decor: only search for DecorationModalStyles once 2024-01-03 09:58:09 -03:00
nexpid
1eb2510353
feat(Decor): Enforce guidelines more (#2035) 2024-01-03 09:49:03 -03:00
Jack
a963a19bdc
fix(Decor): Fix AvatarDecorationModalPreview find (#2089) 2024-01-03 12:37:56 +00:00
Lewis Crichton
d0dfdbbd5f
fix: Vencord_cloudSecret check (#2077)
finally got around to fixing it - `null` is never a valid return value,
it's `undefined` 🤦
2024-01-03 09:31:57 -03:00
V
7e395fc696
Update codeburger mirror 2023-12-31 05:02:05 +01:00
jd
30bc979c8d
feat(Urban Dictionary): Chooses top rated definition & more results. (#2080) 2023-12-31 00:06:19 +00:00
496 changed files with 27732 additions and 9701 deletions

View file

@ -1,98 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"ignorePatterns": ["dist", "browser"],
"plugins": [
"@typescript-eslint",
"simple-header",
"simple-import-sort",
"unused-imports",
"path-alias"
],
"settings": {
"import/resolver": {
"alias": {
"map": [
["@webpack", "./src/webpack"],
["@webpack/common", "./src/webpack/common"],
["@utils", "./src/utils"],
["@api", "./src/api"],
["@components", "./src/components"]
]
}
}
},
"rules": {
// Since it's only been a month and Vencord has already been stolen
// by random skids who rebranded it to "AlphaCord" and erased all license
// information
"simple-header/header": [
"error",
{
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
"templates": { "author": [".*", "Vendicated and contributors"] }
}
],
"quotes": ["error", "double", { "avoidEscape": true }],
"jsx-quotes": ["error", "prefer-double"],
"no-mixed-spaces-and-tabs": "error",
"indent": ["error", 4, { "SwitchCase": 1 }],
"arrow-parens": ["error", "as-needed"],
"eol-last": ["error", "always"],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": ["error", "always"],
"semi-style": ["error", "last"],
"space-in-parens": ["error", "never"],
"block-spacing": ["error", "always"],
"object-curly-spacing": ["error", "always"],
"eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error",
"prefer-destructuring": ["error", {
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}],
"operator-assignment": ["error", "always"],
"no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
"no-invalid-regexp": "error",
"no-constant-condition": ["error", { "checkLoops": false }],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"dot-notation": "error",
"no-useless-escape": [
"error",
{
"extra": "i"
}
],
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
"no-cond-assign": "error",
"no-dupe-else-if": "error",
"no-duplicate-case": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-prototype-builtins": "error",
"no-regex-spaces": "error",
"no-shadow-restricted-names": "error",
"no-unexpected-multiline": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error",
"use-isnan": "error",
"prefer-const": "error",
"prefer-spread": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"unused-imports/no-unused-imports": "error",
"path-alias/no-relative": "error"
}
}

View file

@ -12,7 +12,8 @@ body:
DO NOT USE THIS FORM, unless DO NOT USE THIS FORM, unless
- you are a vencord contributor - you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server - you were given explicit permission to use this form by a moderator in our support server
- you are filing a security related report
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
- type: textarea - type: textarea
id: content id: content

View file

@ -18,21 +18,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
- name: Use Node.js 19 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 19 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build web - name: Build web
run: pnpm buildWeb --standalone run: pnpm buildWebStandalone
- name: Build - name: Build
run: pnpm build --standalone run: pnpm build --standalone

View file

@ -13,10 +13,10 @@ jobs:
if: github.repository == 'Vendicated/Vencord' if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1 - uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
with: with:
target_repo_url: "git@codeberg.org:Ven/cord.git" target_repo_url: "git@codeberg.org:Vee/cord.git"
ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }} ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }}

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: check that tag matches package.json version - name: check that tag matches package.json version
run: | run: |
@ -20,19 +20,19 @@ jobs:
exit 1 exit 1
fi fi
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
- name: Use Node.js 19 - name: Use Node.js 19
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 19 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build web - name: Build web
run: pnpm buildWeb --standalone run: pnpm buildWebStandalone
- name: Publish extension - name: Publish extension
run: | run: |

View file

@ -11,37 +11,40 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
if: ${{ github.event_name == 'schedule' }} if: ${{ github.event_name == 'schedule' }}
with: with:
ref: dev ref: dev
- uses: actions/checkout@v3 - uses: actions/checkout@v4
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' }}
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
- name: Use Node.js 19 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 19 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
run: | run: |
pnpm install --frozen-lockfile pnpm install --frozen-lockfile
pnpm add puppeteer
sudo apt-get install -y chromium-browser - name: Install Google Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2
with:
chrome-version: stable
- name: Build web - name: Build Vencord Reporter Version
run: pnpm buildWeb --standalone --dev run: pnpm buildReporter
- name: Create Report - name: Create Report
timeout-minutes: 10 timeout-minutes: 10
run: | run: |
export PATH="$PWD/node_modules/.bin:$PATH" export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser) export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
esbuild scripts/generateReport.ts > dist/report.mjs esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY node dist/report.mjs >> $GITHUB_STEP_SUMMARY
@ -54,7 +57,7 @@ jobs:
if: success() || failure() # even run if previous one failed if: success() || failure() # even run if previous one failed
run: | run: |
export PATH="$PWD/node_modules/.bin:$PATH" export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser) export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
export USE_CANARY=true export USE_CANARY=true
esbuild scripts/generateReport.ts > dist/report.mjs esbuild scripts/generateReport.ts > dist/report.mjs

View file

@ -10,13 +10,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json - uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18 - name: Use Node.js 20
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies

2
.gitignore vendored
View file

@ -18,7 +18,5 @@ lerna-debug.log*
.pnpm-debug.log* .pnpm-debug.log*
*.tsbuildinfo *.tsbuildinfo
src/userplugins
ExtensionCache/ ExtensionCache/
settings/ settings/

18
.gitmodules vendored Normal file
View file

@ -0,0 +1,18 @@
[submodule "src/userplugins/ToastNotificationsMerge"]
path = src/userplugins/ToastNotificationsMerge
url = https://github.com/ethan-davies/ToastNotificationsMerge.git
[submodule "src/userplugins/vc-message-logger-enhanced"]
path = src/userplugins/vc-message-logger-enhanced
url = https://github.com/Syncxv/vc-message-logger-enhanced.git
[submodule "src/userplugins/DontLeak"]
path = src/userplugins/DontLeak
url = https://github.com/pernydev/DontLeak.git
[submodule "src/userplugins/VencordPlugins"]
path = src/userplugins/VencordPlugins
url = https://github.com/Kyuuhachi/VencordPlugins.git
[submodule "src/userplugins/vc-timezones"]
path = src/userplugins/vc-timezones
url = https://github.com/Syncxv/vc-timezones.git
[submodule "src/userplugins/ServerDownload"]
path = src/userplugins/ServerDownload
url = https://github.com/cheesesamwich/ServerDownload.git

1
.npmrc
View file

@ -1 +1,2 @@
strict-peer-dependencies=false strict-peer-dependencies=false
package-manager-strict=false

View file

@ -1,6 +1,11 @@
{ {
"extends": "stylelint-config-standard", "extends": "stylelint-config-standard",
"rules": { "rules": {
"indentation": 4 "selector-class-pattern": [
"^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$",
{
"message": "Expected class selector to be kebab-case with camelCase segments"
}
]
} }
} }

View file

@ -1,11 +1,9 @@
{ {
"recommendations": [ "recommendations": [
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"eamodio.gitlens",
"EditorConfig.EditorConfig", "EditorConfig.EditorConfig",
"ExodiusStudios.comment-anchors",
"formulahendry.auto-rename-tag",
"GregorBiswanger.json2ts", "GregorBiswanger.json2ts",
"stylelint.vscode-stylelint" "stylelint.vscode-stylelint",
"Vendicated.vencord-companion"
] ]
} }

View file

@ -16,5 +16,6 @@ DON'T
Repetitive violations of these guidelines might get your access to the repository restricted. Repetitive violations of these guidelines might get your access to the repository restricted.
If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from vigilantism
If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from publicly challenging them and instead contact a Moderator on our Discord server or send an email to vendicated+conduct@riseup.net! and instead report the issue to a moderator! The best way is joining our [official Discord community](https://vencord.dev/discord)
and opening a modmail ticket.

View file

@ -1,82 +1,55 @@
# Contribution Guide # Contributing to Vencord
First of all, thank you for contributing! :3 Vencord is a community project and welcomes any kind of contribution from anyone!
To ensure your contribution is robust, please follow the below guide! We have development documentation for new contributors, which can be found at <https://docs.vencord.dev>.
For a friendly introduction to plugins, see [Megu's Plugin Guide!](docs/2_PLUGINS.md) All contributions should be made in accordance with our [Code of Conduct](./CODE_OF_CONDUCT.md).
## Style Guide ## How to contribute
- This project has a very minimal .editorconfig. Make sure your editor supports this! Contributions can be sent via pull requests. If you're new to Git, check [this guide](https://opensource.com/article/19/7/create-pull-request-github).
If you are using VSCode, it should automatically recommend you the extension; If not,
please install the Editorconfig extension
- Try to follow the formatting in the rest of the project and stay consistent
- Follow the file naming convention. File names should usually be camelCase, unless they export a Class
or React Component, in which case they should be PascalCase
## Contributing a Plugin Pull requests can be made either to the `main` or the `dev` branch. However, unless you're an advanced user, I recommend sticking to `main`. This is because the dev branch might contain unstable changes and be force pushed frequently, which could cause conflicts in your pull request.
Because plugins modify code directly, incompatibilities are a problem. ## Write a plugin
Thus, 3rd party plugins are not supported, instead all plugins are part of Vencord itself. Writing a plugin is the primary way to contribute.
This way we can ensure compatibility and high quality patches.
Follow the below guide to make your first plugin! Before starting your plugin:
- Check existing pull requests to see if someone is already working on a similar plugin
- Check our [plugin requests tracker](https://github.com/Vencord/plugin-requests/issues) to see if there is an existing request, or if the same idea has been rejected
- If there isn't an existing request, [open one](https://github.com/Vencord/plugin-requests/issues/new?assignees=&labels=&projects=&template=request.yml) yourself
and include that you'd like to work on this yourself. Then wait for feedback to see if the idea even has any chance of being accepted. Or maybe others have some ideas to improve it!
- Familarise yourself with our plugin rules below to ensure your plugin is not banned
### Finding the right module to patch ### Plugin Rules
If the thing you want to patch is an action performed when interacting with a part of the UI, use React DevTools. - No simple slash command plugins like `/cat`. Instead, make a [user installable Discord bot](https://discord.com/developers/docs/change-log#userinstallable-apps-preview)
They come preinstalled and can be found as the "Components" tab in DevTools. - No simple text replace plugins like Let me Google that for you. The TextReplace plugin can do this
Use the Selector (top left) to select the UI Element. Now you can see all callbacks, props or jump to the source - No raw DOM manipulation. Use proper patches and React
directly. - No FakeDeafen or FakeMute
- No StereoMic
- No plugins that simply hide or redesign ui elements. This can be done with CSS
- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc)
- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones
- No plugins that require the user to enter their own API key
- Do not introduce new dependencies unless absolutely necessary and warranted
If it is anything else, or you're too lazy to use React DevTools, hit `CTRL + Shift + F` while in DevTools and ## Improve Vencord itself
enter a search term, for example "getUser" to search all source files.
Look at the results until you find something promising. Set a breakpoint and trigger the execution of that part of Code to inspect arguments, locals, etc...
### Writing a robust patch If you have any ideas on how to improve Vencord itself, or want to propose a new plugin API, feel free to open a feature request so we can discuss.
##### "find" Or if you notice any bugs or typos, feel free to fix them!
First you need to find a good `find` value. This should be a string that is unique to your module. ## Contribute to our Documentation
If you want to patch the `getUser` function, usually a good first try is `getUser:` or `function getUser()`,
depending on how the module is structured. Again, make sure this string is unique to your module and is not
found in any other module. To verify this, search for it in all bundles (CTRL + Shift + F)
##### "match" The source code of our documentation is available at <https://github.com/Vencord/Docs>
This is the regex that will operate on the module found with "find". Just like in find, you should make sure If you see anything outdated, incorrect or lacking, please fix it!
this only matches exactly the part you want to patch and no other parts in the file. If you think a new page should be added, feel free to suggest it via an issue and we can discuss.
The easiest way to write and test your regex is the following: ## Help out users in our Discord community
- Get the ID of the module you want to patch. To do this, go to it in the sources tab and scroll up until you We have an open support channel in our [Discord community](https://vencord.dev/discord).
see something like `447887: (e,t,n)=>{` (Obviously the number will differ). Helping out users there is always appreciated! The more, the merrier.
- Now paste the following into the console: `Vencord.Webpack.wreq.m[447887].toString()` (Changing the number to your ID)
- Now either test regexes on this string in the console or use a tool like https://regex101.com
Also pay attention to the following:
- Never hardcode variable or parameter names or any other minified names. They will change in the future. The only Exception to this rule
are the react props parameter which seems to always be `e`, but even then only rely on this if it is necessary.
Instead, use one of the following approaches where applicable:
- Match 1 or 2 of any character: `.{1,2}`, for example to match the variable name in `var a=b`, `var (.{1,2})=`
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
`var .{1,2}=([^;]+);`
- If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters
- Additionally, as you might have noticed, all of the 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"
This is the replacement for the match. This is the second argument to [String.replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace), so refer to those docs for info.
Never hardcode minified variable or parameter names here. Instead, use capture groups in your regex to capture the variable names
and use those in your replacement
Make sure your replacement does not introduce any whitespace. While this might seem weird, random whitespace may mess up other patches.
This includes spaces, tabs and especially newlines
---
And that's it! Now open a Pull Request with your Plugin

View file

@ -1,11 +1,11 @@
# Vencord # Vencord
[![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Ven/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Ven/cord) [![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Vee/cord)
The cutest Discord client mod The cutest Discord client mod
| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) | | ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) |
|:--:| | :--------------------------------------------------------------------------------------------------: |
| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) | | A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
## Features ## Features
@ -33,7 +33,7 @@ https://discord.gg/D9uwnFnqmd
## Sponsors ## Sponsors
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** | | **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|:--:| | :------------------------------------------------------------------------------------------: |
| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) | | [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) |
| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* | | *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |

View file

@ -62,7 +62,7 @@ function GM_fetch(url, opt) {
resp.arrayBuffer = () => blobTo("arrayBuffer", blob); resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
resp.text = () => blobTo("text", blob); resp.text = () => blobTo("text", blob);
resp.json = async () => JSON.parse(await blobTo("text", blob)); resp.json = async () => JSON.parse(await blobTo("text", blob));
resp.headers = new Headers(parseHeaders(resp.responseHeaders)); resp.headers = parseHeaders(resp.responseHeaders);
resp.ok = resp.status >= 200 && resp.status < 300; resp.ok = resp.status >= 200 && resp.status < 300;
resolve(resp); resolve(resp);
}; };

View file

@ -19,13 +19,14 @@
/// <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 monacoHtmlLocal from "~fileContent/monacoWin.html"; import monacoHtmlLocal from "file://monacoWin.html?minify";
import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html"; import monacoHtmlCdn from "file://../src/main/monacoWin.html?minify";
import * as DataStore from "../src/api/DataStore"; import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils"; import { debounce } from "../src/utils";
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
import { getTheme, Theme } from "../src/utils/discord"; import { getTheme, Theme } from "../src/utils/discord";
import { getThemeInfo } from "../src/utils/themes/bd"; import { getThemeInfo } from "../src/main/themes";
import { Settings } from "../src/Vencord";
// Discord deletes this so need to store in variable // Discord deletes this so need to store in variable
const { localStorage } = window; const { localStorage } = window;
@ -96,8 +97,15 @@ window.VencordNative = {
}, },
settings: { settings: {
get: () => localStorage.getItem("VencordSettings") || "{}", get: () => {
set: async (s: string) => localStorage.setItem("VencordSettings", s), try {
return JSON.parse(localStorage.getItem("VencordSettings") || "{}");
} catch (e) {
console.error("Failed to parse settings from localStorage: ", e);
return {};
}
},
set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)),
getSettingsDir: async () => "LocalStorage" getSettingsDir: async () => "LocalStorage"
}, },

View file

@ -2,23 +2,22 @@ if (typeof browser === "undefined") {
var browser = chrome; var browser = chrome;
} }
const script = document.createElement("script");
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";
style.rel = "stylesheet"; style.rel = "stylesheet";
style.href = browser.runtime.getURL("dist/Vencord.css"); style.href = browser.runtime.getURL("dist/Vencord.css");
document.documentElement.append(script);
document.addEventListener( document.addEventListener(
"DOMContentLoaded", "DOMContentLoaded",
() => document.documentElement.append(style), () => {
document.documentElement.append(style);
window.postMessage({
type: "vencord:meta",
meta: {
EXTENSION_VERSION: browser.runtime.getManifest().version,
EXTENSION_BASE_URL: browser.runtime.getURL(""),
}
});
},
{ once: true } { once: true }
); );

View file

@ -1,6 +1,6 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"minimum_chrome_version": "91", "minimum_chrome_version": "111",
"name": "Vencord Web", "name": "Vencord Web",
"description": "The cutest Discord mod now in your browser", "description": "The cutest Discord mod now in your browser",
@ -22,7 +22,15 @@
"run_at": "document_start", "run_at": "document_start",
"matches": ["*://*.discord.com/*"], "matches": ["*://*.discord.com/*"],
"js": ["content.js"], "js": ["content.js"],
"all_frames": true "all_frames": true,
"world": "ISOLATED"
},
{
"run_at": "document_start",
"matches": ["*://*.discord.com/*"],
"js": ["dist/Vencord.js"],
"all_frames": true,
"world": "MAIN"
} }
], ],

View file

@ -22,7 +22,15 @@
"run_at": "document_start", "run_at": "document_start",
"matches": ["*://*.discord.com/*"], "matches": ["*://*.discord.com/*"],
"js": ["content.js"], "js": ["content.js"],
"all_frames": true "all_frames": true,
"world": "ISOLATED"
},
{
"run_at": "document_start",
"matches": ["*://*.discord.com/*"],
"js": ["dist/Vencord.js"],
"all_frames": true,
"world": "MAIN"
} }
], ],
@ -35,7 +43,7 @@
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "vencord-firefox@vendicated.dev", "id": "vencord-firefox@vendicated.dev",
"strict_min_version": "91.0" "strict_min_version": "128.0"
} }
} }
} }

View file

@ -1,99 +0,0 @@
> [!WARNING]
> These instructions are only for advanced users. If you're not a Developer, you should use our [graphical installer](https://github.com/Vendicated/VencordInstaller#usage) instead.
> No support will be provided for installing in this fashion. If you cannot figure it out, you should just stick to a regular install.
# Installation Guide
Welcome to Megu's Installation Guide! In this file, you will learn about how to download, install, and uninstall Vencord!
## Sections
- [Installation Guide](#installation-guide)
- [Sections](#sections)
- [Dependencies](#dependencies)
- [Installing Vencord](#installing-vencord)
- [Updating Vencord](#updating-vencord)
- [Uninstalling Vencord](#uninstalling-vencord)
## Dependencies
- Install Git from https://git-scm.com/download
- Install Node.JS LTS from here: https://nodejs.dev/en/
## Installing Vencord
Install `pnpm`:
> :exclamation: This next command may need to be run as admin/root depending on your system, and you may need to close and reopen your terminal for pnpm to be in your PATH.
```shell
npm i -g pnpm
```
> :exclamation: **IMPORTANT** Make sure you aren't using an admin/root terminal from here onwards. It **will** mess up your Discord/Vencord instance and you **will** most likely have to reinstall.
Clone Vencord:
```shell
git clone https://github.com/Vendicated/Vencord
cd Vencord
```
Install dependencies:
```shell
pnpm install --frozen-lockfile
```
Build Vencord:
```shell
pnpm build
```
Inject vencord into your client:
```shell
pnpm inject
```
Then fully close Discord from your taskbar or task manager, and restart it. Vencord should be injected - you can check this by looking for the Vencord section in Discord settings.
## Updating Vencord
If you're using Discord already, go into the `Updater` tab in settings.
Sometimes it may be necessary to manually update if the GUI updater fails.
To pull latest changes:
```shell
git pull
```
If this fails, you likely need to reset your local changes to vencord to resolve merge errors:
> :exclamation: This command will remove any local changes you've made to vencord. Make sure you back up if you made any code changes you don't want to lose!
```shell
git reset --hard
git pull
```
and then to build the changes:
```shell
pnpm build
```
Then just refresh your client
## Uninstalling Vencord
Simply run:
```shell
pnpm uninject
```
If you need more help, ask in the support channel in our [Discord Server](https://discord.gg/D9uwnFnqmd).

View file

@ -1,111 +0,0 @@
# Plugins Guide
Welcome to Megu's Plugin Guide! In this file, you will learn about how to write your own plugin!
You don't need to run `pnpm build` every time you make a change. Instead, use `pnpm watch` - this will auto-compile Vencord whenever you make a change. If using code patches (recommended), you will need to CTRL+R to load the changes.
## Plugin Entrypoint
> If it doesn't already exist, create a folder called `userplugins` in the `src` directory of this repo.
1. Create a folder in `src/userplugins/` with the name of your plugin. For example, `src/userplugins/epicPlugin/` - All of your plugin files will go here.
2. Create a file in that folder called `index.ts`
3. In `index.ts`, copy-paste the following template code:
```ts
import definePlugin from "@utils/types";
export default definePlugin({
name: "Epic Plugin",
description: "This plugin is absolutely epic",
authors: [
{
id: 12345n,
name: "Your Name",
},
],
patches: [],
// Delete these two below if you are only using code patches
start() {},
stop() {},
});
```
Change the name, description, and authors to your own information.
Replace `12345n` with your user ID ending in `n` (e.g., `545581357812678656n`). If you don't want to share your Discord account, use `0n` instead!
## How Plugins Work In Vencord
Vencord uses a different way of making mods than you're used to.
Instead of monkeypatching webpack, we directly modify the code before Discord loads it.
This is _significantly_ more efficient than monkeypatching webpack, and is surprisingly easy, but it may be confusing at first.
## Making your patch
For an in-depth guide into patching code, see [CONTRIBUTING.md](../CONTRIBUTING.md)
in the `index.ts` file we made earlier, you'll see a `patches` array.
> You'll see examples of how patches are used in all the existing plugins, and it'll be easier to understand by looking at those examples, so do that first, and then return here!
> For a good example of a plugin using code patches AND runtime patching, check `src/plugins/unindent.ts`, which uses code patches to run custom runtime code.
One of the patches in the `isStaff` plugin, looks like this:
```ts
{
match: /(\w+)\.isStaff=function\(\){return\s*!1};/,
replace: "$1.isStaff=function(){return true};",
},
```
The above regex matches the string in discord that will look something like:
```js
abc.isStaff = function () {
return !1;
};
```
Remember that Discord code is minified, so there won't be any newlines, and there will only be spaces where necessary. So the source code looks something like:
```
abc.isStaff=function(){return!1;}
```
You can find these snippets by opening the devtools (`ctrl+shift+i`) and pressing `ctrl+shift+f`, searching for what you're looking to modify in there, and beautifying the file to make it more readable.
In the `match` regex in the example shown above, you'll notice at the start there is a `(\w+)`.
Anything in the brackets will be accessible in the `replace` string using `$<number>`. e.g., the first pair of brackets will be `$1`, the second will be `$2`, etc.
The replacement string we used is:
```
"$1.isStaff=function(){return true;};"
```
Which, using the above example, would replace the code with:
> **Note**
> In this example, `$1` becomes `abc`
```js
abc.isStaff = function () {
return true;
};
```
The match value _can_ be a string, rather than regex, however usually regex will be better suited, as it can work with unknown values, whereas strings must be exact matches.
Once you've made your plugin, make sure you run `pnpm test` and make sure your code is nice and clean!
If you want to publish your plugin into the Vencord repo, move your plugin from `src/userplugins` into the `src/plugins` folder and open a PR!
> **Warning**
> Make sure you've read [CONTRIBUTING.md](../CONTRIBUTING.md) before opening a PR
If you need more help, ask in the support channel in our [Discord Server](https://discord.gg/D9uwnFnqmd).

126
eslint.config.mjs Normal file
View file

@ -0,0 +1,126 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
// @ts-check
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import header from "eslint-plugin-simple-header";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports";
import tseslint from "typescript-eslint";
export default tseslint.config(
{ ignores: ["dist", "browser", "packages/vencord-types"] },
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
plugins: {
"simple-header": header,
"@stylistic": stylistic,
"@typescript-eslint": tseslint.plugin,
"simple-import-sort": simpleImportSort,
"unused-imports": unusedImports,
"path-alias": pathAlias,
},
settings: {
"import/resolver": {
map: [
["@webpack", "./src/webpack"],
["@webpack/common", "./src/webpack/common"],
["@utils", "./src/utils"],
["@api", "./src/api"],
["@components", "./src/components"]
]
}
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: ["./tsconfig.json"],
tsconfigRootDir: import.meta.dirname
}
},
rules: {
/*
* Since it's only been a month and Vencord has already been stolen
* by random skids who rebranded it to "AlphaCord" and erased all license
* information
*/
"simple-header/header": [
"error",
{
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
"templates": { "author": [".*", "Vendicated and contributors"] }
}
],
// Style Rules
"@stylistic/jsx-quotes": ["error", "prefer-double"],
"@stylistic/quotes": ["error", "double", { "avoidEscape": true }],
"@stylistic/no-mixed-spaces-and-tabs": "error",
"@stylistic/arrow-parens": ["error", "as-needed"],
"@stylistic/eol-last": ["error", "always"],
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/no-whitespace-before-property": "error",
"@stylistic/semi": ["error", "always"],
"@stylistic/semi-style": ["error", "last"],
"@stylistic/space-in-parens": ["error", "never"],
"@stylistic/block-spacing": ["error", "always"],
"@stylistic/object-curly-spacing": ["error", "always"],
"@stylistic/spaced-comment": ["error", "always", { "markers": ["!"] }],
"@stylistic/no-extra-semi": "error",
// TS Rules
"@stylistic/func-call-spacing": ["error", "never"],
// ESLint Rules
"yoda": "error",
"eqeqeq": ["error", "always", { "null": "ignore" }],
"prefer-destructuring": ["error", {
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}],
"operator-assignment": ["error", "always"],
"no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
"no-invalid-regexp": "error",
"no-constant-condition": ["error", { "checkLoops": false }],
"no-duplicate-imports": "error",
"dot-notation": "error",
"no-useless-escape": [
"error",
{
"extra": "i"
}
],
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
"no-cond-assign": "error",
"no-dupe-else-if": "error",
"no-duplicate-case": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-prototype-builtins": "error",
"no-regex-spaces": "error",
"no-shadow-restricted-names": "error",
"no-unexpected-multiline": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error",
"use-isnan": "error",
"prefer-const": "error",
"prefer-spread": "error",
// Plugin Rules
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"unused-imports/no-unused-imports": "error",
"path-alias/no-relative": "error"
}
}
);

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.6.5", "version": "1.10.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": {
@ -13,74 +13,76 @@
}, },
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"author": "Vendicated", "author": "Vendicated",
"directories": {
"doc": "docs"
},
"scripts": { "scripts": {
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs", "build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
"buildStandalone": "pnpm build --standalone",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
"buildWebStandalone": "pnpm buildWeb --standalone",
"buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
"buildReporterDesktop": "pnpm build --reporter",
"watch": "pnpm build --watch",
"dev": "pnpm watch",
"watchWeb": "pnpm buildWeb --watch",
"generatePluginJson": "tsx scripts/generatePluginList.ts", "generatePluginJson": "tsx scripts/generatePluginList.ts",
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
"inject": "node scripts/runInstaller.mjs", "inject": "node scripts/runInstaller.mjs",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins", "uninject": "node scripts/runInstaller.mjs",
"lint": "eslint",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix", "lint:fix": "pnpm lint --fix",
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson", "test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit", "testTsc": "tsc --noEmit"
"uninject": "node scripts/runInstaller.mjs",
"watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch"
}, },
"dependencies": { "dependencies": {
"@sapphi-red/web-noise-suppressor": "0.3.3", "@sapphi-red/web-noise-suppressor": "0.3.5",
"@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.8.2",
"fflate": "^0.7.4",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.43.0", "monaco-editor": "^0.50.0",
"nanoid": "^4.0.2", "nanoid": "^5.0.7",
"usercss-meta": "^0.12.0",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.246", "@stylistic/eslint-plugin": "^2.6.1",
"@types/diff": "^5.0.3", "@types/chrome": "^0.0.269",
"@types/less": "^3.0.4", "@types/diff": "^5.2.1",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.17.7",
"@types/node": "^18.16.3", "@types/node": "^22.0.3",
"@types/react": "^18.2.0", "@types/react": "^18.3.3",
"@types/react-dom": "^18.2.1", "@types/react-dom": "^18.3.0",
"@types/stylus": "^0.48.39", "@types/yazl": "^2.4.5",
"@types/tinycolor2": "^1.4.3", "diff": "^5.2.0",
"@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"diff": "^5.1.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.15.18",
"eslint": "^8.46.0", "eslint": "^9.8.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-header": "^1.1.1",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-simple-import-sort": "^12.1.1",
"highlight.js": "10.6.0", "eslint-plugin-unused-imports": "^4.0.1",
"moment": "^2.29.4", "highlight.js": "10.7.3",
"puppeteer-core": "^19.11.1", "html-minifier-terser": "^7.2.0",
"moment": "^2.30.1",
"puppeteer-core": "^22.15.0",
"standalone-electron-types": "^1.0.0", "standalone-electron-types": "^1.0.0",
"stylelint": "^15.6.0", "stylelint": "^16.8.1",
"stylelint-config-standard": "^33.0.0", "stylelint-config-standard": "^36.0.1",
"tsx": "^3.12.7", "ts-patch": "^3.2.1",
"type-fest": "^3.9.0", "ts-pattern": "^5.3.1",
"typescript": "^5.0.4", "tsx": "^4.16.5",
"zip-local": "^0.3.5", "type-fest": "^4.23.0",
"zustand": "^3.7.2" "typescript": "^5.5.4",
"typescript-eslint": "^8.0.0",
"typescript-transform-paths": "^3.4.7",
"zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@8.10.2", "packageManager": "pnpm@9.1.0",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch", "eslint@9.8.0": "patches/eslint@9.8.0.patch",
"eslint@8.46.0": "patches/eslint@8.46.0.patch", "eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
"@types/less@3.0.4": "patches/@types__less@3.0.4.patch"
}, },
"peerDependencyRules": { "peerDependencyRules": {
"ignoreMissing": [ "ignoreMissing": [
@ -104,6 +106,6 @@
}, },
"engines": { "engines": {
"node": ">=18", "node": ">=18",
"pnpm": ">=8" "pnpm": ">=9"
} }
} }

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

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

View file

@ -0,0 +1,4 @@
node_modules
prepare.ts
.gitignore
HOW2PUB.md

View file

@ -0,0 +1,5 @@
# How to publish
1. run `pnpm generateTypes` in the project root
2. bump package.json version
3. npm publish

View file

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

24
packages/vencord-types/globals.d.ts vendored Normal file
View file

@ -0,0 +1,24 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare global {
export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord");
}
export { };

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

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

View file

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

View file

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

View file

@ -1,13 +0,0 @@
diff --git a/index.d.ts b/index.d.ts
index eb4f07d47b932fb9cc8c8cd451ab107f648bd013..18a3e15a1997734e1773718e5be55d252ed9478c 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -306,7 +306,5 @@ interface LessStatic {
}
declare module "less" {
- export = less;
+ export = LessStatic;
}
-
-declare var less: LessStatic;

View file

@ -1,13 +0,0 @@
diff --git a/lib/rules/no-relative.js b/lib/rules/no-relative.js
index 71594c83f1f4f733ffcc6047d7f7084348335dbe..d8623d87c89499c442171db3272cba07c9efabbe 100644
--- a/lib/rules/no-relative.js
+++ b/lib/rules/no-relative.js
@@ -41,7 +41,7 @@ module.exports = {
ImportDeclaration(node) {
const importPath = node.source.value;
- if (!/^(\.?\.\/)/.test(importPath)) {
+ if (!/^(\.\.\/)/.test(importPath)) {
return;
}

View file

@ -0,0 +1,14 @@
diff --git a/dist/index.js b/dist/index.js
index 67de6fb139070fd0e49beca65e3b63c531202e16..aa2883c8126e4952a42872ee920f59547a066430 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1 +1 @@
-var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.?\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}};
+var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}};
diff --git a/dist/index.mjs b/dist/index.mjs
index 96de18e06d4cc413e11af038cd760e4804c32e59..27e8c4e3e2c942400cc3982e52159904ca6eedfa 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -1 +1 @@
-var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.?\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default};
+var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default};

File diff suppressed because it is too large Load diff

2
pnpm-workspace.yaml Normal file
View file

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

View file

@ -21,19 +21,21 @@ import esbuild from "esbuild";
import { readdir } from "fs/promises"; import { readdir } from "fs/promises";
import { join } from "path"; import { join } from "path";
import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isDev, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs";
const defines = { const defines = {
IS_STANDALONE: isStandalone, IS_STANDALONE,
IS_DEV: JSON.stringify(isDev), IS_DEV,
IS_UPDATER_DISABLED: updaterDisabled, IS_REPORTER,
IS_UPDATER_DISABLED,
IS_WEB: false, IS_WEB: false,
IS_EXTENSION: false, IS_EXTENSION: false,
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP, BUILD_TIMESTAMP
}; };
if (defines.IS_STANDALONE === "false")
// If this is a local build (not standalone), optimise if (defines.IS_STANDALONE === false)
// If this is a local build (not standalone), optimize
// for the specific platform we're on // for the specific platform we're on
defines["process.platform"] = JSON.stringify(process.platform); defines["process.platform"] = JSON.stringify(process.platform);
@ -46,7 +48,7 @@ const nodeCommonOpts = {
platform: "node", platform: "node",
target: ["esnext"], target: ["esnext"],
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external], external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
define: defines, define: defines
}; };
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
@ -73,23 +75,21 @@ const globNativesPlugin = {
let i = 0; let i = 0;
for (const dir of pluginDirs) { for (const dir of pluginDirs) {
const dirPath = join("src", dir); const dirPath = join("src", dir);
if (!await existsAsync(dirPath)) continue; if (!await exists(dirPath)) continue;
const plugins = await readdir(dirPath); const plugins = await readdir(dirPath, { withFileTypes: true });
for (const p of plugins) { for (const file of plugins) {
const nativePath = join(dirPath, p, "native.ts"); const fileName = file.name;
const indexNativePath = join(dirPath, p, "native/index.ts"); const nativePath = join(dirPath, fileName, "native.ts");
const indexNativePath = join(dirPath, fileName, "native/index.ts");
if (!(await existsAsync(nativePath)) && !(await existsAsync(indexNativePath))) if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
continue; continue;
const nameParts = p.split("."); const pluginName = await resolvePluginName(dirPath, file);
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
// pluginName.thing.desktop -> PluginName.thing
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
const mod = `p${i}`; const mod = `p${i}`;
code += `import * as ${mod} from "./${dir}/${p}/native";\n`; code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`;
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`; natives += `${JSON.stringify(pluginName)}:${mod},\n`;
i++; i++;
} }
} }
@ -131,7 +131,7 @@ await Promise.all([
sourcemap, sourcemap,
plugins: [ plugins: [
globPlugins("discordDesktop"), globPlugins("discordDesktop"),
...commonOpts.plugins ...commonRendererPlugins
], ],
define: { define: {
...defines, ...defines,
@ -180,7 +180,7 @@ await Promise.all([
sourcemap, sourcemap,
plugins: [ plugins: [
globPlugins("vencordDesktop"), globPlugins("vencordDesktop"),
...commonOpts.plugins ...commonRendererPlugins
], ],
define: { define: {
...defines, ...defines,

View file

@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises
import { join } from "path"; import { join } from "path";
import Zip from "zip-local"; import Zip from "zip-local";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isDev, VERSION } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
/** /**
* @type {esbuild.BuildOptions} * @type {esbuild.BuildOptions}
@ -33,22 +33,23 @@ const commonOptions = {
entryPoints: ["browser/Vencord.ts"], entryPoints: ["browser/Vencord.ts"],
globalName: "Vencord", globalName: "Vencord",
format: "iife", format: "iife",
external: ["plugins", "git-hash", "/assets/*"], external: ["~plugins", "~git-hash", "/assets/*"],
plugins: [ plugins: [
globPlugins("web"), globPlugins("web"),
...commonOpts.plugins, ...commonRendererPlugins
], ],
target: ["esnext"], target: ["esnext"],
define: { define: {
IS_WEB: "true", IS_WEB: true,
IS_EXTENSION: "false", IS_EXTENSION: false,
IS_STANDALONE: "true", IS_STANDALONE: true,
IS_DEV: JSON.stringify(isDev), IS_DEV,
IS_DISCORD_DESKTOP: "false", IS_REPORTER,
IS_VESKTOP: "false", IS_DISCORD_DESKTOP: false,
IS_UPDATER_DISABLED: "true", IS_VESKTOP: false,
IS_UPDATER_DISABLED: true,
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP, BUILD_TIMESTAMP
} }
}; };
@ -87,16 +88,16 @@ await Promise.all(
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
outfile: "dist/browser.js", outfile: "dist/browser.js",
footer: { js: "//# sourceURL=VencordWeb" }, footer: { js: "//# sourceURL=VencordWeb" }
}), }),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
outfile: "dist/extension.js", outfile: "dist/extension.js",
define: { define: {
...commonOptions?.define, ...commonOptions?.define,
IS_EXTENSION: "true", IS_EXTENSION: true,
}, },
footer: { js: "//# sourceURL=VencordWeb" }, footer: { js: "//# sourceURL=VencordWeb" }
}), }),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
@ -112,10 +113,15 @@ await Promise.all(
footer: { footer: {
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});" js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
}, }
}) })
] ]
); ).catch(err => {
console.error("Build failed");
console.error(err.message);
if (!commonOpts.watch)
process.exit(1);
});;
/** /**
* @type {(dir: string) => Promise<string[]>} * @type {(dir: string) => Promise<string[]>}
@ -165,7 +171,7 @@ async function buildExtension(target, files) {
f.startsWith("manifest") ? "manifest.json" : f, f.startsWith("manifest") ? "manifest.json" : f,
content content
]; ];
}))), })))
}; };
await rm(target, { recursive: true, force: true }); await rm(target, { recursive: true, force: true });
@ -192,14 +198,19 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
return appendFile("dist/Vencord.user.js", cssRuntime); return appendFile("dist/Vencord.user.js", cssRuntime);
}); });
await Promise.all([ if (!process.argv.includes("--skip-extension")) {
await Promise.all([
appendCssRuntime, appendCssRuntime,
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]), buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
]); ]);
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip"); Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
console.info("Packed Chromium Extension written to dist/extension-chrome.zip"); console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip"); Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
console.info("Packed Firefox Extension written to dist/extension-firefox.zip"); console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
} else {
await appendCssRuntime;
}

View file

@ -20,36 +20,68 @@ import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js"; import "../checkNodeVersion.js";
import { exec, execSync } from "child_process"; import { exec, execSync } from "child_process";
import esbuild from "esbuild";
import { constants as FsConstants, readFileSync } from "fs"; import { constants as FsConstants, readFileSync } from "fs";
import { access, readdir, readFile } from "fs/promises"; import { access, readdir, readFile } from "fs/promises";
import { minify as minifyHtml } from "html-minifier-terser";
import { join, relative } from "path"; import { join, relative } from "path";
import { promisify } from "util"; import { promisify } from "util";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
import { getPluginTarget } from "../utils.mjs"; import { getPluginTarget } from "../utils.mjs";
import { builtinModules } from "module";
/** @type {import("../../package.json")} */
const PackageJSON = JSON.parse(readFileSync("package.json"));
export const VERSION = PackageJSON.version; export const VERSION = PackageJSON.version;
// https://reproducible-builds.org/docs/source-date-epoch/ // https://reproducible-builds.org/docs/source-date-epoch/
export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now(); 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 isDev = watch || process.argv.includes("--dev"); export const IS_DEV = watch || process.argv.includes("--dev");
export const isStandalone = JSON.stringify(process.argv.includes("--standalone")); export const IS_REPORTER = process.argv.includes("--reporter");
export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater")); export const IS_STANDALONE = process.argv.includes("--standalone");
export const IS_UPDATER_DISABLED = process.argv.includes("--disable-updater");
export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); 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: ${IS_STANDALONE}
// Platform: ${isStandalone === "false" ? process.platform : "Universal"} // Platform: ${IS_STANDALONE === false ? process.platform : "Universal"}
// Updater disabled: ${updaterDisabled} // Updater Disabled: ${IS_UPDATER_DISABLED}
`.trim() `.trim()
}; };
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs")); const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
/**
* @param {string} base
* @param {import("fs").Dirent} dirent
*/
export async function resolvePluginName(base, dirent) {
const fullPath = join(base, dirent.name);
const content = dirent.isFile()
? await readFile(fullPath, "utf-8")
: await (async () => {
for (const file of ["index.ts", "index.tsx"]) {
try {
return await readFile(join(fullPath, file), "utf-8");
} catch {
continue;
}
}
throw new Error(`Invalid plugin ${fullPath}: could not resolve entry point`);
})();
export function existsAsync(path) { return PluginDefinitionNameMatcher.exec(content)?.[3]
return access(path, FsConstants.F_OK) ?? (() => {
throw new Error(`Invalid plugin ${fullPath}: must contain definePlugin call with simple string name property as first property`);
})();
}
export async function exists(path) {
return await access(path, FsConstants.F_OK)
.then(() => true) .then(() => true)
.catch(() => false); .catch(() => false);
} }
@ -63,7 +95,7 @@ export const makeAllPackagesExternalPlugin = {
setup(build) { setup(build) {
const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../" const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
build.onResolve({ filter }, args => ({ path: args.path, external: true })); build.onResolve({ filter }, args => ({ path: args.path, external: true }));
}, }
}; };
/** /**
@ -83,31 +115,48 @@ export const globPlugins = kind => ({
build.onLoad({ filter, namespace: "import-plugins" }, async () => { build.onLoad({ filter, namespace: "import-plugins" }, async () => {
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"]; const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
let code = ""; let code = "";
let plugins = "\n"; let pluginsCode = "\n";
let metaCode = "\n";
let excludedCode = "\n";
let i = 0; let i = 0;
for (const dir of pluginDirs) { for (const dir of pluginDirs) {
if (!await existsAsync(`./src/${dir}`)) continue; const userPlugin = dir === "userplugins";
const files = await readdir(`./src/${dir}`);
for (const file of files) {
if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue;
const target = getPluginTarget(file); const fullDir = `./src/${dir}`;
if (target) { if (!await exists(fullDir)) continue;
if (target === "dev" && !watch) continue; const files = await readdir(fullDir, { withFileTypes: true });
if (target === "web" && kind === "discordDesktop") continue; for (const file of files) {
if (target === "desktop" && kind === "web") continue; const fileName = file.name;
if (target === "discordDesktop" && kind !== "discordDesktop") continue; if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue; if (fileName === "index.ts") continue;
const target = getPluginTarget(fileName);
if (target && !IS_REPORTER) {
const excluded =
(target === "dev" && !IS_DEV) ||
(target === "web" && kind === "discordDesktop") ||
(target === "desktop" && kind === "web") ||
(target === "discordDesktop" && kind !== "discordDesktop") ||
(target === "vencordDesktop" && kind !== "vencordDesktop");
if (excluded) {
const name = await resolvePluginName(fullDir, file);
excludedCode += `${JSON.stringify(name)}:${JSON.stringify(target)},\n`;
continue;
}
} }
const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
const mod = `p${i}`; const mod = `p${i}`;
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`; code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
plugins += `[${mod}.name]:${mod},\n`; pluginsCode += `[${mod}.name]:${mod},\n`;
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
i++; i++;
} }
} }
code += `export default {${plugins}};`; code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`;
return { return {
contents: code, contents: code,
resolveDir: "./src" resolveDir: "./src"
@ -160,21 +209,60 @@ export const gitRemotePlugin = {
/** /**
* @type {import("esbuild").Plugin} * @type {import("esbuild").Plugin}
*/ */
export const fileIncludePlugin = { export const fileUrlPlugin = {
name: "file-include-plugin", name: "file-uri-plugin",
setup: build => { setup: build => {
const filter = /^~fileContent\/.+$/; const filter = /^file:\/\/.+$/;
build.onResolve({ filter }, args => ({ build.onResolve({ filter }, args => ({
namespace: "include-file", namespace: "file-uri",
path: args.path, path: args.path,
pluginData: { pluginData: {
path: join(args.resolveDir, args.path.slice("include-file/".length)) uri: args.path,
path: join(args.resolveDir, args.path.slice("file://".length).split("?")[0])
} }
})); }));
build.onLoad({ filter, namespace: "include-file" }, async ({ pluginData: { path } }) => { build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => {
const [name, format] = path.split(";"); const { searchParams } = new URL(uri);
const base64 = searchParams.has("base64");
const minify = IS_STANDALONE === true && searchParams.has("minify");
const noTrim = searchParams.get("trim") === "false";
const encoding = base64 ? "base64" : "utf-8";
let content;
if (!minify) {
content = await readFile(path, encoding);
if (!noTrim) content = content.trimEnd();
} else {
if (path.endsWith(".html")) {
content = await minifyHtml(await readFile(path, "utf-8"), {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
});
} else if (/[mc]?[jt]sx?$/.test(path)) {
const res = await esbuild.build({
entryPoints: [path],
write: false,
minify: true
});
content = res.outputFiles[0].text;
} else {
throw new Error(`Don't know how to minify file type: ${path}`);
}
if (base64)
content = Buffer.from(content).toString("base64");
}
return { return {
contents: `export default ${JSON.stringify(await readFile(name, format ?? "utf-8"))}` contents: `export default ${JSON.stringify(content)}`
}; };
}); });
} }
@ -205,6 +293,18 @@ export const stylePlugin = {
} }
}; };
/**
* @type {(filter: RegExp, message: string) => import("esbuild").Plugin}
*/
export const banImportPlugin = (filter, message) => ({
name: "ban-imports",
setup: build => {
build.onResolve({ filter }, () => {
return { errors: [{ text: message }] };
});
}
});
/** /**
* @type {import("esbuild").BuildOptions} * @type {import("esbuild").BuildOptions}
*/ */
@ -216,7 +316,7 @@ export const commonOpts = {
sourcemap: watch ? "inline" : "", sourcemap: watch ? "inline" : "",
legalComments: "linked", legalComments: "linked",
banner, banner,
plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin], plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"], external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
inject: ["./scripts/build/inject/react.mjs"], inject: ["./scripts/build/inject/react.mjs"],
jsxFactory: "VencordCreateElement", jsxFactory: "VencordCreateElement",
@ -224,3 +324,16 @@ export const commonOpts = {
// Work around https://github.com/evanw/esbuild/issues/2460 // Work around https://github.com/evanw/esbuild/issues/2460
tsconfig: "./scripts/build/tsconfig.esbuild.json" tsconfig: "./scripts/build/tsconfig.esbuild.json"
}; };
const escapedBuiltinModules = builtinModules
.map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
.join("|");
const builtinModuleRegex = new RegExp(`^(node:)?(${escapedBuiltinModules})$`);
export const commonRendererPlugins = [
banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"),
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"),
...commonOpts.plugins
];

View file

@ -39,7 +39,7 @@ interface PluginData {
hasCommands: boolean; hasCommands: boolean;
required: boolean; required: boolean;
enabledByDefault: boolean; enabledByDefault: boolean;
target: "discordDesktop" | "vencordDesktop" | "web" | "dev"; target: "discordDesktop" | "vencordDesktop" | "desktop" | "web" | "dev";
filePath: string; filePath: string;
} }

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* eslint-disable no-fallthrough */
// eslint-disable-next-line spaced-comment // eslint-disable-next-line spaced-comment
/// <reference types="../src/globals" /> /// <reference types="../src/globals" />
// eslint-disable-next-line spaced-comment // eslint-disable-next-line spaced-comment
@ -34,16 +36,18 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
const CANARY = process.env.USE_CANARY === "true"; const CANARY = process.env.USE_CANARY === "true";
const browser = await pup.launch({ const browser = await pup.launch({
headless: "new", headless: true,
executablePath: process.env.CHROMIUM_BIN executablePath: process.env.CHROMIUM_BIN
}); });
const page = await browser.newPage(); const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"); await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
await page.setBypassCSP(true);
function maybeGetError(handle: JSHandle) { async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
return (handle as JSHandle<Error>)?.getProperty("message") return await (handle as JSHandle<Error>)?.getProperty("message")
.then(m => m.jsonValue()); .then(m => m?.jsonValue())
.catch(() => undefined);
} }
const report = { const report = {
@ -59,6 +63,7 @@ const report = {
error: string; error: string;
}[], }[],
otherErrors: [] as string[], otherErrors: [] as string[],
ignoredErrors: [] as string[],
badWebpackFinds: [] as string[] badWebpackFinds: [] as string[]
}; };
@ -67,12 +72,15 @@ const IGNORED_DISCORD_ERRORS = [
"Unable to process domain list delta: Client revision number is null", "Unable to process domain list delta: Client revision number is null",
"Downloading the full bad domains file", "Downloading the full bad domains file",
/\[GatewaySocket\].{0,110}Cannot access '/, /\[GatewaySocket\].{0,110}Cannot access '/,
"search for 'name' in undefined" "search for 'name' in undefined",
"Attempting to set fast connect zstd when unsupported"
] as Array<string | RegExp>; ] as Array<string | RegExp>;
function toCodeBlock(s: string) { function toCodeBlock(s: string, indentation = 0, isDiscord = false) {
s = s.replace(/```/g, "`\u200B`\u200B`"); s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```";
const indentationStr = Array(!isDiscord ? indentation : 0).fill(" ").join("");
return `\`\`\`\n${s.split("\n").map(s => indentationStr + s).join("\n")}\n${indentationStr}\`\`\``;
} }
async function printReport() { async function printReport() {
@ -86,44 +94,35 @@ async function printReport() {
report.badPatches.forEach(p => { report.badPatches.forEach(p => {
console.log(`- ${p.plugin} (${p.type})`); console.log(`- ${p.plugin} (${p.type})`);
console.log(` - ID: \`${p.id}\``); console.log(` - ID: \`${p.id}\``);
console.log(` - Match: ${toCodeBlock(p.match)}`); console.log(` - Match: ${toCodeBlock(p.match, " - Match: ".length)}`);
if (p.error) console.log(` - Error: ${toCodeBlock(p.error)}`); if (p.error) console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`);
}); });
console.log(); console.log();
console.log("## Bad Webpack Finds"); console.log("## Bad Webpack Finds");
report.badWebpackFinds.forEach(p => console.log("- " + p)); report.badWebpackFinds.forEach(p => console.log("- " + toCodeBlock(p, "- ".length)));
console.log(); console.log();
console.log("## Bad Starts"); console.log("## Bad Starts");
report.badStarts.forEach(p => { report.badStarts.forEach(p => {
console.log(`- ${p.plugin}`); console.log(`- ${p.plugin}`);
console.log(` - Error: ${toCodeBlock(p.error)}`); console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`);
}); });
console.log(); console.log();
const ignoredErrors = [] as string[];
report.otherErrors = report.otherErrors.filter(e => {
if (IGNORED_DISCORD_ERRORS.some(regex => e.match(regex))) {
ignoredErrors.push(e);
return false;
}
return true;
});
console.log("## Discord Errors"); console.log("## Discord Errors");
report.otherErrors.forEach(e => { report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e, "- ".length)}`);
}); });
console.log(); console.log();
console.log("## Ignored Discord Errors"); console.log("## Ignored Discord Errors");
ignoredErrors.forEach(e => { report.ignoredErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e, "- ".length)}`);
}); });
console.log(); console.log();
@ -137,7 +136,6 @@ async function printReport() {
body: JSON.stringify({ body: JSON.stringify({
description: "Here's the latest Vencord Report!", description: "Here's the latest Vencord Report!",
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""), username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
avatar_url: "https://cdn.discordapp.com/avatars/1017176847865352332/c312b6b44179ae6817de7e4b09e9c6af.webp?size=512",
embeds: [ embeds: [
{ {
title: "Bad Patches", title: "Bad Patches",
@ -145,16 +143,16 @@ async function printReport() {
const lines = [ const lines = [
`**__${p.plugin} (${p.type}):__**`, `**__${p.plugin} (${p.type}):__**`,
`ID: \`${p.id}\``, `ID: \`${p.id}\``,
`Match: ${toCodeBlock(p.match)}` `Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
]; ];
if (p.error) lines.push(`Error: ${toCodeBlock(p.error)}`); if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
return lines.join("\n"); return lines.join("\n");
}).join("\n\n") || "None", }).join("\n\n") || "None",
color: report.badPatches.length ? 0xff0000 : 0x00ff00 color: report.badPatches.length ? 0xff0000 : 0x00ff00
}, },
{ {
title: "Bad Webpack Finds", title: "Bad Webpack Finds",
description: report.badWebpackFinds.map(toCodeBlock).join("\n") || "None", description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00 color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
}, },
{ {
@ -162,7 +160,7 @@ async function printReport() {
description: report.badStarts.map(p => { description: report.badStarts.map(p => {
const lines = [ const lines = [
`**__${p.plugin}:__**`, `**__${p.plugin}:__**`,
toCodeBlock(p.error) toCodeBlock(p.error, 0, true)
]; ];
return lines.join("\n"); return lines.join("\n");
} }
@ -171,7 +169,7 @@ async function printReport() {
}, },
{ {
title: "Discord Errors", title: "Discord Errors",
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None", description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
color: report.otherErrors.length ? 0xff0000 : 0x00ff00 color: report.otherErrors.length ? 0xff0000 : 0x00ff00
} }
] ]
@ -187,33 +185,39 @@ page.on("console", async e => {
const level = e.type(); const level = e.type();
const rawArgs = e.args(); const rawArgs = e.args();
const firstArg = await rawArgs[0]?.jsonValue(); async function getText() {
if (firstArg === "[PUPPETEER_TEST_DONE_SIGNAL]") { try {
await browser.close(); return await Promise.all(
await printReport(); e.args().map(async a => {
process.exit(); return await maybeGetError(a) || await a.jsonValue();
})
).then(a => a.join(" ").trim());
} catch {
return e.text();
} }
}
const firstArg = await rawArgs[0]?.jsonValue();
const isVencord = firstArg === "[Vencord]"; const isVencord = firstArg === "[Vencord]";
const isDebug = firstArg === "[PUP_DEBUG]"; const isDebug = firstArg === "[PUP_DEBUG]";
const isWebpackFindFail = firstArg === "[PUP_WEBPACK_FIND_FAIL]";
if (isWebpackFindFail) { outer:
process.exitCode = 1; if (isVencord) {
report.badWebpackFinds.push(await rawArgs[1].jsonValue() as string); try {
var args = await Promise.all(e.args().map(a => a.jsonValue()));
} catch {
break outer;
} }
if (isVencord) { const [, tag, message, otherMessage] = args as Array<string>;
const args = await Promise.all(e.args().map(a => a.jsonValue()));
const [, tag, message] = args as Array<string>;
const cause = await maybeGetError(e.args()[3]);
switch (tag) { switch (tag) {
case "WebpackInterceptor:": case "WebpackInterceptor:":
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!; const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
if (!patchFailMatch) break; if (!patchFailMatch) break;
console.error(await getText());
process.exitCode = 1; process.exitCode = 1;
const [, plugin, type, id, regex] = patchFailMatch; const [, plugin, type, id, regex] = patchFailMatch;
@ -222,7 +226,7 @@ page.on("console", async e => {
type, type,
id, id,
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"), match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
error: cause error: await maybeGetError(e.args()[3])
}); });
break; break;
@ -230,85 +234,72 @@ page.on("console", async e => {
const failedToStartMatch = message.match(/Failed to start (.+)/); const failedToStartMatch = message.match(/Failed to start (.+)/);
if (!failedToStartMatch) break; if (!failedToStartMatch) break;
console.error(await getText());
process.exitCode = 1; process.exitCode = 1;
const [, name] = failedToStartMatch; const [, name] = failedToStartMatch;
report.badStarts.push({ report.badStarts.push({
plugin: name, plugin: name,
error: cause error: await maybeGetError(e.args()[3]) ?? "Unknown error"
}); });
break; break;
case "LazyChunkLoader:":
console.error(await getText());
switch (message) {
case "A fatal error occurred:":
process.exit(1);
}
break;
case "Reporter:":
console.error(await getText());
switch (message) {
case "A fatal error occurred:":
process.exit(1);
case "Webpack Find Fail:":
process.exitCode = 1;
report.badWebpackFinds.push(otherMessage);
break;
case "Finished test":
await browser.close();
await printReport();
process.exit();
}
} }
} }
if (isDebug) { if (isDebug) {
console.error(e.text()); console.error(await getText());
} else if (level === "error") { } else if (level === "error") {
const text = await Promise.all( const text = await getText();
e.args().map(async a => {
try {
return await maybeGetError(a) || await a.jsonValue();
} catch (e) {
return a.toString();
}
})
).then(a => a.join(" ").trim());
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
report.ignoredErrors.push(text);
} else {
console.error("[Unexpected Error]", text); console.error("[Unexpected Error]", text);
report.otherErrors.push(text); report.otherErrors.push(text);
} }
} }
}
}); });
page.on("error", e => console.error("[Error]", e)); page.on("error", e => console.error("[Error]", e.message));
page.on("pageerror", e => console.error("[Page Error]", e)); page.on("pageerror", e => {
if (e.message.includes("Sentry successfully disabled")) return;
await page.setBypassCSP(true); if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
console.error("[Page Error]", e.message);
function runTime(token: string) { report.otherErrors.push(e.message);
console.log("[PUP_DEBUG]", "Starting test..."); } else {
report.ignoredErrors.push(e.message);
try { }
// Spoof languages to not be suspicious });
Object.defineProperty(navigator, "languages", {
get: function () {
return ["en-US", "en"];
},
});
// Monkey patch Logger to not log with custom css
// @ts-ignore
Vencord.Util.Logger.prototype._log = function (level, levelColor, args) {
if (level === "warn" || level === "error")
console[level]("[Vencord]", this.name + ":", ...args);
};
// Force enable all plugins and patches
Vencord.Plugins.patches.length = 0;
Object.values(Vencord.Plugins.plugins).forEach(p => {
// Needs native server to run
if (p.name === "WebRichPresence (arRPC)") return;
Vencord.Settings.plugins[p.name].enabled = true;
p.patches?.forEach(patch => {
patch.plugin = p.name;
delete patch.predicate;
delete patch.group;
if (!Array.isArray(patch.replacement))
patch.replacement = [patch.replacement];
patch.replacement.forEach(r => {
delete r.predicate;
});
Vencord.Plugins.patches.push(patch);
});
});
async function reporterRuntime(token: string) {
Vencord.Webpack.waitFor( Vencord.Webpack.waitFor(
"loginToken", "loginToken",
m => { m => {
@ -316,162 +307,13 @@ function runTime(token: string) {
m.loginToken(token); m.loginToken(token);
} }
); );
// Force load all chunks
Vencord.Webpack.onceReady.then(() => setTimeout(async () => {
console.log("[PUP_DEBUG]", "Webpack is ready!");
const { wreq } = Vencord.Webpack;
console.log("[PUP_DEBUG]", "Loading all chunks...");
let chunks = null as Record<number, string[]> | null;
const sym = Symbol("Vencord.chunksExtract");
Object.defineProperty(Object.prototype, sym, {
get() {
chunks = this;
},
set() { },
configurable: true,
});
await (wreq as any).el(sym);
delete Object.prototype[sym];
const validChunksEntryPoints = new Set<string>();
const validChunks = new Set<string>();
const invalidChunks = new Set<string>();
if (!chunks) throw new Error("Failed to get chunks");
for (const entryPoint in chunks) {
const chunkIds = chunks[entryPoint];
let invalidEntryPoint = false;
for (const id of chunkIds) {
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text())
.then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
if (isWasm) {
invalidChunks.add(id);
invalidEntryPoint = true;
continue;
}
validChunks.add(id);
}
if (!invalidEntryPoint)
validChunksEntryPoints.add(entryPoint);
}
for (const entryPoint of validChunksEntryPoints) {
try {
// Loads all chunks required for an entry point
await (wreq as any).el(entryPoint);
} catch (err) { }
}
// Matches "id" or id:
const chunkIdRegex = /(?:"(\d+?)")|(?:(\d+?):)/g;
const wreqU = wreq.u.toString();
const allChunks = [] as string[];
let currentMatch: RegExpExecArray | null;
while ((currentMatch = chunkIdRegex.exec(wreqU)) != null) {
const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue;
allChunks.push(id);
}
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
const chunksLeft = allChunks.filter(id => {
return !(validChunks.has(id) || invalidChunks.has(id));
});
for (const id of chunksLeft) {
const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text())
.then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
// Loads a chunk
if (!isWasm) await wreq.e(id as any);
}
// Make sure every chunk has finished loading
await new Promise(r => setTimeout(r, 1000));
for (const entryPoint of validChunksEntryPoints) {
try {
if (wreq.m[entryPoint]) wreq(entryPoint as any);
} catch (err) {
console.error(err);
}
}
console.log("[PUP_DEBUG]", "Finished loading all chunks!");
for (const patch of Vencord.Plugins.patches) {
if (!patch.all) {
new Vencord.Util.Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
}
}
for (const [searchType, args] of Vencord.Webpack.lazyWebpackSearchHistory) {
let method = searchType;
if (searchType === "findComponent") method = "find";
if (searchType === "findExportedComponent") method = "findByProps";
if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") {
if (typeof args[0] === "string") method = "findByProps";
else method = "find";
}
try {
let result: any;
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
const [factory] = args;
result = factory();
} else if (method === "extractAndLoadChunks") {
const [code, matcher] = args;
const module = Vencord.Webpack.findModuleFactory(...code);
if (module) result = module.toString().match(Vencord.Util.canonicalizeMatch(matcher));
} else {
// @ts-ignore
result = Vencord.Webpack[method](...args);
}
if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
} catch (e) {
let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage);
}
}
setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000);
}, 1000));
} catch (e) {
console.log("[PUP_DEBUG]", "A fatal error occurred:", e);
process.exit(1);
}
} }
await page.evaluateOnNewDocument(` await page.evaluateOnNewDocument(`
${readFileSync("./dist/browser.js", "utf-8")} if (location.host.endsWith("discord.com")) {
${readFileSync("./dist/browser.js", "utf-8")};
;(${runTime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)}); (${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
}
`); `);
await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login"); await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login");

View file

@ -35,11 +35,11 @@ const ETAG_FILE = join(FILE_DIR, "etag.txt");
function getFilename() { function getFilename() {
switch (process.platform) { switch (process.platform) {
case "win32": case "win32":
return "VencordInstaller.exe"; return "VencordInstallerCli.exe";
case "darwin": case "darwin":
return "VencordInstaller.MacOS.zip"; return "VencordInstaller.MacOS.zip";
case "linux": case "linux":
return "VencordInstaller-" + (process.env.WAYLAND_DISPLAY ? "wayland" : "x11"); return "VencordInstallerCli-linux";
default: default:
throw new Error("Unsupported platform: " + process.platform); throw new Error("Unsupported platform: " + process.platform);
} }
@ -118,11 +118,15 @@ const installerBin = await ensureBinary();
console.log("Now running Installer..."); console.log("Now running Installer...");
execFileSync(installerBin, { try {
execFileSync(installerBin, {
stdio: "inherit", stdio: "inherit",
env: { env: {
...process.env, ...process.env,
VENCORD_USER_DATA_DIR: BASE_DIR, VENCORD_USER_DATA_DIR: BASE_DIR,
VENCORD_DEV_INSTALL: "1" VENCORD_DEV_INSTALL: "1"
} }
}); });
} catch {
console.error("Something went wrong. Please check the logs above.");
}

View file

@ -17,6 +17,7 @@
*/ */
export * as Api from "./api"; export * as Api from "./api";
export * as Components from "./components";
export * as Plugins from "./plugins"; export * as Plugins from "./plugins";
export * as Util from "./utils"; export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss"; export * as QuickCss from "./utils/quickCss";
@ -27,6 +28,7 @@ export { PlainSettings, Settings };
import "./utils/quickCss"; import "./utils/quickCss";
import "./webpack/patchWebpack"; import "./webpack/patchWebpack";
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
import { StartAt } from "@utils/types"; import { StartAt } from "@utils/types";
import { get as dsGet } from "./api/DataStore"; import { get as dsGet } from "./api/DataStore";
@ -40,11 +42,15 @@ import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack"; import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common"; import { SettingsRouter } from "./webpack/common";
if (IS_REPORTER) {
require("./debug/runReporter");
}
async function syncSettings() { async function syncSettings() {
// pre-check for local shared settings // pre-check for local shared settings
if ( if (
Settings.cloud.authenticated && Settings.cloud.authenticated &&
await dsGet("Vencord_cloudSecret") === null // this has been enabled due to local settings share or some other bug !await dsGet("Vencord_cloudSecret") // this has been enabled due to local settings share or some other bug
) { ) {
// show a notification letting them know and tell them how to fix it // show a notification letting them know and tell them how to fix it
showNotification({ showNotification({
@ -85,7 +91,7 @@ async function init() {
syncSettings(); syncSettings();
if (!IS_WEB) { if (!IS_WEB && !IS_UPDATER_DISABLED) {
try { try {
const isOutdated = await checkForUpdates(); const isOutdated = await checkForUpdates();
if (!isOutdated) return; if (!isOutdated) return;
@ -103,15 +109,12 @@ async function init() {
return; return;
} }
if (Settings.notifyAboutUpdates)
setTimeout(() => showNotification({ setTimeout(() => showNotification({
title: "A Vencord update is available!", title: "A Vencord update is available!",
body: "Click here to view the update", body: "Click here to view the update",
permanent: true, permanent: true,
noPersist: true, noPersist: true,
onClick() { onClick: openUpdaterModal!
SettingsRouter.open("VencordUpdater");
}
}), 10_000); }), 10_000);
} catch (err) { } catch (err) {
UpdateLogger.error("Failed to check for updates", err); UpdateLogger.error("Failed to check for updates", err);
@ -145,4 +148,3 @@ document.addEventListener("DOMContentLoaded", () => {
})); }));
} }
}, { once: true }); }, { once: true });

View file

@ -4,10 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { IpcEvents } from "@utils/IpcEvents"; import { PluginIpcMappings } from "@main/ipcPlugins";
import type { UserThemeHeader } from "@main/themes";
import { IpcEvents } from "@shared/IpcEvents";
import { IpcRes } from "@utils/types"; import { IpcRes } from "@utils/types";
import type { Settings } from "api/Settings";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { PluginIpcMappings } from "main/ipcPlugins";
function invoke<T = any>(event: IpcEvents, ...args: any[]) { function invoke<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.invoke(event, ...args) as Promise<T>; return ipcRenderer.invoke(event, ...args) as Promise<T>;
@ -32,7 +34,7 @@ export default {
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData), uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName), deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR), getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
getThemesList: () => invoke<Array<{ fileName: string; content: string; }>>(IpcEvents.GET_THEMES_LIST), getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName), getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES), getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES),
}, },
@ -45,8 +47,8 @@ export default {
}, },
settings: { settings: {
get: () => sendSync<string>(IpcEvents.GET_SETTINGS), get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
set: (settings: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings), set: (settings: Settings, pathToNotify?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, pathToNotify),
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR), getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
}, },

View file

@ -17,7 +17,6 @@
*/ */
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { User } from "discord-types/general";
import { ComponentType, HTMLProps } from "react"; import { ComponentType, HTMLProps } from "react";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -36,7 +35,7 @@ export interface ProfileBadge {
image?: string; image?: string;
link?: string; link?: string;
/** Action to perform when you click the badge */ /** Action to perform when you click the badge */
onClick?(): void; onClick?(event: React.MouseEvent<HTMLButtonElement, MouseEvent>, props: BadgeUserArgs): void;
/** Should the user display this badge? */ /** Should the user display this badge? */
shouldShow?(userInfo: BadgeUserArgs): boolean; shouldShow?(userInfo: BadgeUserArgs): boolean;
/** Optional props (e.g. style) for the badge, ignored for component badges */ /** Optional props (e.g. style) for the badge, ignored for component badges */
@ -45,6 +44,11 @@ export interface ProfileBadge {
position?: BadgePosition; position?: BadgePosition;
/** The badge name to display, Discord uses this. Required for component badges */ /** The badge name to display, Discord uses this. Required for component badges */
key?: string; key?: string;
/**
* Allows dynamically returning multiple badges
*/
getBadges?(userInfo: BadgeUserArgs): ProfileBadge[];
} }
const Badges = new Set<ProfileBadge>(); const Badges = new Set<ProfileBadge>();
@ -74,22 +78,27 @@ export function _getBadges(args: BadgeUserArgs) {
const badges = [] as ProfileBadge[]; const badges = [] as ProfileBadge[];
for (const badge of Badges) { for (const badge of Badges) {
if (!badge.shouldShow || badge.shouldShow(args)) { if (!badge.shouldShow || badge.shouldShow(args)) {
const b = badge.getBadges
? badge.getBadges(args).map(b => {
b.component &&= ErrorBoundary.wrap(b.component, { noop: true });
return b;
})
: [{ ...badge, ...args }];
badge.position === BadgePosition.START badge.position === BadgePosition.START
? badges.unshift({ ...badge, ...args }) ? badges.unshift(...b)
: badges.push({ ...badge, ...args }); : badges.push(...b);
} }
} }
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id); const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
if (donorBadges) badges.unshift(...donorBadges); if (donorBadges) badges.unshift(...donorBadges);
return badges; return badges;
} }
export interface BadgeUserArgs { export interface BadgeUserArgs {
user: User; userId: string;
profile: Profile; guildId: string;
premiumSince: Date;
premiumGuildSince?: Date;
} }
interface ConnectedAccount { interface ConnectedAccount {

4
src/api/ChatButton.css Normal file
View file

@ -0,0 +1,4 @@
.vc-chatbar-button {
display: flex;
align-items: center;
}

128
src/api/ChatButtons.tsx Normal file
View file

@ -0,0 +1,128 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./ChatButton.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Logger } from "@utils/Logger";
import { waitFor } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Channel } from "discord-types/general";
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
export interface ChatBarProps {
channel: Channel;
disabled: boolean;
isEmpty: boolean;
type: {
analyticsName: string;
attachments: boolean;
autocomplete: {
addReactionShortcut: boolean,
forceChatLayer: boolean,
reactions: boolean;
},
commands: {
enabled: boolean;
},
drafts: {
type: number,
commandType: number,
autoSave: boolean;
},
emojis: {
button: boolean;
},
gifs: {
button: boolean,
allowSending: boolean;
},
gifts: {
button: boolean;
},
permissions: {
requireSendMessages: boolean;
},
showThreadPromptOnReply: boolean,
stickers: {
button: boolean,
allowSending: boolean,
autoSuggest: boolean;
},
users: {
allowMentioning: boolean;
},
submit: {
button: boolean,
ignorePreference: boolean,
disableEnterToSubmit: boolean,
clearOnSubmit: boolean,
useDisabledStylesOnSubmit: boolean;
},
uploadLongMessages: boolean,
upsellLongMessages: {
iconOnly: boolean;
},
showCharacterCount: boolean,
sedReplace: boolean;
};
}
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
const buttonFactories = new Map<string, ChatBarButton>();
const logger = new Logger("ChatButtons");
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
if (props.disabled) return;
for (const [key, Button] of buttonFactories) {
buttons.push(
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
<Button {...props} isMainChat={props.type.analyticsName === "normal"} />
</ErrorBoundary>
);
}
}
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
export interface ChatBarButtonProps {
children: ReactNode;
tooltip: string;
onClick: MouseEventHandler<HTMLButtonElement>;
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
}
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
return (
<Tooltip text={props.tooltip}>
{({ onMouseEnter, onMouseLeave }) => (
<div className={`expression-picker-chat-input-button ${ChannelTextAreaClasses?.buttonContainer ?? ""} vc-chatbar-button`}>
<Button
aria-label={props.tooltip}
size=""
look={ButtonLooks.BLANK}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
onClick={props.onClick}
onContextMenu={props.onContextMenu}
{...props.buttonProps}
>
<div className={ButtonWrapperClasses.buttonWrapper}>
{props.children}
</div>
</Button>
</div>
)}
</Tooltip>
);
}, { noop: true });

View file

@ -16,15 +16,15 @@
* 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 { mergeDefaults } from "@utils/misc"; import { mergeDefaults } from "@utils/mergeDefaults";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { MessageActions, SnowflakeUtils } from "@webpack/common"; import { MessageActions, SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
import type { PartialDeep } from "type-fest"; import type { PartialDeep } from "type-fest";
import { Argument } from "./types"; import { Argument } from "./types";
const MessageCreator = findByPropsLazy("createBotMessage"); const createBotMessage = findByCodeLazy('username:"Clyde"');
export function generateId() { export function generateId() {
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`; return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
@ -37,7 +37,7 @@ export function generateId() {
* @returns {Message} * @returns {Message}
*/ */
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message { export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] }); const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage)); MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage));

View file

@ -16,6 +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 { Logger } from "@utils/Logger";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import { sendBotMessage } from "./commandHelpers"; import { sendBotMessage } from "./commandHelpers";
@ -46,10 +47,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder;
export const _init = function (cmds: Command[]) { export const _init = function (cmds: Command[]) {
try { try {
BUILT_IN = cmds; BUILT_IN = cmds;
OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0]; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0]; RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0];
} catch (e) { } catch (e) {
console.error("Failed to load CommandsApi"); new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds);
} }
return cmds; return cmds;
} as never; } as never;
@ -138,6 +139,8 @@ export function registerCommand<C extends Command>(command: C, plugin: string) {
throw new Error(`Command '${command.name}' already exists.`); throw new Error(`Command '${command.name}' already exists.`);
command.isVencordCommand = true; command.isVencordCommand = true;
command.untranslatedName ??= command.name;
command.untranslatedDescription ??= command.description;
command.id ??= `-${BUILT_IN.length + 1}`; command.id ??= `-${BUILT_IN.length + 1}`;
command.applicationId ??= "-1"; // BUILT_IN; command.applicationId ??= "-1"; // BUILT_IN;
command.type ??= ApplicationCommandType.CHAT_INPUT; command.type ??= ApplicationCommandType.CHAT_INPUT;

View file

@ -93,8 +93,10 @@ export interface Command {
isVencordCommand?: boolean; isVencordCommand?: boolean;
name: string; name: string;
untranslatedName?: string;
displayName?: string; displayName?: string;
description: string; description: string;
untranslatedDescription?: string;
displayDescription?: string; displayDescription?: string;
options?: Option[]; options?: Option[];

View file

@ -17,22 +17,20 @@
*/ */
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Menu, React } from "@webpack/common";
import type { ReactElement } from "react"; import type { ReactElement } from "react";
type ContextMenuPatchCallbackReturn = (() => void) | void;
/** /**
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/ */
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn; export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
/** /**
* @param navId The navId of the context menu being patched * @param navId The navId of the context menu being patched
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/ */
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn; export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
const ContextMenuLogger = new Logger("ContextMenu"); const ContextMenuLogger = new Logger("ContextMenu");
@ -92,15 +90,21 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children * A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
* @param id The id of the child. If an array is specified, all ids will be tried * @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children * @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id
*/ */
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>, _itemsArray?: Array<ReactElement | null>): Array<ReactElement | null> | null { export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
for (const child of children) { for (const child of children) {
if (child == null) continue; if (child == null) continue;
if (Array.isArray(child)) {
const found = findGroupChildrenByChildId(id, child, matchSubstring);
if (found !== null) return found;
}
if ( if (
(Array.isArray(id) && id.some(id => child.props?.id === id)) (Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|| child.props?.id === id || (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
) return _itemsArray ?? null; ) return children;
let nextChildren = child.props?.children; let nextChildren = child.props?.children;
if (nextChildren) { if (nextChildren) {
@ -109,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
child.props.children = nextChildren; child.props.children = nextChildren;
} }
const found = findGroupChildrenByChildId(id, nextChildren, nextChildren); const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
if (found !== null) return found; if (found !== null) return found;
} }
} }
@ -126,9 +130,12 @@ interface ContextMenuProps {
onClose: (callback: (...args: Array<any>) => any) => void; onClose: (callback: (...args: Array<any>) => any) => void;
} }
const patchedMenus = new WeakSet(); export function _usePatchContextMenu(props: ContextMenuProps) {
props = {
...props,
children: cloneMenuChildren(props.children),
};
export function _patchContextMenu(props: ContextMenuProps) {
props.contextMenuApiArguments ??= []; props.contextMenuApiArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId); const contextMenuPatches = navPatches.get(props.navId);
@ -137,8 +144,7 @@ export function _patchContextMenu(props: ContextMenuProps) {
if (contextMenuPatches) { if (contextMenuPatches) {
for (const patch of contextMenuPatches) { for (const patch of contextMenuPatches) {
try { try {
const callback = patch(props.children, ...props.contextMenuApiArguments); patch(props.children, ...props.contextMenuApiArguments);
if (!patchedMenus.has(props)) callback?.();
} catch (err) { } catch (err) {
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
} }
@ -147,12 +153,30 @@ export function _patchContextMenu(props: ContextMenuProps) {
for (const patch of globalPatches) { for (const patch of globalPatches) {
try { try {
const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments); patch(props.navId, props.children, ...props.contextMenuApiArguments);
if (!patchedMenus.has(props)) callback?.();
} catch (err) { } catch (err) {
ContextMenuLogger.error("Global patch errored,", err); ContextMenuLogger.error("Global patch errored,", err);
} }
} }
patchedMenus.add(props); return props;
}
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
if (Array.isArray(obj)) {
return obj.map(cloneMenuChildren);
}
if (React.isValidElement(obj)) {
obj = React.cloneElement(obj);
if (
obj?.props?.children &&
(obj.type !== Menu.MenuControlItem || obj.type === Menu.MenuControlItem && obj.props.control != null)
) {
obj.props.children = cloneMenuChildren(obj.props.children);
}
}
return obj;
} }

View file

@ -49,7 +49,7 @@ let defaultGetStoreFunc: UseStore | undefined;
function defaultGetStore() { function defaultGetStore() {
if (!defaultGetStoreFunc) { if (!defaultGetStoreFunc) {
defaultGetStoreFunc = createStore("VencordData", "VencordStore"); defaultGetStoreFunc = createStore(!IS_REPORTER ? "VencordData" : "VencordDataReporter", "VencordStore");
} }
return defaultGetStoreFunc; return defaultGetStoreFunc;
} }

View file

@ -74,7 +74,7 @@ export interface MessageExtra {
} }
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>; export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void>; export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
const sendListeners = new Set<SendListener>(); const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>(); const editListeners = new Set<EditListener>();
@ -84,7 +84,7 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
for (const listener of sendListeners) { for (const listener of sendListeners) {
try { try {
const result = await listener(channelId, messageObj, extra); const result = await listener(channelId, messageObj, extra);
if (result && result.cancel === true) { if (result?.cancel) {
return true; return true;
} }
} catch (e) { } catch (e) {
@ -97,11 +97,15 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) { export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
for (const listener of editListeners) { for (const listener of editListeners) {
try { try {
await listener(channelId, messageId, messageObj); const result = await listener(channelId, messageId, messageObj);
if (result?.cancel) {
return true;
}
} catch (e) { } catch (e) {
MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e); MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e);
} }
} }
return false;
} }
/** /**

View file

@ -16,16 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
import type { MouseEventHandler } from "react"; import type { ComponentType, MouseEventHandler } from "react";
const logger = new Logger("MessagePopover"); const logger = new Logger("MessagePopover");
export interface ButtonItem { export interface ButtonItem {
key?: string, key?: string,
label: string, label: string,
icon: React.ComponentType<any>, icon: ComponentType<any>,
message: Message, message: Message,
channel: Channel, channel: Channel,
onClick?: MouseEventHandler<HTMLButtonElement>, onClick?: MouseEventHandler<HTMLButtonElement>,
@ -48,22 +49,26 @@ export function removeButton(identifier: string) {
} }
export function _buildPopoverElements( export function _buildPopoverElements(
msg: Message, Component: React.ComponentType<ButtonItem>,
makeButton: (item: ButtonItem) => React.ComponentType message: Message
) { ) {
const items = [] as React.ComponentType[]; const items: React.ReactNode[] = [];
for (const [identifier, getItem] of buttons.entries()) { for (const [identifier, getItem] of buttons.entries()) {
try { try {
const item = getItem(msg); const item = getItem(message);
if (item) { if (item) {
item.key ??= identifier; item.key ??= identifier;
items.push(makeButton(item)); items.push(
<ErrorBoundary noop>
<Component {...item} />
</ErrorBoundary>
);
} }
} catch (err) { } catch (err) {
logger.error(`[${identifier}]`, err); logger.error(`[${identifier}]`, err);
} }
} }
return items; return <>{items}</>;
} }

29
src/api/MessageUpdater.ts Normal file
View file

@ -0,0 +1,29 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { MessageCache, MessageStore } from "@webpack/common";
import { FluxStore } from "@webpack/types";
import { Message } from "discord-types/general";
/**
* Update and re-render a message
* @param channelId The channel id of the message
* @param messageId The message id
* @param fields The fields of the message to change. Leave empty if you just want to re-render
*/
export function updateMessage(channelId: string, messageId: string, fields?: Partial<Message & Record<string, any>>) {
const channelMessageCache = MessageCache.getOrCreate(channelId);
if (!channelMessageCache.has(messageId)) return;
// To cause a message to re-render, we basically need to create a new instance of the message and obtain a new reference
// If we have fields to modify we can use the merge method of the class, otherwise we just create a new instance with the old fields
const newChannelMessageCache = channelMessageCache.update(messageId, (oldMessage: any) => {
return fields ? oldMessage.merge(fields) : new oldMessage.constructor(oldMessage);
});
MessageCache.commit(newChannelMessageCache);
(MessageStore as unknown as FluxStore).emitChange();
}

View file

@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
{timeout !== 0 && !permanent && ( {timeout !== 0 && !permanent && (
<div <div
className="vc-notification-progressbar" className="vc-notification-progressbar"
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-experiment)" }} style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-500)" }}
/> />
)} )}
</button> </button>

View file

@ -100,6 +100,7 @@ export async function showNotification(data: NotificationData) {
const n = new Notification(title, { const n = new Notification(title, {
body, body,
icon, icon,
// @ts-expect-error ts is drunk
image image
}); });
n.onclick = onClick; n.onclick = onClick;

View file

@ -19,9 +19,11 @@
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { DispatchWithoutAction } from "react"; import type { DispatchWithoutAction } from "react";
@ -129,7 +131,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
richBody={ richBody={
<div className={cl("body")}> <div className={cl("body")}>
{data.body} {data.body}
<Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} /> <Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
</div> </div>
} }
/> />
@ -170,8 +172,14 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>
<Flex>
<Button onClick={openNotificationSettingsModal}>
Notification Settings
</Button>
<Button <Button
disabled={log.length === 0} disabled={log.length === 0}
color={Button.Colors.RED}
onClick={() => { onClick={() => {
Alerts.show({ Alerts.show({
title: "Are you sure?", title: "Are you sure?",
@ -188,6 +196,7 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
> >
Clear Notification Log Clear Notification Log
</Button> </Button>
</Flex>
</ModalFooter> </ModalFooter>
</ModalRoot> </ModalRoot>
); );

View file

@ -16,10 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { debounce } from "@utils/debounce"; import { debounce } from "@shared/debounce";
import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore";
import { localStorage } from "@utils/localStorage"; import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/misc"; import { mergeDefaults } from "@utils/mergeDefaults";
import { putCloudSettings } from "@utils/settingsSync"; import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
@ -28,7 +29,6 @@ import plugins from "~plugins";
const logger = new Logger("Settings"); const logger = new Logger("Settings");
export interface Settings { export interface Settings {
notifyAboutUpdates: boolean;
autoUpdate: boolean; autoUpdate: boolean;
autoUpdateNotification: boolean, autoUpdateNotification: boolean,
useQuickCss: boolean; useQuickCss: boolean;
@ -52,7 +52,6 @@ export interface Settings {
| "under-page" | "under-page"
| "window" | "window"
| undefined; | undefined;
macosTranslucency: boolean | undefined;
disableMinSize: boolean; disableMinSize: boolean;
winNativeTitleBar: boolean; winNativeTitleBar: boolean;
plugins: { plugins: {
@ -75,17 +74,10 @@ export interface Settings {
settingsSync: boolean; settingsSync: boolean;
settingsSyncVersion: number; settingsSyncVersion: number;
}; };
userCssVars: {
[fileName: string]: {
[varName: string]: string;
};
};
} }
const DefaultSettings: Settings = { const DefaultSettings: Settings = {
notifyAboutUpdates: true, autoUpdate: true,
autoUpdate: false,
autoUpdateNotification: true, autoUpdateNotification: true,
useQuickCss: true, useQuickCss: true,
themeLinks: [], themeLinks: [],
@ -94,8 +86,6 @@ const DefaultSettings: Settings = {
frameless: false, frameless: false,
transparent: false, transparent: false,
winCtrlQ: false, winCtrlQ: false,
// Replaced by macosVibrancyStyle
macosTranslucency: undefined,
macosVibrancyStyle: undefined, macosVibrancyStyle: undefined,
disableMinSize: false, disableMinSize: false,
winNativeTitleBar: false, winNativeTitleBar: false,
@ -113,18 +103,11 @@ const DefaultSettings: Settings = {
url: "https://api.vencord.dev/", url: "https://api.vencord.dev/",
settingsSync: false, settingsSync: false,
settingsSyncVersion: 0 settingsSyncVersion: 0
}, }
userCssVars: {}
}; };
try { const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings;
var settings = JSON.parse(VencordNative.settings.get()) as Settings; mergeDefaults(settings, DefaultSettings);
mergeDefaults(settings, DefaultSettings);
} catch (err) {
var settings = mergeDefaults({} as Settings, DefaultSettings);
logger.error("An error occurred while loading the settings. Corrupt settings file?\n", err);
}
const saveSettingsOnFrequentAction = debounce(async () => { const saveSettingsOnFrequentAction = debounce(async () => {
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) { if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
@ -133,79 +116,52 @@ const saveSettingsOnFrequentAction = debounce(async () => {
} }
}, 60_000); }, 60_000);
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _paths?: Array<string>; _exact?: boolean; };
const subscriptions = new Set<SubscriptionCallback>();
const proxyCache = {} as Record<string, any>; export const SettingsStore = new SettingsStoreClass(settings, {
readOnly: true,
getDefaultValue({
target,
key,
path
}) {
const v = target[key];
if (!plugins) return v; // plugins not initialised yet. this means this path was reached by being called on the top level
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values if (path === "plugins" && key in plugins)
function makeProxy(settings: any, root = settings, path = ""): Settings { return target[key] = {
return proxyCache[path] ??= new Proxy(settings, { enabled: IS_REPORTER || plugins[key].required || plugins[key].enabledByDefault || false
get(target, p: string) { };
const v = target[p];
// using "in" is important in the following cases to properly handle falsy or nullish values
if (!(p in target)) {
// Return empty for plugins with no settings
if (path === "plugins" && p in plugins)
return target[p] = makeProxy({
enabled: plugins[p].required ?? plugins[p].enabledByDefault ?? false
}, root, `plugins.${p}`);
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve // Since the property is not set, check if this is a plugin's setting and if so, try to resolve
// the default value. // the default value.
if (path.startsWith("plugins.")) { if (path.startsWith("plugins.")) {
const plugin = path.slice("plugins.".length); const plugin = path.slice("plugins.".length);
if (plugin in plugins) { if (plugin in plugins) {
const setting = plugins[plugin].options?.[p]; const setting = plugins[plugin].options?.[key];
if (!setting) return v; if (!setting) return v;
if ("default" in setting) if ("default" in setting)
// normal setting with a default value // normal setting with a default value
return (target[p] = setting.default); return (target[key] = setting.default);
if (setting.type === OptionType.SELECT) { if (setting.type === OptionType.SELECT) {
const def = setting.options.find(o => o.default); const def = setting.options.find(o => o.default);
if (def) if (def)
target[p] = def.value; target[key] = def.value;
return def?.value; return def?.value;
} }
} }
} }
return v; return v;
} }
});
// Recursively proxy Objects with the updated property path if (!IS_REPORTER) {
if (typeof v === "object" && !Array.isArray(v) && v !== null) SettingsStore.addGlobalChangeListener((_, path) => {
return makeProxy(v, root, `${path}${path && "."}${p}`); SettingsStore.plain.cloud.settingsSyncVersion = Date.now();
// primitive or similar, no need to proxy further
return v;
},
set(target, p: string, v) {
// avoid unnecessary updates to React Components and other listeners
if (target[p] === v) return true;
target[p] = v;
// Call any listeners that are listening to a setting of this path
const setPath = `${path}${path && "."}${p}`;
delete proxyCache[setPath];
for (const subscription of subscriptions) {
if (
!subscription._paths ||
(subscription._exact
? subscription._paths.includes(setPath)
: subscription._paths.some(p => setPath.startsWith(p)))
) {
subscription(v, setPath);
}
}
// And don't forget to persist the settings!
PlainSettings.cloud.settingsSyncVersion = Date.now();
localStorage.Vencord_settingsDirty = true; localStorage.Vencord_settingsDirty = true;
saveSettingsOnFrequentAction(); saveSettingsOnFrequentAction();
VencordNative.settings.set(JSON.stringify(root, null, 4)); VencordNative.settings.set(SettingsStore.plain, path);
return true;
}
}); });
} }
@ -223,7 +179,7 @@ export const PlainSettings = settings;
* the updated settings to disk. * the updated settings to disk.
* This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings} * This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings}
*/ */
export const Settings = makeProxy(settings); export const Settings = SettingsStore.store;
/** /**
* Settings hook for React components. Returns a smart settings * Settings hook for React components. Returns a smart settings
@ -233,50 +189,24 @@ export const Settings = makeProxy(settings);
* @returns Settings * @returns Settings
*/ */
// TODO: Representing paths as essentially "string[].join('.')" wont allow dots in paths, change to "paths?: string[][]" later // TODO: Representing paths as essentially "string[].join('.')" wont allow dots in paths, change to "paths?: string[][]" later
export function useSettings(paths?: UseSettings<Settings>[], exact = true) { export function useSettings(paths?: UseSettings<Settings>[]) {
const [, forceUpdate] = React.useReducer(() => ({}), {}); const [, forceUpdate] = React.useReducer(() => ({}), {});
const onUpdate: SubscriptionCallback = paths
? (value, path) =>
(exact
? paths.includes(path as UseSettings<Settings>)
: paths.some(p => path.startsWith(p))) && forceUpdate()
: forceUpdate;
React.useEffect(() => { React.useEffect(() => {
subscriptions.add(onUpdate); if (paths) {
return () => void subscriptions.delete(onUpdate); paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
} else {
SettingsStore.addGlobalChangeListener(forceUpdate);
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
}
}, []); }, []);
return Settings; return SettingsStore.store;
}
// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
type ResolvePropDeep<T, P> = P extends "" ? T :
P extends `${infer Pre}.${infer Suf}` ?
Pre extends keyof T ? ResolvePropDeep<T[Pre], Suf> : never : P extends keyof T ? T[P] : never;
/**
* Add a settings listener that will be invoked whenever the desired setting is updated
* @param path Path to the setting that you want to watch, for example "plugins.Unindent.enabled" will fire your callback
* whenever Unindent is toggled. Pass an empty string to get notified for all changes
* @param onUpdate Callback function whenever a setting matching path is updated. It gets passed the new value and the path
* to the updated setting. This path will be the same as your path argument, unless it was an empty string.
*
* @example addSettingsListener("", (newValue, path) => console.log(`${path} is now ${newValue}`))
* addSettingsListener("plugins.Unindent.enabled", v => console.log("Unindent is now", v ? "enabled" : "disabled"))
*/
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void, exact?: boolean): void;
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void, exact?: boolean): void;
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void, exact = true) {
if (path)
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
(onUpdate as SubscriptionCallback)._exact = exact;
subscriptions.add(onUpdate);
} }
export function migratePluginSettings(name: string, ...oldNames: string[]) { export function migratePluginSettings(name: string, ...oldNames: string[]) {
const { plugins } = settings; const { plugins } = SettingsStore.plain;
if (name in plugins) return; if (name in plugins) return;
for (const oldName of oldNames) { for (const oldName of oldNames) {
@ -284,7 +214,7 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
logger.info(`Migrating settings from old name ${oldName} to ${name}`); logger.info(`Migrating settings from old name ${oldName} to ${name}`);
plugins[name] = plugins[oldName]; plugins[name] = plugins[oldName];
delete plugins[oldName]; delete plugins[oldName];
VencordNative.settings.set(JSON.stringify(settings, null, 4)); SettingsStore.markAsChanged();
break; break;
} }
} }
@ -300,6 +230,10 @@ export function definePluginSettings<
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
return Settings.plugins[definedSettings.pluginName] as any; return Settings.plugins[definedSettings.pluginName] as any;
}, },
get plain() {
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
return PlainSettings.plugins[definedSettings.pluginName] as any;
},
use: settings => useSettings( use: settings => useSettings(
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[] settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
).plugins[definedSettings.pluginName] as any, ).plugins[definedSettings.pluginName] as any,

81
src/api/UserSettings.ts Normal file
View file

@ -0,0 +1,81 @@
/*
* 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 { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
interface UserSettingDefinition<T> {
/**
* Get the setting value
*/
getSetting(): T;
/**
* Update the setting value
* @param value The new value
*/
updateSetting(value: T): Promise<void>;
/**
* Update the setting value
* @param value A callback that accepts the old value as the first argument, and returns the new value
*/
updateSetting(value: (old: T) => T): Promise<void>;
/**
* Stateful React hook for this setting value
*/
useSetting(): T;
userSettingsAPIGroup: string;
userSettingsAPIName: string;
}
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
const modId = findModuleId('"textAndImages","renderSpoilers"');
if (modId == null) return new Logger("UserSettingsAPI ").error("Didn't find settings module.");
return wreq(modId as any);
});
/**
* Get the setting with the given setting group and name.
*
* @param group The setting group
* @param name The name of the setting
*/
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency.");
for (const key in UserSettings) {
const userSetting = UserSettings[key];
if (userSetting.userSettingsAPIGroup === group && userSetting.userSettingsAPIName === name) {
return userSetting;
}
}
}
/**
* {@link getUserSettingDefinition}, lazy.
*
* Get the setting with the given setting group and name.
*
* @param group The setting group
* @param name The name of the setting
*/
export function getUserSettingLazy<T = any>(group: string, name: string) {
return proxyLazy(() => getUserSetting<T>(group, name));
}

View file

@ -17,6 +17,7 @@
*/ */
import * as $Badges from "./Badges"; import * as $Badges from "./Badges";
import * as $ChatButtons from "./ChatButtons";
import * as $Commands from "./Commands"; import * as $Commands from "./Commands";
import * as $ContextMenu from "./ContextMenu"; import * as $ContextMenu from "./ContextMenu";
import * as $DataStore from "./DataStore"; import * as $DataStore from "./DataStore";
@ -25,11 +26,13 @@ import * as $MessageAccessories from "./MessageAccessories";
import * as $MessageDecorations from "./MessageDecorations"; import * as $MessageDecorations from "./MessageDecorations";
import * as $MessageEventsAPI from "./MessageEvents"; import * as $MessageEventsAPI from "./MessageEvents";
import * as $MessagePopover from "./MessagePopover"; import * as $MessagePopover from "./MessagePopover";
import * as $MessageUpdater from "./MessageUpdater";
import * as $Notices from "./Notices"; import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications"; import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList"; import * as $ServerList from "./ServerList";
import * as $Settings from "./Settings"; import * as $Settings from "./Settings";
import * as $Styles from "./Styles"; import * as $Styles from "./Styles";
import * as $UserSettings from "./UserSettings";
/** /**
* An API allowing you to listen to Message Clicks or run your own logic * An API allowing you to listen to Message Clicks or run your own logic
@ -104,3 +107,18 @@ export const Notifications = $Notifications;
* An api allowing you to patch and add/remove items to/from context menus * An api allowing you to patch and add/remove items to/from context menus
*/ */
export const ContextMenu = $ContextMenu; export const ContextMenu = $ContextMenu;
/**
* An API allowing you to add buttons to the chat input
*/
export const ChatButtons = $ChatButtons;
/**
* An API allowing you to update and re-render messages
*/
export const MessageUpdater = $MessageUpdater;
/**
* An API allowing you to get an user setting
*/
export const UserSettings = $UserSettings;

View file

@ -1,7 +1,6 @@
.vc-expandableheader-center-flex { .vc-expandableheader-center-flex {
display: flex; display: flex;
justify-items: center; place-items: center;
align-items: center;
} }
.vc-expandableheader-btn { .vc-expandableheader-btn {

View file

@ -16,10 +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 "./ExpandableHeader.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Text, Tooltip, useState } from "@webpack/common"; import { Text, Tooltip, useState } from "@webpack/common";
export const cl = classNameFactory("vc-expandableheader-");
import "./ExpandableHeader.css"; const cl = classNameFactory("vc-expandableheader-");
export interface ExpandableHeaderProps { export interface ExpandableHeaderProps {
onMoreClick?: () => void; onMoreClick?: () => void;
@ -29,10 +31,20 @@ export interface ExpandableHeaderProps {
headerText: string; headerText: string;
children: React.ReactNode; children: React.ReactNode;
buttons?: React.ReactNode[]; buttons?: React.ReactNode[];
forceOpen?: boolean;
} }
export default function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) { export function ExpandableHeader({
const [showContent, setShowContent] = useState(defaultState); children,
onMoreClick,
buttons,
moreTooltipText,
onDropDownClick,
headerText,
defaultState = false,
forceOpen = false,
}: ExpandableHeaderProps) {
const [showContent, setShowContent] = useState(defaultState || forceOpen);
return ( return (
<> <>
@ -88,6 +100,7 @@ export default function ExpandableHeader({ children, onMoreClick, buttons, moreT
setShowContent(v => !v); setShowContent(v => !v);
onDropDownClick?.(showContent); onDropDownClick?.(showContent);
}} }}
disabled={forceOpen}
> >
<svg <svg
width="24" width="24"

28
src/components/Grid.tsx Normal file
View file

@ -0,0 +1,28 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { CSSProperties } from "react";
interface Props {
columns: number;
gap?: string;
inline?: boolean;
}
export function Grid(props: Props & JSX.IntrinsicElements["div"]) {
const style: CSSProperties = {
display: props.inline ? "inline-grid" : "grid",
gridTemplateColumns: `repeat(${props.columns}, 1fr)`,
gap: props.gap,
...props.style
};
return (
<div {...props} style={style}>
{props.children}
</div>
);
}

View file

@ -18,19 +18,17 @@
import "./iconStyles.css"; import "./iconStyles.css";
import { getTheme, Theme } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { i18n } from "@webpack/common"; import { i18n } from "@webpack/common";
import type { PropsWithChildren, SVGProps } from "react"; import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps { interface BaseIconProps extends IconProps {
viewBox: string; viewBox: string;
} }
interface IconProps extends SVGProps<SVGSVGElement> { type IconProps = JSX.IntrinsicElements["svg"];
className?: string; type ImageProps = JSX.IntrinsicElements["img"];
height?: string | 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>) {
return ( return (
@ -67,8 +65,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
} }
/** /**
* Discord's copy icon, as seen in the user popout right of the username when clicking * Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks
* your own username in the bottom left user panel
*/ */
export function CopyIcon(props: IconProps) { export function CopyIcon(props: IconProps) {
return ( return (
@ -78,8 +75,9 @@ export function CopyIcon(props: IconProps) {
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<g fill="currentColor"> <g fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" /> <path d="M3 16a1 1 0 0 1-1-1v-5a8 8 0 0 1 8-8h5a1 1 0 0 1 1 1v.5a.5.5 0 0 1-.5.5H10a6 6 0 0 0-6 6v5.5a.5.5 0 0 1-.5.5H3Z" />
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" /> <path d="M6 18a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-4h-3a5 5 0 0 1-5-5V6h-4a4 4 0 0 0-4 4v8Z" />
<path d="M21.73 12a3 3 0 0 0-.6-.88l-4.25-4.24a3 3 0 0 0-.88-.61V9a3 3 0 0 0 3 3h2.73Z" />
</g> </g>
</Icon> </Icon>
); );
@ -256,24 +254,6 @@ export function DeleteIcon(props: IconProps) {
); );
} }
/**
* A plugin icon, created by CorellanStoma. https://github.com/CreArts-Community/Settings-Icons
*/
export function PluginIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-plugin-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7s2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"
/>
</Icon>
);
}
export function PlusIcon(props: IconProps) { export function PlusIcon(props: IconProps) {
return ( return (
<Icon <Icon
@ -308,3 +288,142 @@ export function NoEntrySignIcon(props: IconProps) {
</Icon> </Icon>
); );
} }
export function SafetyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-safety-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
d="M4.27 5.22A2.66 2.66 0 0 0 3 7.5v2.3c0 5.6 3.3 10.68 8.42 12.95.37.17.79.17 1.16 0A14.18 14.18 0 0 0 21 9.78V7.5c0-.93-.48-1.78-1.27-2.27l-6.17-3.76a3 3 0 0 0-3.12 0L4.27 5.22ZM6 7.68l6-3.66V12H6.22C6.08 11.28 6 10.54 6 9.78v-2.1Zm6 12.01V12h5.78A11.19 11.19 0 0 1 12 19.7Z"
/>
</Icon>
);
}
export function NotesIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-notes-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M8 3C7.44771 3 7 3.44772 7 4V5C7 5.55228 7.44772 6 8 6H16C16.5523 6 17 5.55228 17 5V4C17 3.44772 16.5523 3 16 3H15.1245C14.7288 3 14.3535 2.82424 14.1002 2.52025L13.3668 1.64018C13.0288 1.23454 12.528 1 12 1C11.472 1 10.9712 1.23454 10.6332 1.64018L9.8998 2.52025C9.64647 2.82424 9.27121 3 8.8755 3H8Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M19 4.49996V4.99996C19 6.65681 17.6569 7.99996 16 7.99996H8C6.34315 7.99996 5 6.65681 5 4.99996V4.49996C5 4.22382 4.77446 3.99559 4.50209 4.04109C3.08221 4.27826 2 5.51273 2 6.99996V19C2 20.6568 3.34315 22 5 22H19C20.6569 22 22 20.6568 22 19V6.99996C22 5.51273 20.9178 4.27826 19.4979 4.04109C19.2255 3.99559 19 4.22382 19 4.49996ZM8 12C7.44772 12 7 12.4477 7 13C7 13.5522 7.44772 14 8 14H16C16.5523 14 17 13.5522 17 13C17 12.4477 16.5523 12 16 12H8ZM7 17C7 16.4477 7.44772 16 8 16H13C13.5523 16 14 16.4477 14 17C14 17.5522 13.5523 18 13 18H8C7.44772 18 7 17.5522 7 17Z"
/>
</Icon>
);
}
export function FolderIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-folder-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M2 5a3 3 0 0 1 3-3h3.93a2 2 0 0 1 1.66.9L12 5h7a3 3 0 0 1 3 3v11a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5Z"
/>
</Icon>
);
}
export function LogIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-log-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
d="M3.11 8H6v10.82c0 .86.37 1.68 1 2.27.46.43 1.02.71 1.63.84A1 1 0 0 0 9 22h10a4 4 0 0 0 4-4v-1a2 2 0 0 0-2-2h-1V5a3 3 0 0 0-3-3H4.67c-.87 0-1.7.32-2.34.9-.63.6-1 1.42-1 2.28 0 .71.3 1.35.52 1.75a5.35 5.35 0 0 0 .48.7l.01.01h.01L3.11 7l-.76.65a1 1 0 0 0 .76.35Zm1.56-4c-.38 0-.72.14-.97.37-.24.23-.37.52-.37.81a1.69 1.69 0 0 0 .3.82H6v-.83c0-.29-.13-.58-.37-.8C5.4 4.14 5.04 4 4.67 4Zm5 13a3.58 3.58 0 0 1 0 3H19a2 2 0 0 0 2-2v-1H9.66ZM3.86 6.35ZM11 8a1 1 0 1 0 0 2h5a1 1 0 1 0 0-2h-5Zm-1 5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1Z"
/>
</Icon>
);
}
export function RestartIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-restart-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M4 12a8 8 0 0 1 14.93-4H15a1 1 0 1 0 0 2h6a1 1 0 0 0 1-1V3a1 1 0 1 0-2 0v3a9.98 9.98 0 0 0-18 6 10 10 0 0 0 16.29 7.78 1 1 0 0 0-1.26-1.56A8 8 0 0 1 4 12Z"
/>
</Icon>
);
}
export function PaintbrushIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-paintbrush-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
d="M15.35 7.24C15.9 6.67 16 5.8 16 5a3 3 0 1 1 3 3c-.8 0-1.67.09-2.24.65a1.5 1.5 0 0 0 0 2.11l1.12 1.12a3 3 0 0 1 0 4.24l-5 5a3 3 0 0 1-4.25 0l-5.76-5.75a3 3 0 0 1 0-4.24l4.04-4.04.97-.97a3 3 0 0 1 4.24 0l1.12 1.12c.58.58 1.52.58 2.1 0ZM6.9 9.9 4.3 12.54a1 1 0 0 0 0 1.42l2.17 2.17.83-.84a1 1 0 0 1 1.42 1.42l-.84.83.59.59 1.83-1.84a1 1 0 0 1 1.42 1.42l-1.84 1.83.17.17a1 1 0 0 0 1.42 0l2.63-2.62L6.9 9.9Z"
/>
</Icon>
);
}
export function PencilIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-pencil-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m13.96 5.46 4.58 4.58a1 1 0 0 0 1.42 0l1.38-1.38a2 2 0 0 0 0-2.82l-3.18-3.18a2 2 0 0 0-2.82 0l-1.38 1.38a1 1 0 0 0 0 1.42ZM2.11 20.16l.73-4.22a3 3 0 0 1 .83-1.61l7.87-7.87a1 1 0 0 1 1.42 0l4.58 4.58a1 1 0 0 1 0 1.42l-7.87 7.87a3 3 0 0 1-1.6.83l-4.23.73a1.5 1.5 0 0 1-1.73-1.73Z"
/>
</Icon>
);
}
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
export function GithubIcon(props: ImageProps) {
const src = getTheme() === Theme.Light
? GithubIconLight
: GithubIconDark;
return <img {...props} src={src} />;
}
export function WebsiteIcon(props: ImageProps) {
const src = getTheme() === Theme.Light
? WebsiteIconLight
: WebsiteIconDark;
return <img {...props} src={src} />;
}

View file

@ -9,20 +9,18 @@ import "./contributorModal.css";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link";
import { DevsById } from "@utils/constants"; import { DevsById } from "@utils/constants";
import { fetchUserProfile, getTheme, Theme } from "@utils/discord"; import { fetchUserProfile } from "@utils/discord";
import { classes, pluralise } from "@utils/misc";
import { ModalContent, ModalRoot, openModal } from "@utils/modal"; import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import Plugins from "~plugins"; import Plugins from "~plugins";
import { PluginCard } from "."; import { PluginCard } from ".";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
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-"); const cl = classNameFactory("vc-author-modal-");
@ -38,16 +36,6 @@ export function openContributorModal(user: User) {
); );
} }
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; }) { function ContributorModal({ user }: { user: User; }) {
useSettings(); useSettings();
@ -72,6 +60,8 @@ function ContributorModal({ user }: { user: User; }) {
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false)); .sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
}, [user.id, user.username]); }, [user.id, user.username]);
const ContributedHyperLink = <Link href="https://vencord.dev/source">contributed</Link>;
return ( return (
<> <>
<div className={cl("header")}> <div className={cl("header")}>
@ -82,22 +72,33 @@ function ContributorModal({ user }: { user: User; }) {
/> />
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle> <Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
<div className={cl("links")}> <div className={classes("vc-settings-modal-links", cl("links"))}>
{website && ( {website && (
<MaskedLink <WebsiteButton
href={"https://" + website} text={website}
> href={`https://${website}`}
<WebsiteIcon /> />
</MaskedLink>
)} )}
{githubName && ( {githubName && (
<MaskedLink href={`https://github.com/${githubName}`}> <GithubButton
<GithubIcon /> text={githubName}
</MaskedLink> href={`https://github.com/${githubName}`}
/>
)} )}
</div> </div>
</div> </div>
{plugins.length ? (
<Forms.FormText>
This person has {ContributedHyperLink} to {pluralise(plugins.length, "plugin")}!
</Forms.FormText>
) : (
<Forms.FormText>
This person has not made any plugins. They likely {ContributedHyperLink} to Vencord in other ways!
</Forms.FormText>
)}
{!!plugins.length && (
<div className={cl("plugins")}> <div className={cl("plugins")}>
{plugins.map(p => {plugins.map(p =>
<PluginCard <PluginCard
@ -108,6 +109,7 @@ function ContributorModal({ user }: { user: User; }) {
/> />
)} )}
</div> </div>
)}
</> </>
); );
} }

View file

@ -0,0 +1,12 @@
.vc-settings-modal-link-icon {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-tertiary);
box-sizing: border-box
}
.vc-settings-modal-links {
display: flex;
gap: 0.2em;
}

View file

@ -0,0 +1,39 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./LinkIconButton.css";
import { MaskedLink, Tooltip } from "@webpack/common";
import { GithubIcon, WebsiteIcon } from "..";
export function GithubLinkIcon() {
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
}
export function WebsiteLinkIcon() {
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
}
interface Props {
text: string;
href: string;
}
function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) {
return (
<Tooltip text={text}>
{props => (
<MaskedLink {...props} href={href}>
<Icon />
</MaskedLink>
)}
</Tooltip>
);
}
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;

View file

@ -0,0 +1,7 @@
.vc-plugin-modal-info {
align-items: center;
}
.vc-plugin-modal-description {
flex-grow: 1;
}

View file

@ -16,20 +16,26 @@
* 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 "./PluginModal.css";
import { generateId } from "@api/Commands"; import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { gitRemote } from "@shared/vencordUserAgent";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } 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, openModal } from "@utils/modal";
import { OptionType, Plugin } from "@utils/types"; import { OptionType, Plugin } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { Constructor } from "type-fest"; import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins";
import { import {
ISettingElementProps, ISettingElementProps,
SettingBooleanComponent, SettingBooleanComponent,
@ -40,6 +46,9 @@ import {
SettingTextComponent SettingTextComponent
} from "./components"; } from "./components";
import { openContributorModal } from "./ContributorModal"; import { openContributorModal } from "./ContributorModal";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
const cl = classNameFactory("vc-plugin-modal-");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
@ -180,16 +189,54 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
); );
} }
/*
function switchToPopout() {
onClose();
const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`;
PopoutActions.open(
PopoutKey,
() => <PluginModal
transitionState={transitionState}
plugin={plugin}
onRestartNeeded={onRestartNeeded}
onClose={() => PopoutActions.close(PopoutKey)}
/>
);
}
*/
const pluginMeta = PluginMeta[plugin.name];
return ( return (
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable"> <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
<ModalHeader separator={false}> <ModalHeader separator={false}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
{/*
<Button look={Button.Looks.BLANK} onClick={switchToPopout}>
<OpenExternalIcon aria-label="Open in Popout" />
</Button>
*/}
<ModalCloseButton onClick={onClose} /> <ModalCloseButton onClick={onClose} />
</ModalHeader> </ModalHeader>
<ModalContent> <ModalContent>
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle> <Flex className={cl("info")}>
<Forms.FormText>{plugin.description}</Forms.FormText> <Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
{!pluginMeta.userPlugin && (
<div className="vc-settings-modal-links">
<WebsiteButton
text="View more info"
href={`https://vencord.dev/plugins/${plugin.name}`}
/>
<GithubButton
text="View source code"
href={`https://github.com/${gitRemote}/tree/main/src/plugins/${pluginMeta.folderName}`}
/>
</div>
)}
</Flex>
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle> <Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
<div style={{ width: "fit-content", marginBottom: 8 }}> <div style={{ width: "fit-content", marginBottom: 8 }}>
<UserSummaryItem <UserSummaryItem
@ -263,3 +310,13 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
</ModalRoot> </ModalRoot>
); );
} }
export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) {
openModal(modalProps => (
<PluginModal
{...modalProps}
plugin={plugin}
onRestartNeeded={() => onRestartNeeded?.(plugin.name)}
/>
));
}

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { OptionType, PluginOptionNumber } from "@utils/types"; import { OptionType, PluginOptionNumber } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common"; import { Forms, React, TextInput } from "@webpack/common";
@ -54,7 +56,8 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle> <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<TextInput <TextInput
type="number" type="number"
pattern="-?[0-9]+" pattern="-?[0-9]+"

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSelect } from "@utils/types"; import { PluginOptionSelect } from "@utils/types";
import { Forms, React, Select } from "@webpack/common"; import { Forms, React, Select } from "@webpack/common";
@ -44,7 +46,8 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle> <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
<Select <Select
isDisabled={option.disabled?.call(definedSettings) ?? false} isDisabled={option.disabled?.call(definedSettings) ?? false}
options={option.options} options={option.options}

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSlider } from "@utils/types"; import { PluginOptionSlider } from "@utils/types";
import { Forms, React, Slider } from "@webpack/common"; import { Forms, React, Slider } from "@webpack/common";
@ -50,7 +52,8 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle> <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<Slider <Slider
disabled={option.disabled?.call(definedSettings) ?? false} disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers} markers={option.markers}

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionString } from "@utils/types"; import { PluginOptionString } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common"; import { Forms, React, TextInput } from "@webpack/common";
@ -41,7 +43,8 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle> <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<TextInput <TextInput
type="text" type="text"
value={state} value={state}

View file

@ -25,11 +25,13 @@
display: block; display: block;
position: absolute; position: absolute;
height: 100%; height: 100%;
width: 16px; width: 32px;
background: var(--background-tertiary); background: var(--background-tertiary);
z-index: -1; z-index: -1;
left: -16px; left: -32px;
top: 0; top: 0;
border-top-left-radius: 9999px;
border-bottom-left-radius: 9999px;
} }
.vc-author-modal-avatar { .vc-author-modal-avatar {
@ -40,19 +42,10 @@
.vc-author-modal-links { .vc-author-modal-links {
margin-left: auto; 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 { .vc-author-modal-plugins {
display: grid; display: grid;
gap: 0.5em; gap: 0.5em;
margin-top: 0.75em;
} }

View file

@ -23,28 +23,28 @@ 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 { CogWheel, InfoIcon } from "@components/Icons";
import PluginModal from "@components/PluginSettings/PluginModal"; import { openPluginModal } 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 { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc"; import { classes, isObjectEmpty } from "@utils/misc";
import { openModalLazy } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
import Plugins from "~plugins"; import Plugins, { ExcludedPlugins } from "~plugins";
import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
// Avoid circular dependency
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
const cl = classNameFactory("vc-plugins-"); const cl = classNameFactory("vc-plugins-");
const logger = new Logger("PluginSettings", "#a6d189"); const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
@ -68,7 +68,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
<Forms.FormText className={cl("dep-text")}> <Forms.FormText className={cl("dep-text")}>
Restart now to apply new plugins and their settings Restart now to apply new plugins and their settings
</Forms.FormText> </Forms.FormText>
<Button color={Button.Colors.YELLOW} onClick={() => location.reload()}> <Button onClick={() => location.reload()}>
Restart Restart
</Button> </Button>
</> </>
@ -95,14 +95,6 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
const isEnabled = () => settings.enabled ?? false; const isEnabled = () => settings.enabled ?? false;
function openModal() {
openModalLazy(async () => {
return modalProps => {
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
};
});
}
function toggleEnabled() { function toggleEnabled() {
const wasEnabled = isEnabled(); const wasEnabled = isEnabled();
@ -159,7 +151,11 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
infoButton={ infoButton={
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}> <button
role="switch"
onClick={() => openPluginModal(plugin, onRestartNeeded)}
className={classes(ButtonClasses.button, cl("info-button"))}
>
{plugin.options && !isObjectEmpty(plugin.options) {plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel /> ? <CogWheel />
: <InfoIcon />} : <InfoIcon />}
@ -176,6 +172,37 @@ const enum SearchStatus {
NEW NEW
} }
function ExcludedPluginsList({ search }: { search: string; }) {
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
.filter(([name]) => name.toLowerCase().includes(search));
const ExcludedReasons: Record<"web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev", string> = {
desktop: "Discord Desktop app or Vesktop",
discordDesktop: "Discord Desktop app",
vencordDesktop: "Vesktop app",
web: "Vesktop app and the Web version of Discord",
dev: "Developer version of Vencord"
};
return (
<Text variant="text-md/normal" className={Margins.top16}>
{matchingExcludedPlugins.length
? <>
<Forms.FormText>Are you looking for:</Forms.FormText>
<ul>
{matchingExcludedPlugins.map(([name, reason]) => (
<li key={name}>
<b>{name}</b>: Only available on the {ExcludedReasons[reason]}
</li>
))}
</ul>
</>
: "No plugins meet the search criteria."
}
</Text>
);
}
export default function PluginSettings() { export default function PluginSettings() {
const settings = useSettings(); const settings = useSettings();
const changes = React.useMemo(() => new ChangeList<string>(), []); const changes = React.useMemo(() => new ChangeList<string>(), []);
@ -214,26 +241,27 @@ export default function PluginSettings() {
return o; return o;
}, []); }, []);
const sortedPlugins = React.useMemo(() => Object.values(Plugins) const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []); .sort((a, b) => a.name.localeCompare(b.name)), []);
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL }); const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
const search = searchValue.value.toLowerCase();
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query })); const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status })); const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => { const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
const enabled = settings.plugins[plugin.name]?.enabled; const { status } = searchValue;
if (enabled && searchValue.status === SearchStatus.DISABLED) return false; const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; if (enabled && status === SearchStatus.DISABLED) return false;
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false; if (!enabled && status === SearchStatus.ENABLED) return false;
if (!searchValue.value.length) return true; if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (!search.length) return true;
const v = searchValue.value.toLowerCase();
return ( return (
plugin.name.toLowerCase().includes(v) || plugin.name.toLowerCase().includes(search) ||
plugin.description.toLowerCase().includes(v) || plugin.description.toLowerCase().includes(search) ||
plugin.tags?.some(t => t.toLowerCase().includes(v)) plugin.tags?.some(t => t.toLowerCase().includes(search))
); );
}; };
@ -254,22 +282,20 @@ export default function PluginSettings() {
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins; return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
})); }));
type P = JSX.Element | JSX.Element[]; const plugins = [] as JSX.Element[];
let plugins: P, requiredPlugins: P; const requiredPlugins = [] as JSX.Element[];
if (sortedPlugins?.length) {
plugins = [];
requiredPlugins = [];
const showApi = searchValue.value.includes("API");
for (const p of sortedPlugins) { for (const p of sortedPlugins) {
if (!p.options && p.name.endsWith("API") && searchValue.value !== "API") if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
continue; continue;
if (!pluginFilter(p)) continue; if (!pluginFilter(p)) continue;
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled); const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
if (isRequired) { if (isRequired) {
const tooltipText = p.required const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Vencord to function." ? "This plugin is required for Vencord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled)); : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
@ -282,6 +308,7 @@ export default function PluginSettings() {
onRestartNeeded={name => changes.handleChange(name)} onRestartNeeded={name => changes.handleChange(name)}
disabled={true} disabled={true}
plugin={p} plugin={p}
key={p.name}
/> />
)} )}
</Tooltip> </Tooltip>
@ -297,10 +324,6 @@ export default function PluginSettings() {
/> />
); );
} }
}
} else {
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
} }
return ( return (
@ -311,11 +334,10 @@ export default function PluginSettings() {
Filters Filters
</Forms.FormTitle> </Forms.FormTitle>
<div className={cl("filter-controls")}> <div className={classes(Margins.bottom20, cl("filter-controls"))}>
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} /> <TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
<div className={InputStyles.inputWrapper}> <div className={InputStyles.inputWrapper}>
<Select <Select
className={InputStyles.inputDefault}
options={[ options={[
{ label: "Show All", value: SearchStatus.ALL, default: true }, { label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Enabled", value: SearchStatus.ENABLED }, { label: "Show Enabled", value: SearchStatus.ENABLED },
@ -326,15 +348,25 @@ export default function PluginSettings() {
select={onStatusChange} select={onStatusChange}
isSelected={v => v === searchValue.status} isSelected={v => v === searchValue.status}
closeOnSelect={true} closeOnSelect={true}
className={InputStyles.inputDefault}
/> />
</div> </div>
</div> </div>
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
{plugins.length || requiredPlugins.length
? (
<div className={cl("grid")}> <div className={cl("grid")}>
{plugins} {plugins.length
? plugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div> </div>
)
: <ExcludedPluginsList search={search} />
}
<Forms.FormDivider className={Margins.top20} /> <Forms.FormDivider className={Margins.top20} />
@ -342,7 +374,10 @@ export default function PluginSettings() {
Required Plugins Required Plugins
</Forms.FormTitle> </Forms.FormTitle>
<div className={cl("grid")}> <div className={cl("grid")}>
{requiredPlugins} {requiredPlugins.length
? requiredPlugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div> </div>
</SettingsTab > </SettingsTab >
); );

View file

@ -78,6 +78,7 @@
.vc-plugins-restart-card button { .vc-plugins-restart-card button {
margin-top: 0.5em; margin-top: 0.5em;
background: var(--info-warning-foreground) !important;
} }
.vc-plugins-info-button svg:not(:hover, :focus) { .vc-plugins-info-button svg:not(:hover, :focus) {

View file

@ -1,114 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { useSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Text } from "@webpack/common";
import type { ReactNode } from "react";
import { UserstyleHeader } from "usercss-meta";
import { SettingBooleanComponent, SettingColorComponent, SettingNumberComponent, SettingRangeComponent, SettingSelectComponent, SettingTextComponent } from "./components";
interface UserCSSSettingsModalProps {
modalProps: ModalProps;
theme: UserstyleHeader;
}
export function UserCSSSettingsModal({ modalProps, theme }: UserCSSSettingsModalProps) {
// @ts-expect-error UseSettings<> can't determine this is a valid key
const themeSettings = useSettings(["userCssVars"], false).userCssVars[theme.id];
const controls: ReactNode[] = [];
for (const [name, varInfo] of Object.entries(theme.vars)) {
switch (varInfo.type) {
case "text": {
controls.push(
<SettingTextComponent
label={varInfo.label}
name={name}
themeSettings={themeSettings}
/>
);
break;
}
case "checkbox": {
controls.push(
<SettingBooleanComponent
label={varInfo.label}
name={name}
themeSettings={themeSettings}
/>
);
break;
}
case "color": {
controls.push(
<SettingColorComponent
label={varInfo.label}
name={name}
themeSettings={themeSettings}
/>
);
break;
}
case "number": {
controls.push(
<SettingNumberComponent
label={varInfo.label}
name={name}
themeSettings={themeSettings}
/>
);
break;
}
case "select": {
controls.push(
<SettingSelectComponent
label={varInfo.label}
name={name}
options={varInfo.options}
default={varInfo.default}
themeSettings={themeSettings}
/>
);
break;
}
case "range": {
controls.push(
<SettingRangeComponent
label={varInfo.label}
name={name}
default={varInfo.default}
min={varInfo.min}
max={varInfo.max}
step={varInfo.step}
themeSettings={themeSettings}
/>
);
break;
}
}
}
return (
<ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Settings for {theme.name}</Text>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{controls}</Flex>
</ModalContent>
</ModalRoot>
);
}

View file

@ -1,39 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Forms, Switch, useState } from "@webpack/common";
interface Props {
label: string;
name: string;
themeSettings: Record<string, string>;
}
export function SettingBooleanComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: boolean) {
const corrected = value ? "1" : "0";
setValue(corrected);
themeSettings[name] = corrected;
}
return (
<Forms.FormSection>
<Switch
key={name}
value={value === "1"}
onChange={handleChange}
hideBorder
style={{ marginBottom: "0.5em" }}
>
{label}
</Switch>
</Forms.FormSection>
);
}

View file

@ -1,56 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./colorStyles.css";
import { classNameFactory } from "@api/Styles";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { Forms, useMemo, useState } from "@webpack/common";
interface ColorPickerProps {
color: number | null;
showEyeDropper?: boolean;
onChange(value: number | null): void;
}
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
// TinyColor is completely unmangled and it's duplicated in two modules! Fun!
const TinyColor: tinycolor.Constructor = findByCodeLazy("this._gradientType=");
const cl = classNameFactory("vc-usercss-settings-color-");
interface Props {
label: string;
name: string;
themeSettings: Record<string, string>;
}
export function SettingColorComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: number) {
const corrected = "#" + (value?.toString(16).padStart(6, "0") ?? "000000");
setValue(corrected);
themeSettings[name] = corrected;
}
const normalizedValue = useMemo(() => parseInt(TinyColor(value).toHex(), 16), [value]);
return (
<Forms.FormSection>
<div className={cl("swatch-row")}>
<span>{label}</span>
<ColorPicker
key={name}
color={normalizedValue}
onChange={handleChange}
/>
</div>
</Forms.FormSection>
);
}

View file

@ -1,36 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Forms, TextInput, useState } from "@webpack/common";
interface Props {
label: string;
name: string;
themeSettings: Record<string, string>;
}
export function SettingNumberComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: string) {
setValue(value);
themeSettings[name] = value;
}
return (
<Forms.FormSection>
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<TextInput
type="number"
pattern="-?[0-9]+"
key={name}
value={value}
onChange={handleChange}
/>
</Forms.FormSection>
);
}

View file

@ -1,56 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Forms, Slider, useMemo, useState } from "@webpack/common";
interface Props {
label: string;
name: string;
default: number;
min?: number;
max?: number;
step?: number;
themeSettings: Record<string, string>;
}
export function SettingRangeComponent({ label, name, default: def, min, max, step, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: number) {
const corrected = value.toString();
setValue(corrected);
themeSettings[name] = corrected;
}
const markers = useMemo(() => {
const markers: number[] = [];
// defaults taken from https://github.com/openstyles/stylus/wiki/Writing-UserCSS#default-value
for (let i = (min ?? 0); i <= (max ?? 10); i += (step ?? 1)) {
markers.push(i);
}
return markers;
}, [min, max, step]);
return (
<Forms.FormSection>
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<Slider
initialValue={parseInt(value, 10)}
defaultValue={def}
onValueChange={handleChange}
minValue={min}
maxValue={max}
markers={markers}
stickToMarkers={true}
/>
</Forms.FormSection>
);
}

View file

@ -1,55 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { identity } from "@utils/misc";
import { ComponentTypes, Forms, Select, useMemo, useState } from "@webpack/common";
interface Props {
label: string;
name: string;
options: {
name: string;
label: string;
value: string;
}[];
default: string;
themeSettings: Record<string, string>;
}
export function SettingSelectComponent({ label, name, options, default: def, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: string) {
setValue(value);
themeSettings[name] = value;
}
const opts = useMemo(() => options.map(option => ({
disabled: false,
key: option.name,
value: option.value,
default: def === option.name,
label: option.label
} satisfies ComponentTypes.SelectOption)), [options, def]);
return (
<Forms.FormSection>
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<Select
placeholder={label}
key={name}
options={opts}
closeOnSelect={true}
select={handleChange}
isSelected={v => v === value}
serialize={identity}
/>
</Forms.FormSection>
);
}

View file

@ -1,34 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Forms, TextInput, useState } from "@webpack/common";
interface Props {
label: string;
name: string;
themeSettings: Record<string, string>;
}
export function SettingTextComponent({ label, name, themeSettings }: Props) {
const [value, setValue] = useState(themeSettings[name]);
function handleChange(value: string) {
setValue(value);
themeSettings[name] = value;
}
return (
<Forms.FormSection>
<Forms.FormTitle tag="h5">{label}</Forms.FormTitle>
<TextInput
key={name}
value={value}
onChange={handleChange}
/>
</Forms.FormSection>
);
}

View file

@ -1,19 +0,0 @@
.vc-usercss-settings-color-swatch-row {
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
}
.vc-usercss-settings-color-swatch-row > span {
display: block;
flex: 1;
overflow: hidden;
margin-top: 0;
margin-bottom: 0;
color: var(--header-primary);
line-height: 24px;
font-size: 16px;
font-weight: 500;
word-wrap: break-word;
}

View file

@ -1,12 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export * from "./SettingBooleanComponent";
export * from "./SettingColorComponent";
export * from "./SettingNumberComponent";
export * from "./SettingRangeComponent";
export * from "./SettingSelectComponent";
export * from "./SettingTextComponent";

View file

@ -21,7 +21,7 @@ import "./addonCard.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Badge } from "@components/Badge"; import { Badge } from "@components/Badge";
import { Switch } from "@components/Switch"; import { Switch } from "@components/Switch";
import { Text } from "@webpack/common"; import { Text, useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react"; import type { MouseEventHandler, ReactNode } from "react";
const cl = classNameFactory("vc-addon-"); const cl = classNameFactory("vc-addon-");
@ -42,6 +42,8 @@ interface Props {
} }
export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) { export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) {
const titleRef = useRef<HTMLDivElement>(null);
const titleContainerRef = useRef<HTMLDivElement>(null);
return ( return (
<div <div
className={cl("card", { "card-disabled": disabled })} className={cl("card", { "card-disabled": disabled })}
@ -51,7 +53,21 @@ export function AddonCard({ disabled, isNew, name, infoButton, footer, author, e
<div className={cl("header")}> <div className={cl("header")}>
<div className={cl("name-author")}> <div className={cl("name-author")}>
<Text variant="text-md/bold" className={cl("name")}> <Text variant="text-md/bold" className={cl("name")}>
{name}{isNew && <Badge text="NEW" color="#ED4245" />} <div ref={titleContainerRef} className={cl("title-container")}>
<div
ref={titleRef}
className={cl("title")}
onMouseOver={() => {
const title = titleRef.current!;
const titleContainer = titleContainerRef.current!;
title.style.setProperty("--offset", `${titleContainer.clientWidth - title.scrollWidth}px`);
title.style.setProperty("--duration", `${Math.max(0.5, (title.scrollWidth - titleContainer.clientWidth) / 7)}s`);
}}
>
{name}
</div>
</div>{isNew && <Badge text="NEW" color="#ED4245" />}
</Text> </Text>
{!!author && ( {!!author && (
<Text variant="text-md/normal" className={cl("author")}> <Text variant="text-md/normal" className={cl("author")}>

View file

@ -19,6 +19,7 @@
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { Grid } from "@components/Grid";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
@ -39,9 +40,7 @@ function validateUrl(url: string) {
async function eraseAllData() { async function eraseAllData() {
const res = await fetch(new URL("/v1/", getCloudUrl()), { const res = await fetch(new URL("/v1/", getCloudUrl()), {
method: "DELETE", method: "DELETE",
headers: new Headers({ headers: { Authorization: await getCloudAuth() }
Authorization: await getCloudAuth()
})
}); });
if (!res.ok) { if (!res.ok) {
@ -87,7 +86,9 @@ function SettingsSyncSection() {
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => putCloudSettings(true)} onClick={() => putCloudSettings(true)}
>Sync to Cloud</Button> >
Sync to Cloud
</Button>
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!"> <Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<Button <Button
@ -97,7 +98,9 @@ function SettingsSyncSection() {
color={Button.Colors.RED} color={Button.Colors.RED}
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => getCloudSettings(true, true)} onClick={() => getCloudSettings(true, true)}
>Sync from Cloud</Button> >
Sync from Cloud
</Button>
)} )}
</Tooltip> </Tooltip>
<Button <Button
@ -105,7 +108,9 @@ function SettingsSyncSection() {
color={Button.Colors.RED} color={Button.Colors.RED}
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => deleteCloudSettings()} onClick={() => deleteCloudSettings()}
>Delete Cloud Settings</Button> >
Delete Cloud Settings
</Button>
</div> </div>
</Forms.FormSection> </Forms.FormSection>
); );
@ -126,7 +131,12 @@ function CloudTab() {
<Switch <Switch
key="backend" key="backend"
value={settings.cloud.authenticated} value={settings.cloud.authenticated}
onChange={v => { v && authorizeCloud(); if (!v) settings.cloud.authenticated = v; }} onChange={v => {
if (v)
authorizeCloud();
else
settings.cloud.authenticated = v;
}}
note="This will request authorization if you have not yet set up cloud integrations." note="This will request authorization if you have not yet set up cloud integrations."
> >
Enable Cloud Integrations Enable Cloud Integrations
@ -138,11 +148,27 @@ function CloudTab() {
<CheckedTextInput <CheckedTextInput
key="backendUrl" key="backendUrl"
value={settings.cloud.url} value={settings.cloud.url}
onChange={v => { settings.cloud.url = v; settings.cloud.authenticated = false; deauthorizeCloud(); }} onChange={async v => {
settings.cloud.url = v;
settings.cloud.authenticated = false;
deauthorizeCloud();
}}
validate={validateUrl} validate={validateUrl}
/> />
<Grid columns={2} gap="1em" className={Margins.top8}>
<Button
size={Button.Sizes.MEDIUM}
disabled={!settings.cloud.authenticated}
onClick={async () => {
await deauthorizeCloud();
settings.cloud.authenticated = false;
await authorizeCloud();
}}
>
Reauthorise
</Button>
<Button <Button
className={Margins.top8}
size={Button.Sizes.MEDIUM} size={Button.Sizes.MEDIUM}
color={Button.Colors.RED} color={Button.Colors.RED}
disabled={!settings.cloud.authenticated} disabled={!settings.cloud.authenticated}
@ -154,7 +180,11 @@ function CloudTab() {
confirmColor: "vc-cloud-erase-data-danger-btn", confirmColor: "vc-cloud-erase-data-danger-btn",
cancelText: "Nevermind" cancelText: "Nevermind"
})} })}
>Erase All Data</Button> >
Erase All Data
</Button>
</Grid>
<Forms.FormDivider className={Margins.top16} /> <Forms.FormDivider className={Margins.top16} />
</Forms.FormSection > </Forms.FormSection >
<SettingsSyncSection /> <SettingsSyncSection />

View file

@ -0,0 +1,106 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { useSettings } from "@api/Settings";
import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Forms, Select, Slider, Text } from "@webpack/common";
import { ErrorCard } from "..";
export function NotificationSettings() {
const settings = useSettings().notifications;
return (
<div style={{ padding: "1em 0" }}>
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
{settings.useNative !== "never" && Notification?.permission === "denied" && (
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
</ErrorCard>
)}
<Forms.FormText className={Margins.bottom8}>
Some plugins may show you notifications. These come in two styles:
<ul>
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
</ul>
</Forms.FormText>
<Select
placeholder="Notification Style"
options={[
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
{ label: "Always use Desktop notifications", value: "always" },
{ label: "Always use Vencord notifications", value: "never" },
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
closeOnSelect={true}
select={v => settings.useNative = v}
isSelected={v => v === settings.useNative}
serialize={identity}
/>
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
<Select
isDisabled={settings.useNative === "always"}
placeholder="Notification Position"
options={[
{ label: "Bottom Right", value: "bottom-right", default: true },
{ label: "Top Right", value: "top-right" },
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
select={v => settings.position = v}
isSelected={v => v === settings.position}
serialize={identity}
/>
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
<Slider
disabled={settings.useNative === "always"}
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
minValue={0}
maxValue={20_000}
initialValue={settings.timeout}
onValueChange={v => settings.timeout = v}
onValueRender={v => (v / 1000).toFixed(2) + "s"}
onMarkerRender={v => (v / 1000) + "s"}
stickToMarkers={false}
/>
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16}>
The amount of notifications to save in the log until old ones are removed.
Set to <code>0</code> to disable Notification log and <code></code> to never automatically remove old Notifications
</Forms.FormText>
<Slider
markers={[0, 25, 50, 75, 100, 200]}
minValue={0}
maxValue={200}
stickToMarkers={true}
initialValue={settings.logLimit}
onValueChange={v => settings.logLimit = v}
onValueRender={v => v === 200 ? "∞" : v}
onMarkerRender={v => v === 200 ? "∞" : v}
/>
</div>
);
}
export function openNotificationSettingsModal() {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Settings</Text>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<NotificationSettings />
</ModalContent>
</ModalRoot>
));
}

View file

@ -16,15 +16,14 @@
* 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 { CheckedTextInput } from "@components/CheckedTextInput";
import { CodeBlock } from "@components/CodeBlock"; import { CodeBlock } from "@components/CodeBlock";
import { debounce } from "@utils/debounce"; import { debounce } from "@shared/debounce";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import { ReplaceFn } from "@utils/types"; import { Patch, ReplaceFn } from "@utils/types";
import { search } from "@webpack"; import { search } from "@webpack";
import { Button, Clipboard, Forms, Parser, React, Switch, TextInput } from "@webpack/common"; import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared"; import { SettingsTab, wrapTab } from "./shared";
@ -47,7 +46,7 @@ const findCandidates = debounce(function ({ find, setModule, setError }) {
interface ReplacementComponentProps { interface ReplacementComponentProps {
module: [id: number, factory: Function]; module: [id: number, factory: Function];
match: string | RegExp; match: string;
replacement: string | ReplaceFn; replacement: string | ReplaceFn;
setReplacementError(error: any): void; setReplacementError(error: any): void;
} }
@ -58,7 +57,13 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
const [patchedCode, matchResult, diff] = React.useMemo(() => { const [patchedCode, matchResult, diff] = React.useMemo(() => {
const src: string = fact.toString().replaceAll("\n", ""); const src: string = fact.toString().replaceAll("\n", "");
const canonicalMatch = canonicalizeMatch(match);
try {
new RegExp(match);
} catch (e) {
return ["", [], []];
}
const canonicalMatch = canonicalizeMatch(new RegExp(match));
try { try {
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin"); const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
var patched = src.replace(canonicalMatch, canonicalReplace as string); var patched = src.replace(canonicalMatch, canonicalReplace as string);
@ -180,7 +185,8 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
return ( return (
<> <>
<Forms.FormTitle>replacement</Forms.FormTitle> {/* FormTitle adds a class if className is not set, so we set it to an empty string to prevent that */}
<Forms.FormTitle className="">replacement</Forms.FormTitle>
<TextInput <TextInput
value={replacement?.toString()} value={replacement?.toString()}
onChange={onChange} onChange={onChange}
@ -188,7 +194,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
/> />
{!isFunc && ( {!isFunc && (
<div className="vc-text-selectable"> <div className="vc-text-selectable">
<Forms.FormTitle>Cheat Sheet</Forms.FormTitle> <Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle>
{Object.entries({ {Object.entries({
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
"$$": "Insert a $", "$$": "Insert a $",
@ -218,8 +224,66 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
); );
} }
interface FullPatchInputProps {
setFind(v: string): void;
setParsedFind(v: string | RegExp): void;
setMatch(v: string): void;
setReplacement(v: string | ReplaceFn): void;
}
function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) {
const [fullPatch, setFullPatch] = React.useState<string>("");
const [fullPatchError, setFullPatchError] = React.useState<string>("");
function update() {
if (fullPatch === "") {
setFullPatchError("");
setFind("");
setParsedFind("");
setMatch("");
setReplacement("");
return;
}
try {
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
if (!parsed.find) throw new Error("No 'find' field");
if (!parsed.replacement) throw new Error("No 'replacement' field");
if (parsed.replacement instanceof Array) {
if (parsed.replacement.length === 0) throw new Error("Invalid replacement");
parsed.replacement = {
match: parsed.replacement[0].match,
replace: parsed.replacement[0].replace
};
}
if (!parsed.replacement.match) throw new Error("No 'replacement.match' field");
if (!parsed.replacement.replace) throw new Error("No 'replacement.replace' field");
setFind(parsed.find instanceof RegExp ? parsed.find.toString() : parsed.find);
setParsedFind(parsed.find);
setMatch(parsed.replacement.match instanceof RegExp ? parsed.replacement.match.source : parsed.replacement.match);
setReplacement(parsed.replacement.replace);
setFullPatchError("");
} catch (e) {
setFullPatchError((e as Error).message);
}
}
return <>
<Forms.FormText className={Margins.bottom8}>Paste your full JSON patch here to fill out the fields</Forms.FormText>
<TextArea value={fullPatch} onChange={setFullPatch} onBlur={update} />
{fullPatchError !== "" && <Forms.FormText style={{ color: "var(--text-danger)" }}>{fullPatchError}</Forms.FormText>}
</>;
}
function PatchHelper() { function PatchHelper() {
const [find, setFind] = React.useState<string>(""); const [find, setFind] = React.useState<string>("");
const [parsedFind, setParsedFind] = React.useState<string | RegExp>("");
const [match, setMatch] = React.useState<string>(""); const [match, setMatch] = React.useState<string>("");
const [replacement, setReplacement] = React.useState<string | ReplaceFn>(""); const [replacement, setReplacement] = React.useState<string | ReplaceFn>("");
@ -227,40 +291,60 @@ function PatchHelper() {
const [module, setModule] = React.useState<[number, Function]>(); const [module, setModule] = React.useState<[number, Function]>();
const [findError, setFindError] = React.useState<string>(); const [findError, setFindError] = React.useState<string>();
const [matchError, setMatchError] = React.useState<string>();
const code = React.useMemo(() => { const code = React.useMemo(() => {
return ` return `
{ {
find: ${JSON.stringify(find)}, find: ${parsedFind instanceof RegExp ? parsedFind.toString() : JSON.stringify(parsedFind)},
replacement: { replacement: {
match: /${match.replace(/(?<!\\)\//g, "\\/")}/, match: /${match.replace(/(?<!\\)\//g, "\\/")}/,
replace: ${typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement)} replace: ${typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement)}
} }
} }
`.trim(); `.trim();
}, [find, match, replacement]); }, [parsedFind, match, replacement]);
function onFindChange(v: string) { function onFindChange(v: string) {
setFindError(void 0);
setFind(v); setFind(v);
if (v.length) {
findCandidates({ find: v, setModule, setError: setFindError });
}
}
function onMatchChange(v: string) {
try { try {
new RegExp(v); let parsedFind = v as string | RegExp;
if (/^\/.+?\/$/.test(v)) parsedFind = new RegExp(v.slice(1, -1));
setFindError(void 0); setFindError(void 0);
setMatch(v); setParsedFind(parsedFind);
if (v.length) {
findCandidates({ find: parsedFind, setModule, setError: setFindError });
}
} catch (e: any) { } catch (e: any) {
setFindError((e as Error).message); setFindError((e as Error).message);
} }
} }
function onMatchChange(v: string) {
setMatch(v);
try {
new RegExp(v);
setMatchError(void 0);
} catch (e: any) {
setMatchError((e as Error).message);
}
}
return ( return (
<SettingsTab title="Patch Helper"> <SettingsTab title="Patch Helper">
<Forms.FormTitle>find</Forms.FormTitle> <Forms.FormTitle>full patch</Forms.FormTitle>
<FullPatchInput
setFind={onFindChange}
setParsedFind={setParsedFind}
setMatch={onMatchChange}
setReplacement={setReplacement}
/>
<Forms.FormTitle className={Margins.top8}>find</Forms.FormTitle>
<TextInput <TextInput
type="text" type="text"
value={find} value={find}
@ -268,19 +352,15 @@ function PatchHelper() {
error={findError} error={findError}
/> />
<Forms.FormTitle>match</Forms.FormTitle> <Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle>
<CheckedTextInput <TextInput
type="text"
value={match} value={match}
onChange={onMatchChange} onChange={onMatchChange}
validate={v => { error={matchError}
try {
return (new RegExp(v), true);
} catch (e) {
return (e as Error).message;
}
}}
/> />
<div className={Margins.top8} />
<ReplacementInput <ReplacementInput
replacement={replacement} replacement={replacement}
setReplacement={setReplacement} setReplacement={setReplacement}
@ -291,7 +371,7 @@ function PatchHelper() {
{module && ( {module && (
<ReplacementComponent <ReplacementComponent
module={module} module={module}
match={new RegExp(match)} match={match}
replacement={replacement} replacement={replacement}
setReplacementError={setReplacementError} setReplacementError={setReplacementError}
/> />
@ -302,6 +382,7 @@ function PatchHelper() {
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
<CodeBlock lang="js" content={code} /> <CodeBlock lang="js" content={code} />
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button> <Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
<Button className={Margins.top8} onClick={() => Clipboard.copy("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
</> </>
)} )}
</SettingsTab> </SettingsTab>

View file

@ -16,31 +16,26 @@
* 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 "./themesStyles.css";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { CogWheel, DeleteIcon, PluginIcon } from "@components/Icons"; import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { AddonCard } from "@components/VencordSettings/AddonCard"; import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared"; import type { UserThemeHeader } from "@main/themes";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { showItemInFolder } from "@utils/native"; import { showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import type { ThemeHeader } from "@utils/themes"; import { findLazy } from "@webpack";
import { getThemeInfo, stripBOM, type UserThemeHeader } from "@utils/themes/bd"; import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
import { usercssParse } from "@utils/themes/usercss";
import { findByPropsLazy, findLazy } from "@webpack";
import { Button, Card, Forms, React, showToast, TabBar, TextArea, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common";
import type { ComponentType, Ref, SyntheticEvent } from "react"; import type { ComponentType, Ref, SyntheticEvent } from "react";
import type { UserstyleHeader } from "usercss-meta";
import { isPluginEnabled } from "../../plugins"; import Plugins from "~plugins";
import { UserCSSSettingsModal } from "./UserCSSModal";
import { AddonCard } from "./AddonCard";
import { QuickAction, QuickActionCard } from "./quickActions";
import { SettingsTab, wrapTab } from "./shared";
type FileInput = ComponentType<{ type FileInput = ComponentType<{
ref: Ref<HTMLInputElement>; ref: Ref<HTMLInputElement>;
@ -49,13 +44,10 @@ type FileInput = ComponentType<{
filters?: { name?: string; extensions: string[]; }[]; filters?: { name?: string; extensions: string[]; }[];
}>; }>;
const InviteActions = findByPropsLazy("resolveInvite");
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef); const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
const cl = classNameFactory("vc-settings-theme-"); const cl = classNameFactory("vc-settings-theme-");
function Validator({ link }: { link: string; }) { function Validator({ link }: { link: string; }) {
const [res, err, pending] = useAwaiter(() => fetch(link).then(res => { const [res, err, pending] = useAwaiter(() => fetch(link).then(res => {
if (res.status > 300) throw `${res.status} ${res.statusText}`; if (res.status > 300) throw `${res.status} ${res.statusText}`;
@ -85,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle> <Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText> <Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
<div> <div>
{themeLinks.map(link => ( {themeLinks.map(rawLink => {
<Card style={{ const { label, link } = (() => {
const match = /^@(light|dark) (.*)/.exec(rawLink);
if (!match) return { label: rawLink, link: rawLink };
const [, mode, link] = match;
return { label: `[${mode} mode only] ${link}`, link };
})();
return <Card style={{
padding: ".5em", padding: ".5em",
marginBottom: ".5em", marginBottom: ".5em",
marginTop: ".5em" marginTop: ".5em"
@ -94,83 +94,24 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
<Forms.FormTitle tag="h5" style={{ <Forms.FormTitle tag="h5" style={{
overflowWrap: "break-word" overflowWrap: "break-word"
}}> }}>
{link} {label}
</Forms.FormTitle> </Forms.FormTitle>
<Validator link={link} /> <Validator link={link} />
</Card> </Card>;
))} })}
</div> </div>
</> </>
); );
} }
interface OtherThemeCardProps { interface ThemeCardProps {
theme: UserThemeHeader; theme: UserThemeHeader;
enabled: boolean; enabled: boolean;
onChange: (enabled: boolean) => void; onChange: (enabled: boolean) => void;
onDelete: () => void; onDelete: () => void;
} }
interface UserCSSCardProps { function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
theme: UserstyleHeader;
enabled: boolean;
onChange: (enabled: boolean) => void;
onDelete: () => void;
}
function UserCSSThemeCard({ theme, enabled, onChange, onDelete }: UserCSSCardProps) {
const missingPlugins = useMemo(() =>
theme.requiredPlugins?.filter(p => !isPluginEnabled(p)), [theme]);
return (
<AddonCard
name={theme.name ?? "Unknown"}
description={theme.description}
author={theme.author ?? "Unknown"}
enabled={enabled}
setEnabled={onChange}
infoButton={
<>
{missingPlugins && missingPlugins.length > 0 && (
<Tooltip text={"The following plugins are required, but aren't enabled: " + missingPlugins.join(", ")}>
{({ onMouseLeave, onMouseEnter }) => (
<div
style={{ color: "var(--status-warning" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<PluginIcon />
</div>
)}
</Tooltip>
)}
{theme.vars && (
<div style={{ cursor: "pointer" }} onClick={
() => openModal(modalProps =>
<UserCSSSettingsModal modalProps={modalProps} theme={theme} />)
}>
<CogWheel />
</div>
)}
{IS_WEB && (
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
<DeleteIcon />
</div>
)}
</>
}
footer={
<Flex flexDirection="row" style={{ gap: "0.2em" }}>
{!!theme.homepageURL && <Link href={theme.homepageURL}>Homepage</Link>}
{!!(theme.homepageURL && theme.supportURL) && " • "}
{!!theme.supportURL && <Link href={theme.supportURL}>Support</Link>}
</Flex>
}
/>
);
}
function OtherThemeCard({ theme, enabled, onChange, onDelete }: OtherThemeCardProps) {
return ( return (
<AddonCard <AddonCard
name={theme.name} name={theme.name}
@ -217,7 +158,7 @@ function ThemesTab() {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n")); const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
const [userThemes, setUserThemes] = useState<ThemeHeader[] | null>(null); const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null);
const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir); const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir);
useEffect(() => { useEffect(() => {
@ -226,57 +167,7 @@ function ThemesTab() {
async function refreshLocalThemes() { async function refreshLocalThemes() {
const themes = await VencordNative.themes.getThemesList(); const themes = await VencordNative.themes.getThemesList();
setUserThemes(themes);
const themeInfo: ThemeHeader[] = [];
for (const { fileName, content } of themes) {
if (!fileName.endsWith(".css")) continue;
if ((!IS_WEB || "armcord" in window) && fileName.endsWith(".user.css")) {
// handle it as usercss
const header = await usercssParse(content, fileName);
themeInfo.push({
type: "usercss",
header
});
Settings.userCssVars[header.id] ??= {};
for (const [name, varInfo] of Object.entries(header.vars ?? {})) {
let normalizedValue = "";
switch (varInfo.type) {
case "text":
case "color":
normalizedValue = varInfo.default;
break;
case "select":
normalizedValue = varInfo.options.find(v => v.name === varInfo.default)!.value;
break;
case "checkbox":
normalizedValue = varInfo.default ? "1" : "0";
break;
case "range":
normalizedValue = `${varInfo.default}${varInfo.units}`;
break;
case "number":
normalizedValue = String(varInfo.default);
break;
}
Settings.userCssVars[header.id][name] ??= normalizedValue;
}
} else {
// presumably BD but could also be plain css
themeInfo.push({
type: "other",
header: getThemeInfo(stripBOM(content), fileName)
});
}
}
setUserThemes(themeInfo);
} }
// When a local theme is enabled/disabled, update the settings // When a local theme is enabled/disabled, update the settings
@ -329,14 +220,13 @@ function ThemesTab() {
</Card> </Card>
<Forms.FormSection title="Local Themes"> <Forms.FormSection title="Local Themes">
<Card className="vc-settings-quick-actions-card"> <QuickActionCard>
<> <>
{IS_WEB ? {IS_WEB ?
( (
<Button <QuickAction
size={Button.Sizes.SMALL} text={
disabled={themeDirPending} <span style={{ position: "relative" }}>
>
Upload Theme Upload Theme
<FileInput <FileInput
ref={fileInputRef} ref={fileInputRef}
@ -344,35 +234,42 @@ function ThemesTab() {
multiple={true} multiple={true}
filters={[{ extensions: ["css"] }]} filters={[{ extensions: ["css"] }]}
/> />
</Button> </span>
}
Icon={PlusIcon}
/>
) : ( ) : (
<Button <QuickAction
onClick={() => showItemInFolder(themeDir!)} text="Open Themes Folder"
size={Button.Sizes.SMALL} action={() => showItemInFolder(themeDir!)}
disabled={themeDirPending} disabled={themeDirPending}
> Icon={FolderIcon}
Open Themes Folder />
</Button> )}
<QuickAction
text="Load missing Themes"
action={refreshLocalThemes}
Icon={RestartIcon}
/>
<QuickAction
text="Edit QuickCSS"
action={() => VencordNative.quickCss.openEditor()}
Icon={PaintbrushIcon}
/>
{Settings.plugins.ClientTheme.enabled && (
<QuickAction
text="Edit ClientTheme"
action={() => openPluginModal(Plugins.ClientTheme)}
Icon={PencilIcon}
/>
)} )}
<Button
onClick={refreshLocalThemes}
size={Button.Sizes.SMALL}
>
Load missing Themes
</Button>
<Button
onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL}
>
Edit QuickCSS
</Button>
</> </>
</Card> </QuickActionCard>
<div className={cl("grid")}> <div className={cl("grid")}>
{userThemes?.map(({ type, header: theme }: ThemeHeader) => ( {userThemes?.map(theme => (
type === "other" ? ( <ThemeCard
<OtherThemeCard
key={theme.fileName} key={theme.fileName}
enabled={settings.enabledThemes.includes(theme.fileName)} enabled={settings.enabledThemes.includes(theme.fileName)}
onChange={enabled => onLocalThemeChange(theme.fileName, enabled)} onChange={enabled => onLocalThemeChange(theme.fileName, enabled)}
@ -381,21 +278,9 @@ function ThemesTab() {
await VencordNative.themes.deleteTheme(theme.fileName); await VencordNative.themes.deleteTheme(theme.fileName);
refreshLocalThemes(); refreshLocalThemes();
}} }}
theme={theme as UserThemeHeader} theme={theme}
/> />
) : ( ))}
<UserCSSThemeCard
key={theme.fileName}
enabled={settings.enabledThemes.includes(theme.fileName)}
onChange={enabled => onLocalThemeChange(theme.fileName, enabled)}
onDelete={async () => {
onLocalThemeChange(theme.fileName, false);
await VencordNative.themes.deleteTheme(theme.fileName);
refreshLocalThemes();
}}
theme={theme as UserstyleHeader}
/>
)))}
</div> </div>
</Forms.FormSection> </Forms.FormSection>
</> </>
@ -419,6 +304,7 @@ function ThemesTab() {
<Card className="vc-settings-card vc-text-selectable"> <Card className="vc-settings-card vc-text-selectable">
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
<Forms.FormText>One link per line</Forms.FormText> <Forms.FormText>One link per line</Forms.FormText>
<Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText> <Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
</Card> </Card>
@ -426,7 +312,7 @@ function ThemesTab() {
<TextArea <TextArea
value={themeText} value={themeText}
onChange={setThemeText} onChange={setThemeText}
className={classes(TextAreaProps.textarea, "vc-settings-theme-links")} className={"vc-settings-theme-links"}
placeholder="Theme Links" placeholder="Theme Links"
spellCheck={false} spellCheck={false}
onBlur={onBlur} onBlur={onBlur}

View file

@ -22,6 +22,7 @@ import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater"; import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
@ -29,7 +30,7 @@ import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@web
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import { SettingsTab, wrapTab } from "./shared"; import { handleSettingsTabError, SettingsTab, wrapTab } from "./shared";
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) { function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
return async () => { return async () => {
@ -38,21 +39,24 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
await action(); await action();
} catch (e: any) { } catch (e: any) {
UpdateLogger.error("Failed to update", e); UpdateLogger.error("Failed to update", e);
let err: string;
if (!e) { if (!e) {
var err = "An unknown error occurred (error is undefined).\nPlease try again."; err = "An unknown error occurred (error is undefined).\nPlease try again.";
} else if (e.code && e.cmd) { } else if (e.code && e.cmd) {
const { code, path, cmd, stderr } = e; const { code, path, cmd, stderr } = e;
if (code === "ENOENT") if (code === "ENOENT")
var err = `Command \`${path}\` not found.\nPlease install it and try again`; err = `Command \`${path}\` not found.\nPlease install it and try again`;
else { else {
var err = `An error occurred while running \`${cmd}\`:\n`; err = `An error occurred while running \`${cmd}\`:\n`;
err += stderr || `Code \`${code}\`. See the console for more info`; err += stderr || `Code \`${code}\`. See the console for more info`;
} }
} else { } else {
var err = "An unknown error occurred. See the console for more info."; err = "An unknown error occurred. See the console for more info.";
} }
Alerts.show({ Alerts.show({
title: "Oops!", title: "Oops!",
body: ( body: (
@ -81,9 +85,12 @@ function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
return ( return (
<Card style={{ padding: ".5em" }}> <Card style={{ padding: "0 0.5em" }}>
{updates.map(({ hash, author, message }) => ( {updates.map(({ hash, author, message }) => (
<div> <div style={{
marginTop: "0.5em",
marginBottom: "0.5em"
}}>
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code> <code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
<span style={{ <span style={{
marginLeft: "0.5em", marginLeft: "0.5em",
@ -113,7 +120,7 @@ function Updatable(props: CommonProps) {
</> </>
) : ( ) : (
<Forms.FormText className={Margins.bottom8}> <Forms.FormText className={Margins.bottom8}>
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"} {isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
</Forms.FormText> </Forms.FormText>
)} )}
@ -183,7 +190,7 @@ function Newer(props: CommonProps) {
} }
function Updater() { function Updater() {
const settings = useSettings(["notifyAboutUpdates", "autoUpdate", "autoUpdateNotification"]); const settings = useSettings(["autoUpdate", "autoUpdateNotification"]);
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." }); const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
@ -200,14 +207,6 @@ function Updater() {
return ( return (
<SettingsTab title="Vencord Updater"> <SettingsTab title="Vencord Updater">
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
<Switch
value={settings.notifyAboutUpdates}
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
note="Shows a notification on startup"
disabled={settings.autoUpdate}
>
Get notified about new updates
</Switch>
<Switch <Switch
value={settings.autoUpdate} value={settings.autoUpdate}
onChange={(v: boolean) => settings.autoUpdate = v} onChange={(v: boolean) => settings.autoUpdate = v}
@ -250,3 +249,20 @@ function Updater() {
} }
export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater"); export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");
export const openUpdaterModal = IS_UPDATER_DISABLED ? null : function () {
const UpdaterTab = wrapTab(Updater, "Updater");
try {
openModal(wrapTab((modalProps: ModalProps) => (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalContent className="vc-updater-modal">
<ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" />
<UpdaterTab />
</ModalContent>
</ModalRoot>
), "UpdaterModal"));
} catch {
handleSettingsTabError();
}
};

View file

@ -17,16 +17,20 @@
*/ */
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/Settings"; import { 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 { openPluginModal } from "@components/PluginSettings/PluginModal";
import { gitRemote } from "@shared/vencordUserAgent";
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";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common"; import { Button, Card, Forms, React, Select, Switch } from "@webpack/common";
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
import { openNotificationSettingsModal } from "./NotificationSettings";
import { QuickAction, QuickActionCard } from "./quickActions";
import { SettingsTab, wrapTab } from "./shared"; import { SettingsTab, wrapTab } from "./shared";
const cl = classNameFactory("vc-settings-"); const cl = classNameFactory("vc-settings-");
@ -38,6 +42,7 @@ type KeysOfType<Object, Type> = {
[K in keyof Object]: Object[K] extends Type ? K : never; [K in keyof Object]: Object[K] extends Type ? K : never;
}[keyof Object]; }[keyof Object];
function VencordSettings() { function VencordSettings() {
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, { const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
fallbackValue: "Loading..." fallbackValue: "Loading..."
@ -50,14 +55,6 @@ function VencordSettings() {
const isMac = navigator.platform.toLowerCase().startsWith("mac"); const isMac = navigator.platform.toLowerCase().startsWith("mac");
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac; const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
// One-time migration of the old setting to the new one if necessary.
React.useEffect(() => {
if (settings.macosTranslucency === true && !settings.macosVibrancyStyle) {
settings.macosVibrancyStyle = "sidebar";
settings.macosTranslucency = undefined;
}
}, []);
const Switches: Array<false | { const Switches: Array<false | {
key: KeysOfType<typeof settings, boolean>; key: KeysOfType<typeof settings, boolean>;
title: string; title: string;
@ -83,10 +80,10 @@ function VencordSettings() {
title: "Use Windows' native title bar instead of Discord's custom one", title: "Use Windows' native title bar instead of Discord's custom one",
note: "Requires a full restart" note: "Requires a full restart"
}), }),
!IS_WEB && false /* This causes electron to freeze / white screen for some people */ && { !IS_WEB && {
key: "transparent", key: "transparent",
title: "Enable window transparency", title: "Enable window transparency.",
note: "Requires a full restart" note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart"
}, },
!IS_WEB && isWindows && { !IS_WEB && isWindows && {
key: "winCtrlQ", key: "winCtrlQ",
@ -104,45 +101,53 @@ function VencordSettings() {
<SettingsTab title="Vencord Settings"> <SettingsTab title="Vencord Settings">
<DonateCard image={donateImage} /> <DonateCard image={donateImage} />
<Forms.FormSection title="Quick Actions"> <Forms.FormSection title="Quick Actions">
<Card className={cl("quick-actions-card")}> <QuickActionCard>
<React.Fragment> <QuickAction
Icon={LogIcon}
text="Notification Log"
action={openNotificationLogModal}
/>
<QuickAction
Icon={PaintbrushIcon}
text="Edit QuickCSS"
action={() => VencordNative.quickCss.openEditor()}
/>
{!IS_WEB && ( {!IS_WEB && (
<Button <QuickAction
onClick={relaunch} Icon={RestartIcon}
size={Button.Sizes.SMALL}> text="Relaunch Discord"
Restart Client action={relaunch}
</Button> />
)} )}
<Button
onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}>
Open QuickCSS File
</Button>
{!IS_WEB && ( {!IS_WEB && (
<Button <QuickAction
onClick={() => showItemInFolder(settingsDir)} Icon={FolderIcon}
size={Button.Sizes.SMALL} text="Open Settings Folder"
disabled={settingsDirPending}> action={() => showItemInFolder(settingsDir)}
Open Settings Folder />
</Button>
)} )}
<Button <QuickAction
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")} Icon={GithubIcon}
size={Button.Sizes.SMALL} text="View Source Code"
disabled={settingsDirPending}> action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
Open in GitHub />
</Button> </QuickActionCard>
</React.Fragment>
</Card>
</Forms.FormSection> </Forms.FormSection>
<Forms.FormDivider /> <Forms.FormDivider />
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5"> <Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
<Forms.FormText className={Margins.bottom20}> <Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
Hint: You can change the position of this settings section in the settings of the "Settings" plugin! Hint: You can change the position of this settings section in the
{" "}<Button
look={Button.Looks.BLANK}
style={{ color: "var(--text-link)", display: "inline-block" }}
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
>
settings of the Settings plugin
</Button>!
</Forms.FormText> </Forms.FormText>
{Switches.map(s => s && ( {Switches.map(s => s && (
<Switch <Switch
key={s.key} key={s.key}
@ -164,7 +169,7 @@ function VencordSettings() {
options={[ options={[
// Sorted from most opaque to most transparent // Sorted from most opaque to most transparent
{ {
label: "No vibrancy", default: !settings.macosTranslucency, value: undefined label: "No vibrancy", value: undefined
}, },
{ {
label: "Under Page (window tinting)", label: "Under Page (window tinting)",
@ -191,9 +196,8 @@ function VencordSettings() {
value: "header" value: "header"
}, },
{ {
label: "Sidebar (old value for transparent windows)", label: "Sidebar",
value: "sidebar", value: "sidebar"
default: settings.macosTranslucency
}, },
{ {
label: "Tooltip", label: "Tooltip",
@ -221,91 +225,17 @@ function VencordSettings() {
serialize={identity} /> serialize={identity} />
</>} </>}
{typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />} <Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
</SettingsTab> <Flex>
); <Button onClick={openNotificationSettingsModal}>
} Notification Settings
function NotificationSection({ settings }: { settings: typeof Settings["notifications"]; }) {
return (
<>
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
{settings.useNative !== "never" && Notification?.permission === "denied" && (
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
</ErrorCard>
)}
<Forms.FormText className={Margins.bottom8}>
Some plugins may show you notifications. These come in two styles:
<ul>
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
</ul>
</Forms.FormText>
<Select
placeholder="Notification Style"
options={[
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
{ label: "Always use Desktop notifications", value: "always" },
{ label: "Always use Vencord notifications", value: "never" },
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
closeOnSelect={true}
select={v => settings.useNative = v}
isSelected={v => v === settings.useNative}
serialize={identity}
/>
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
<Select
isDisabled={settings.useNative === "always"}
placeholder="Notification Position"
options={[
{ label: "Bottom Right", value: "bottom-right", default: true },
{ label: "Top Right", value: "top-right" },
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
select={v => settings.position = v}
isSelected={v => v === settings.position}
serialize={identity}
/>
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
<Slider
disabled={settings.useNative === "always"}
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
minValue={0}
maxValue={20_000}
initialValue={settings.timeout}
onValueChange={v => settings.timeout = v}
onValueRender={v => (v / 1000).toFixed(2) + "s"}
onMarkerRender={v => (v / 1000) + "s"}
stickToMarkers={false}
/>
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16}>
The amount of notifications to save in the log until old ones are removed.
Set to <code>0</code> to disable Notification log and <code></code> to never automatically remove old Notifications
</Forms.FormText>
<Slider
markers={[0, 25, 50, 75, 100, 200]}
minValue={0}
maxValue={200}
stickToMarkers={true}
initialValue={settings.logLimit}
onValueChange={v => settings.logLimit = v}
onValueRender={v => v === 200 ? "∞" : v}
onMarkerRender={v => v === 200 ? "∞" : v}
/>
<Button
onClick={openNotificationLogModal}
disabled={settings.logLimit === 0}
>
Open Notification Log
</Button> </Button>
</> <Button onClick={openNotificationLogModal}>
View Notification Log
</Button>
</Flex>
</Forms.FormSection>
</SettingsTab>
); );
} }

View file

@ -62,3 +62,36 @@
.vc-addon-author::before { .vc-addon-author::before {
content: "by "; content: "by ";
} }
.vc-addon-title-container {
width: 100%;
overflow: hidden;
height: 1.25em;
position: relative;
}
.vc-addon-title {
position: absolute;
inset: 0;
overflow: hidden;
text-overflow: ellipsis;
}
@keyframes vc-addon-title {
0% {
transform: translateX(0);
}
50% {
transform: translateX(var(--offset));
}
100% {
transform: translateX(0);
}
}
.vc-addon-title:hover {
overflow: visible;
animation: vc-addon-title var(--duration) linear infinite;
}

View file

@ -0,0 +1,33 @@
.vc-settings-quickActions-card {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, max-content));
gap: 0.5em;
justify-content: center;
padding: 0.5em 0;
margin-bottom: 1em;
}
.vc-settings-quickActions-pill {
all: unset;
background: var(--background-secondary);
color: var(--header-secondary);
display: flex;
align-items: center;
gap: 0.5em;
padding: 8px 12px;
border-radius: 9999px;
}
.vc-settings-quickActions-pill:hover {
background: var(--background-secondary-alt);
}
.vc-settings-quickActions-pill:focus-visible {
outline: 2px solid var(--focus-primary);
outline-offset: 2px;
}
.vc-settings-quickActions-img {
width: 24px;
height: 24px;
}

View file

@ -0,0 +1,39 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./quickActions.css";
import { classNameFactory } from "@api/Styles";
import { Card } from "@webpack/common";
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
const cl = classNameFactory("vc-settings-quickActions-");
export interface QuickActionProps {
Icon: ComponentType<{ className?: string; }>;
text: ReactNode;
action?: () => void;
disabled?: boolean;
}
export function QuickAction(props: QuickActionProps) {
const { Icon, action, text, disabled } = props;
return (
<button className={cl("pill")} onClick={action} disabled={disabled}>
<Icon className={cl("img")} />
{text}
</button>
);
}
export function QuickActionCard(props: PropsWithChildren) {
return (
<Card className={cl("card")}>
{props.children}
</Card>
);
}

View file

@ -10,17 +10,6 @@
margin-bottom: -2px; margin-bottom: -2px;
} }
.vc-settings-quick-actions-card {
padding: 1em;
display: flex;
gap: 1em;
align-items: center;
justify-content: space-evenly;
flex-grow: 1;
flex-flow: row wrap;
margin-bottom: 1em;
}
.vc-settings-donate { .vc-settings-donate {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -44,6 +33,20 @@
padding: 0.5em; padding: 0.5em;
border: 1px solid var(--background-modifier-accent); border: 1px solid var(--background-modifier-accent);
max-height: unset; max-height: unset;
background-color: transparent;
box-sizing: border-box;
font-size: 12px;
line-height: 14px;
resize: none;
width: 100%;
}
.vc-settings-theme-links::placeholder {
color: var(--header-secondary);
}
.vc-settings-theme-links:focus {
background-color: var(--background-tertiary);
} }
.vc-cloud-settings-sync-grid { .vc-cloud-settings-sync-grid {
@ -65,3 +68,11 @@
/* discord also sets cursor: default which prevents the cursor from showing as text */ /* discord also sets cursor: default which prevents the cursor from showing as text */
cursor: initial; cursor: initial;
} }
.vc-updater-modal {
padding: 1.5em !important;
}
.vc-updater-modal-close-button {
float: right;
}

View file

@ -17,6 +17,7 @@
*/ */
import "./settingsStyles.css"; import "./settingsStyles.css";
import "./themesStyles.css";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { handleComponentFailed } from "@components/handleComponentFailed"; import { handleComponentFailed } from "@components/handleComponentFailed";
@ -41,11 +42,11 @@ export function SettingsTab({ title, children }: PropsWithChildren<{ title: stri
); );
} }
const onError = onlyOnce(handleComponentFailed); export const handleSettingsTabError = onlyOnce(handleComponentFailed);
export function wrapTab(component: ComponentType, tab: string) { export function wrapTab(component: ComponentType<any>, tab: string) {
return ErrorBoundary.wrap(component, { return ErrorBoundary.wrap(component, {
message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`, message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`,
onError, onError: handleSettingsTabError,
}); });
} }

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