Compare commits

...

122 commits
dev2 ... main

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
198 changed files with 2593 additions and 1619 deletions

2
.gitignore vendored
View file

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

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

@ -5,6 +5,7 @@
// @author Vendicated (https://github.com/Vendicated) // @author Vendicated (https://github.com/Vendicated)
// @namespace https://github.com/Vendicated/Vencord // @namespace https://github.com/Vendicated/Vencord
// @supportURL 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 // @license GPL-3.0
// @match *://*.discord.com/* // @match *://*.discord.com/*
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.9.8", "version": "1.10.6",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -35,6 +35,7 @@
"testTsc": "tsc --noEmit" "testTsc": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@intrnl/xxhash64": "^0.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5", "@sapphi-red/web-noise-suppressor": "0.3.5",
"@vap/core": "0.0.12", "@vap/core": "0.0.12",
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
@ -70,6 +71,7 @@
"stylelint": "^16.8.1", "stylelint": "^16.8.1",
"stylelint-config-standard": "^36.0.1", "stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.2.1", "ts-patch": "^3.2.1",
"ts-pattern": "^5.3.1",
"tsx": "^4.16.5", "tsx": "^4.16.5",
"type-fest": "^4.23.0", "type-fest": "^4.23.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",

View file

@ -16,6 +16,9 @@ importers:
.: .:
dependencies: dependencies:
'@intrnl/xxhash64':
specifier: ^0.1.2
version: 0.1.2
'@sapphi-red/web-noise-suppressor': '@sapphi-red/web-noise-suppressor':
specifier: 0.3.5 specifier: 0.3.5
version: 0.3.5 version: 0.3.5
@ -116,6 +119,9 @@ importers:
ts-patch: ts-patch:
specifier: ^3.2.1 specifier: ^3.2.1
version: 3.2.1 version: 3.2.1
ts-pattern:
specifier: ^5.3.1
version: 5.3.1
tsx: tsx:
specifier: ^4.16.5 specifier: ^4.16.5
version: 4.16.5 version: 4.16.5
@ -534,6 +540,9 @@ packages:
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'} engines: {node: '>=18.18'}
'@intrnl/xxhash64@0.1.2':
resolution: {integrity: sha512-1+lx7j99fdph+uy3EnjQyr39KQZ7LP56+aWOr6finJWpgYpvb7XrhFUqDwnEk/wpPC98nCjAT6RulpW3crWjlg==}
'@jridgewell/gen-mapping@0.3.5': '@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -2524,6 +2533,9 @@ packages:
resolution: {integrity: sha512-hlR43v+GUIUy8/ZGFP1DquEqPh7PFKQdDMTAmYt671kCCA6AkDQMoeFaFmZ7ObPLYOmpMgyKUqL1C+coFMf30w==} resolution: {integrity: sha512-hlR43v+GUIUy8/ZGFP1DquEqPh7PFKQdDMTAmYt671kCCA6AkDQMoeFaFmZ7ObPLYOmpMgyKUqL1C+coFMf30w==}
hasBin: true hasBin: true
ts-pattern@5.3.1:
resolution: {integrity: sha512-1RUMKa8jYQdNfmnK4jyzBK3/PS/tnjcZ1CW0v1vWDeYe5RBklc/nquw03MEoB66hVBm4BnlCfmOqDVxHyT1DpA==}
tsconfig-paths@3.15.0: tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
@ -2933,6 +2945,8 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {} '@humanwhocodes/retry@0.3.0': {}
'@intrnl/xxhash64@0.1.2': {}
'@jridgewell/gen-mapping@0.3.5': '@jridgewell/gen-mapping@0.3.5':
dependencies: dependencies:
'@jridgewell/set-array': 1.2.1 '@jridgewell/set-array': 1.2.1
@ -5158,6 +5172,8 @@ snapshots:
semver: 7.6.3 semver: 7.6.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
ts-pattern@5.3.1: {}
tsconfig-paths@3.15.0: tsconfig-paths@3.15.0:
dependencies: dependencies:
'@types/json5': 0.0.29 '@types/json5': 0.0.29

View file

@ -334,5 +334,6 @@ export const commonRendererPlugins = [
banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), 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(/^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(/^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 ...commonOpts.plugins
]; ];

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

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

View file

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

View file

@ -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 * A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
* @param id The id of the child. If an array is specified, all ids will be tried * @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children * @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id
*/ */
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null { export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
for (const child of children) { for (const child of children) {
if (child == null) continue; if (child == null) continue;
if (Array.isArray(child)) { if (Array.isArray(child)) {
const found = findGroupChildrenByChildId(id, child); const found = findGroupChildrenByChildId(id, child, matchSubstring);
if (found !== null) return found; if (found !== null) return found;
} }
if ( if (
(Array.isArray(id) && id.some(id => child.props?.id === id)) (Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|| child.props?.id === id || (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
) return children; ) return children;
let nextChildren = child.props?.children; let nextChildren = child.props?.children;
@ -112,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
child.props.children = nextChildren; child.props.children = nextChildren;
} }
const found = findGroupChildrenByChildId(id, nextChildren); const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
if (found !== null) return found; if (found !== null) return found;
} }
} }

View file

@ -18,9 +18,8 @@
import "./iconStyles.css"; import "./iconStyles.css";
import { getTheme, Theme } from "@utils/discord"; import { getIntlMessage, getTheme, Theme } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { i18n } from "@webpack/common";
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps { 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 * Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks
* your own username in the bottom left user panel
*/ */
export function CopyIcon(props: IconProps) { export function CopyIcon(props: IconProps) {
return ( return (
@ -76,8 +74,9 @@ export function CopyIcon(props: IconProps) {
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<g fill="currentColor"> <g fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" /> <path d="M3 16a1 1 0 0 1-1-1v-5a8 8 0 0 1 8-8h5a1 1 0 0 1 1 1v.5a.5.5 0 0 1-.5.5H10a6 6 0 0 0-6 6v5.5a.5.5 0 0 1-.5.5H3Z" />
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" /> <path d="M6 18a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-4h-3a5 5 0 0 1-5-5V6h-4a4 4 0 0 0-4 4v8Z" />
<path d="M21.73 12a3 3 0 0 0-.6-.88l-4.25-4.24a3 3 0 0 0-.88-.61V9a3 3 0 0 0 3 3h2.73Z" />
</g> </g>
</Icon> </Icon>
); );
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
export function OwnerCrownIcon(props: IconProps) { export function OwnerCrownIcon(props: IconProps) {
return ( return (
<Icon <Icon
aria-label={i18n.Messages.GUILD_OWNER} aria-label={getIntlMessage("GUILD_OWNER")}
{...props} {...props}
className={classes(props.className, "vc-owner-crown-icon")} className={classes(props.className, "vc-owner-crown-icon")}
role="img" role="img"

View file

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

View file

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

View file

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

View file

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

View file

@ -93,7 +93,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name]; const settings = Settings.plugins[plugin.name];
const isEnabled = () => settings.enabled ?? false; const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);
function toggleEnabled() { function toggleEnabled() {
const wasEnabled = isEnabled(); const wasEnabled = isEnabled();
@ -292,10 +292,10 @@ export default function PluginSettings() {
if (!pluginFilter(p)) continue; if (!pluginFilter(p)) continue;
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled); const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
if (isRequired) { if (isRequired) {
const tooltipText = p.required const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Vencord to function." ? "This plugin is required for Vencord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled)); : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));

View file

@ -382,6 +382,7 @@ function PatchHelper() {
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
<CodeBlock lang="js" content={code} /> <CodeBlock lang="js" content={code} />
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button> <Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
<Button className={Margins.top8} onClick={() => Clipboard.copy("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
</> </>
)} )}
</SettingsTab> </SettingsTab>

View file

@ -77,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle> <Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText> <Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
<div> <div>
{themeLinks.map(link => ( {themeLinks.map(rawLink => {
<Card style={{ const { label, link } = (() => {
const match = /^@(light|dark) (.*)/.exec(rawLink);
if (!match) return { label: rawLink, link: rawLink };
const [, mode, link] = match;
return { label: `[${mode} mode only] ${link}`, link };
})();
return <Card style={{
padding: ".5em", padding: ".5em",
marginBottom: ".5em", marginBottom: ".5em",
marginTop: ".5em" marginTop: ".5em"
@ -86,11 +94,11 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
<Forms.FormTitle tag="h5" style={{ <Forms.FormTitle tag="h5" style={{
overflowWrap: "break-word" overflowWrap: "break-word"
}}> }}>
{link} {label}
</Forms.FormTitle> </Forms.FormTitle>
<Validator link={link} /> <Validator link={link} />
</Card> </Card>;
))} })}
</div> </div>
</> </>
); );
@ -296,6 +304,7 @@ function ThemesTab() {
<Card className="vc-settings-card vc-text-selectable"> <Card className="vc-settings-card vc-text-selectable">
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle> <Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
<Forms.FormText>One link per line</Forms.FormText> <Forms.FormText>One link per line</Forms.FormText>
<Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText> <Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
</Card> </Card>

View file

@ -31,9 +31,7 @@ export async function loadLazyChunks() {
const lazyChunks = factoryCode.matchAll(LazyChunkRegex); const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>(); 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 const shouldForceDefer = false;
// the chunk containing the component
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : []; const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];

View file

@ -17,7 +17,7 @@
*/ */
import { onceDefined } from "@shared/onceDefined"; 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 { dirname, join } from "path";
import { initIpc } from "./ipcMain"; import { initIpc } from "./ipcMain";
@ -100,6 +100,19 @@ if (!IS_VANILLA) {
super(options); super(options);
initIpc(this); 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); } else super(options);
} }
} }

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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./fixBadgeOverflow.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
@ -79,7 +77,7 @@ export default definePlugin({
replace: "...$1.props,$& $1.image??" 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 }) :" replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
}, },
// conditionally override their onClick with badge.onClick if it exists // 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),/, match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
replace: "$&vencordProps=$1," 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))," replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
} }
] ]

