Compare commits

..

189 commits

Author SHA1 Message Date
Ulysia 1d5709763a Merge branch 'main' of https://github.com/Vendicated/Vencord 2024-11-06 06:33:51 +01:00
sadan4 9d4e859a0a
NoBlockedMessages: Fix for new i18n lib (#2996) 2024-11-05 19:01:52 -03:00
Nuckyz 439a4f8eb6
Bump to 1.10.6 2024-11-05 16:51:18 -03:00
Nuckyz 00f82e96bd
Fix all plugins for new i18n lib (#2993) 2024-11-05 16:50:26 -03:00
Nuckyz 5216bcca1e
Fix settings & updater for Canary 2024-11-03 15:47:19 -03:00
Vendicated e7e298d2e7
ThemeAttributes: fix freezing when changing avatar 2024-10-26 13:48:22 +02:00
sadan4 d897dab054
ShowHiddenChannels: Fix viewing voice channels (#2979) 2024-10-26 07:32:32 -03:00
sadan4 88e8fa7e90
NoPendingCount: Fix hiding offers count (#2975) 2024-10-25 06:48:11 -03:00
Nuckyz f5f59be1b6
Fix plugins using ImageModals (again) 2024-10-24 08:10:37 -03:00
sadan4 534ab3eb5f
ConsoleJanitor: Brush react-spring deprecation (#2973) 2024-10-24 11:09:12 +00:00
sadan4 a6ea03bacc
ImageZoom: Fix when multiple images with carrousel (#2966)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2024-10-24 11:08:02 +00:00
Ulysia c3bdc71512 Merge remote-tracking branch 'upstream/main' 2024-10-23 12:35:49 +02:00
Vendicated e620431210
bump to v1.10.5 2024-10-23 04:47:29 +02:00
Aiden 5afc24b41a
ArmCord -> Legcord (#2948) 2024-10-23 04:39:43 +02:00
Vendicated 7f8e241b9c
"fix" OpenInApp 2024-10-23 04:33:30 +02:00
sadan4 553293ceee
fix MutualGroupDMs (#2964)
Co-authored-by: v <vendicated@riseup.net>
2024-10-23 04:28:30 +02:00
Vendicated 0af820c874
Fix ImageZoom
Also fixes the image modal being off centre when having the plugin enabled

Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com>
2024-10-23 04:22:11 +02:00
Vendicated a11ccde40f
Fix ViewIcons & plugins that use image modals
Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com>
2024-10-23 03:57:46 +02:00
Nuckyz 58c3032bb2
Rework PronounDB -> UserMessagesPronouns 2024-10-19 08:08:34 -03:00
sadan4 4e89352758
Fix ShowHiddenChannels & FakeNitro broken functionality (#2959) 2024-10-19 11:06:42 +00:00
Nuckyz e818905520
Workaround https://github.com/electron/electron/issues/43367 2024-10-10 09:17:32 -03:00
sadan4 aa1b446c07
ShowHiddenChannels: Fix re-organizing channels (#2942) 2024-10-10 09:16:36 -03:00
programminglaboratorys 2dce060cf9
MessageClickActions: Fix editing messages which failed to send (#2677) 2024-10-09 08:57:30 +00:00
Vendicated 89bb3ee30a
SupportHelper: fix DM warning card 2024-10-09 03:18:31 +02:00
Pavel Djundik 47db61d00e
Add icon to userscript meta (#2936) 2024-10-07 17:11:29 +02:00
Ulysia a592586af7 Added origin setup 2024-10-07 07:05:24 +00:00
Ulysia 0567320fc6 Merge branch 'main' of https://github.com/Vendicated/Vencord 2024-10-07 07:01:53 +00:00
Nuckyz 416d85dcf0
new plugin FixImagesQuality 2024-10-06 13:20:06 -03:00
Nuckyz 013c8d061d
Bump to 1.10.4 2024-10-05 14:24:39 -03:00
Nuckyz b5f626d1ff
Fix multiple plugins (again) 2024-10-05 08:01:40 -03:00
sadan4 1e01f85217
NoBlockedMessages: Fix conflict with MessageLogger (#2921) 2024-10-04 09:29:59 +00:00
sadan4 91a32e22de
PermissionsViewer: Fix profile button (#2925) 2024-10-04 06:28:36 -03:00
ryanamay 43b3c137ce
BlurNSFW: Fix not blurring embeds (#2862) 2024-10-03 07:11:37 +00:00
Nuckyz 18f7b74210
Fix required plugins being shown as disabled 2024-09-27 05:46:50 -03:00
sadan4 eab0cf9966
VolumeBooster: fix stream on web based clients (#2916)
Co-authored-by: v <vendicated@riseup.net>
2024-09-27 00:26:13 +02:00
Nuckyz e7956413e2
Optimize slow patches 2024-09-26 14:02:36 -03:00
Vendicated 832e874c35
bump to v1.10.3 2024-09-26 18:19:08 +02:00
Heli-o 55146db760 Merge branch 'main' of https://github.com/Vendicated/Vencord 2024-09-26 16:52:09 +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
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
Heli-o fd0fbace2e Merge branch 'main' of https://github.com/Vendicated/Vencord 2024-09-22 19:55:21 +02: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
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
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
Heli-o 9d67421b34 Merge branch 'main' of https://github.com/Vendicated/Vencord 2024-09-13 11:36:12 +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
Heli-o c07d78fa96 Merge branch 'main' of https://github.com/Vendicated/Vencord; branch 'main' of https://git.derg.cz/ulysia/Vencord 2024-09-06 21:20:48 +02:00
Relitrix 56459bdcad
new plugin AccountPanelServerProfile (#2836) 2024-09-06 12:33:23 -03: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
Heli-o ee9fbc4d25 added steps 2024-08-30 17:16:12 +02:00
Heli-o 0cea347264 Merge branch 'main' of https://github.com/Vendicated/Vencord 2024-08-30 16:57:58 +02: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
Heli-o cdf72cf4be added a userplugin 2024-08-26 21:21:02 +02: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
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
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
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
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
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
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
278 changed files with 5570 additions and 4404 deletions

View file

@ -1,98 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"ignorePatterns": ["dist", "browser", "packages/vencord-types"],
"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"
}
}

2
.gitignore vendored
View file

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

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "src/userplugins/vc-message-logger-enhanced"]
path = src/userplugins/vc-message-logger-enhanced
url = https://github.com/Syncxv/vc-message-logger-enhanced.git

View file

@ -1,7 +1,6 @@
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4,
"selector-class-pattern": [
"^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$",
{

View file

@ -4,7 +4,6 @@
"EditorConfig.EditorConfig",
"GregorBiswanger.json2ts",
"stylelint.vscode-stylelint",
"Vendicated.vencord-companion",
"lokalise.i18n-ally"
"Vendicated.vencord-companion"
]
}

View file

@ -1,16 +0,0 @@
languageIds:
- javascript
- typescript
- javascriptreact
- typescriptreact
usageMatchRegex:
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
- "<Translate ?.* i18nKey=\\{?['\"`]({key})['\"`]"
refactorTemplates:
- "t(\"$1\")"
- "{t(\"$1\")}"
- "<Translate i18nKey=\"$1\" />"
monopoly: true

10
.vscode/settings.json vendored
View file

@ -14,18 +14,10 @@
"typescript.preferences.quoteStyle": "double",
"javascript.preferences.quoteStyle": "double",
"eslint.experimental.useFlatConfig": false,
"gitlens.remotes": [
{
"domain": "codeberg.org",
"type": "Gitea"
}
],
"i18n-ally.namespace": true,
"i18n-ally.localesPaths": ["./translations"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.extract.keygenStyle": "camelCase",
"i18n-ally.sortKeys": true,
"i18n-ally.keystyle": "nested"
]
}

View file

@ -5,6 +5,7 @@
// @author Vendicated (https://github.com/Vendicated)
// @namespace https://github.com/Vendicated/Vencord
// @supportURL https://github.com/Vendicated/Vencord
// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png
// @license GPL-3.0
// @match *://*.discord.com/*
// @grant GM_xmlhttpRequest

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",
"private": "true",
"version": "1.9.3",
"version": "1.10.6",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -21,12 +21,13 @@
"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",
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
"inject": "node scripts/runInstaller.mjs",
"uninject": "node scripts/runInstaller.mjs",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
"lint": "eslint",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix",
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
@ -34,54 +35,55 @@
"testTsc": "tsc --noEmit"
},
"dependencies": {
"@fluent/langneg": "^0.7.0",
"@sapphi-red/web-noise-suppressor": "0.3.3",
"@intrnl/xxhash64": "^0.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",
"eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4",
"fflate": "^0.8.2",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.50.0",
"nanoid": "^4.0.2",
"nanoid": "^5.0.7",
"virtual-merge": "^1.0.1"
},
"devDependencies": {
"@types/chrome": "^0.0.246",
"@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194",
"@types/node": "^18.16.3",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"diff": "^5.1.0",
"@stylistic/eslint-plugin": "^2.6.1",
"@types/chrome": "^0.0.269",
"@types/diff": "^5.2.1",
"@types/lodash": "^4.17.7",
"@types/node": "^22.0.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/yazl": "^2.4.5",
"diff": "^5.2.0",
"discord-types": "^1.3.26",
"esbuild": "^0.15.18",
"eslint": "^8.46.0",
"eslint": "^9.8.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"highlight.js": "10.6.0",
"eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-simple-header": "^1.1.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.0.1",
"highlight.js": "10.7.3",
"html-minifier-terser": "^7.2.0",
"moment": "^2.29.4",
"puppeteer-core": "^19.11.1",
"moment": "^2.30.1",
"puppeteer-core": "^22.15.0",
"standalone-electron-types": "^1.0.0",
"stylelint": "^15.6.0",
"stylelint-config-standard": "^33.0.0",
"ts-patch": "^3.1.2",
"tsx": "^3.12.7",
"type-fest": "^3.9.0",
"typescript": "^5.4.5",
"stylelint": "^16.8.1",
"stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.2.1",
"ts-pattern": "^5.3.1",
"tsx": "^4.16.5",
"type-fest": "^4.23.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.0",
"typescript-transform-paths": "^3.4.7",
"zip-local": "^0.3.5"
},
"packageManager": "pnpm@9.1.0",
"pnpm": {
"patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
"eslint@8.46.0": "patches/eslint@8.46.0.patch"
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
},
"peerDependencyRules": {
"ignoreMissing": [

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

View file

@ -21,7 +21,7 @@ import esbuild from "esbuild";
import { readdir } from "fs/promises";
import { join } from "path";
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, 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 = {
IS_STANDALONE,
@ -131,7 +131,7 @@ await Promise.all([
sourcemap,
plugins: [
globPlugins("discordDesktop"),
...commonOpts.plugins
...commonRendererPlugins
],
define: {
...defines,
@ -180,7 +180,7 @@ await Promise.all([
sourcemap,
plugins: [
globPlugins("vencordDesktop"),
...commonOpts.plugins
...commonRendererPlugins
],
define: {
...defines,

View file

@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises
import { join } from "path";
import Zip from "zip-local";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@ -36,7 +36,7 @@ const commonOptions = {
external: ["~plugins", "~git-hash", "/assets/*"],
plugins: [
globPlugins("web"),
...commonOpts.plugins,
...commonRendererPlugins
],
target: ["esnext"],
define: {
@ -116,7 +116,12 @@ await Promise.all(
}
})
]
);
).catch(err => {
console.error("Build failed");
console.error(err.message);
if (!commonOpts.watch)
process.exit(1);
});;
/**
* @type {(dir: string) => Promise<string[]>}

View file

@ -28,6 +28,7 @@ import { join, relative } from "path";
import { promisify } from "util";
import { getPluginTarget } from "../utils.mjs";
import { builtinModules } from "module";
/** @type {import("../../package.json")} */
const PackageJSON = JSON.parse(readFileSync("package.json"));
@ -293,37 +294,16 @@ export const stylePlugin = {
};
/**
* @type {import("esbuild").Plugin}
* @type {(filter: RegExp, message: string) => import("esbuild").Plugin}
*/
export const translationPlugin = {
name: "translation-plugin",
setup: ({ onResolve, onLoad }) => {
const filter = /^~translations$/;
onResolve({ filter }, ({ path }) => ({
namespace: "translations", path
}));
onLoad({ filter, namespace: "translations" }, async () => {
const translations = {};
const locales = await readdir("./translations");
for (const locale of locales) {
const translationBundles = await readdir(`./translations/${locale}`);
for (const bundle of translationBundles) {
const name = bundle.replace(/\.json$/, "");
translations[locale] ??= {};
translations[locale][name] = JSON.parse(await readFile(`./translations/${locale}/${bundle}`, "utf-8"));
}
}
return {
contents: `export default ${JSON.stringify(translations)}`,
};
export const banImportPlugin = (filter, message) => ({
name: "ban-imports",
setup: build => {
build.onResolve({ filter }, () => {
return { errors: [{ text: message }] };
});
}
};
});
/**
* @type {import("esbuild").BuildOptions}
@ -336,11 +316,24 @@ export const commonOpts = {
sourcemap: watch ? "inline" : "",
legalComments: "linked",
banner,
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin, translationPlugin],
external: ["~plugins", "~git-hash", "~git-remote", "~translations", "/assets/*"],
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
inject: ["./scripts/build/inject/react.mjs"],
jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment",
// Work around https://github.com/evanw/esbuild/issues/2460
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

@ -20,7 +20,7 @@ import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs";
import { access, readFile } from "fs/promises";
import { join, sep } from "path";
import { normalize as posixNormalize, sep as posixSep } from "path/posix";
import { BigIntLiteral, CallExpression, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, LiteralExpression, NamedDeclaration, Node, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript";
import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript";
import { getPluginTarget } from "./utils.mjs";
@ -90,38 +90,6 @@ function parseDevs() {
throw new Error("Could not find Devs constant");
}
function isTranslationExpression(node: Node): node is CallExpression {
if (!isCallExpression(node)) return false;
const literal = node.expression as LiteralExpression;
if (literal.text !== "t") return false;
return true;
}
async function getTranslation(node: Node): Promise<string | null> {
if (!isTranslationExpression(node)) return null;
const translationString = node.arguments[0];
if (!isStringLiteral(translationString)) return null;
const splitPath = translationString.text.split(".");
const namespace = splitPath.shift();
const path = splitPath.join(".");
// load the namespace
const bundle = JSON.parse(
await readFile(`./translations/en/${namespace}.json`, "utf-8")
);
const dotProp = (key: string, object: any) =>
key.split(".").reduce((obj, key) => obj?.[key], object);
return dotProp(path, bundle);
}
async function parseFile(fileName: string) {
const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest);
@ -152,16 +120,10 @@ async function parseFile(fileName: string) {
switch (key) {
case "name":
case "description":
if (!isStringLiteral(value)) throw fail(`${key} is not a string literal`);
data[key] = value.text;
break;
case "description":
if (isStringLiteral(value))
data[key] = value.text;
else if (isTranslationExpression(value))
data[key] = (await getTranslation(value))!;
else throw fail(`${key} is not a string literal or a translation function call`);
break;
case "patches":
data.hasPatches = true;
break;

View file

@ -36,7 +36,7 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
const CANARY = process.env.USE_CANARY === "true";
const browser = await pup.launch({
headless: "new",
headless: true,
executablePath: process.env.CHROMIUM_BIN
});

2
setup-origins.sh Executable file
View file

@ -0,0 +1,2 @@
git remote add upstream https://github.com/Vendicated/Vencord.git
git remote set-url --pull upstream DISABLED

View file

@ -38,7 +38,6 @@ import { patches, PMLogger, startAllPlugins } from "./plugins";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
import { t } from "./utils/translation";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common";
@ -55,8 +54,9 @@ async function syncSettings() {
) {
// show a notification letting them know and tell them how to fix it
showNotification({
title: t("vencord.cloudIntegrations"),
body: t("vencord.cloud.integrations.reauthenticate"),
title: "Cloud Integrations",
body: "We've noticed you have cloud integrations enabled in another client! Due to limitations, you will " +
"need to re-authenticate to continue using them. Click here to go to the settings page to do so!",
color: "var(--yellow-360)",
onClick: () => SettingsRouter.open("VencordCloud")
});
@ -76,8 +76,8 @@ async function syncSettings() {
// there was an error to notify the user, but besides that we only want to show one notification instead of all
// of the possible ones it has (such as when your settings are newer).
showNotification({
title: t("vencord.cloudSettings"),
body: t("vencord.cloud.settings.updated"),
title: "Cloud Settings",
body: "Your settings have been updated! Click here to restart to fully apply changes!",
color: "var(--green-360)",
onClick: relaunch
});
@ -100,8 +100,8 @@ async function init() {
await update();
if (Settings.autoUpdateNotification)
setTimeout(() => showNotification({
title: t("vencord.update.updated"),
body: t("vencord.update.clickToRestart"),
title: "Vencord has been updated!",
body: "Click here to restart",
permanent: true,
noPersist: true,
onClick: relaunch
@ -110,8 +110,8 @@ async function init() {
}
setTimeout(() => showNotification({
title: t("vencord.update.available"),
body: t("vencord.update.clickToView"),
title: "A Vencord update is available!",
body: "Click here to view the update",
permanent: true,
noPersist: true,
onClick: openUpdaterModal!

View file

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

View file

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

View file

@ -90,19 +90,20 @@ 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
* @param id The id of the child. If an array is specified, all ids will be tried
* @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>): 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) {
if (child == null) continue;
if (Array.isArray(child)) {
const found = findGroupChildrenByChildId(id, child);
const found = findGroupChildrenByChildId(id, child, matchSubstring);
if (found !== null) return found;
}
if (
(Array.isArray(id) && id.some(id => child.props?.id === id))
|| child.props?.id === id
(Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|| (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
) return children;
let nextChildren = child.props?.children;
@ -112,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
child.props.children = nextChildren;
}
const found = findGroupChildrenByChildId(id, nextChildren);
const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
if (found !== null) return found;
}
}

View file

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

View file

@ -230,6 +230,10 @@ export function definePluginSettings<
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
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(
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
).plugins[definedSettings.pluginName] as any,

View file

@ -19,7 +19,6 @@
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { LazyComponent } from "@utils/react";
import { t } from "@utils/translation";
import { React } from "@webpack/common";
import { ErrorCard } from "./ErrorCard";
@ -86,11 +85,11 @@ const ErrorBoundary = LazyComponent(() => {
{...this.state}
/>;
const msg = this.props.message || t("vencord.errorBoundaryDescription");
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
return (
<ErrorCard style={{ overflow: "hidden" }}>
<h1>{t("vencord.ohNo")}</h1>
<h1>Oh no!</h1>
<p>{msg}</p>
<code>
{this.state.message}

View file

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

View file

@ -18,9 +18,8 @@
import "./iconStyles.css";
import { getTheme, Theme } from "@utils/discord";
import { getIntlMessage, getTheme, Theme } from "@utils/discord";
import { classes } from "@utils/misc";
import { i18n } from "@webpack/common";
import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
@ -65,8 +64,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
* your own username in the bottom left user panel
* Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks
*/
export function CopyIcon(props: IconProps) {
return (
@ -76,8 +74,9 @@ export function CopyIcon(props: IconProps) {
viewBox="0 0 24 24"
>
<g fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
<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="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="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>
</Icon>
);
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
export function OwnerCrownIcon(props: IconProps) {
return (
<Icon
aria-label={i18n.Messages.GUILD_OWNER}
aria-label={getIntlMessage("GUILD_OWNER")}
{...props}
className={classes(props.className, "vc-owner-crown-icon")}
role="img"

View file

@ -12,9 +12,8 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link";
import { DevsById } from "@utils/constants";
import { fetchUserProfile } from "@utils/discord";
import { classes } from "@utils/misc";
import { classes, pluralise } from "@utils/misc";
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import { t, Translate } from "@utils/translation";
import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general";
@ -61,6 +60,8 @@ function ContributorModal({ user }: { user: User; }) {
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
}, [user.id, user.username]);
const ContributedHyperLink = <Link href="https://vencord.dev/source">contributed</Link>;
return (
<>
<div className={cl("header")}>
@ -87,11 +88,15 @@ function ContributorModal({ user }: { user: User; }) {
</div>
</div>
<Forms.FormText>
<Translate i18nKey="vencord.pluginContributed" variables={{ count: plugins.length }}>
<Link href="https://vencord.dev/source" />
</Translate>
</Forms.FormText>
{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")}>
@ -100,7 +105,7 @@ function ContributorModal({ user }: { user: User; }) {
key={p.name}
plugin={p}
disabled={p.required ?? false}
onRestartNeeded={() => showToast(t("vencord.pluginRestart"))}
onRestartNeeded={() => showToast("Restart to apply changes!")}
/>
)}
</div>

View file

@ -28,7 +28,6 @@ import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import { OptionType, Plugin } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
@ -139,7 +138,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
function renderSettings() {
if (!hasSettings || !plugin.options) {
return <Forms.FormText>{t("vencord.noSettings")}</Forms.FormText>;
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
} else {
const options = Object.entries(plugin.options).map(([key, setting]) => {
if (setting.hidden) return null;
@ -275,7 +274,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
</div>
)}
<Forms.FormSection className={Margins.bottom16}>
<Forms.FormTitle tag="h3">{t("vencord.settings")}</Forms.FormTitle>
<Forms.FormTitle tag="h3">Settings</Forms.FormTitle>
{renderSettings()}
</Forms.FormSection>
</ModalContent>
@ -290,7 +289,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
>
Cancel
</Button>
<Tooltip text={t("vencord.settingsErrors")} shouldShow={!canSubmit()}>
<Tooltip text="You must fix all errors before saving" shouldShow={!canSubmit()}>
{({ onMouseEnter, onMouseLeave }) => (
<Button
size={Button.Sizes.SMALL}
@ -300,12 +299,12 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
onMouseLeave={onMouseLeave}
disabled={!canSubmit()}
>
{t("vencord.saveAndClose")}
Save & Close
</Button>
)}
</Tooltip>
</Flex>
{saveError && <Text variant="text-md/semibold" style={{ color: "var(--text-danger)" }}>{t("vencord.settingsSaveError", { saveError })}</Text>}
{saveError && <Text variant="text-md/semibold" style={{ color: "var(--text-danger)" }}>Error while saving: {saveError}</Text>}
</Flex>
</ModalFooter>}
</ModalRoot>

View file

@ -16,6 +16,8 @@
* 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 { Forms, React, TextInput } from "@webpack/common";
@ -54,7 +56,8 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
return (
<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
type="number"
pattern="-?[0-9]+"

View file

@ -16,6 +16,8 @@
* 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 { Forms, React, Select } from "@webpack/common";
@ -44,7 +46,8 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
return (
<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
isDisabled={option.disabled?.call(definedSettings) ?? false}
options={option.options}

View file

@ -16,6 +16,8 @@
* 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 { Forms, React, Slider } from "@webpack/common";
@ -50,7 +52,8 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings
return (
<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
disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers}

View file

@ -16,6 +16,8 @@
* 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 { Forms, React, TextInput } from "@webpack/common";
@ -41,7 +43,8 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
return (
<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
type="text"
value={state}

View file

@ -32,7 +32,6 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import { t } from "@utils/translation";
import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
@ -65,19 +64,19 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
<Card className={cl("info-card", { "restart-card": required })}>
{required ? (
<>
<Forms.FormTitle tag="h5">{t("vencord.pluginHeader.reloadHeader")}</Forms.FormTitle>
<Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle>
<Forms.FormText className={cl("dep-text")}>
{t("vencord.pluginHeader.reloadDescription")}
Restart now to apply new plugins and their settings
</Forms.FormText>
<Button onClick={() => location.reload()}>
{t("vencord.pluginHeader.restart")}
Restart
</Button>
</>
) : (
<>
<Forms.FormTitle tag="h5">{t("vencord.pluginHeader.managementHeader")}</Forms.FormTitle>
<Forms.FormText>{t("vencord.pluginHeader.iconInformation")}</Forms.FormText>
<Forms.FormText>{t("vencord.pluginHeader.cogWheel")}</Forms.FormText>
<Forms.FormTitle tag="h5">Plugin Management</Forms.FormTitle>
<Forms.FormText>Press the cog wheel or info icon to get more info on a plugin</Forms.FormText>
<Forms.FormText>Plugins with a cog wheel have settings you can modify!</Forms.FormText>
</>
)}
</Card>
@ -94,7 +93,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name];
const isEnabled = () => settings.enabled ?? false;
const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);
function toggleEnabled() {
const wasEnabled = isEnabled();
@ -210,10 +209,10 @@ export default function PluginSettings() {
React.useEffect(() => {
return () => void (changes.hasChanges && Alerts.show({
title: t("vencord.restartRequired"),
title: "Restart required",
body: (
<>
<p>$t("vencord.pluginsNeedRestart")</p>
<p>The following plugins require a restart:</p>
<div>{changes.map((s, i) => (
<>
{i > 0 && ", "}
@ -222,8 +221,8 @@ export default function PluginSettings() {
))}</div>
</>
),
confirmText: t("vencord.restartNow"),
cancelText: t("vencord.restartLater"),
confirmText: "Restart now",
cancelText: "Later!",
onConfirm: () => location.reload()
}));
}, []);
@ -293,11 +292,11 @@ export default function PluginSettings() {
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) {
const tooltipText = p.required
? t("vencord.requiredPlugin")
const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Vencord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
requiredPlugins.push(
@ -332,18 +331,18 @@ export default function PluginSettings() {
<ReloadRequiredCard required={changes.hasChanges} />
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
{t("vencord.pluginFilters")}
Filters
</Forms.FormTitle>
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
<TextInput autoFocus value={searchValue.value} placeholder={t("vencord.search.placeholder")} onChange={onSearch} />
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
<div className={InputStyles.inputWrapper}>
<Select
options={[
{ label: t("vencord.search.all"), value: SearchStatus.ALL, default: true },
{ label: t("vencord.search.enabled"), value: SearchStatus.ENABLED },
{ label: t("vencord.search.disabled"), value: SearchStatus.DISABLED },
{ label: t("vencord.search.new"), value: SearchStatus.NEW }
{ label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Enabled", value: SearchStatus.ENABLED },
{ label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show New", value: SearchStatus.NEW }
]}
serialize={String}
select={onStatusChange}
@ -354,7 +353,7 @@ export default function PluginSettings() {
</div>
</div>
<Forms.FormTitle className={Margins.top20}>{t("vencord.plugins")}</Forms.FormTitle>
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
{plugins.length || requiredPlugins.length
? (
@ -372,7 +371,7 @@ export default function PluginSettings() {
<Forms.FormDivider className={Margins.top20} />
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
{t("vencord.requiredPlugins")}
Required Plugins
</Forms.FormTitle>
<div className={cl("grid")}>
{requiredPlugins.length
@ -387,7 +386,7 @@ export default function PluginSettings() {
function makeDependencyList(deps: string[]) {
return (
<React.Fragment>
<Forms.FormText>{t("vencord.pluginRequiredBy")}</Forms.FormText>
<Forms.FormText>This plugin is required by:</Forms.FormText>
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
</React.Fragment>
);

View file

@ -21,7 +21,6 @@ import "./addonCard.css";
import { classNameFactory } from "@api/Styles";
import { Badge } from "@components/Badge";
import { Switch } from "@components/Switch";
import { t } from "@utils/translation";
import { Text, useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";
@ -68,7 +67,7 @@ export function AddonCard({ disabled, isNew, name, infoButton, footer, author, e
>
{name}
</div>
</div>{isNew && <Badge text={t("vencord.new")} color="#ED4245" />}
</div>{isNew && <Badge text="NEW" color="#ED4245" />}
</Text>
{!!author && (
<Text variant="text-md/normal" className={cl("author")}>

View file

@ -20,7 +20,6 @@ import { Flex } from "@components/Flex";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
import { t } from "@utils/translation";
import { Button, Card, Text } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared";
@ -30,19 +29,21 @@ function BackupRestoreTab() {
<SettingsTab title="Backup & Restore">
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
<Flex flexDirection="column">
<strong>{t("vencord.warning")}</strong>
<span>{t("vencord.backupAndRestore.importWarning")}</span>
<strong>Warning</strong>
<span>Importing a settings file will overwrite your current settings.</span>
</Flex>
</Card>
<Text variant="text-md/normal" className={Margins.bottom8}>
{t("vencord.backupAndRestore.description")}
You can import and export your Vencord settings as a JSON file.
This allows you to easily transfer your settings to another device,
or recover your settings after reinstalling Vencord or Discord.
</Text>
<Text variant="text-md/normal" className={Margins.bottom8}>
{t("vencord.backupAndRestore.exportContains")}
Settings Export contains:
<ul>
<li>&mdash; {t("vencord.backupAndRestore.customQuickcss")}</li>
<li>&mdash; {t("vencord.backupAndRestore.themeLinks")}</li>
<li>&mdash; {t("vencord.backupAndRestore.pluginSettings")}</li>
<li>&mdash; Custom QuickCSS</li>
<li>&mdash; Theme Links</li>
<li>&mdash; Plugin Settings</li>
</ul>
</Text>
<Flex>
@ -50,13 +51,13 @@ function BackupRestoreTab() {
onClick={() => uploadSettingsBackup()}
size={Button.Sizes.SMALL}
>
{t("vencord.backupAndRestore.importSettings")}
Import Settings
</Button>
<Button
onClick={downloadSettingsBackup}
size={Button.Sizes.SMALL}
>
{t("vencord.backupAndRestore.exportSettings")}
Export Settings
</Button>
</Flex>
</SettingsTab>

View file

@ -24,7 +24,6 @@ import { Link } from "@components/Link";
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
import { Margins } from "@utils/margins";
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
import { t, Translate } from "@utils/translation";
import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common";
import { SettingsTab, wrapTab } from "./shared";
@ -47,8 +46,8 @@ async function eraseAllData() {
if (!res.ok) {
cloudLogger.error(`Failed to erase data, API returned ${res.status}`);
showNotification({
title: t("vencord.cloudIntegrations"),
body: t("vencord.cloud.integrations.eraseError", { status: res.status }),
title: "Cloud Integrations",
body: `Could not erase all data (API returned ${res.status}), please contact support.`,
color: "var(--red-360)"
});
return;
@ -58,8 +57,8 @@ async function eraseAllData() {
await deauthorizeCloud();
showNotification({
title: t("vencord.cloudIntegrations"),
body: t("vencord.cloud.integrations.eraseSuccess"),
title: "Cloud Integrations",
body: "Successfully erased all data.",
color: "var(--green-360)"
});
}
@ -71,7 +70,8 @@ function SettingsSyncSection() {
return (
<Forms.FormSection title="Settings Sync" className={Margins.top16}>
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
{t("vencord.cloud.settings.description")}
Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with
minimal effort.
</Forms.FormText>
<Switch
key="cloud-sync"
@ -79,7 +79,7 @@ function SettingsSyncSection() {
value={cloud.settingsSync}
onChange={v => { cloud.settingsSync = v; }}
>
{t("vencord.settingsSync")}
Settings Sync
</Switch>
<div className="vc-cloud-settings-sync-grid">
<Button
@ -87,9 +87,9 @@ function SettingsSyncSection() {
disabled={!sectionEnabled}
onClick={() => putCloudSettings(true)}
>
{t("vencord.cloud.settings.syncToCloud")}
Sync to Cloud
</Button>
<Tooltip text={t("vencord.cloud.settings.overwriteWarning")}>
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
{({ onMouseLeave, onMouseEnter }) => (
<Button
onMouseLeave={onMouseLeave}
@ -99,7 +99,7 @@ function SettingsSyncSection() {
disabled={!sectionEnabled}
onClick={() => getCloudSettings(true, true)}
>
{t("vencord.cloud.settings.syncFromCloud")}
Sync from Cloud
</Button>
)}
</Tooltip>
@ -109,7 +109,7 @@ function SettingsSyncSection() {
disabled={!sectionEnabled}
onClick={() => deleteCloudSettings()}
>
{t("vencord.cloud.settings.deleteCloudSettings")}
Delete Cloud Settings
</Button>
</div>
</Forms.FormSection>
@ -121,12 +121,12 @@ function CloudTab() {
return (
<SettingsTab title="Vencord Cloud">
<Forms.FormSection title={t("vencord.cloudSettings")} className={Margins.top16}>
<Forms.FormSection title="Cloud Settings" className={Margins.top16}>
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
<Translate i18nKey="vencord.cloud.integrations.description">
<Link href="https://vencord.dev/cloud/privacy" />
<Link href="https://github.com/Vencord/Backend" />
</Translate>
Vencord comes with a cloud integration that adds goodies like settings sync across devices.
It <Link href="https://vencord.dev/cloud/privacy">respects your privacy</Link>, and
the <Link href="https://github.com/Vencord/Backend">source code</Link> is AGPL 3.0 licensed so you
can host it yourself.
</Forms.FormText>
<Switch
key="backend"
@ -137,13 +137,13 @@ function CloudTab() {
else
settings.cloud.authenticated = v;
}}
note={t("vencord.cloud.integrations.authorizationNote")}
note="This will request authorization if you have not yet set up cloud integrations."
>
{t("vencord.cloud.integrations.enable")}
Enable Cloud Integrations
</Switch>
<Forms.FormTitle tag="h5">{t("vencord.cloud.integrations.backendUrl")}</Forms.FormTitle>
<Forms.FormTitle tag="h5">Backend URL</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>
{t("vencord.cloud.integrations.backendNote")}
Which backend to use when using cloud integrations.
</Forms.FormText>
<CheckedTextInput
key="backendUrl"
@ -166,24 +166,25 @@ function CloudTab() {
await authorizeCloud();
}}
>
{t("vencord.reauthorise")}
Reauthorise
</Button>
<Button
size={Button.Sizes.MEDIUM}
color={Button.Colors.RED}
disabled={!settings.cloud.authenticated}
onClick={() => Alerts.show({
title: t("vencord.areYouSure"),
body: t("vencord.cloud.integrations.eraseWarning"),
title: "Are you sure?",
body: "Once your data is erased, we cannot recover it. There's no going back!",
onConfirm: eraseAllData,
confirmText: t("vencord.cloud.integrations.eraseIt"),
confirmText: "Erase it!",
confirmColor: "vc-cloud-erase-data-danger-btn",
cancelText: t("vencord.nevermind")
cancelText: "Nevermind"
})}
>
Erase All Data
</Button>
</Grid>
<Forms.FormDivider className={Margins.top16} />
</Forms.FormSection >
<SettingsSyncSection />

View file

@ -382,6 +382,7 @@ function PatchHelper() {
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
<CodeBlock lang="js" content={code} />
<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>

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { useSettings } from "@api/Settings";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
@ -25,14 +25,14 @@ import { openPluginModal } from "@components/PluginSettings/PluginModal";
import type { UserThemeHeader } from "@main/themes";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { t } from "@utils/translation";
import { findByPropsLazy, findLazy } from "@webpack";
import { findLazy } from "@webpack";
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
import type { ComponentType, Ref, SyntheticEvent } from "react";
import Plugins from "~plugins";
import { AddonCard } from "./AddonCard";
import { QuickAction, QuickActionCard } from "./quickActions";
import { SettingsTab, wrapTab } from "./shared";
@ -44,9 +44,7 @@ type FileInput = ComponentType<{
filters?: { name?: string; extensions: string[]; }[];
}>;
const InviteActions = findByPropsLazy("resolveInvite");
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-");
@ -79,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
<div>
{themeLinks.map(link => (
<Card style={{
{themeLinks.map(rawLink => {
const { label, link } = (() => {
const match = /^@(light|dark) (.*)/.exec(rawLink);
if (!match) return { label: rawLink, link: rawLink };
const [, mode, link] = match;
return { label: `[${mode} mode only] ${link}`, link };
})();
return <Card style={{
padding: ".5em",
marginBottom: ".5em",
marginTop: ".5em"
@ -88,11 +94,11 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
<Forms.FormTitle tag="h5" style={{
overflowWrap: "break-word"
}}>
{link}
{label}
</Forms.FormTitle>
<Validator link={link} />
</Card>
))}
</Card>;
})}
</div>
</>
);
@ -203,68 +209,60 @@ function ThemesTab() {
return (
<>
<Card className="vc-settings-card">
<Forms.FormTitle tag="h5">{t("vencord.themes.findThemes")}</Forms.FormTitle>
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
<div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}>
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
{t("vencord.themes.betterDiscord")}
BetterDiscord Themes
</Link>
<Link href="https://github.com/search?q=discord+theme">GitHub</Link>
</div>
<Forms.FormText>{t("vencord.themes.betterDiscordNote")}</Forms.FormText>
<Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText>
</Card>
<Forms.FormSection title={t("vencord.themes.local")}>
<Card className="vc-settings-quick-actions-card">
<Forms.FormSection title="Local Themes">
<QuickActionCard>
<>
{IS_WEB ?
(
<Button
size={Button.Sizes.SMALL}
disabled={themeDirPending}
>
{t("vencord.themes.upload")}
<FileInput
ref={fileInputRef}
onChange={onFileUpload}
multiple={true}
filters={[{ extensions: ["css"] }]}
/>
</Button>
<QuickAction
text={
<span style={{ position: "relative" }}>
Upload Theme
<FileInput
ref={fileInputRef}
onChange={onFileUpload}
multiple={true}
filters={[{ extensions: ["css"] }]}
/>
</span>
}
Icon={PlusIcon}
/>
) : (
<QuickAction
text={t("vencord.themes.openFolder")}
text="Open Themes Folder"
action={() => showItemInFolder(themeDir!)}
disabled={themeDirPending}
>
{t("vencord.themes.openFolder")}
</Button>
Icon={FolderIcon}
/>
)}
<Button
onClick={refreshLocalThemes}
size={Button.Sizes.SMALL}
>
{t("vencord.themes.loadMissing")}
</Button>
<Button
onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL}
>
{t("vencord.themes.editQuickCss")}
</Button>
<QuickAction
text="Load missing Themes"
action={refreshLocalThemes}
Icon={RestartIcon}
/>
<QuickAction
text="Edit QuickCSS"
action={() => VencordNative.quickCss.openEditor()}
Icon={PaintbrushIcon}
/>
{Vencord.Settings.plugins.ClientTheme.enabled && (
<Button
onClick={() => openModal(modalProps => (
<PluginModal
{...modalProps}
plugin={Vencord.Plugins.plugins.ClientTheme}
onRestartNeeded={() => { }}
/>
))}
size={Button.Sizes.SMALL}
>
{t("clientTheme.edit")}
</Button>
{Settings.plugins.ClientTheme.enabled && (
<QuickAction
text="Edit ClientTheme"
action={() => openPluginModal(Plugins.ClientTheme)}
Icon={PencilIcon}
/>
)}
</>
</QuickActionCard>
@ -304,16 +302,17 @@ function ThemesTab() {
return (
<>
<Card className="vc-settings-card vc-text-selectable">
<Forms.FormTitle tag="h5">{t("vencord.themes.pasteLinks")}</Forms.FormTitle>
<Forms.FormText>{t("vencord.themes.oneLinkPerLine")}</Forms.FormText>
<Forms.FormText>{t("vencord.themes.useDirect")}</Forms.FormText>
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
<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>
</Card>
<Forms.FormSection title={t("vencord.themes.online")} tag="h5">
<Forms.FormSection title="Online Themes" tag="h5">
<TextArea
value={themeText}
onChange={setThemeText}
className={classes(TextAreaProps.textarea, "vc-settings-theme-links")}
className={"vc-settings-theme-links"}
placeholder="Theme Links"
spellCheck={false}
onBlur={onBlur}
@ -338,13 +337,13 @@ function ThemesTab() {
className="vc-settings-tab-bar-item"
id={ThemeTab.LOCAL}
>
{t("vencord.themes.local")}
Local Themes
</TabBar.Item>
<TabBar.Item
className="vc-settings-tab-bar-item"
id={ThemeTab.ONLINE}
>
{t("vencord.themes.online")}
Online Themes
</TabBar.Item>
</TabBar>

View file

@ -33,6 +33,20 @@
padding: 0.5em;
border: 1px solid var(--background-modifier-accent);
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 {

View file

@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { t } from "@utils/translation";
import { maybePromptToUpdate } from "@utils/updater";
export function handleComponentFailed() {
maybePromptToUpdate(
t("vencord.failureUpdate")
"Uh Oh! Failed to render this Page." +
" However, there is an update available that might fix it." +
" Would you like to update and restart now?"
);
}

View file

@ -15,9 +15,9 @@ export async function loadLazyChunks() {
try {
LazyChunkLoaderLogger.log("Loading all chunks...");
const validChunks = new Set<string>();
const invalidChunks = new Set<string>();
const deferredRequires = new Set<string>();
const validChunks = new Set<number>();
const invalidChunks = new Set<number>();
const deferredRequires = new Set<number>();
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
@ -29,14 +29,12 @@ export async function loadLazyChunks() {
async function searchAndLoadLazyChunks(factoryCode: string) {
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
// the chunk containing the component
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
const shouldForceDefer = false;
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
if (chunkIds.length === 0) {
return;
@ -61,7 +59,7 @@ export async function loadLazyChunks() {
}
if (!invalidChunkGroup) {
validChunkGroups.add([chunkIds, entryPoint]);
validChunkGroups.add([chunkIds, Number(entryPoint)]);
}
}));
@ -131,14 +129,14 @@ export async function loadLazyChunks() {
}
// All chunks Discord has mapped to asset files, even if they are not used anymore
const allChunks = [] as string[];
const allChunks = [] as number[];
// Matches "id" or id:
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue;
allChunks.push(id);
allChunks.push(Number(id));
}
if (allChunks.length === 0) throw new Error("Failed to get all chunks");

View file

@ -17,7 +17,7 @@
*/
import { onceDefined } from "@shared/onceDefined";
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
import electron, { app, BrowserWindowConstructorOptions, Menu, nativeTheme } from "electron";
import { dirname, join } from "path";
import { initIpc } from "./ipcMain";
@ -100,6 +100,19 @@ if (!IS_VANILLA) {
super(options);
initIpc(this);
// Workaround for https://github.com/electron/electron/issues/43367. Vesktop also has its own workaround
// @TODO: Remove this when the issue is fixed
if (IS_DISCORD_DESKTOP) {
this.webContents.on("devtools-opened", () => {
if (!nativeTheme.shouldUseDarkColors) return;
nativeTheme.themeSource = "light";
setTimeout(() => {
nativeTheme.themeSource = "dark";
}, 100);
});
}
} else super(options);
}
}

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { get } from "@main/utils/simpleGet";
import { IpcEvents } from "@shared/IpcEvents";
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { ipcMain } from "electron";
@ -25,7 +26,6 @@ import { join } from "path";
import gitHash from "~git-hash";
import gitRemote from "~git-remote";
import { get } from "../utils/simpleGet";
import { serializeErrors, VENCORD_FILES } from "./common";
const API_BASE = `https://api.github.com/repos/${gitRemote}`;

View file

@ -35,7 +35,8 @@ export const ALLOWED_PROTOCOLS = [
"steam:",
"spotify:",
"com.epicgames.launcher:",
"tidal:"
"tidal:",
"itunes:",
];
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");

6
src/modules.d.ts vendored
View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// eslint-disable-next-line spaced-comment
/// <reference types="standalone-electron-types"/>
declare module "~plugins" {
@ -43,11 +42,6 @@ declare module "~git-remote" {
export default remote;
}
declare module "~translations" {
const translations: Record<string, Record<string, any>>;
export default translations;
}
declare module "file://*" {
const content: string;
export default content;

View file

@ -1,3 +0,0 @@
[class*="profileBadges"] {
flex: none;
}

View file

@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./fixBadgeOverflow.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary";
@ -62,36 +60,8 @@ export default definePlugin({
authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
required: true,
patches: [
/* Patch the badge list component on user profiles */
{
find: 'id:"premium",',
replacement: [
{
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
replace: "$&$1.unshift(...$self.getBadges(arguments[0]));",
},
{
// alt: "", aria-hidden: false, src: originalSrc
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
// ...badge.props, ..., src: badge.image ?? ...
replace: "...$1.props,$& $1.image??"
},
// replace their component with ours if applicable
{
match: /(?<=text:(\i)\.description,spacing:12,.{0,50})children:/,
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
},
// conditionally override their onClick with badge.onClick if it exists
{
match: /href:(\i)\.link/,
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
}
]
},
/* new profiles */
{
find: ".PANEL]:14",
find: ".FULL_SIZE]:26",
replacement: {
match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/,
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&"
@ -107,7 +77,7 @@ export default definePlugin({
replace: "...$1.props,$& $1.image??"
},
{
match: /(?<=text:(\i)\.description,.{0,50})children:/,
match: /(?<=text:(\i)\.description,.{0,200})children:/,
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
},
// conditionally override their onClick with badge.onClick if it exists

View file

@ -0,0 +1,24 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "DynamicImageModalAPI",
authors: [Devs.sadan, Devs.Nuckyz],
description: "Allows you to omit either width or height when opening an image modal",
patches: [
{
find: "SCALE_DOWN:",
replacement: {
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
}
}
]
});

View file

@ -31,7 +31,7 @@ export default definePlugin({
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
replace: "$&vencordProps=$1,"
}, {
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
}
]

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Cyn],
patches: [
{
find: ".Messages.REMOVE_ATTACHMENT_BODY",
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
replacement: {
match: /(?<=.container\)?,children:)(\[.+?\])/,
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",

View file

@ -27,7 +27,7 @@ export default definePlugin({
{
find: '"Message Username"',
replacement: {
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
}
}

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [
{
find: ".Messages.EDIT_TEXTAREA_HELP",
find: "#{intl::EDIT_TEXTAREA_HELP}",
replacement: {
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
replace: (match, args) => "" +

View file

@ -24,15 +24,10 @@ export default definePlugin({
description: "API to add buttons to message popovers.",
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
patches: [{
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
// foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other))
match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"/,
replace: (m, makeElement) => {
const msg = m.match(/message:(.{1,3}),/)?.[1];
if (!msg) throw new Error("Could not find message variable");
return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`;
}
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.string\(\i\.\i#{intl::MESSAGE_ACTION_REPLY}.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
}
}],
});

View file

@ -25,16 +25,16 @@ export default definePlugin({
description: "Api required for plugins that modify the server list",
patches: [
{
find: "Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
replacement: {
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
}
},
{
find: "Messages.SERVERS,children",
find: "#{intl::SERVERS}),children",
replacement: {
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
}
}

View file

@ -48,7 +48,7 @@ export default definePlugin({
},
},
{
find: ".METRICS,",
find: ".METRICS",
replacement: [
{
match: /this\._intervalId=/,

View file

@ -25,8 +25,9 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
import VencordTab from "@components/VencordSettings/VencordTab";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { i18n, React } from "@webpack/common";
import { React } from "@webpack/common";
import gitHash from "~git-hash";
@ -57,20 +58,20 @@ export default definePlugin({
]
},
{
find: "Messages.ACTIVITY_SETTINGS",
find: ".SEARCH_NO_RESULTS&&0===",
replacement: [
{
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
},
{
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
}
]
},
{
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.open($1);return;"
@ -148,13 +149,18 @@ export default definePlugin({
if (!header) return;
const names = {
top: i18n.Messages.USER_SETTINGS,
aboveNitro: i18n.Messages.BILLING_SETTINGS,
belowNitro: i18n.Messages.APP_SETTINGS,
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
};
return header === names[settingsLocation];
try {
const names = {
top: getIntlMessage("USER_SETTINGS"),
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
belowNitro: getIntlMessage("APP_SETTINGS"),
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
};
return header === names[settingsLocation];
} catch {
return firstChild === "PREMIUM";
}
},
patchedSettings: new WeakSet(),
@ -197,7 +203,7 @@ export default definePlugin({
},
get electronVersion() {
return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
},
get chromiumVersion() {

View file

@ -77,7 +77,7 @@ async function generateDebugInfoMessage() {
const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
// @ts-expect-error
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
@ -142,15 +142,15 @@ export default definePlugin({
required: true,
description: "Helps us provide support to you",
authors: [Devs.Ven],
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
settings,
patches: [{
find: ".BEGINNING_DM.format",
find: "#{intl::BEGINNING_DM}",
replacement: {
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
}
}],
@ -235,7 +235,8 @@ export default definePlugin({
}
},
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
const userId = channel.getRecipientId();
if (!isPluginDev(userId)) return null;
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;

View file

@ -1,27 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import { setLocale } from "@utils/translation";
import definePlugin from "@utils/types";
import { i18n } from "@webpack/common";
export default definePlugin({
name: "Translation",
required: true,
description: "Assists with translating Vencord",
authors: [Devs.lewisakura],
flux: {
USER_SETTINGS_PROTO_UPDATE({ settings }) {
setLocale(settings.proto.localization.locale.value);
}
},
start() {
setLocale(i18n.getLocale());
}
});

View file

@ -0,0 +1,7 @@
# AccountPanelServerProfile
Right click your account panel in the bottom left to view your profile in the current server
![](https://github.com/user-attachments/assets/3228497d-488f-479c-93d2-a32ccdb08f0f)
![](https://github.com/user-attachments/assets/6fc45363-d95f-4810-812f-2f9fb28b41b5)

View file

@ -0,0 +1,134 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
import { User } from "discord-types/general";
interface UserProfileProps {
popoutProps: Record<string, any>;
currentUser: User;
originalPopout: () => React.ReactNode;
}
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
const styles = findByPropsLazy("accountProfilePopoutWrapper");
let openAlternatePopout = false;
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
return (
<Menu.Menu
navId="vc-ap-server-profile"
onClose={ContextMenuApi.closeContextMenu}
>
<Menu.MenuItem
id="vc-ap-view-alternate-popout"
label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"}
disabled={getCurrentChannel()?.getGuildId() == null}
action={e => {
openAlternatePopout = true;
accountPanelRef.current?.props.onMouseDown();
accountPanelRef.current?.props.onClick(e);
}}
/>
<Menu.MenuCheckboxItem
id="vc-ap-prioritize-server-profile"
label="Prioritize Server Profile"
checked={prioritizeServerProfile}
action={() => settings.store.prioritizeServerProfile = !prioritizeServerProfile}
/>
</Menu.Menu>
);
}, { noop: true });
const settings = definePluginSettings({
prioritizeServerProfile: {
type: OptionType.BOOLEAN,
description: "Prioritize Server Profile when left clicking your account panel",
default: false
}
});
export default definePlugin({
name: "AccountPanelServerProfile",
description: "Right click your account panel in the bottom left to view your profile in the current server",
authors: [Devs.Nuckyz, Devs.relitrix],
settings,
patches: [
{
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
group: true,
replacement: [
{
match: /(?<=\.SIZE_32\)}\);)/,
replace: "$self.useAccountPanelRef();"
},
{
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
},
{
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
replace: "$&onRequestClose:$self.onPopoutClose,"
},
{
match: /(?<=.avatarWrapper,)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
}
]
}
],
get accountPanelRef() {
return accountPanelRef;
},
useAccountPanelRef() {
useEffect(() => () => {
accountPanelRef.current = null;
}, []);
return (accountPanelRef = useRef(null));
},
openAccountPanelContextMenu(event: React.UIEvent) {
ContextMenuApi.openContextMenu(event, AccountPanelContextMenu);
},
onPopoutClose() {
openAlternatePopout = false;
},
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
if (
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
) {
return originalPopout();
}
const currentChannel = getCurrentChannel();
if (currentChannel?.getGuildId() == null) {
return originalPopout();
}
return (
<div className={styles.accountProfilePopoutWrapper}>
<UserProfile {...popoutProps} userId={currentUser.id} guildId={currentChannel.getGuildId()} channelId={currentChannel.id} />
</div>
);
}, { noop: true })
});

View file

@ -41,7 +41,7 @@ export default definePlugin({
},
{
// Status emojis
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/,
replace: "!0"

View file

@ -0,0 +1,3 @@
# Always Expand Roles
Always expands the role list in profile popouts

View file

@ -1,6 +1,6 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,20 +16,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("AlwaysExpandRoles", "ShowAllRoles");
export default definePlugin({
name: "TimeBarAllActivities",
description: "Adds the Spotify time bar to all activities if they have start and end timestamps",
authors: [Devs.fawn],
name: "AlwaysExpandRoles",
description: "Always expands the role list in profile popouts",
authors: [Devs.surgedevs],
patches: [
{
find: "}renderTimeBar(",
find: 'action:"EXPAND_ROLES"',
replacement: {
match: /renderTimeBar\((.{1,3})\){.{0,50}?let/,
replace: "renderTimeBar($1){let"
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
}
}
],
]
});

View file

@ -71,7 +71,7 @@ export default definePlugin({
description: "Anonymise uploaded file names",
patches: [
{
find: "instantBatchUpload:function",
find: "instantBatchUpload:",
replacement: {
match: /uploadFiles:(\i),/,
replace:
@ -86,9 +86,9 @@ export default definePlugin({
}
},
{
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
replacement: {
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
},
},

View file

@ -24,7 +24,7 @@ interface ActivityButton {
}
interface Activity {
state: string;
state?: string;
details?: string;
timestamps?: {
start?: number;
@ -52,8 +52,8 @@ const enum ActivityFlag {
export interface TrackData {
name: string;
album: string;
artist: string;
album?: string;
artist?: string;
appleMusicLink?: string;
songLink?: string;
@ -61,8 +61,8 @@ export interface TrackData {
albumArtwork?: string;
artistArtwork?: string;
playerPosition: number;
duration: number;
playerPosition?: number;
duration?: number;
}
const enum AssetImageType {
@ -120,7 +120,7 @@ const settings = definePluginSettings({
stateString: {
type: OptionType.STRING,
description: "Activity state format string",
default: "{artist}"
default: "{artist} · {album}"
},
largeImageType: {
type: OptionType.SELECT,
@ -155,8 +155,8 @@ const settings = definePluginSettings({
function customFormat(formatStr: string, data: TrackData) {
return formatStr
.replaceAll("{name}", data.name)
.replaceAll("{album}", data.album)
.replaceAll("{artist}", data.artist);
.replaceAll("{album}", data.album ?? "")
.replaceAll("{artist}", data.artist ?? "");
}
function getImageAsset(type: AssetImageType, data: TrackData) {
@ -212,14 +212,16 @@ export default definePlugin({
const assets: ActivityAssets = {};
const isRadio = Number.isNaN(trackData.duration) && (trackData.playerPosition === 0);
if (settings.store.largeImageType !== AssetImageType.Disabled) {
assets.large_image = largeImageAsset;
assets.large_text = customFormat(settings.store.largeTextString, trackData);
if (!isRadio) assets.large_text = customFormat(settings.store.largeTextString, trackData);
}
if (settings.store.smallImageType !== AssetImageType.Disabled) {
assets.small_image = smallImageAsset;
assets.small_text = customFormat(settings.store.smallTextString, trackData);
if (!isRadio) assets.small_text = customFormat(settings.store.smallTextString, trackData);
}
const buttons: ActivityButton[] = [];
@ -243,17 +245,17 @@ export default definePlugin({
name: customFormat(settings.store.nameString, trackData),
details: customFormat(settings.store.detailsString, trackData),
state: customFormat(settings.store.stateString, trackData),
state: isRadio ? undefined : customFormat(settings.store.stateString, trackData),
timestamps: (settings.store.enableTimestamps ? {
timestamps: (trackData.playerPosition && trackData.duration && settings.store.enableTimestamps) ? {
start: Date.now() - (trackData.playerPosition * 1000),
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
} : undefined),
} : undefined,
assets,
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
metadata: { button_urls: buttons.map(v => v.url) || undefined, },
buttons: !isRadio && buttons.length ? buttons.map(v => v.label) : undefined,
metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,
type: settings.store.activityType,
flags: ActivityFlag.INSTANCE,

View file

@ -11,37 +11,11 @@ import type { TrackData } from ".";
const exec = promisify(execFile);
// function exec(file: string, args: string[] = []) {
// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => {
// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] });
// let stdout: string | null = null;
// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; });
// let stderr: string | null = null;
// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; });
// process.on("exit", code => { resolve({ code, stdout, stderr }); });
// process.on("error", err => reject(err));
// });
// }
async function applescript(cmds: string[]) {
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
return stdout;
}
function makeSearchUrl(type: string, query: string) {
const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json");
url.searchParams.set("types", type);
url.searchParams.set("limit", "1");
url.searchParams.set("term", query);
return url;
}
const requestOptions: RequestInit = {
headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" },
};
interface RemoteData {
appleMusicLink?: string,
songLink?: string,
@ -51,6 +25,24 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
let cachedToken: string | undefined = undefined;
const getToken = async () => {
if (cachedToken) return cachedToken;
const html = await fetch("https://music.apple.com/").then(r => r.text());
const bundleUrl = new URL(html.match(APPLE_MUSIC_BUNDLE_REGEX)![1], "https://music.apple.com/");
const bundle = await fetch(bundleUrl).then(r => r.text());
const token = bundle.match(APPLE_MUSIC_TOKEN_REGEX)![1];
cachedToken = token;
return token;
};
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data;
@ -58,21 +50,39 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
}
try {
const [songData, artistData] = await Promise.all([
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()),
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json())
]);
const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search");
dataUrl.searchParams.set("platform", "web");
dataUrl.searchParams.set("l", "en-US");
dataUrl.searchParams.set("limit", "1");
dataUrl.searchParams.set("with", "serverBubbles");
dataUrl.searchParams.set("types", "songs");
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
dataUrl.searchParams.set("include[songs]", "artists");
const appleMusicLink = songData?.songs?.data[0]?.attributes.url;
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;
const token = await getToken();
const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
const songData = await fetch(dataUrl, {
headers: {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"authorization": `Bearer ${token}`,
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"origin": "https://music.apple.com",
},
})
.then(r => r.json())
.then(data => data.results.song.data[0]);
cachedRemoteData = {
id,
data: { appleMusicLink, songLink, albumArtwork, artistArtwork }
data: {
appleMusicLink: songData.attributes.url,
songLink: `https://song.link/i/${songData.id}`,
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"),
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"),
}
};
return cachedRemoteData.data;
} catch (e) {
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);

View file

@ -73,8 +73,8 @@ export default definePlugin({
},
async start() {
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
if ("armcord" in window) return;
// Legcord comes with its own arRPC implementation, so this plugin just confuses users
if ("legcord" in window) return;
if (ws) ws.close();
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket

View file

@ -1,5 +0,0 @@
# AutomodContext
Allows you to jump to the messages surrounding an automod hit
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/d13740c8-2062-4553-b975-82fd3d6cc08b)

View file

@ -1,73 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Text } from "@webpack/common";
const { selectChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel");
function jumpToMessage(channelId: string, messageId: string) {
const guildId = ChannelStore.getChannel(channelId)?.guild_id;
selectChannel({
guildId,
channelId,
messageId,
jumpType: "INSTANT"
});
}
function findChannelId(message: any): string | null {
const { embeds: [embed] } = message;
const channelField = embed.fields.find(({ rawName }) => rawName === "channel_id");
if (!channelField) {
return null;
}
return channelField.rawValue;
}
export default definePlugin({
name: "AutomodContext",
description: "Allows you to jump to the messages surrounding an automod hit.",
authors: [Devs.JohnyTheCarrot],
patches: [
{
find: ".Messages.GUILD_AUTOMOD_REPORT_ISSUES",
replacement: {
match: /\.Messages\.ACTIONS.+?}\)(?=,(\(0.{0,40}\.dot.*?}\)),)/,
replace: (m, dot) => `${m},${dot},$self.renderJumpButton({message:arguments[0].message})`
}
}
],
renderJumpButton: ErrorBoundary.wrap(({ message }: { message: any; }) => {
const channelId = findChannelId(message);
if (!channelId) {
return null;
}
return (
<Button
style={{ padding: "2px 8px" }}
look={Button.Looks.LINK}
size={Button.Sizes.SMALL}
color={Button.Colors.LINK}
onClick={() => jumpToMessage(channelId, message.id)}
>
<Text color="text-link" variant="text-xs/normal">
Jump to Surrounding
</Text>
</Button>
);
}, { noop: true })
});

View file

@ -16,28 +16,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({
source: {
description: "Source to replace ban GIF with (Video or Gif)",
type: OptionType.STRING,
default: "https://i.imgur.com/wp5q52C.mp4",
restartNeeded: true,
}
});
export default definePlugin({
name: "BANger",
description: "Replaces the GIF in the ban dialogue with a custom one.",
authors: [Devs.Xinto, Devs.Glitch],
settings,
patches: [
{
find: "BAN_CONFIRM_TITLE.",
find: "#{intl::BAN_CONFIRM_TITLE}",
replacement: {
match: /src:\i\("?\d+"?\)/g,
replace: "src: Vencord.Settings.plugins.BANger.source"
replace: "src:$self.source"
}
}
],
options: {
source: {
description: "Source to replace ban GIF with (Video or Gif)",
type: OptionType.STRING,
default: "https://i.imgur.com/wp5q52C.mp4",
restartNeeded: true,
}
get source() {
return settings.store.source;
}
});

View file

@ -0,0 +1,11 @@
# Better Folders
Better Folders offers a variety of options to improve your folder experience
Always show the folder icon, regardless of if the folder is open or not
Only have one folder open at a time
Open folders in a sidebar:
![A folder open in a separate sidebar](https://github.com/user-attachments/assets/432d3146-8091-4bae-9c1e-c19046c72947)

View file

@ -18,9 +18,10 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
import { FluxDispatcher, useMemo } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
@ -30,9 +31,9 @@ enum FolderIconDisplay {
MoreThanOneFolderExpanded
}
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const SortedGuildStore = findStoreLazy("SortedGuildStore");
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null;
@ -118,22 +119,22 @@ export default definePlugin({
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{
match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0]?.isBetterFolders,betterFoldersOriginalTree,arguments[0]?.betterFoldersExpandedIds)`
},
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
{
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
},
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
{
match: /unreadMentionsIndicatorBottom,.+?}\)\]/,
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0]?.isBetterFolders))"
},
// Export the isBetterFolders variable to the folders component
{
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/,
replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
}
]
},
@ -167,31 +168,31 @@ export default definePlugin({
{
predicate: () => settings.store.keepIcons,
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0]?.isBetterFolders&&${isExpanded};`
},
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{
predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
replace: "!!arguments[0].isBetterFolders&&"
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
replace: "$self.shouldShowTransition(arguments[0])&&"
},
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{
predicate: () => !settings.store.keepIcons,
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
replace: (m, isExpanded) => `${m}$self.shouldRenderContents(arguments[0],${isExpanded})?null:`
},
{
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
},
{
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:"
}
]
},
@ -200,12 +201,12 @@ export default definePlugin({
predicate: () => settings.store.sidebar,
replacement: {
// Render the Better Folders sidebar
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
replace: ",$self.FolderSideBar($1)"
match: /(container.{0,50}({className:\i\.guilds,themeOverride:\i})\))/,
replace: "$1,$self.FolderSideBar({...$2})"
}
},
{
find: ".Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
predicate: () => settings.store.closeAllHomeButton,
replacement: {
// Close all folders when clicking the home button
@ -249,6 +250,10 @@ export default definePlugin({
dispatchingFoldersClose = false;
});
}
},
LOGOUT() {
closeFolders();
}
},
@ -270,12 +275,16 @@ export default definePlugin({
},
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
}
try {
return child => {
if (isBetterFolders) {
return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
}
return true;
};
} catch {
return true;
};
}
},
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
@ -302,7 +311,20 @@ export default definePlugin({
}
},
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
shouldShowTransition(props: any) {
// Pending guilds
if (props?.folderNode?.id === 1) return true;
closeFolders
return !!props?.isBetterFolders;
},
shouldRenderContents(props: any, isExpanded: boolean) {
// Pending guilds
if (props?.folderNode?.id === 1) return false;
return !props?.isBetterFolders && isExpanded;
},
FolderSideBar,
closeFolders,
});

View file

@ -34,9 +34,9 @@ export default definePlugin({
},
},
{
find: ".Messages.GIF,",
find: "#{intl::GIF}",
replacement: {
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
match: /alt:(\i)=(\i\.\i\.string\(\i\.\i#{intl::GIF}\))(?=,[^}]*\}=(\i))/,
replace:
// rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2",

View file

@ -16,19 +16,31 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
const settings = definePluginSettings({
hide: {
type: OptionType.BOOLEAN,
description: "Hide notes",
default: false,
restartNeeded: true
},
noSpellCheck: {
type: OptionType.BOOLEAN,
description: "Disable spellcheck in notes",
disabled: () => Settings.plugins.BetterNotesBox.hide,
default: false
}
});
export default definePlugin({
name: "BetterNotesBox",
description: "Hide notes or disable spellcheck (Configure in settings!!)",
authors: [Devs.Ven],
settings,
patches: [
{
@ -36,7 +48,7 @@ export default definePlugin({
all: true,
// Some modules match the find but the replacement is returned untouched
noWarn: true,
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
predicate: () => settings.store.hide,
replacement: {
match: /hideNote:.+?(?=([,}].*?\)))/g,
replace: (m, rest) => {
@ -51,40 +63,15 @@ export default definePlugin({
}
},
{
find: "Messages.NOTE_PLACEHOLDER",
find: "#{intl::NOTE_PLACEHOLDER}",
replacement: {
match: /\.NOTE_PLACEHOLDER,/,
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
}
},
{
find: ".popularApplicationCommandIds,",
replacement: {
match: /lastSection:(!?\i)}\),/,
replace: "$&$self.patchPadding({lastSection:$1}),"
match: /#{intl::NOTE_PLACEHOLDER}\),/,
replace: "$&spellCheck:!$self.noSpellCheck,"
}
}
],
options: {
hide: {
type: OptionType.BOOLEAN,
description: "Hide notes",
default: false,
restartNeeded: true
},
noSpellCheck: {
type: OptionType.BOOLEAN,
description: "Disable spellcheck in notes",
disabled: () => Settings.plugins.BetterNotesBox.hide,
default: false
}
},
patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
if (!lastSection) return null;
return (
<div className={UserPopoutSectionCssClasses.lastSection} ></div>
);
})
get noSpellCheck() {
return settings.store.noSpellCheck;
}
});

View file

@ -99,7 +99,11 @@ export default definePlugin({
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
openImageModal({
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
height: 128,
width: 128
});
}}
icon={ImageIcon}
/>

View file

@ -47,7 +47,7 @@ export default definePlugin({
},
{
find: ".ADD_ROLE_A11Y_LABEL",
find: "#{intl::ADD_ROLE_A11Y_LABEL}",
all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,

View file

@ -60,7 +60,7 @@ export default definePlugin({
patches: [
{
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT",
find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
replacement: [
// Replace children with a single label with state
{

View file

@ -0,0 +1,69 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { getIntlMessage } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc";
import { Alerts, Menu, useMemo, useState } from "@webpack/common";
import Plugins from "~plugins";
function onRestartNeeded() {
Alerts.show({
title: "Restart required",
body: <p>You have changed settings that require a restart.</p>,
confirmText: "Restart now",
cancelText: "Later!",
onConfirm: () => location.reload()
});
}
export default function PluginsSubmenu() {
const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []);
const [query, setQuery] = useState("");
const search = query.toLowerCase();
const include = (p: typeof Plugins[keyof typeof Plugins]) => (
Vencord.Plugins.isPluginEnabled(p.name)
&& p.options && !isObjectEmpty(p.options)
&& (
p.name.toLowerCase().includes(search)
|| p.description.toLowerCase().includes(search)
|| p.tags?.some(t => t.toLowerCase().includes(search))
)
);
const plugins = sortedPlugins.filter(include);
return (
<>
<Menu.MenuControlItem
id="vc-plugins-search"
control={(props, ref) => (
<Menu.MenuSearchControl
{...props}
query={query}
onChange={setQuery}
ref={ref}
placeholder={getIntlMessage("SEARCH")}
/>
)}
/>
{!!plugins.length && <Menu.MenuSeparator />}
{plugins.map(p => (
<Menu.MenuItem
key={p.name}
id={p.name}
label={p.name}
action={() => openPluginModal(p, onRestartNeeded)}
/>
))}
</>
);
}

View file

@ -7,12 +7,15 @@
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react";
import PluginsSubmenu from "./PluginsSubmenu";
type SettingsEntry = { section: string, label: string; };
const cl = classNameFactory("");
@ -109,7 +112,7 @@ export default definePlugin({
predicate: () => settings.store.disableFade
},
{ // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)(),"
@ -117,14 +120,22 @@ export default definePlugin({
predicate: () => settings.store.eagerLoad
},
{ // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: {
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
replace: "$1$self.wrapMenu($2)"
}
}
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: [
{
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
replace: "$1$self.wrapMenu($2)"
},
{
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
replace: "$&case 'VencordPlugins':return $self.PluginsSubmenu();"
}
]
},
],
PluginsSubmenu,
// This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary
// without possibly also catching unrelated errors of children.
//
@ -149,7 +160,7 @@ export default definePlugin({
if (item.section === "HEADER") {
items.push({ label: item.label, items: [] });
} else if (item.section === "DIVIDER") {
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
} else {
items.at(-1)!.items.push(item);
}

View file

@ -25,11 +25,9 @@ export default definePlugin({
description: "Upload with a single click, open menu with right click",
patches: [
{
find: "Messages.CHAT_ATTACH_UPLOAD_OR_INVITE",
find: '"ChannelAttachButton"',
replacement: {
// Discord merges multiple props here with Object.assign()
// This patch passes a third object to it with which we override onClick and onContextMenu
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
},
},

View file

@ -57,7 +57,11 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
if (!previewUrl) return;
openImageModal(previewUrl);
openImageModal({
url: previewUrl,
height: 720,
width: 1280
});
};
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {

View file

@ -45,8 +45,8 @@ export default definePlugin({
{
find: ".embedWrapper,embed",
replacement: [{
match: /\.embedWrapper(?=.+?channel_id:(\i)\.id)/g,
replace: "$&+($1.nsfw?' vc-nsfw-img':'')"
match: /\.container/,
replace: "$&+(this.props.channel.nsfw? ' vc-nsfw-img': '')"
}]
}
],

View file

@ -155,4 +155,5 @@ export const defaultRules = [
"igshid",
"igsh",
"share_id@reddit.com",
"si@soundcloud.com",
];

View file

@ -10,12 +10,11 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { t } from "@utils/translation";
import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, Forms, useStateFromStores } from "@webpack/common";
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
@ -31,13 +30,12 @@ function onPickColor(color: number) {
updateColorVars(hexColor);
}
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE",settings:{useSystemTheme:"system"===');
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"===');
function setTheme(theme: string) {
saveClientTheme({ theme });
}
const ThemeStore = findStoreLazy("ThemeStore");
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
function ThemeSettings() {
@ -65,8 +63,8 @@ function ThemeSettings() {
<div className="client-theme-settings">
<div className="client-theme-container">
<div className="client-theme-settings-labels">
<Forms.FormTitle tag="h3">{t("clientTheme.settingsTitle")}</Forms.FormTitle>
<Forms.FormText>{t("clientTheme.settingsDescription")}</Forms.FormText>
<Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle>
<Forms.FormText>Add a color to your Discord client theme</Forms.FormText>
</div>
<ColorPicker
color={parseInt(settings.store.color, 16)}
@ -79,12 +77,12 @@ function ThemeSettings() {
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
<div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}>
<div className="client-theme-warning">
<Forms.FormText>{t("clientTheme.warningTitle")}</Forms.FormText>
{contrastWarning && <Forms.FormText>{t("clientTheme.warnings.badContrast")}</Forms.FormText>}
{nitroThemeEnabled && <Forms.FormText>{t("clientTheme.warnings.nitro")}</Forms.FormText>}
<Forms.FormText>Warning, your theme won't look good:</Forms.FormText>
{contrastWarning && <Forms.FormText>Selected color won't contrast well with text</Forms.FormText>}
{nitroThemeEnabled && <Forms.FormText>Nitro themes aren't supported</Forms.FormText>}
</div>
{(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>{t(`clientTheme.switchToOpposite.${oppositeTheme}`)}</Button>}
{(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>{t("clientTheme.disableNitroTheme")}</Button>}
{(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>}
{(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>}
</div>
</>)}
</div>
@ -93,18 +91,18 @@ function ThemeSettings() {
const settings = definePluginSettings({
color: {
description: t("clientTheme.colorDescription"),
description: "Color your Discord client theme will be based around. Light mode isn't supported",
type: OptionType.COMPONENT,
default: "313338",
component: () => <ThemeSettings />
},
resetColor: {
description: t("clientTheme.resetColorDescription"),
description: "Reset Theme Color",
type: OptionType.COMPONENT,
default: "313338",
component: () => (
<Button onClick={() => onPickColor(0x313338)}>
{t("clientTheme.resetButton")}
Reset Theme Color
</Button>
)
}
@ -113,7 +111,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ClientTheme",
authors: [Devs.F53, Devs.Nuckyz],
description: t("clientTheme.description"),
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
settings,
startAt: StartAt.DOMContentLoaded,

View file

@ -1,5 +1,5 @@
# ConsoleJanitor
Disables annoying console messages/errors. This plugin mainly removes errors/warnings that happen all the time and noisy/spammy logging messages.
Disables annoying console messages/errors. This plugin mainly removes errors/warnings that happen all the time and Discord logger messages.
Some of the disabled messages include the "notosans-400-normalitalic" error and MessageActionCreators, Routing/Utils loggers.
One of the disabled messages is the "Window state not initialized" warning, for example.

View file

@ -6,7 +6,7 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { OptionType, StartAt } from "@utils/types";
const Noop = () => { };
const NoopLogger = {
@ -22,10 +22,12 @@ const NoopLogger = {
fileOnly: Noop
};
const logAllow = new Set();
const settings = definePluginSettings({
disableNoisyLoggers: {
disableLoggers: {
type: OptionType.BOOLEAN,
description: "Disable noisy loggers like the MessageActionCreators",
description: "Disables Discords loggers",
default: false,
restartNeeded: true
},
@ -34,18 +36,43 @@ const settings = definePluginSettings({
description: "Disable the Spotify logger, which leaks account information and access token",
default: true,
restartNeeded: true
},
whitelistedLoggers: {
type: OptionType.STRING,
description: "Semi colon separated list of loggers to allow even if others are hidden",
default: "GatewaySocket; Routing/Utils",
onChange(newVal: string) {
logAllow.clear();
newVal.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
}
}
});
export default definePlugin({
name: "ConsoleJanitor",
description: "Disables annoying console messages/errors",
authors: [Devs.Nuckyz],
authors: [Devs.Nuckyz, Devs.sadan],
settings,
startAt: StartAt.Init,
start() {
logAllow.clear();
this.settings.store.whitelistedLoggers?.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
},
NoopLogger: () => NoopLogger,
shouldLog(logger: string) {
return logAllow.has(logger);
},
patches: [
{
find: 'react-spring: The "interpolate" function',
replacement: {
match: /,console.warn\('react-spring: The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
replace: ""
}
},
{
find: 'console.warn("Window state not initialized"',
replacement: {
@ -60,13 +87,6 @@ export default definePlugin({
replace: ""
}
},
{
find: "notosans-400-normalitalic",
replacement: {
match: /,"notosans-.+?"/g,
replace: ""
}
},
{
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
all: true,
@ -110,34 +130,13 @@ export default definePlugin({
replace: ""
}
},
...[
'("MessageActionCreators")', '("ChannelMessages")',
'("Routing/Utils")', '("RTCControlSocket")',
'("ConnectionEventFramerateReducer")', '("RTCLatencyTestManager")',
'("OverlayBridgeStore")', '("RPCServer:WSS")'
].map(logger => ({
find: logger,
predicate: () => settings.store.disableNoisyLoggers,
all: true,
replacement: {
match: new RegExp(String.raw`new \i\.\i${logger.replace(/([()])/g, "\\$1")}`),
replace: `$self.NoopLogger${logger}`
}
})),
// Patches discords generic logger function
{
find: '"Experimental codecs: "',
predicate: () => settings.store.disableNoisyLoggers,
find: "Σ:",
predicate: () => settings.store.disableLoggers,
replacement: {
match: /new \i\.\i\("Connection\("\.concat\(\i,"\)"\)\)/,
replace: "$self.NoopLogger()"
}
},
{
find: '"Handling ping: "',
predicate: () => settings.store.disableNoisyLoggers,
replacement: {
match: /new \i\.\i\("RTCConnection\("\.concat.+?\)\)(?=,)/,
replace: "$self.NoopLogger()"
match: /(?<=&&)(?=console)/,
replace: "$self.shouldLog(arguments[0])&&"
}
},
{
@ -148,5 +147,5 @@ export default definePlugin({
replace: "$self.NoopLogger()"
}
}
]
],
});

View file

@ -18,6 +18,7 @@
import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
@ -104,6 +105,7 @@ function makeShortcuts() {
canonicalizeMatch,
canonicalizeReplace,
canonicalizeReplacement,
runtimeHashMessageKey,
fakeRender: (component: ComponentType, props: any) => {
const prevWin = fakeRenderWin?.deref();
const win = prevWin?.closed === false

View file

@ -0,0 +1,5 @@
# CopyFileContents
Adds a button to text file attachments to copy their contents.
![](https://github.com/user-attachments/assets/b1a0f6f4-106f-4953-94d9-4c5ef5810bca)

View file

@ -0,0 +1,60 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./style.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { CopyIcon, NoEntrySignIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc";
import definePlugin from "@utils/types";
import { Tooltip, useState } from "@webpack/common";
const CheckMarkIcon = () => {
return <svg width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M21.7 5.3a1 1 0 0 1 0 1.4l-12 12a1 1 0 0 1-1.4 0l-6-6a1 1 0 1 1 1.4-1.4L9 16.58l11.3-11.3a1 1 0 0 1 1.4 0Z"></path>
</svg>;
};
export default definePlugin({
name: "CopyFileContents",
description: "Adds a button to text file attachments to copy their contents",
authors: [Devs.Obsidian, Devs.Nuckyz],
patches: [
{
find: "#{intl::PREVIEW_BYTES_LEFT}",
replacement: {
match: /\.footerGap.+?url:\i,fileName:\i,fileSize:\i}\),(?<=fileContents:(\i),bytesLeft:(\i).+?)/g,
replace: "$&$self.addCopyButton({fileContents:$1,bytesLeft:$2}),"
}
}
],
addCopyButton: ErrorBoundary.wrap(({ fileContents, bytesLeft }: { fileContents: string, bytesLeft: number; }) => {
const [recentlyCopied, setRecentlyCopied] = useState(false);
return (
<Tooltip text={recentlyCopied ? "Copied!" : bytesLeft > 0 ? "File too large to copy" : "Copy File Contents"}>
{tooltipProps => (
<div
{...tooltipProps}
className="vc-cfc-button"
role="button"
onClick={() => {
if (!recentlyCopied && bytesLeft <= 0) {
copyWithToast(fileContents);
setRecentlyCopied(true);
setTimeout(() => setRecentlyCopied(false), 2000);
}
}}
>
{recentlyCopied ? <CheckMarkIcon /> : bytesLeft > 0 ? <NoEntrySignIcon color="var(--channel-icon)" /> : <CopyIcon />}
</div>
)}
</Tooltip>
);
}, { noop: true }),
});

View file

@ -0,0 +1,9 @@
.vc-cfc-button {
color: var(--interactive-normal);
cursor: pointer;
padding-left: 4px;
}
.vc-cfc-button:hover {
color: var(--interactive-hover);
}

View file

@ -67,7 +67,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
find: "#{intl::ERRORS_UNEXPECTED_CRASH}",
replacement: {
match: /this\.setState\((.+?)\)/,
replace: "$self.handleCrash(this,$1);"
@ -175,7 +175,7 @@ export default definePlugin({
}
if (settings.store.attemptToNavigateToHome) {
try {
NavigationRouter.transitionTo("/channels/@me");
NavigationRouter.transitionToGuild("@me");
} catch (err) {
CrashHandlerLogger.debug("Failed to navigate to home", err);
}

View file

@ -39,6 +39,15 @@ export default definePlugin({
}
}),
patches: [
// Only one of the two patches will be at effect; Discord often updates to switch between them.
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
{
find: ".ENTER&&(!",
replacement: {
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
replace: "$self.shouldSubmit($1, $2)"
}
},
{
find: "!this.hasOpenCodeBlock()",
replacement: {

View file

@ -26,12 +26,11 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
@ -436,8 +435,8 @@ export default definePlugin({
<Forms.FormDivider className={Margins.top8} />
<div style={{ width: "284px", ...profileThemeStyle }}>
{activity[0] && <ActivityComponent activity={activity[0]} className={ActivityClassName.activity} channelId={SelectedChannelStore.getChannelId()}
<div style={{ width: "284px", ...profileThemeStyle, padding: 8, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
{activity[0] && <ActivityComponent activity={activity[0]} channelId={SelectedChannelStore.getChannelId()}
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
application={{ id: settings.store.appID }}
user={UserStore.getCurrentUser()} />}

View file

@ -37,8 +37,8 @@ export default definePlugin({
find: 'type:"IDLE",idle:',
replacement: [
{
match: /(?<=Date\.now\(\)-\i>)\i\.\i/,
replace: "$self.getIdleTimeout()"
match: /(?<=Date\.now\(\)-\i>)\i\.\i\|\|/,
replace: "$self.getIdleTimeout()||"
},
{
match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,

View file

@ -46,7 +46,7 @@ const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/;
async function embedDidMount(this: Component<Props>) {
try {
const { embed } = this.props;
const { replaceElements } = settings.store;
const { replaceElements, dearrowByDefault } = settings.store;
if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return;
@ -63,18 +63,22 @@ async function embedDidMount(this: Component<Props>) {
if (!hasTitle && !hasThumb) return;
embed.dearrow = {
enabled: true
enabled: dearrowByDefault
};
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
}
const replacementTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
embed.dearrow.oldTitle = dearrowByDefault ? embed.rawTitle : replacementTitle;
if (dearrowByDefault) embed.rawTitle = replacementTitle;
}
if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
embed.dearrow.oldThumb = dearrowByDefault ? embed.thumbnail.proxyURL : replacementProxyURL;
if (dearrowByDefault) embed.thumbnail.proxyURL = replacementProxyURL;
}
this.forceUpdate();
@ -96,6 +100,7 @@ function DearrowButton({ component }: { component: Component<Props>; }) {
className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")}
onClick={() => {
const { enabled, oldThumb, oldTitle } = embed.dearrow;
settings.store.dearrowByDefault = !enabled;
embed.dearrow.enabled = !enabled;
if (oldTitle) {
embed.dearrow.oldTitle = embed.rawTitle;
@ -153,6 +158,12 @@ const settings = definePluginSettings({
{ label: "Titles", value: ReplaceElements.ReplaceTitlesOnly },
{ label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly },
],
},
dearrowByDefault: {
description: "Dearrow videos automatically",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: false
}
});

View file

@ -41,7 +41,7 @@ export default definePlugin({
{
find: "DefaultCustomizationSections",
replacement: {
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/,
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
replace: "$self.DecorSection(),"
}
},
@ -54,7 +54,7 @@ export default definePlugin({
replace: "$self.DecorationGridItem=$&"
},
{
match: /(?<==)\i=>{let{user:\i,avatarDecoration.{300,600}decorationGridItemChurned/,
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
replace: "$self.DecorationGridDecoration=$&"
},
// Remove NEW label from decor avatar decorations
@ -91,7 +91,7 @@ export default definePlugin({
replacement: [
// Use Decor avatar decoration hook
{
match: /(?<=\i\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/,
match: /(?<=\i\)\({avatarDecoration:)(\i)(?=,)(?<=currentUser:(\i).+?)/,
replace: "$self.useUserDecorAvatarDecoration($1)??$&"
}
]

View file

@ -5,7 +5,8 @@
*/
import { PlusIcon } from "@components/Icons";
import { i18n, Text } from "@webpack/common";
import { getIntlMessage } from "@utils/discord";
import { Text } from "@webpack/common";
import { HTMLProps } from "react";
import { DecorationGridItem } from ".";
@ -24,7 +25,7 @@ export default function DecorationGridCreate(props: DecorationGridCreateProps) {
variant="text-xs/normal"
color="header-primary"
>
{i18n.Messages.CREATE}
{getIntlMessage("CREATE")}
</Text>
</DecorationGridItem >;
}

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