View file

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

View file

@ -27,7 +27,7 @@ export default definePlugin({
{ {
find: '"Message Username"', find: '"Message Username"',
replacement: { 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])" replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
} }
} }

View file

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

View file

@ -24,9 +24,9 @@ export default definePlugin({
description: "API to add buttons to message popovers.", description: "API to add buttons to message popovers.",
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz], authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
patches: [{ patches: [{
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL", find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: { replacement: {
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.Messages\.MESSAGE_ACTION_REPLY.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/, 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)" 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", description: "Api required for plugins that modify the server list",
patches: [ patches: [
{ {
find: "Messages.DISCODO_DISABLED", find: "#{intl::DISCODO_DISABLED}",
replacement: { 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))" replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
} }
}, },
{ {
find: "Messages.SERVERS,children", find: "#{intl::SERVERS}),children",
replacement: { 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($&)" 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: [ replacement: [
{ {
match: /this\._intervalId=/, match: /this\._intervalId=/,

View file

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

View file

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

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

View file

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

View file

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

View file

@ -11,37 +11,11 @@ import type { TrackData } from ".";
const exec = promisify(execFile); 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[]) { async function applescript(cmds: string[]) {
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat()); const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
return stdout; 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 { interface RemoteData {
appleMusicLink?: string, appleMusicLink?: string,
songLink?: string, songLink?: string,
@ -51,6 +25,24 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; 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; }) { async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
if (id === cachedRemoteData?.id) { if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data; if ("data" in cachedRemoteData) return cachedRemoteData.data;
@ -58,21 +50,39 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
} }
try { try {
const [songData, artistData] = await Promise.all([ const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search");
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()), dataUrl.searchParams.set("platform", "web");
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json()) 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 token = await getToken();
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;
const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512"); const songData = await fetch(dataUrl, {
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512"); 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 = { cachedRemoteData = {
id, 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; return cachedRemoteData.data;
} catch (e) { } catch (e) {
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e); console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);

View file

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

View file

@ -36,7 +36,7 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
{ {
find: "BAN_CONFIRM_TITLE.", find: "#{intl::BAN_CONFIRM_TITLE}",
replacement: { replacement: {
match: /src:\i\("?\d+"?\)/g, match: /src:\i\("?\d+"?\)/g,
replace: "src:$self.source" replace: "src:$self.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 { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n, useMemo } from "@webpack/common"; import { FluxDispatcher, useMemo } from "@webpack/common";
import FolderSideBar from "./FolderSideBar"; import FolderSideBar from "./FolderSideBar";
@ -30,9 +31,9 @@ enum FolderIconDisplay {
MoreThanOneFolderExpanded MoreThanOneFolderExpanded
} }
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const SortedGuildStore = findStoreLazy("SortedGuildStore");
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null; 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 // 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\(\).+?}\))(?=,)/, 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 // 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.+?\]}\)\]/, 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 // 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,.+?}\)\]/, match: /unreadMentionsIndicatorBottom,.+?}\)\]/,
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))" replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0]?.isBetterFolders))"
}, },
// Export the isBetterFolders variable to the folders component // Export the isBetterFolders variable to the folders component
{ {
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/, match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/,
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
} }
] ]
}, },
@ -167,31 +168,31 @@ export default definePlugin({
{ {
predicate: () => settings.store.keepIcons, predicate: () => settings.store.keepIcons,
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/, 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 // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{ {
predicate: () => !settings.store.keepIcons, predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/, match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
replace: "!!arguments[0].isBetterFolders&&" replace: "$self.shouldShowTransition(arguments[0])&&"
}, },
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{ {
predicate: () => !settings.store.keepIcons, predicate: () => !settings.store.keepIcons,
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/, 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 // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/, 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 // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/, 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, predicate: () => settings.store.sidebar,
replacement: { replacement: {
// Render the Better Folders sidebar // Render the Better Folders sidebar
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/, match: /(container.{0,50}({className:\i\.guilds,themeOverride:\i})\))/,
replace: ",$self.FolderSideBar($1)" replace: "$1,$self.FolderSideBar({...$2})"
} }
}, },
{ {
find: ".Messages.DISCODO_DISABLED", find: "#{intl::DISCODO_DISABLED}",
predicate: () => settings.store.closeAllHomeButton, predicate: () => settings.store.closeAllHomeButton,
replacement: { replacement: {
// Close all folders when clicking the home button // Close all folders when clicking the home button
@ -274,12 +275,16 @@ export default definePlugin({
}, },
makeGuildsBarGuildListFilter(isBetterFolders: boolean) { makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => { try {
if (isBetterFolders) { return child => {
return child?.props?.["aria-label"] === i18n.Messages.SERVERS; if (isBetterFolders) {
} return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
}
return true;
};
} catch {
return true; return true;
}; }
}, },
makeGuildsBarTreeFilter(isBetterFolders: boolean) { makeGuildsBarTreeFilter(isBetterFolders: boolean) {
@ -306,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: { replacement: {
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/, match: /alt:(\i)=(\i\.\i\.string\(\i\.\i#{intl::GIF}\))(?=,[^}]*\}=(\i))/,
replace: replace:
// rename prop so we can always use default value // rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2", "alt_$$:$1=$self.altify($3)||$2",

View file

@ -63,9 +63,9 @@ export default definePlugin({
} }
}, },
{ {
find: "Messages.NOTE_PLACEHOLDER", find: "#{intl::NOTE_PLACEHOLDER}",
replacement: { replacement: {
match: /\.NOTE_PLACEHOLDER,/, match: /#{intl::NOTE_PLACEHOLDER}\),/,
replace: "$&spellCheck:!$self.noSpellCheck," replace: "$&spellCheck:!$self.noSpellCheck,"
} }
} }

View file

@ -99,7 +99,11 @@ export default definePlugin({
id="vc-view-role-icon" id="vc-view-role-icon"
label="View Role Icon" label="View Role Icon"
action={() => { 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} icon={ImageIcon}
/> />

View file

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

View file

@ -60,7 +60,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT", find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
replacement: [ replacement: [
// Replace children with a single label with state // 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 { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { waitFor } from "@webpack"; 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 type { HTMLAttributes, ReactElement } from "react";
import PluginsSubmenu from "./PluginsSubmenu";
type SettingsEntry = { section: string, label: string; }; type SettingsEntry = { section: string, label: string; };
const cl = classNameFactory(""); const cl = classNameFactory("");
@ -109,7 +112,7 @@ export default definePlugin({
predicate: () => settings.store.disableFade predicate: () => settings.store.disableFade
}, },
{ // Load menu TOC eagerly { // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format", find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
replacement: { replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/, match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)()," replace: "$&(async ()=>$2)(),"
@ -117,14 +120,22 @@ export default definePlugin({
predicate: () => settings.store.eagerLoad predicate: () => settings.store.eagerLoad
}, },
{ // Settings cog context menu { // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: { replacement: [
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, {
replace: "$1$self.wrapMenu($2)" 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 // 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. // without possibly also catching unrelated errors of children.
// //
@ -149,7 +160,7 @@ export default definePlugin({
if (item.section === "HEADER") { if (item.section === "HEADER") {
items.push({ label: item.label, items: [] }); items.push({ label: item.label, items: [] });
} else if (item.section === "DIVIDER") { } else if (item.section === "DIVIDER") {
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] }); items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
} else { } else {
items.at(-1)!.items.push(item); items.at(-1)!.items.push(item);
} }

View file

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

View file

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

View file

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

View file

@ -12,9 +12,9 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin, { OptionType, StartAt } from "@utils/types"; import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; 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 = [ const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
@ -36,7 +36,6 @@ function setTheme(theme: string) {
saveClientTheme({ theme }); saveClientTheme({ theme });
} }
const ThemeStore = findStoreLazy("ThemeStore");
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
function ThemeSettings() { function ThemeSettings() {

View file

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

View file

@ -18,6 +18,7 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
@ -104,6 +105,7 @@ function makeShortcuts() {
canonicalizeMatch, canonicalizeMatch,
canonicalizeReplace, canonicalizeReplace,
canonicalizeReplacement, canonicalizeReplacement,
runtimeHashMessageKey,
fakeRender: (component: ComponentType, props: any) => { fakeRender: (component: ComponentType, props: any) => {
const prevWin = fakeRenderWin?.deref(); const prevWin = fakeRenderWin?.deref();
const win = prevWin?.closed === false 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: [ patches: [
{ {
find: ".Messages.ERRORS_UNEXPECTED_CRASH", find: "#{intl::ERRORS_UNEXPECTED_CRASH}",
replacement: { replacement: {
match: /this\.setState\((.+?)\)/, match: /this\.setState\((.+?)\)/,
replace: "$self.handleCrash(this,$1);" replace: "$self.handleCrash(this,$1);"
@ -175,7 +175,7 @@ export default definePlugin({
} }
if (settings.store.attemptToNavigateToHome) { if (settings.store.attemptToNavigateToHome) {
try { try {
NavigationRouter.transitionTo("/channels/@me"); NavigationRouter.transitionToGuild("@me");
} catch (err) { } catch (err) {
CrashHandlerLogger.debug("Failed to navigate to home", err); CrashHandlerLogger.debug("Failed to navigate to home", err);
} }

View file

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

View file

@ -37,8 +37,8 @@ export default definePlugin({
find: 'type:"IDLE",idle:', find: 'type:"IDLE",idle:',
replacement: [ replacement: [
{ {
match: /(?<=Date\.now\(\)-\i>)\i\.\i/, match: /(?<=Date\.now\(\)-\i>)\i\.\i\|\|/,
replace: "$self.getIdleTimeout()" replace: "$self.getIdleTimeout()||"
}, },
{ {
match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/, 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>) { async function embedDidMount(this: Component<Props>) {
try { try {
const { embed } = this.props; 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; 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; if (!hasTitle && !hasThumb) return;
embed.dearrow = { embed.dearrow = {
enabled: true enabled: dearrowByDefault
}; };
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
embed.dearrow.oldTitle = embed.rawTitle; const replacementTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
embed.rawTitle = 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) { if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL; const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
embed.thumbnail.proxyURL = `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(); this.forceUpdate();
@ -96,6 +100,7 @@ function DearrowButton({ component }: { component: Component<Props>; }) {
className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")} className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")}
onClick={() => { onClick={() => {
const { enabled, oldThumb, oldTitle } = embed.dearrow; const { enabled, oldThumb, oldTitle } = embed.dearrow;
settings.store.dearrowByDefault = !enabled;
embed.dearrow.enabled = !enabled; embed.dearrow.enabled = !enabled;
if (oldTitle) { if (oldTitle) {
embed.dearrow.oldTitle = embed.rawTitle; embed.dearrow.oldTitle = embed.rawTitle;
@ -153,6 +158,12 @@ const settings = definePluginSettings({
{ label: "Titles", value: ReplaceElements.ReplaceTitlesOnly }, { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly },
{ label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly }, { 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", find: "DefaultCustomizationSections",
replacement: { replacement: {
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/, match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
replace: "$self.DecorSection()," replace: "$self.DecorSection(),"
} }
}, },
@ -54,7 +54,7 @@ export default definePlugin({
replace: "$self.DecorationGridItem=$&" replace: "$self.DecorationGridItem=$&"
}, },
{ {
match: /(?<==)\i=>{let{user:\i,avatarDecoration.{300,600}decorationGridItemChurned/, match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
replace: "$self.DecorationGridDecoration=$&" replace: "$self.DecorationGridDecoration=$&"
}, },
// Remove NEW label from decor avatar decorations // Remove NEW label from decor avatar decorations

View file

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

View file

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

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
patches: [ patches: [
{ {
find: ".Messages.BOT_CALL_IDLE_DISCONNECT", find: "#{intl::BOT_CALL_IDLE_DISCONNECT_2}",
replacement: { replacement: {
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/, match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
replace: ";return;" replace: ";return;"

View file

@ -23,12 +23,13 @@ import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy, findLazy } from "@webpack";
import { Forms, React } from "@webpack/common"; import { Forms, React } from "@webpack/common";
import hideBugReport from "./hideBugReport.css?managed"; import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByPropsLazy("key", "combo"); const KbdStyles = findByPropsLazy("key", "combo");
const BugReporterExperiment = findLazy(m => m?.definition?.id === "2024-09_bug_reporter");
const settings = definePluginSettings({ const settings = definePluginSettings({
toolbarDevMenu: { toolbarDevMenu: {
@ -78,8 +79,8 @@ export default definePlugin({
{ {
find: "toolbar:function", find: "toolbar:function",
replacement: { replacement: {
match: /\i\.isStaff\(\)/, match: /hasBugReporterAccess:(\i)/,
replace: "true" replace: "_hasBugReporterAccess:$1=true"
}, },
predicate: () => settings.store.toolbarDevMenu predicate: () => settings.store.toolbarDevMenu
}, },
@ -91,10 +92,18 @@ export default definePlugin({
match: /\i\.isDM\(\)\|\|\i\.isThread\(\)/, match: /\i\.isDM\(\)\|\|\i\.isThread\(\)/,
replace: "false", replace: "false",
} }
},
// enable option to always record clips even if you are not streaming
{
find: "isDecoupledGameClippingEnabled(){",
replacement: {
match: /\i\.isStaff\(\)/,
replace: "true"
}
} }
], ],
start: () => enableStyle(hideBugReport), start: () => !BugReporterExperiment.getCurrentConfig().hasBugReporterAccess && enableStyle(hideBugReport),
stop: () => disableStyle(hideBugReport), stop: () => disableStyle(hideBugReport),
settingsAboutComponent: () => { settingsAboutComponent: () => {

View file

@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType, Patch } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Emoji } from "@webpack/types"; import type { Emoji } from "@webpack/types";
@ -194,6 +194,26 @@ const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId,
const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS); const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS);
const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES); const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES);
function makeBypassPatches(): Omit<Patch, "plugin"> {
const mapping: Array<{ func: string, predicate?: () => boolean; }> = [
{ func: "canUseCustomStickersEverywhere", predicate: () => settings.store.enableStickerBypass },
{ func: "canUseHighVideoUploadQuality", predicate: () => settings.store.enableStreamQualityBypass },
{ func: "canStreamQuality", predicate: () => settings.store.enableStreamQualityBypass },
{ func: "canUseClientThemes" },
{ func: "canUseCustomNotificationSounds" },
{ func: "canUsePremiumAppIcons" }
];
return {
find: "canUseCustomStickersEverywhere:",
replacement: mapping.map(({ func, predicate }) => ({
match: new RegExp(String.raw`(?<=${func}:function\(\i(?:,\i)?\){)`),
replace: "return true;",
predicate
}))
};
}
export default definePlugin({ export default definePlugin({
name: "FakeNitro", name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN], authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
@ -203,6 +223,17 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
// General bypass patches
makeBypassPatches(),
// Patch the emoji picker in voice calls to not be bypassed by fake nitro
{
find: "emojiItemDisabled]",
predicate: () => settings.store.enableEmojiBypass,
replacement: {
match: /CHAT/,
replace: "STATUS"
}
},
{ {
find: ".PREMIUM_LOCKED;", find: ".PREMIUM_LOCKED;",
group: true, group: true,
@ -243,15 +274,6 @@ export default definePlugin({
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}` replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
} }
}, },
// Allow stickers to be sent everywhere
{
find: "canUseCustomStickersEverywhere:function",
predicate: () => settings.store.enableStickerBypass,
replacement: {
match: /canUseCustomStickersEverywhere:function\(\i\){/,
replace: "$&return true;"
},
},
// Make stickers always available // Make stickers always available
{ {
find: '"SENDABLE"', find: '"SENDABLE"',
@ -261,37 +283,15 @@ export default definePlugin({
replace: "true?" replace: "true?"
} }
}, },
// Allow streaming with high quality
{
find: "canUseHighVideoUploadQuality:function",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: [
"canUseHighVideoUploadQuality",
"canStreamQuality",
].map(func => {
return {
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
replace: "$&return true;"
};
})
},
// Remove boost requirements to stream with high quality // Remove boost requirements to stream with high quality
{ {
find: "STREAM_FPS_OPTION.format", find: "#{intl::STREAM_FPS_OPTION}",
predicate: () => settings.store.enableStreamQualityBypass, predicate: () => settings.store.enableStreamQualityBypass,
replacement: { replacement: {
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g, match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
replace: "" replace: ""
} }
}, },
// Allow client themes to be changeable
{
find: "canUseClientThemes:function",
replacement: {
match: /canUseClientThemes:function\(\i\){/,
replace: "$&return true;"
}
},
{ {
find: '"UserSettingsProtoStore"', find: '"UserSettingsProtoStore"',
replacement: [ replacement: [
@ -350,13 +350,13 @@ export default definePlugin({
{ {
// Filter attachments to remove fake nitro stickers or emojis // Filter attachments to remove fake nitro stickers or emojis
predicate: () => settings.store.transformStickers, predicate: () => settings.store.transformStickers,
match: /renderAttachments\(\i\){let{attachments:(\i).+?;/, match: /renderAttachments\(\i\){.+?{attachments:(\i).+?;/,
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});` replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
} }
] ]
}, },
{ {
find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format", find: "#{intl::STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformStickers, predicate: () => settings.store.transformStickers,
replacement: [ replacement: [
{ {
@ -381,7 +381,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION", find: "#{intl::EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
// Add the fake nitro emoji notice // Add the fake nitro emoji notice
@ -389,14 +389,6 @@ export default definePlugin({
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)` replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)`
} }
}, },
// Allow using custom app icons
{
find: "canUsePremiumAppIcons:function",
replacement: {
match: /canUsePremiumAppIcons:function\(\i\){/,
replace: "$&return true;"
}
},
// Separate patch for allowing using custom app icons // Separate patch for allowing using custom app icons
{ {
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/, find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
@ -412,14 +404,6 @@ export default definePlugin({
match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)\i\.available/g, match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)\i\.available/g,
replace: "true" replace: "true"
} }
},
// Allow using custom notification sounds
{
find: "canUseCustomNotificationSounds:function",
replacement: {
match: /canUseCustomNotificationSounds:function\(\i\){/,
replace: "$&return true;"
}
} }
], ],

View file

@ -108,10 +108,10 @@ interface ProfileModalProps {
isTryItOutFlow: boolean; isTryItOutFlow: boolean;
} }
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER"); const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER");
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); const requireColorPicker = extractAndLoadChunksLazy(["#{intl::USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON}"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
export default definePlugin({ export default definePlugin({
name: "FakeProfileThemes", name: "FakeProfileThemes",
@ -126,9 +126,9 @@ export default definePlugin({
} }
}, },
{ {
find: ".USER_SETTINGS_RESET_PROFILE_THEME", find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}",
replacement: { replacement: {
match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\)}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
} }
} }

View file

@ -0,0 +1,3 @@
# Fix Images Quality
Prevents images from being loaded as webp, which can cause quality loss

View file

@ -8,16 +8,15 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "NoDefaultHangStatus", name: "FixImagesQuality",
description: "Disable the default hang status when joining voice channels", description: "Prevents images from being loaded as webp, which can cause quality loss",
authors: [Devs.D3SOX], authors: [Devs.Nuckyz],
patches: [ patches: [
{ {
find: ".CHILLING)", find: "getFormatQuality(){",
replacement: { replacement: {
match: /{enableHangStatus:(\i),/, match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
replace: "{_enableHangStatus:$1=false," replace: ""
} }
} }
] ]

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.D3SOX, Devs.Nickyux], authors: [Devs.D3SOX, Devs.Nickyux],
patches: [ patches: [
{ {
find: ".Messages.GUILD_OWNER,", find: "#{intl::GUILD_OWNER}",
replacement: { replacement: {
match: /,isOwner:(\i),/, match: /,isOwner:(\i),/,
replace: ",_isOwner:$1=$self.isGuildOwner(e)," replace: ",_isOwner:$1=$self.isGuildOwner(e),"

View file

@ -27,7 +27,6 @@ export default definePlugin({
name: "FriendInvites", name: "FriendInvites",
description: "Create and manage friend invite links via slash commands (/create friend invite, /view friend invites, /revoke friend invites).", description: "Create and manage friend invite links via slash commands (/create friend invite, /view friend invites, /revoke friend invites).",
authors: [Devs.afn, Devs.Dziurwa], authors: [Devs.afn, Devs.Dziurwa],
dependencies: ["CommandsAPI"],
commands: [ commands: [
{ {
name: "create friend invite", name: "create friend invite",

View file

@ -7,16 +7,15 @@
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Heading, RelationshipStore, Text } from "@webpack/common"; import { RelationshipStore, Text } from "@webpack/common";
const containerWrapper = findByPropsLazy("memberSinceWrapper"); const containerWrapper = findByPropsLazy("memberSinceWrapper");
const container = findByPropsLazy("memberSince"); const container = findByPropsLazy("memberSince");
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
const locale = findByPropsLazy("getLocale"); const locale = findByPropsLazy("getLocale");
const section = findLazy((m: any) => m.section !== void 0 && m.heading !== void 0 && Object.values(m).length === 2); const Section = findComponentByCodeLazy('"auto":"smooth"', ".section");
export default definePlugin({ export default definePlugin({
name: "FriendsSince", name: "FriendsSince",
@ -27,43 +26,28 @@ export default definePlugin({
{ {
find: ".PANEL}),nicknameIcons", find: ".PANEL}),nicknameIcons",
replacement: { replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/, match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id)}\)}\)/,
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})" replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})"
} }
}, },
// User Profile Modal // User Profile Modal
{ {
find: "action:\"PRESS_APP_CONNECTION\"", find: "action:\"PRESS_APP_CONNECTION\"",
replacement: { replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/, match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:false})," replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false}),"
} }
} }
], ],
getFriendSince(userId: string) { FriendsSinceComponent: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
try {
if (!RelationshipStore.isFriend(userId)) return null;
return RelationshipStore.getSince(userId);
} catch (err) {
new Logger("FriendsSince").error(err);
return null;
}
},
friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
if (!RelationshipStore.isFriend(userId)) return null; if (!RelationshipStore.isFriend(userId)) return null;
const friendsSince = RelationshipStore.getSince(userId); const friendsSince = RelationshipStore.getSince(userId);
if (!friendsSince) return null; if (!friendsSince) return null;
return ( return (
<section className={section.section}> <Section heading="Friends Since">
<Heading variant="text-xs/semibold" style={isSidebar ? {} : { color: "var(--header-secondary)" }}>
Friends Since
</Heading>
{ {
isSidebar ? ( isSidebar ? (
<Text variant="text-sm/normal"> <Text variant="text-sm/normal">
@ -91,8 +75,7 @@ export default definePlugin({
</div> </div>
) )
} }
</Section>
</section>
); );
}, { noop: true }), }, { noop: true }),
}); });

View file

@ -0,0 +1,5 @@
# FullSearchContext
Makes the message context menu in message search results have all options you'd expect.
![](https://github.com/user-attachments/assets/472d1327-3935-44c7-b7c4-0978b5348550)

View file

@ -0,0 +1,112 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { NoopComponent } from "@utils/react";
import definePlugin from "@utils/types";
import { filters, findByPropsLazy, waitFor } from "@webpack";
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
interface CopyIdMenuItemProps {
id: string;
label: string;
}
let CopyIdMenuItem: (props: CopyIdMenuItemProps) => React.ReactElement | null = NoopComponent;
waitFor(filters.componentByCode('"devmode-copy-id-".concat'), m => CopyIdMenuItem = m);
function MessageMenu({ message, channel, onHeightUpdate }) {
const canReport = message.author &&
!(message.author.id === UserStore.getCurrentUser().id || message.author.system);
return useMessageMenu({
navId: "message-actions",
ariaLabel: getIntlMessage("MESSAGE_UTILITIES_A11Y_LABEL"),
message,
channel,
canReport,
onHeightUpdate,
onClose: () => ContextMenuApi.closeContextMenu(),
textSelection: "",
favoriteableType: null,
favoriteableId: null,
favoriteableName: null,
itemHref: void 0,
itemSrc: void 0,
itemSafeSrc: void 0,
itemTextContent: void 0,
isFullSearchContextMenu: true
});
}
interface MessageActionsProps {
message: Message;
isFullSearchContextMenu?: boolean;
}
const contextMenuPatch: NavContextMenuPatchCallback = (children, props: MessageActionsProps) => {
if (props?.isFullSearchContextMenu == null) return;
const group = findGroupChildrenByChildId("devmode-copy-id", children, true);
group?.push(
CopyIdMenuItem({ id: props.message.author.id, label: getIntlMessage("COPY_ID_AUTHOR") })
);
};
migratePluginSettings("FullSearchContext", "SearchReply");
export default definePlugin({
name: "FullSearchContext",
description: "Makes the message context menu in message search results have all options you'd expect",
authors: [Devs.Ven, Devs.Aria],
patches: [{
find: "onClick:this.handleMessageClick,",
replacement: {
match: /this(?=\.handleContextMenu\(\i,\i\))/,
replace: "$self"
}
}],
handleContextMenu(event: React.MouseEvent, message: Message) {
const channel = ChannelStore.getChannel(message.channel_id);
if (!channel) return;
event.stopPropagation();
ContextMenuApi.openContextMenu(event, contextMenuProps =>
<MessageMenu
message={message}
channel={channel}
onHeightUpdate={contextMenuProps.onHeightUpdate}
/>
);
},
contextMenus: {
"message-actions": contextMenuPatch
}
});

View file

@ -92,7 +92,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED", find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: { replacement: {
match: /this\.renderNameZone\(\).+?children:\[/, match: /this\.renderNameZone\(\).+?children:\[/,
replace: "$&$self.GameActivityToggleButton()," replace: "$&$self.GameActivityToggleButton(),"

View file

@ -166,7 +166,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "Messages.WELCOME_CTA_LABEL", find: "#{intl::WELCOME_CTA_LABEL}",
replacement: { replacement: {
match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/, match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1)," replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"

View file

@ -0,0 +1,13 @@
# IgnoreActivities
Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings.
![](https://github.com/user-attachments/assets/f0c19060-0ecf-4f1c-8165-a5aa40143c82)
![](https://github.com/user-attachments/assets/73c3fa7a-5b90-41ee-a4d6-91fa76458b74)
![](https://github.com/user-attachments/assets/1ab3fe73-3911-48d1-8a08-e976af614b41)
The activity stays showing as a detected game even if ignored, differently from the stock Toggle Detection button from Discord:
![](https://github.com/user-attachments/assets/08ea60c3-3a31-42de-ae4c-7535fbf1b45a)

View file

@ -26,6 +26,11 @@ interface IgnoredActivity {
type: ActivitiesTypes; type: ActivitiesTypes;
} }
const enum FilterMode {
Whitelist,
Blacklist
}
const RunningGameStore = findStoreLazy("RunningGameStore"); const RunningGameStore = findStoreLazy("RunningGameStore");
const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!; const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!;
@ -70,14 +75,17 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity); if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex); else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
// Trigger activities recalculation recalculateActivities();
}
function recalculateActivities() {
ShowCurrentGame.updateSetting(old => old); ShowCurrentGame.updateSetting(old => old);
} }
function ImportCustomRPCComponent() { function ImportCustomRPCComponent() {
return ( return (
<Flex flexDirection="column"> <Flex flexDirection="column">
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Import the application id of the CustomRPC plugin to the allowed list</Forms.FormText> <Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText>
<div> <div>
<Button <Button
onClick={() => { onClick={() => {
@ -86,7 +94,7 @@ function ImportCustomRPCComponent() {
return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE); return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE);
} }
const isAlreadyAdded = allowedIdsPushID?.(id); const isAlreadyAdded = idsListPushID?.(id);
if (isAlreadyAdded) { if (isAlreadyAdded) {
showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE); showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE);
} }
@ -99,39 +107,39 @@ function ImportCustomRPCComponent() {
); );
} }
let allowedIdsPushID: ((id: string) => boolean) | null = null; let idsListPushID: ((id: string) => boolean) | null = null;
function AllowedIdsComponent(props: { setValue: (value: string) => void; }) { function IdsListComponent(props: { setValue: (value: string) => void; }) {
const [allowedIds, setAllowedIds] = useState<string>(settings.store.allowedIds ?? ""); const [idsList, setIdsList] = useState<string>(settings.store.idsList ?? "");
allowedIdsPushID = (id: string) => { idsListPushID = (id: string) => {
const currentIds = new Set(allowedIds.split(",").map(id => id.trim()).filter(Boolean)); const currentIds = new Set(idsList.split(",").map(id => id.trim()).filter(Boolean));
const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false); const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false);
const ids = Array.from(currentIds).join(", "); const ids = Array.from(currentIds).join(", ");
setAllowedIds(ids); setIdsList(ids);
props.setValue(ids); props.setValue(ids);
return isAlreadyAdded; return isAlreadyAdded;
}; };
useEffect(() => () => { useEffect(() => () => {
allowedIdsPushID = null; idsListPushID = null;
}, []); }, []);
function handleChange(newValue: string) { function handleChange(newValue: string) {
setAllowedIds(newValue); setIdsList(newValue);
props.setValue(newValue); props.setValue(newValue);
} }
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle tag="h3">Allowed List</Forms.FormTitle> <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Comma separated list of activity IDs to allow (Useful for allowing RPC activities and CustomRPC)</Forms.FormText> <Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>
<TextInput <TextInput
type="text" type="text"
value={allowedIds} value={idsList}
onChange={handleChange} onChange={handleChange}
placeholder="235834946571337729, 343383572805058560" placeholder="235834946571337729, 343383572805058560"
/> />
@ -145,40 +153,62 @@ const settings = definePluginSettings({
description: "", description: "",
component: () => <ImportCustomRPCComponent /> component: () => <ImportCustomRPCComponent />
}, },
allowedIds: { listMode: {
type: OptionType.SELECT,
description: "Change the mode of the filter list",
options: [
{
label: "Whitelist",
value: FilterMode.Whitelist,
default: true
},
{
label: "Blacklist",
value: FilterMode.Blacklist,
}
],
onChange: recalculateActivities
},
idsList: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "", description: "",
default: "", default: "",
onChange(newValue: string) { onChange(newValue: string) {
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean)); const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
settings.store.allowedIds = Array.from(ids).join(", "); settings.store.idsList = Array.from(ids).join(", ");
recalculateActivities();
}, },
component: props => <AllowedIdsComponent setValue={props.setValue} /> component: props => <IdsListComponent setValue={props.setValue} />
}, },
ignorePlaying: { ignorePlaying: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Ignore all playing activities (These are usually game and RPC activities)", description: "Ignore all playing activities (These are usually game and RPC activities)",
default: false default: false,
onChange: recalculateActivities
}, },
ignoreStreaming: { ignoreStreaming: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Ignore all streaming activities", description: "Ignore all streaming activities",
default: false default: false,
onChange: recalculateActivities
}, },
ignoreListening: { ignoreListening: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Ignore all listening activities (These are usually spotify activities)", description: "Ignore all listening activities (These are usually spotify activities)",
default: false default: false,
onChange: recalculateActivities
}, },
ignoreWatching: { ignoreWatching: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Ignore all watching activities", description: "Ignore all watching activities",
default: false default: false,
onChange: recalculateActivities
}, },
ignoreCompeting: { ignoreCompeting: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Ignore all competing activities (These are normally special game activities)", description: "Ignore all competing activities (These are normally special game activities)",
default: false default: false,
onChange: recalculateActivities
} }
}).withPrivateSettings<{ }).withPrivateSettings<{
ignoredActivities: IgnoredActivity[]; ignoredActivities: IgnoredActivity[];
@ -189,8 +219,8 @@ function getIgnoredActivities() {
} }
function isActivityTypeIgnored(type: number, id?: string) { function isActivityTypeIgnored(type: number, id?: string) {
if (id && settings.store.allowedIds.includes(id)) { if (id && settings.store.idsList.includes(id)) {
return false; return settings.store.listMode === FilterMode.Blacklist;
} }
switch (type) { switch (type) {
@ -206,15 +236,15 @@ function isActivityTypeIgnored(type: number, id?: string) {
export default definePlugin({ export default definePlugin({
name: "IgnoreActivities", name: "IgnoreActivities",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz, Devs.Kylie],
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below",
dependencies: ["UserSettingsAPI"], dependencies: ["UserSettingsAPI"],
settings, settings,
patches: [ patches: [
{ {
find: '="LocalActivityStore",', find: '"LocalActivityStore"',
replacement: [ replacement: [
{ {
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/, match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/,
@ -223,19 +253,20 @@ export default definePlugin({
] ]
}, },
{ {
find: '="ActivityTrackingStore",', find: '"ActivityTrackingStore"',
replacement: { replacement: {
match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/, match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/,
replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));` replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`
} }
}, },
{ {
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
replacement: { replacement: {
match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/, match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
} }
}, },
// Discord has 2 different components for activities. Currently, the last is the one being used
{ {
find: ".activityTitleText,variant", find: ".activityTitleText,variant",
replacement: { replacement: {
@ -244,15 +275,21 @@ export default definePlugin({
}, },
}, },
{ {
find: ".activityCardDetails,children", find: ".promotedLabelWrapperNonBanner,children",
replacement: { replacement: {
match: /\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),/, match: /\.appDetailsHeaderContainer.+?children:\i.*?}\),(?<=application:(\i).+?)/,
replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),`
} }
} }
], ],
async start() { async start() {
// Migrate allowedIds
if (Settings.plugins.IgnoreActivities.allowedIds) {
settings.store.idsList = Settings.plugins.IgnoreActivities.allowedIds;
delete Settings.plugins.IgnoreActivities.allowedIds; // Remove allowedIds
}
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities"); const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
if (oldIgnoredActivitiesData != null) { if (oldIgnoredActivitiesData != null) {
@ -279,7 +316,7 @@ export default definePlugin({
if (isActivityTypeIgnored(props.type, props.application_id)) return false; if (isActivityTypeIgnored(props.type, props.application_id)) return false;
if (props.application_id != null) { if (props.application_id != null) {
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || settings.store.allowedIds.includes(props.application_id); return !getIgnoredActivities().some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
} else { } else {
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
if (exePath) { if (exePath) {

View file

@ -156,14 +156,10 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "Messages.OPEN_IN_BROWSER", find: ".contain,SCALE_DOWN:",
replacement: { replacement: {
// there are 2 image thingies. one for carosuel and one for the single image. match: /\.slide,\i\),/g,
// so thats why i added global flag. replace: `$&id:"${ELEMENT_ID}",`
// also idk if this patch is good, should it be more specific?
// https://regex101.com/r/xfvNvV/1
match: /return.{1,200}\.wrapper.{1,200}src:\i,/g,
replace: `$&id: '${ELEMENT_ID}',`
} }
}, },
@ -183,15 +179,13 @@ export default definePlugin({
{ {
match: /componentWillUnmount\(\){/, match: /componentWillUnmount\(\){/,
replace: "$&$self.unMountMagnifier();" replace: "$&$self.unMountMagnifier();"
},
{
match: /componentDidUpdate\(\i\){/,
replace: "$&$self.updateMagnifier(this);"
} }
] ]
},
{
find: ".carouselModal",
replacement: {
match: /(?<=\.carouselModal.{0,100}onClick:)\i,/,
replace: "()=>{},"
}
} }
], ],
@ -226,6 +220,11 @@ export default definePlugin({
} }
}, },
updateMagnifier(instance) {
this.unMountMagnifier();
this.renderMagnifier(instance);
},
unMountMagnifier() { unMountMagnifier() {
this.root?.unmount(); this.root?.unmount();
this.currentMagnifierElement = null; this.currentMagnifierElement = null;

View file

@ -23,12 +23,3 @@
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */ /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
} }
/* make the carousel take up less space so we can click the backdrop and exit out of it */
[class*="modalCarouselWrapper_"] {
top: 0 !important;
}
[class*="carouselModal_"] {
height: 0 !important;
}

View file

@ -32,7 +32,7 @@ export default definePlugin({
patches: [ patches: [
// Counts header // Counts header
{ {
find: ".FRIENDS_ALL_HEADER", find: "#{intl::FRIENDS_ALL_HEADER}",
replacement: { replacement: {
match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/, match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/,
replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED' replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED'
@ -48,9 +48,9 @@ export default definePlugin({
}, },
// Sections header // Sections header
{ {
find: ".FRIENDS_SECTION_ONLINE", find: "#{intl::FRIENDS_SECTION_ONLINE}",
replacement: { replacement: {
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.Messages\.BLOCKED\}\)/, match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&" replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
}, },
}, },
@ -117,7 +117,7 @@ export default definePlugin({
wrapSort(comparator: Function, row: any) { wrapSort(comparator: Function, row: any) {
return row.type === 5 return row.type === 5
? -UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0 ? -(UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0)
: comparator(row); : comparator(row);
}, },

View file

@ -66,14 +66,14 @@ export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
patch.replacement = [patch.replacement]; patch.replacement = [patch.replacement];
} }
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());
if (IS_REPORTER) { if (IS_REPORTER) {
patch.replacement.forEach(r => { patch.replacement.forEach(r => {
delete r.predicate; delete r.predicate;
}); });
} }
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());
patches.push(patch); patches.push(patch);
} }
@ -105,6 +105,11 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
settings[d].enabled = true; settings[d].enabled = true;
dep.isDependency = true; dep.isDependency = true;
}); });
if (p.commands?.length) {
Plugins.CommandsAPI.isDependency = true;
settings.CommandsAPI.enabled = true;
}
} }
for (const p of pluginsValues) { for (const p of pluginsValues) {

View file

@ -111,7 +111,7 @@ export default definePlugin({
patches: [ patches: [
{ {
// Indicator // Indicator
find: ".Messages.MESSAGE_EDITED,", find: "#{intl::MESSAGE_EDITED}",
replacement: { replacement: {
match: /let\{className:\i,message:\i[^}]*\}=(\i)/, match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&" replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"

View file

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common"; import { ChannelRouter, ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
export interface LogoutEvent { export interface LogoutEvent {
type: "LOGOUT"; type: "LOGOUT";
@ -40,16 +40,21 @@ interface PreviousChannel {
let isSwitchingAccount = false; let isSwitchingAccount = false;
let previousCache: PreviousChannel | undefined; let previousCache: PreviousChannel | undefined;
function attemptToNavigateToChannel(guildId: string | null, channelId: string) {
if (!ChannelStore.hasChannel(channelId)) return;
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}`);
}
export default definePlugin({ export default definePlugin({
name: "KeepCurrentChannel", name: "KeepCurrentChannel",
description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.", description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
patches: [
{
find: '"Switching accounts"',
replacement: {
match: /goHomeAfterSwitching:\i/,
replace: "goHomeAfterSwitching:!1"
}
}
],
flux: { flux: {
LOGOUT(e: LogoutEvent) { LOGOUT(e: LogoutEvent) {
({ isSwitchingAccount } = e); ({ isSwitchingAccount } = e);
@ -59,8 +64,13 @@ export default definePlugin({
if (!isSwitchingAccount) return; if (!isSwitchingAccount) return;
isSwitchingAccount = false; isSwitchingAccount = false;
if (previousCache?.channelId) if (previousCache?.channelId) {
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId); if (ChannelStore.hasChannel(previousCache.channelId)) {
ChannelRouter.transitionToChannel(previousCache.channelId);
} else {
NavigationRouter.transitionToGuild("@me");
}
}
}, },
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) { async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
@ -84,7 +94,7 @@ export default definePlugin({
await DataStore.set("KeepCurrentChannel_previousData", previousCache); await DataStore.set("KeepCurrentChannel_previousData", previousCache);
} else if (previousCache.channelId) { } else if (previousCache.channelId) {
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId); ChannelRouter.transitionToChannel(previousCache.channelId);
} }
} }
}); });

View file

@ -62,7 +62,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".LOADING_DID_YOU_KNOW", find: "#{intl::LOADING_DID_YOU_KNOW}",
replacement: [ replacement: [
{ {
match: /"_loadingText".+?(?=(\i)\[.{0,10}\.random)/, match: /"_loadingText".+?(?=(\i)\[.{0,10}\.random)/,

View file

@ -14,7 +14,7 @@ import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id; const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id;
const totalCount = useStateFromStores( const totalCount = useStateFromStores(
[GuildMemberCountStore], [GuildMemberCountStore],
@ -33,7 +33,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
const threadGroups = useStateFromStores( const threadGroups = useStateFromStores(
[ThreadMemberListStore], [ThreadMemberListStore],
() => ThreadMemberListStore.getMemberListSections(currentChannel.id) () => ThreadMemberListStore.getMemberListSections(currentChannel?.id)
); );
if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {

View file

@ -15,8 +15,8 @@ export const OnlineMemberCountStore = proxyLazy(() => {
const onlineMemberMap = new Map<string, number>(); const onlineMemberMap = new Map<string, number>();
class OnlineMemberCountStore extends Flux.Store { class OnlineMemberCountStore extends Flux.Store {
getCount(guildId: string) { getCount(guildId?: string) {
return onlineMemberMap.get(guildId); return onlineMemberMap.get(guildId!);
} }
async _ensureCount(guildId: string) { async _ensureCount(guildId: string) {
@ -25,8 +25,8 @@ export const OnlineMemberCountStore = proxyLazy(() => {
await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id);
} }
ensureCount(guildId: string) { ensureCount(guildId?: string) {
if (onlineMemberMap.has(guildId)) return; if (!guildId || onlineMemberMap.has(guildId)) return;
preloadQueue.push(() => preloadQueue.push(() =>
this._ensureCount(guildId) this._ensureCount(guildId)

View file

@ -28,12 +28,12 @@ import { FluxStore } from "@webpack/types";
import { MemberCount } from "./MemberCount"; import { MemberCount } from "./MemberCount";
export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; }; export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId?: string): number | null; };
export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; getProps(guildId?: string, channelId?: string): { groups: { count: number; id: string; }[]; };
}; };
export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & { export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & {
getMemberListSections(channelId: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; };
}; };
@ -74,7 +74,7 @@ export default definePlugin({
{ {
find: ".invitesDisabledTooltip", find: ".invitesDisabledTooltip",
replacement: { replacement: {
match: /\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100}(?=])/, match: /#{intl::VIEW_AS_ROLES_MENTIONS_WARNING}.{0,100}(?=])/,
replace: "$&,$self.renderTooltip(arguments[0].guild)" replace: "$&,$self.renderTooltip(arguments[0].guild)"
}, },
predicate: () => settings.store.toolTip predicate: () => settings.store.toolTip

View file

@ -1,5 +1,6 @@
# MentionAvatars # MentionAvatars
Shows user avatars inside mentions Shows user avatars and role icons inside mentions
![](https://github.com/user-attachments/assets/fc76ea47-5e19-4063-a592-c57785a75cc7) ![](https://github.com/user-attachments/assets/fc76ea47-5e19-4063-a592-c57785a75cc7)
![](https://github.com/user-attachments/assets/76c4c3d9-7cde-42db-ba84-903cbb40c163)

View file

@ -10,21 +10,42 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { SelectedGuildStore, useState } from "@webpack/common"; import { GuildStore, SelectedGuildStore, useState } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
const settings = definePluginSettings({ const settings = definePluginSettings({
showAtSymbol: { showAtSymbol: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Whether the the @ symbol should be displayed", description: "Whether the the @ symbol should be displayed on user mentions",
default: true default: true
} }
}); });
function DefaultRoleIcon() {
return (
<svg
className="vc-mentionAvatars-icon vc-mentionAvatars-role-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M14 8.00598C14 10.211 12.206 12.006 10 12.006C7.795 12.006 6 10.211 6 8.00598C6 5.80098 7.794 4.00598 10 4.00598C12.206 4.00598 14 5.80098 14 8.00598ZM2 19.006C2 15.473 5.29 13.006 10 13.006C14.711 13.006 18 15.473 18 19.006V20.006H2V19.006Z"
/>
<path
d="M20.0001 20.006H22.0001V19.006C22.0001 16.4433 20.2697 14.4415 17.5213 13.5352C19.0621 14.9127 20.0001 16.8059 20.0001 19.006V20.006Z"
/>
<path
d="M14.8834 11.9077C16.6657 11.5044 18.0001 9.9077 18.0001 8.00598C18.0001 5.96916 16.4693 4.28218 14.4971 4.0367C15.4322 5.09511 16.0001 6.48524 16.0001 8.00598C16.0001 9.44888 15.4889 10.7742 14.6378 11.8102C14.7203 11.8418 14.8022 11.8743 14.8834 11.9077Z"
/>
</svg>
);
}
export default definePlugin({ export default definePlugin({
name: "MentionAvatars", name: "MentionAvatars",
description: "Shows user avatars inside mentions", description: "Shows user avatars and role icons inside mentions",
authors: [Devs.Ven], authors: [Devs.Ven, Devs.SerStars],
patches: [{ patches: [{
find: ".USER_MENTION)", find: ".USER_MENTION)",
@ -32,6 +53,13 @@ export default definePlugin({
match: /children:"@"\.concat\((null!=\i\?\i:\i)\)(?<=\.useName\((\i)\).+?)/, match: /children:"@"\.concat\((null!=\i\?\i:\i)\)(?<=\.useName\((\i)\).+?)/,
replace: "children:$self.renderUsername({username:$1,user:$2})" replace: "children:$self.renderUsername({username:$1,user:$2})"
} }
},
{
find: ".ROLE_MENTION)",
replacement: {
match: /children:\[\i&&.{0,50}\.RoleDot.{0,300},\i(?=\])/,
replace: "$&,$self.renderRoleIcon(arguments[0])"
}
}], }],
settings, settings,
@ -47,12 +75,31 @@ export default definePlugin({
onMouseEnter={() => setIsHovering(true)} onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)} onMouseLeave={() => setIsHovering(false)}
> >
<img src={user.getAvatarURL(SelectedGuildStore.getGuildId(), 16, isHovering)} className="vc-mentionAvatars-avatar" /> <img
src={user.getAvatarURL(SelectedGuildStore.getGuildId(), 16, isHovering)}
className="vc-mentionAvatars-icon"
style={{ borderRadius: "50%" }}
/>
{getUsernameString(username)} {getUsernameString(username)}
</span> </span>
); );
}, { noop: true }) }, { noop: true }),
renderRoleIcon: ErrorBoundary.wrap(({ roleId, guildId }: { roleId: string, guildId: string; }) => {
// Discord uses Role Mentions for uncached users because .... idk
if (!roleId) return null;
const role = GuildStore.getRole(guildId, roleId);
if (!role?.icon) return <DefaultRoleIcon />;
return (
<img
className="vc-mentionAvatars-icon vc-mentionAvatars-role-icon"
src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${roleId}/${role.icon}.webp?size=24&quality=lossless`}
/>
);
}),
}); });
function getUsernameString(username: string) { function getUsernameString(username: string) {

View file

@ -1,8 +1,16 @@
.vc-mentionAvatars-avatar { .vc-mentionAvatars-icon {
vertical-align: middle; vertical-align: middle;
width: 1em !important; /* insane discord sets width: 100% in channel topic */ width: 1em !important; /* insane discord sets width: 100% in channel topic */
height: 1em; height: 1em;
margin: 0 4px 0.2rem 2px; margin: 0 4px 0.2rem 2px;
border-radius: 50%;
box-sizing: border-box; box-sizing: border-box;
} }
.vc-mentionAvatars-role-icon {
margin: 0 2px 0.2rem 4px;
}
/** don't display inside the ServerInfo modal owner mention */
.vc-gp-owner .vc-mentionAvatars-icon {
display: none;
}

View file

@ -74,7 +74,7 @@ export default definePlugin({
if (msg.deleted === true) return; if (msg.deleted === true) return;
if (isMe) { if (isMe) {
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return; if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content); MessageActions.startEditMessage(channel.id, msg.id, msg.content);
event.preventDefault(); event.preventDefault();

View file

@ -24,12 +24,12 @@ import { Settings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy"; import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common"; import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
import overlayStyle from "./deleteStyleOverlay.css?managed"; import overlayStyle from "./deleteStyleOverlay.css?managed";
@ -178,7 +178,7 @@ export default definePlugin({
isEdited={true} isEdited={true}
isInline={false} isInline={false}
> >
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span> <span className={styles.edited}>{" "}({getIntlMessage("MESSAGE_EDITED")})</span>
</Timestamp> </Timestamp>
</div> </div>
))} ))}
@ -312,9 +312,35 @@ export default definePlugin({
); );
}, },
Messages: proxyLazy(() => ({ Messages: {
DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}") // DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}")
})), // TODO: find a better way to generate intl messages
DELETED_MESSAGE_COUNT: () => ({
ast: [[
6,
"count",
{
"=0": ["No deleted messages"],
one: [
[
1,
"count"
],
" deleted message"
],
other: [
[
1,
"count"
],
" deleted messages"
]
},
0,
"cardinal"
]]
})
},
patches: [ patches: [
{ {
@ -445,7 +471,7 @@ export default definePlugin({
{ {
// Message content renderer // Message content renderer
find: "Messages.MESSAGE_EDITED,\")\"", find: "#{intl::MESSAGE_EDITED}",
replacement: [ replacement: [
{ {
// Render editHistory in the deepest div for message content // Render editHistory in the deepest div for message content
@ -497,7 +523,7 @@ export default definePlugin({
}, },
{ {
// Message group rendering // Message group rendering
find: "Messages.NEW_MESSAGES_ESTIMATED_WITH_DATE", find: "#{intl::NEW_MESSAGES_ESTIMATED_WITH_DATE}",
replacement: [ replacement: [
{ {
match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\|\|/, match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\|\|/,

View file

@ -82,7 +82,6 @@ export default definePlugin({
default: true default: true
} }
}, },
dependencies: ["CommandsAPI"],
async start() { async start() {
for (const tag of await getTags()) createTagCommand(tag); for (const tag of await getTags()) createTagCommand(tag);

View file

@ -33,7 +33,6 @@ export default definePlugin({
name: "MoreCommands", name: "MoreCommands",
description: "echo, lenny, mock", description: "echo, lenny, mock",
authors: [Devs.Arjix, Devs.echo, Devs.Samu], authors: [Devs.Arjix, Devs.echo, Devs.Samu],
dependencies: ["CommandsAPI"],
commands: [ commands: [
{ {
name: "echo", name: "echo",

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