Compare commits

...
Sign in to create a new pull request.

165 commits

Author SHA1 Message Date
bd1e45adc4
Add AntiTessie plugin 2025-02-01 22:55:55 +02:00
Nuckyz
4f5ebec4bb
FullUserInChatbox: Fix empty mention when user is unknown
Fixes #3190
2025-01-31 16:24:07 -03:00
jamesbt365
7b9f0a36ba
IrcColors: Allow coloring only users with no color and DMs only (#3186) 2025-01-31 18:54:51 +00:00
Nuckyz
fc4e95806d
Fix ImplicitRelationships and NotificationsVolume (#3184)
Also simplifies MessageEventsAPI patch
2025-01-31 17:55:40 +00:00
Nuckyz
1eff1a02bd
IrcColors: Fix causing react errors sometimes 2025-01-30 16:02:42 -03:00
Nuckyz
414539f45e
Add more FIXME and explain better TODOS for migrations 2025-01-30 16:01:57 -03:00
Nuckyz
b2d5c00a23
Delete NoScreensharePreview ~now a stock feature 2025-01-30 16:01:36 -03:00
Nuckyz
a492f7657b
MessageEventsAPI: Fix for upcoming change 2025-01-30 15:15:40 -03:00
Nuckyz
e280ed2683
Ignore more modules on webpack searching 2025-01-30 14:54:41 -03:00
Sqaaakoi
8fccda4a24
WhoReacted, TypingIndicator: Fix triggering other actions (#3161)
Prevents typing in message user box from activating parent click handlers
Fixes https://github.com/Vendicated/Vencord/issues/3128
2025-01-29 22:41:32 -03:00
jamesbt365
68662c9625
QuickReply: Fix showing toggle mention in guilds (#3181) 2025-01-30 01:36:56 +00:00
sadan4
7d45862023
Fix IgnoreActivities and AlwaysAnimate for canary (#3182) 2025-01-29 22:28:11 -03:00
Vendicated
240195f9bf
Bump to v1.11.3 2025-01-29 20:44:15 +01:00
Nuckyz
5ad35c36e4
Make Option.Component not require description
Also correctly infers the type from "default"
2025-01-29 14:34:44 -03:00
Nuckyz
a2213d4feb
Re-export Modals 2025-01-29 13:42:24 -03:00
Nuckyz
81dda2ce33
Make more finds not depend on non mangled keys 2025-01-29 11:52:49 -03:00
Nuckyz
7415367d6c
Add missing MenuSearchControl webpack find 2025-01-29 10:09:05 -03:00
Nuckyz
33d4f13a24
Fix everything broken by recent Discord update (#3177)
Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2025-01-29 01:04:36 -03:00
Nuckyz
cdc756193e
Settings API: Fix erroring if plugin settings don't exist 2025-01-28 01:13:36 -03:00
jamesbt365
f43baddc55
NoBlockedMessages: Add ignored messages (#3126) 2025-01-27 22:57:16 -03:00
Nuckyz
21ded874a3
Settings API: Add utility to migrate a setting 2025-01-27 21:12:58 -03:00
Vendicated
ea1e96185b
MessageLatency: ErrorBoundary should be noop 2025-01-27 20:54:16 +01:00
Vendicated
ceba9776c4
Delete MoreUserTags for now because it's unstable
This plugin is written in a way that makes it susceptible to crashes.
This is not the first time it has caused crashes and will not be the last.
A rewrite is necessary to make it more robust
2025-01-27 20:44:54 +01:00
Nuckyz
c4f8221f75
IrcColors: Make lightness apply without restart 2025-01-27 14:30:11 -03:00
Vendicated
3350922c09
LastFmRPC: Add option to hide if there is another presence
closes #2866

Co-Authored-By: 54ac <me@54ac.ovh>
2025-01-27 04:25:37 +01:00
vishnyanetchereshnya
f29662c5b3
feat(ViewRaw): add View Role option (#3083)
Co-authored-by: v <vendicated@riseup.net>
2025-01-27 04:12:26 +01:00
Grzesiek11
cf28c65374
Add IrcColors plugin (#2048)
Co-authored-by: V <vendicated@riseup.net>
2025-01-27 03:34:00 +01:00
Suffocate
87cb1fd930
Fix top level settings notifying global listeners (#3166) 2025-01-26 15:32:34 +00:00
sadan4
aac5242dc8
ImageZoom: Fix incorrectly adding context menu in some places (#3150) 2025-01-24 21:15:56 -03:00
sadan4
4036fbab92
ConsoleJanitor: Remove old patch and add getLastCrash (#3151) 2025-01-25 00:01:12 +00:00
jamesbt365
79cbfe96c8
HideAttachments, UnsupressEmbeds: Work with forwarded messages (#2928) 2025-01-24 20:56:39 -03:00
jamesbt365
7ee70e831a
MessageLogger: Make collapseDeleted require a restart (#2923) 2025-01-24 23:46:47 +00:00
jamesbt365
e45b867ff0
ServerInfo: Add Ignored Users tab (#3127) 2025-01-24 23:42:05 +00:00
Nuckyz
78c2f0d61a
Fix calling option onChange listeners for legacy settings 2025-01-24 00:43:33 -03:00
Nuckyz
72ec5e2023
Merge remote-tracking branch main into dev 2025-01-23 21:03:54 -03:00
Vendicated
43501bad07
bump to v1.11.2 2025-01-24 00:33:10 +01:00
Nuckyz
e000a947a3
Optimize slow patches 2025-01-22 23:10:43 -03:00
Nuckyz
5c8ba6e542
Settings API: add support for custom objects / arrays (#3154) 2025-01-23 01:51:11 +00:00
v
317121fc08
Replace API add/remove funcs with methods in plugin definition (#3028) 2025-01-23 01:48:44 +00:00
Nuckyz
30647b6bd9
Fix patches with duplicate finds 2025-01-22 22:44:52 -03:00
Nuckyz
ed99ae7f23
ShowHiddenThings: Fix showing ModView 2025-01-22 22:22:43 -03:00
AutumnVN
17f1ef275e
CustomRPC: improve rich presence preview & UX (#3159)
Co-authored-by: Vendicated <vendicated@riseup.net>
2025-01-23 01:33:11 +01:00
Nuckyz
ea0182a194
Fix BetterUploadButton & FixImagesQuality 2025-01-22 18:03:51 -03:00
Nuckyz
9e9d71d014
AccountPanelServerProfile: Fix buttons unusable and request spam 2025-01-22 17:40:47 -03:00
Vendicated
9bb983d40c
SortFriendRequests: improve formatting & display 2025-01-22 20:01:33 +01:00
Nuckyz
5312514de6
Bump to 1.11.0 2025-01-22 15:09:21 -03:00
Nuckyz
8346dba324
SortFriendRequests: Fix showing dates 2025-01-22 15:07:33 -03:00
Vendicated
47315b0eba
fix plugins modifying message content 2025-01-22 18:19:04 +01:00
sadan4
a60af65b6d
RevealAllSpoilers: Fix error on <C-S-Click> (#3149) 2025-01-18 18:05:06 -03:00
Sqaaakoi
88e3bc037d
ConsoleShortcuts: Set FluxStore toStringTag to store name (#3144) 2025-01-18 20:05:20 +00:00
sadan4
19361ef790
BadgeApi, AccountPanelServerProfile: Fix not working (#3147) 2025-01-18 16:52:35 -03:00
Nuckyz
c8f4ce9785
Fix PinDMs and BetterSettings eager load 2025-01-15 11:17:40 -03:00
Nuckyz
a53257634e
Fix Reporter action 2025-01-11 23:43:25 -03:00
Nuckyz
3243120baa
Reapply "MessagePopoverAPI: Fix buttons not appearing"
Actually applying the fix this time
2025-01-10 01:07:58 -03:00
Nuckyz
4ab297c9e3
Revert "MessagePopoverAPI: Fix buttons not appearing"
I didn't mean to push this yet
2025-01-09 06:22:07 -03:00
sadan4
154f371b14
BetterFolders: Fix for Discord Canary build (#3133) 2025-01-09 09:20:15 +00:00
Nuckyz
263a96c310
MessagePopoverAPI: Fix buttons not appearing 2025-01-08 19:41:13 -03:00
Nuckyz
5a77149b26
PinDMs: Fix real time updating when changing settings
again...
2025-01-08 19:06:09 -03:00
sadan4
2707b10021
BetterFolders: Fix dedicated sidebar (#3129) 2025-01-08 09:14:59 +00:00
v
7be3a40b7c
Add React eslint & update depencenies (#3090)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-01-04 03:24:50 -03:00
sadan4
16a1c44947
PictureInPicture: Fix picture in picture button (#3120) 2025-01-04 02:21:50 -03:00
sadan4
3af06edb95
ConsoleShortcuts: Add openModal and openModalLazy (#3118) 2025-01-04 05:01:58 +00:00
nin0dev
34629307dd
SpotifyControls: Setting to restart playing song if playtime >3s (#3103) 2025-01-04 01:54:40 -03:00
sadan4
20ed7dc96b
new plugin FullUserInChatbox (#2766) 2024-12-30 02:07:26 -03:00
sadan4
0fd76ab15a
NoUnblockToJump: Also allow jump for ignored users (#3110) 2024-12-30 02:28:22 +00:00
fae
0e813e78d0
OpenInApp: Add support for geo.music.apple.com links (#3101) 2024-12-30 02:24:29 +00:00
Sqaaakoi
79e2cb15f1
QuickReply: Prevent caret from moving when selecting message (#3104) 2024-12-29 23:22:10 -03:00
Nuckyz
cca5d7dc09
ShowHiddenThings: Discovery filter bypass is patched
Filtering is now done server-side in Discord
2024-12-21 20:55:41 -03:00
Vendicated
9ccc74bde3
use correct prodversion in chrome extension installer 2024-12-20 15:06:19 +01:00
Nuckyz
48a9aef2eb
PinDMs: Fix real time updating when changing settings 2024-12-16 19:36:15 -03:00
Nuckyz
fc731bc6c8
Bump to 1.10.9 2024-12-14 02:37:15 -03:00
Nuckyz
0a0bd6a713
NoTrack: Make hasClientMods return false 2024-12-14 02:36:16 -03:00
Nuckyz
40a8cf1a85
PinDMs: Fix duplicate channels 2024-12-12 19:46:51 -03:00
Lumap
5f1c5fa370
AppleMusicRichPresence: Fix token fetching Regex (#3071) 2024-12-12 22:40:44 +00:00
sadan4
00c968473e
FavoriteEmojiFirst: Fix sorting emojis (#3074) 2024-12-12 19:35:40 -03:00
sadan4
2dc8c2bf76
Fix multiple plugins for latest Discord update (#3072) 2024-12-12 03:49:21 -03:00
sadan4
464c4a9b61
TypingTweaks: Fix usernames not being colored (#3070) 2024-12-11 01:06:34 -03:00
sadan4
dcfddcbc21
ConsoleJanitor: Add HLJS deprecations (#3062) 2024-12-10 21:37:21 +00:00
Nuckyz
9d3c91e9df
TypingTweaks: Fix plugin 2024-12-10 18:29:15 -03:00
Nuckyz
8d65bcf743
BetterFolders: Fix folder icon setting 2024-12-10 18:09:54 -03:00
Nuckyz
4a5f0838e2
Remove workaround for Devtools theme 2024-12-07 20:32:16 -03:00
Nuckyz
99dc65fe4e
Fix TypingIndicator & CallTimer 2024-12-07 20:31:08 -03:00
Nuckyz
3a339636d1
Remove old plugin migrations 2024-12-07 19:55:37 -03:00
Nuckyz
cea0a3c9d9
NoScreensharePreview: Allow plugin to be turned on/off 2024-12-07 19:15:05 -03:00
Vendicated
a3f5dc39a0
CallTimer: fix crashing on canary 2024-12-07 00:14:08 +01:00
sadan4
df44edd41b
BetterFolders: Fix including open folders in main sidebar (#3064) 2024-12-05 21:07:30 -03:00
Nuckyz
cdfc89b819
NoScreensharePreview: Migrate to stock Discord feature 2024-12-03 22:39:36 -03:00
Nuckyz
8711dd9a4b
WebContextMenus: Fix input bar menu 2024-12-03 21:51:41 -03:00
Sqaaakoi
df454ca952
MutualGroupDMs: Fix in DM sidebar when no mutual friends/servers (#2976) 2024-12-02 23:30:56 -03:00
Etorix
6628624082
CommandHelpers: Make findOption use nullish coalescing (#3047) 2024-12-03 02:16:13 +00:00
Etorix
dd87f360d7
CommandsAPI: Fix spread overwriting omitted subcommand options (#3057) 2024-12-03 02:13:27 +00:00
Nuckyz
3f61fe722d
AlwaysTrust: Fix disabling suspicious file popup 2024-12-02 21:51:29 -03:00
Nuckyz
d70e0f27dc
ServerListIndicators: Account for pending clans count 2024-12-02 20:39:54 -03:00
Nuckyz
0ac80ce9d1
MessagePopoverAPI: Add buttons after quick reactions 2024-12-01 00:10:08 -03:00
Nuckyz
fcece61995
Bump to 1.10.8 2024-11-29 19:26:55 -03:00
sadan4
02f50b161b
ImageZoom: Fix zoom level not saving (#3054) 2024-11-29 22:26:10 +00:00
Vendicated
1150a50355
Badges: fix overflow in Discord's css 2024-11-29 22:46:28 +01:00
v
11321eb693
Update CONTRIBUTING.md
Plugins interacting with specific Discord bots are not allowed.
2024-11-29 16:11:55 +01:00
Nuckyz
60b776669b
WebContextMenus: Fix copy context menu 2024-11-25 20:03:34 -03:00
sadan4
d8df96d1e3
BetterFolders: Fix dedicated sidebar (#3037) 2024-11-25 19:44:29 -03:00
Sqaaakoi
a9d44e3341
PermissionsViewer: Fix permission description tooltip & cleanup (#3040) 2024-11-25 01:14:25 -03:00
Mia Rodriguez
e7a54b0587
SilentTyping: Improve button visual look (#3026) 2024-11-25 00:35:12 +00:00
sadan4
23c9e2ce22
ShowHiddenThings: Allow opening mod view on yourself (#3045) 2024-11-24 21:30:27 -03:00
Etorix
5fb63246ca
Add support for onAuxClick on ChatBarButton (#3043) 2024-11-24 21:25:30 -03:00
samara
2bfeef88ca
Update to newer Discord icons in Vencord Settings (#3029) 2024-11-23 23:23:03 -03:00
Hen
7ca4ea3d13
RoleColorEverywhere: Fix message headers colors (#3036) 2024-11-23 23:16:41 -03:00
Cassie
f8dfe217b1
Remove no-longer desired collaborator (#3032) 2024-11-24 02:08:53 +00:00
sadan4
f22d0e14a4
EmoteCloner: Fix recognizing animated emojis (#3027) 2024-11-24 02:07:46 +00:00
sadan4
ac1b1d44f5
ShowHiddenChannels: Fix viewing voice channels (#3033) 2024-11-24 02:03:59 +00:00
sadan4
13993f3f69
Decor: Fix avatar decorations not showing (again) (#3025) 2024-11-24 02:01:58 +00:00
Nuckyz
a0308e03af
Fix OpenInApp & ShowHiddenThings 2024-11-19 21:17:20 -03:00
Nuckyz
cd61f4e744
RoleColorEverywhere: Fix Online/Offline 2024-11-17 20:45:05 -03:00
jenku
5cf22113cf
Decor: Update notice about joining the server for clarity (#3021) 2024-11-17 18:55:33 -03:00
Nuckyz
ea2772476d
RoleColorEverywhere: Poll Results & Cleanup
Co-authored-by: jamesbt365 <jamesbt365@gmail.com>
2024-11-17 18:45:07 -03:00
nyx
99458da3be
ViewRaw: Add support for Group DMs (#3010) 2024-11-14 20:00:11 -03:00
Lumap
c4f6f151e6
PictureInPicture: Fix button not showing up (#3014) 2024-11-14 19:57:16 -03:00
Frocat
8558b1a589
ShikiCodeblocks: Updated codeblocks themes (#3013) 2024-11-14 19:54:01 -03:00
Nuckyz
76df29fba2
Actually stop searching for CSS debugging chunk 2024-11-14 14:51:58 -03:00
Nuckyz
25ceff5ec2
ChunkLoader: Avoid CSS debugging chunk 2024-11-13 18:44:21 -03:00
Nuckyz
e0d66ff071
NoMosaic: Fix plugin not working in Canary 2024-11-13 18:10:47 -03:00
sadan4
66a75747f8
ViewIcons: Fix conflict with unread Group DMs (#3011) 2024-11-13 17:44:13 -03:00
Nuckyz
211569f7f5
AlwaysExpandRoles: Fix collapse roles button not appearing 2024-11-13 15:41:57 -03:00
sadan4
af1edc88bf
FakeNitro: Fix embedding animated emojis (#3012) 2024-11-13 18:27:21 +00:00
Nuckyz
7ef536c6c6
Decor: Prevent more crashes 2024-11-12 15:02:56 -03:00
Nuckyz
69dc4fd594
Decor: Fix avatar decorations not showing
This reverts & edits commit 3b295e1f6f.
2024-11-12 14:53:11 -03:00
Nuckyz
1fe7912ec1
Decor: Prevent crashing from useUserDecorAvatarDecoration 2024-11-12 14:23:07 -03:00
Nuckyz
0cb84cee83
Bump to 1.10.7 2024-11-09 10:19:32 -03:00
Nuckyz
49c9fa1c8e
Settings: Fix fallback patch 2024-11-08 15:26:42 -03:00
Nuckyz
fd1aba7bab
NoTrack: Remove obsolete patch 2024-11-08 06:54:09 -03:00
Nuckyz
3b295e1f6f
Revert "Decor: Fix crashing"
cd3a998c4b.
2024-11-08 06:21:38 -03:00
Nuckyz
152d4fdbb3
Fix misc plugins errors on account switch 2024-11-07 15:34:28 -03:00
sadan4
ce0740b885
TypingTweaks: Fix crashing in some languages (#2998) 2024-11-07 15:34:28 -03:00
sadan4
64c3dd1c16
PatchHelper: Prevent trailing comma error (#2913) 2024-11-07 18:23:03 +00:00
sadan4
5f7a2c59c6
BetterFolders: Fix try-catch with no effect (#3000) 2024-11-07 18:16:32 +00:00
Nuckyz
cd3a998c4b
Decor: Fix crashing 2024-11-07 15:07:00 -03:00
Nuckyz
2270b88a98
intl macro: Support raw hash 2024-11-06 21:52:26 -03: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
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
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
246 changed files with 5460 additions and 4608 deletions

View file

@ -31,6 +31,7 @@ Before starting your plugin:
- No FakeDeafen or FakeMute
- No StereoMic
- No plugins that simply hide or redesign ui elements. This can be done with CSS
- No plugins that interact with specific Discord bots (official Discord apps like Youtube WatchTogether are okay)
- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc)
- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones
- No plugins that require the user to enter their own API key

View file

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

View file

@ -4,10 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
// @ts-check
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import react from "eslint-plugin-react";
import header from "eslint-plugin-simple-header";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports";
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
export default tseslint.config(
{ ignores: ["dist", "browser", "packages/vencord-types"] },
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
settings: {
react: {
version: "18"
}
},
...react.configs.flat.recommended,
rules: {
...react.configs.flat.recommended.rules,
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/display-name": "off",
"react/no-unescaped-entities": "off",
}
},
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
plugins: {
@ -23,7 +38,7 @@ export default tseslint.config(
"@typescript-eslint": tseslint.plugin,
"simple-import-sort": simpleImportSort,
"unused-imports": unusedImports,
"path-alias": pathAlias,
"path-alias": pathAlias
},
settings: {
"import/resolver": {
@ -90,7 +105,13 @@ export default tseslint.config(
"no-invalid-regexp": "error",
"no-constant-condition": ["error", { "checkLoops": false }],
"no-duplicate-imports": "error",
"dot-notation": "error",
"@typescript-eslint/dot-notation": [
"error",
{
"allowPrivateClassPropertyAccess": true,
"allowProtectedClassPropertyAccess": true
}
],
"no-useless-escape": [
"error",
{

View file

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.10.3",
"version": "1.11.3",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -35,53 +35,55 @@
"testTsc": "tsc --noEmit"
},
"dependencies": {
"@intrnl/xxhash64": "^0.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",
"fflate": "^0.8.2",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.50.0",
"nanoid": "^5.0.7",
"monaco-editor": "^0.52.2",
"nanoid": "^5.0.9",
"virtual-merge": "^1.0.1"
},
"devDependencies": {
"@stylistic/eslint-plugin": "^2.6.1",
"@types/chrome": "^0.0.269",
"@types/diff": "^5.2.1",
"@types/lodash": "^4.17.7",
"@types/node": "^22.0.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@stylistic/eslint-plugin": "^2.12.1",
"@types/chrome": "^0.0.287",
"@types/diff": "^6.0.0",
"@types/lodash": "^4.17.14",
"@types/node": "^22.10.5",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@types/yazl": "^2.4.5",
"diff": "^5.2.0",
"diff": "^7.0.0",
"discord-types": "^1.3.26",
"esbuild": "^0.15.18",
"eslint": "^9.8.0",
"eslint": "^9.17.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-simple-header": "^1.1.1",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-simple-header": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.0.1",
"highlight.js": "10.7.3",
"eslint-plugin-unused-imports": "^4.1.4",
"highlight.js": "11.7.0",
"html-minifier-terser": "^7.2.0",
"moment": "^2.30.1",
"puppeteer-core": "^22.15.0",
"moment": "^2.22.2",
"puppeteer-core": "^23.11.1",
"standalone-electron-types": "^1.0.0",
"stylelint": "^16.8.1",
"stylelint": "^16.12.0",
"stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.2.1",
"ts-pattern": "^5.3.1",
"tsx": "^4.16.5",
"type-fest": "^4.23.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.0",
"typescript-transform-paths": "^3.4.7",
"ts-patch": "^3.3.0",
"ts-pattern": "^5.6.0",
"tsx": "^4.19.2",
"type-fest": "^4.31.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.19.0",
"typescript-transform-paths": "^3.5.3",
"zip-local": "^0.3.5"
},
"packageManager": "pnpm@9.1.0",
"pnpm": {
"patchedDependencies": {
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
},
"peerDependencyRules": {

3267
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,8 @@ const CANARY = process.env.USE_CANARY === "true";
const browser = await pup.launch({
headless: true,
executablePath: process.env.CHROMIUM_BIN
executablePath: process.env.CHROMIUM_BIN,
args: ["--no-sandbox"]
});
const page = await browser.newPage();
@ -225,7 +226,7 @@ page.on("console", async e => {
plugin,
type,
id,
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
error: await maybeGetError(e.args()[3])
});

View file

@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
* Register a new badge with the Badges API
* @param badge The badge to register
*/
export function addBadge(badge: ProfileBadge) {
export function addProfileBadge(badge: ProfileBadge) {
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
Badges.add(badge);
}
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
* Unregister a badge from the Badges API
* @param badge The badge to remove
*/
export function removeBadge(badge: ProfileBadge) {
export function removeProfileBadge(badge: ProfileBadge) {
return Badges.delete(badge);
}
@ -100,20 +100,3 @@ export interface BadgeUserArgs {
userId: string;
guildId: string;
}
interface ConnectedAccount {
type: string;
id: string;
name: string;
verified: boolean;
}
interface Profile {
connectedAccounts: ConnectedAccount[];
premiumType: number;
premiumSince: string;
premiumGuildSince?: any;
lastFetched: number;
profileFetchFailed: boolean;
application?: any;
}

View file

@ -9,9 +9,9 @@ import "./ChatButton.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Logger } from "@utils/Logger";
import { waitFor } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Channel } from "discord-types/general";
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
@ -74,9 +74,9 @@ export interface ChatBarProps {
};
}
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
const buttonFactories = new Map<string, ChatBarButton>();
const buttonFactories = new Map<string, ChatBarButtonFactory>();
const logger = new Logger("ChatButtons");
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
}
}
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
export interface ChatBarButtonProps {
@ -99,7 +99,8 @@ export interface ChatBarButtonProps {
tooltip: string;
onClick: MouseEventHandler<HTMLButtonElement>;
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
onAuxClick?: MouseEventHandler<HTMLButtonElement>;
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu" | "onAuxClick">;
}
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
return (
@ -109,12 +110,13 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
<Button
aria-label={props.tooltip}
size=""
look={ButtonLooks.BLANK}
look={Button.Looks.BLANK}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
onClick={props.onClick}
onContextMenu={props.onContextMenu}
onAuxClick={props.onAuxClick}
{...props.buttonProps}
>
<div className={ButtonWrapperClasses.buttonWrapper}>

View file

@ -54,5 +54,5 @@ export function sendBotMessage(channelId: string, message: PartialDeep<Message>)
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
return (args.find(a => a.name === name)?.value || fallbackValue) as any;
return (args.find(a => a.name === name)?.value ?? fallbackValue) as any;
}

View file

@ -110,6 +110,7 @@ function registerSubCommands(cmd: Command, plugin: string) {
const subCmd = {
...cmd,
...o,
options: o.options !== undefined ? o.options : undefined,
type: ApplicationCommandType.CHAT_INPUT,
name: `${cmd.name} ${o.name}`,
id: `${o.name}-${cmd.id}`,

View file

@ -24,13 +24,13 @@ import type { ReactElement } from "react";
* @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
*/
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
/**
* @param navId The navId of the context menu being patched
* @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
*/
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
const ContextMenuLogger = new Logger("ContextMenu");
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
* @returns Whether the patch was successfully removed from the context menu(s)
*/
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
const navIds = Array.isArray(navId) ? navId : [navId as string];
const navIds: string[] = Array.isArray(navId) ? navId : [navId];
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* @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 | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
for (const child of children) {
if (child == null) continue;
@ -122,9 +122,9 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
}
interface ContextMenuProps {
contextMenuApiArguments?: Array<any>;
contextMenuAPIArguments?: Array<any>;
navId: string;
children: Array<ReactElement | null>;
children: Array<ReactElement<any> | null>;
"aria-label": string;
onSelect: (() => void) | undefined;
onClose: (callback: (...args: Array<any>) => any) => void;
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
children: cloneMenuChildren(props.children),
};
props.contextMenuApiArguments ??= [];
props.contextMenuAPIArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId);
if (!Array.isArray(props.children)) props.children = [props.children];
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
if (contextMenuPatches) {
for (const patch of contextMenuPatches) {
try {
patch(props.children, ...props.contextMenuApiArguments);
patch(props.children, ...props.contextMenuAPIArguments);
} catch (err) {
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
}
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
for (const patch of globalPatches) {
try {
patch(props.navId, props.children, ...props.contextMenuApiArguments);
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
} catch (err) {
ContextMenuLogger.error("Global patch errored,", err);
}
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
return props;
}
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
if (Array.isArray(obj)) {
return obj.map(cloneMenuChildren);
}

View file

@ -16,7 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Channel, User } from "discord-types/general/index.js";
import { JSX } from "react";
interface DecoratorProps {
activities: any[];
@ -38,27 +40,32 @@ interface DecoratorProps {
user: User;
[key: string]: any;
}
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
type OnlyIn = "guilds" | "dms";
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
decorators.set(identifier, { decorator, onlyIn });
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
decorators.set(identifier, { render, onlyIn });
}
export function removeDecorator(identifier: string) {
export function removeMemberListDecorator(identifier: string) {
decorators.delete(identifier);
}
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
const isInGuild = !!(props.guildId);
return Array.from(decorators.values(), decoratorObj => {
const { decorator, onlyIn } = decoratorObj;
// this can most likely be done cleaner
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
return decorator(props);
return Array.from(
decorators.entries(),
([key, { render: Decorator, onlyIn }]) => {
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
return null;
return (
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
<Decorator {...props} />
</ErrorBoundary>
);
}
return null;
});
);
}

View file

@ -16,26 +16,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
export type Accessory = {
callback: AccessoryCallback;
import ErrorBoundary from "@components/ErrorBoundary";
import { JSX, ReactNode } from "react";
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
export type MessageAccessory = {
render: MessageAccessoryFactory;
position?: number;
};
export const accessories = new Map<String, Accessory>();
export const accessories = new Map<string, MessageAccessory>();
export function addAccessory(
export function addMessageAccessory(
identifier: string,
callback: AccessoryCallback,
render: MessageAccessoryFactory,
position?: number
) {
accessories.set(identifier, {
callback,
render,
position,
});
}
export function removeAccessory(identifier: string) {
export function removeMessageAccessory(identifier: string) {
accessories.delete(identifier);
}
@ -43,15 +46,12 @@ export function _modifyAccessories(
elements: JSX.Element[],
props: Record<string, any>
) {
for (const accessory of accessories.values()) {
let accessories = accessory.callback(props);
if (accessories == null)
continue;
if (!Array.isArray(accessories))
accessories = [accessories];
else if (accessories.length === 0)
continue;
for (const [key, accessory] of accessories.entries()) {
const res = (
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
<accessory.render {...props} />
</ErrorBoundary>
);
elements.splice(
accessory.position != null
@ -60,7 +60,7 @@ export function _modifyAccessories(
: accessory.position
: elements.length,
0,
...accessories.filter(e => e != null) as JSX.Element[]
res
);
}

View file

@ -16,9 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Channel, Message } from "discord-types/general/index.js";
import { JSX } from "react";
interface DecorationProps {
export interface MessageDecorationProps {
author: {
/**
* Will be username if the user has no nickname
@ -44,20 +46,25 @@ interface DecorationProps {
message: Message;
[key: string]: any;
}
export type Decoration = (props: DecorationProps) => JSX.Element | null;
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
export const decorations = new Map<string, Decoration>();
export const decorations = new Map<string, MessageDecorationFactory>();
export function addDecoration(identifier: string, decoration: Decoration) {
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
decorations.set(identifier, decoration);
}
export function removeDecoration(identifier: string) {
export function removeMessageDecoration(identifier: string) {
decorations.delete(identifier);
}
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
return [...decorations.values()].map(decoration => {
return decoration(props);
});
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
return Array.from(
decorations.entries(),
([key, Decoration]) => (
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
<Decoration {...props} />
</ErrorBoundary>
)
);
}

View file

@ -73,11 +73,11 @@ export interface MessageExtra {
openWarningPopout: (props: any) => any;
}
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>();
const sendListeners = new Set<MessageSendListener>();
const editListeners = new Set<MessageEditListener>();
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
extra.replyOptions = replyOptions;
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
/**
* Note: This event fires off before a message is sent, allowing you to edit the message.
*/
export function addPreSendListener(listener: SendListener) {
export function addMessagePreSendListener(listener: MessageSendListener) {
sendListeners.add(listener);
return listener;
}
/**
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
*/
export function addPreEditListener(listener: EditListener) {
export function addMessagePreEditListener(listener: MessageEditListener) {
editListeners.add(listener);
return listener;
}
export function removePreSendListener(listener: SendListener) {
export function removeMessagePreSendListener(listener: MessageSendListener) {
return sendListeners.delete(listener);
}
export function removePreEditListener(listener: EditListener) {
export function removeMessagePreEditListener(listener: MessageEditListener) {
return editListeners.delete(listener);
}
// Message clicks
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
const listeners = new Set<ClickListener>();
const listeners = new Set<MessageClickListener>();
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
// message object may be outdated, so (try to) fetch latest one
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
}
}
export function addClickListener(listener: ClickListener) {
export function addMessageClickListener(listener: MessageClickListener) {
listeners.add(listener);
return listener;
}
export function removeClickListener(listener: ClickListener) {
export function removeMessageClickListener(listener: MessageClickListener) {
return listeners.delete(listener);
}

View file

@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
const logger = new Logger("MessagePopover");
export interface ButtonItem {
export interface MessagePopoverButtonItem {
key?: string,
label: string,
icon: ComponentType<any>,
@ -33,23 +33,23 @@ export interface ButtonItem {
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
}
export type getButtonItem = (message: Message) => ButtonItem | null;
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
export const buttons = new Map<string, getButtonItem>();
export const buttons = new Map<string, MessagePopoverButtonFactory>();
export function addButton(
export function addMessagePopoverButton(
identifier: string,
item: getButtonItem,
item: MessagePopoverButtonFactory,
) {
buttons.set(identifier, item);
}
export function removeButton(identifier: string) {
export function removeMessagePopoverButton(identifier: string) {
buttons.delete(identifier);
}
export function _buildPopoverElements(
Component: React.ComponentType<ButtonItem>,
Component: React.ComponentType<MessagePopoverButtonItem>,
message: Message
) {
const items: React.ReactNode[] = [];

View file

@ -16,40 +16,36 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
const logger = new Logger("ServerListAPI");
import ErrorBoundary from "@components/ErrorBoundary";
import { ComponentType } from "react";
export const enum ServerListRenderPosition {
Above,
In,
}
const renderFunctionsAbove = new Set<Function>();
const renderFunctionsIn = new Set<Function>();
const componentsAbove = new Set<ComponentType>();
const componentsBelow = new Set<ComponentType>();
function getRenderFunctions(position: ServerListRenderPosition) {
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
}
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
getRenderFunctions(position).add(renderFunction);
}
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
getRenderFunctions(position).delete(renderFunction);
}
export const renderAll = (position: ServerListRenderPosition) => {
const ret: Array<JSX.Element> = [];
for (const renderFunction of getRenderFunctions(position)) {
try {
ret.unshift(renderFunction());
} catch (e) {
logger.error("Failed to render server list element:", e);
}
}
return ret;
return Array.from(
getRenderFunctions(position),
(Component, i) => (
<ErrorBoundary noop key={i}>
<Component />
</ErrorBoundary>
)
);
};

View file

@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/mergeDefaults";
import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React } from "@webpack/common";
import { React, useEffect } from "@webpack/common";
import plugins from "~plugins";
@ -192,7 +192,7 @@ export const Settings = SettingsStore.store;
export function useSettings(paths?: UseSettings<Settings>[]) {
const [, forceUpdate] = React.useReducer(() => ({}), {});
React.useEffect(() => {
useEffect(() => {
if (paths) {
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
@ -200,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
SettingsStore.addGlobalChangeListener(forceUpdate);
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
}
}, []);
}, [paths]);
return SettingsStore.store;
}
@ -220,6 +220,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
}
}
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
const settings = SettingsStore.plain.plugins[pluginName];
if (!settings) return;
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
settings[newSetting] = settings[oldSetting];
delete settings[oldSetting];
SettingsStore.markAsChanged();
}
export function definePluginSettings<
Def extends SettingsDefinition,
Checks extends SettingsChecks<Def>,

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export function Badge({ text, color }): JSX.Element {
export function Badge({ text, color }) {
return (
<div className="vc-plugins-badge" style={{
backgroundColor: color,

View file

@ -27,7 +27,7 @@ interface Props<T = any> {
/** Render nothing if an error occurs */
noop?: boolean;
/** Fallback component to render if an error occurs */
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
/** called when an error occurs. The props property is only available if using .wrap */
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
/** Custom error message */
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
logger.error("A component threw an Error\n", error);
logger.error("Component Stack", errorInfo.componentStack);
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
}
render() {
@ -80,10 +79,14 @@ const ErrorBoundary = LazyComponent(() => {
if (this.props.noop) return null;
if (this.props.fallback)
return <this.props.fallback
children={this.props.children}
{...this.state}
/>;
return (
<this.props.fallback
wrappedProps={this.props.wrappedProps}
{...this.state}
>
{this.props.children}
</this.props.fallback>
);
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";

View file

@ -1,11 +0,0 @@
.vc-expandableheader-center-flex {
display: flex;
place-items: center;
}
.vc-expandableheader-btn {
all: unset;
cursor: pointer;
width: 24px;
height: 24px;
}

View file

@ -1,121 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./ExpandableHeader.css";
import { classNameFactory } from "@api/Styles";
import { Text, Tooltip, useState } from "@webpack/common";
const cl = classNameFactory("vc-expandableheader-");
export interface ExpandableHeaderProps {
onMoreClick?: () => void;
moreTooltipText?: string;
onDropDownClick?: (state: boolean) => void;
defaultState?: boolean;
headerText: string;
children: React.ReactNode;
buttons?: React.ReactNode[];
forceOpen?: boolean;
}
export function ExpandableHeader({
children,
onMoreClick,
buttons,
moreTooltipText,
onDropDownClick,
headerText,
defaultState = false,
forceOpen = false,
}: ExpandableHeaderProps) {
const [showContent, setShowContent] = useState(defaultState || forceOpen);
return (
<>
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "8px"
}}>
<Text
tag="h2"
variant="eyebrow"
style={{
color: "var(--header-primary)",
display: "inline"
}}
>
{headerText}
</Text>
<div className={cl("center-flex")}>
{
buttons ?? null
}
{
onMoreClick && // only show more button if callback is provided
<Tooltip text={moreTooltipText}>
{tooltipProps => (
<button
{...tooltipProps}
className={cl("btn")}
onClick={onMoreClick}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
>
<path fill="var(--text-normal)" d="M7 12.001C7 10.8964 6.10457 10.001 5 10.001C3.89543 10.001 3 10.8964 3 12.001C3 13.1055 3.89543 14.001 5 14.001C6.10457 14.001 7 13.1055 7 12.001ZM14 12.001C14 10.8964 13.1046 10.001 12 10.001C10.8954 10.001 10 10.8964 10 12.001C10 13.1055 10.8954 14.001 12 14.001C13.1046 14.001 14 13.1055 14 12.001ZM19 10.001C20.1046 10.001 21 10.8964 21 12.001C21 13.1055 20.1046 14.001 19 14.001C17.8954 14.001 17 13.1055 17 12.001C17 10.8964 17.8954 10.001 19 10.001Z" />
</svg>
</button>
)}
</Tooltip>
}
<Tooltip text={showContent ? "Hide " + headerText : "Show " + headerText}>
{tooltipProps => (
<button
{...tooltipProps}
className={cl("btn")}
onClick={() => {
setShowContent(v => !v);
onDropDownClick?.(showContent);
}}
disabled={forceOpen}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
transform={showContent ? "scale(1 -1)" : "scale(1 1)"}
>
<path fill="var(--text-normal)" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
</svg>
</button>
)}
</Tooltip>
</div>
</div>
{showContent && children}
</>
);
}

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { CSSProperties } from "react";
import { CSSProperties, JSX } from "react";
interface Props {
columns: number;

View file

@ -27,7 +27,7 @@ export function Heart() {
>
<path
fill="#db61a2"
fill-rule="evenodd"
fillRule="evenodd"
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
/>
</svg>

View file

@ -18,10 +18,9 @@
import "./iconStyles.css";
import { getTheme, Theme } from "@utils/discord";
import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import { i18n } from "@webpack/common";
import type { PropsWithChildren } from "react";
import type { JSX, PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
viewBox: string;
@ -56,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24"
>
<g fill="none" fill-rule="evenodd">
<g fill="none" fillRule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} />
</g>
@ -123,8 +122,8 @@ export function InfoIcon(props: IconProps) {
>
<path
fill="currentColor"
transform="translate(2 2)"
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
fillRule="evenodd"
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd"
/>
</Icon>
);
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
export function OwnerCrownIcon(props: IconProps) {
return (
<Icon
aria-label={i18n.Messages.GUILD_OWNER}
aria-label={getIntlMessage("GUILD_OWNER")}
{...props}
className={classes(props.className, "vc-owner-crown-icon")}
role="img"
@ -212,9 +211,10 @@ export function CogWheel(props: IconProps) {
viewBox="0 0 24 24"
>
<path
clipRule="evenodd"
fill="currentColor"
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
fillRule="evenodd"
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
clipRule="evenodd"
/>
</Icon>
);
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
viewBox="0 0 18 18"
>
<polygon
fill-rule="nonzero"
fillRule="nonzero"
fill="currentColor"
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
/>
@ -407,23 +407,30 @@ export function PencilIcon(props: IconProps) {
);
}
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
export function GithubIcon(props: ImageProps) {
const src = getTheme() === Theme.Light
? GithubIconLight
: GithubIconDark;
return <img {...props} src={src} />;
export function GithubIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="-3 -3 30 30"
>
<path
fill={props.fill || "currentColor"}
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.745.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.998.108-.775.42-1.305.763-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.398 3-.403 1.02.005 2.043.137 3 .403 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.873.118 3.176.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.625-5.475 5.92.43.37.823 1.102.823 2.222v3.293c0 .32.218.694.825.577C20.565 21.797 24 17.298 24 12c0-6.63-5.37-12-12-12z"
/>
</Icon>
);
}
export function WebsiteIcon(props: ImageProps) {
const src = getTheme() === Theme.Light
? WebsiteIconLight
: WebsiteIconDark;
return <img {...props} src={src} />;
export function WebsiteIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zM4 12c0-.899.156-1.762.431-2.569L6 11l2 2v2l2 2 1 1v1.931C7.061 19.436 4 16.072 4 12zm14.33 4.873C17.677 16.347 16.687 16 16 16v-1a2 2 0 0 0-2-2h-4v-3a2 2 0 0 0 2-2V7h1a2 2 0 0 0 2-2v-.411C17.928 5.778 20 8.65 20 12a7.947 7.947 0 0 1-1.67 4.873z"
/>
</Icon>
);
}

View file

@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
useEffect(() => {
if (!profile && !user.bot && user.id)
fetchUserProfile(user.id);
}, [user.id]);
}, [user.id, user.bot, profile]);
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;

View file

@ -6,16 +6,19 @@
import "./LinkIconButton.css";
import { getTheme, Theme } from "@utils/discord";
import { MaskedLink, Tooltip } from "@webpack/common";
import { GithubIcon, WebsiteIcon } from "..";
export function GithubLinkIcon() {
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
}
export function WebsiteLinkIcon() {
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
}
interface Props {

View file

@ -37,6 +37,7 @@ import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins";
import {
ISettingCustomElementProps,
ISettingElementProps,
SettingBooleanComponent,
SettingCustomComponent,
@ -74,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
return newUser;
}
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
[OptionType.STRING]: SettingTextComponent,
[OptionType.NUMBER]: SettingNumericComponent,
[OptionType.BIGINT]: SettingNumericComponent,
[OptionType.BOOLEAN]: SettingBooleanComponent,
[OptionType.SELECT]: SettingSelectComponent,
[OptionType.SLIDER]: SettingSliderComponent,
[OptionType.COMPONENT]: SettingCustomComponent
[OptionType.COMPONENT]: SettingCustomComponent,
[OptionType.CUSTOM]: () => null,
};
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
@ -109,7 +111,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
setAuthors(a => [...a, author]);
}
})();
}, []);
}, [plugin.authors]);
async function saveAndClose() {
if (!plugin.options) {
@ -129,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
for (const [key, value] of Object.entries(tempSettings)) {
const option = plugin.options[key];
pluginSettings[key] = value;
option?.onChange?.(value);
if (option.type === OptionType.CUSTOM) continue;
if (option?.restartNeeded) restartNeeded = true;
}
if (restartNeeded) onRestartNeeded();
@ -141,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
} else {
const options = Object.entries(plugin.options).map(([key, setting]) => {
if (setting.hidden) return null;
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
function onChange(newValue: any) {
setTempSettings(s => ({ ...s, [key]: newValue }));

View file

@ -18,8 +18,8 @@
import { PluginOptionComponent } from "@utils/types";
import { ISettingElementProps } from ".";
import { ISettingCustomElementProps } from ".";
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
return option.component({ setValue: onChange, setError: onError, option });
}

View file

@ -18,7 +18,7 @@
import { DefinedSettings, PluginOptionBase } from "@utils/types";
export interface ISettingElementProps<T extends PluginOptionBase> {
interface ISettingElementPropsBase<T> {
option: T;
onChange(newValue: any): void;
pluginSettings: {
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
definedSettings?: DefinedSettings;
}
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
export * from "../../Badge";
export * from "./SettingBooleanComponent";
export * from "./SettingCustomComponent";

View file

@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
import { JSX } from "react";
import Plugins, { ExcludedPlugins } from "~plugins";
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
return (
<React.Fragment>
<Forms.FormText>This plugin is required by:</Forms.FormText>
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
</React.Fragment>
);
}

View file

@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
}
function renderDiff() {
return diff?.map(p => {
return diff?.map((p, idx) => {
const color = p.added ? "lime" : p.removed ? "red" : "grey";
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
});
}
@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
}
try {
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
if (!parsed.find) throw new Error("No 'find' field");
if (!parsed.replacement) throw new Error("No 'replacement' field");

View file

@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
title: "Oops!",
body: (
<ErrorCard>
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
</ErrorCard>
)
});
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
return (
<Card style={{ padding: "0 0.5em" }}>
{updates.map(({ hash, author, message }) => (
<div style={{
<div key={hash} style={{
marginTop: "0.5em",
marginBottom: "0.5em"
}}>

View file

@ -10,7 +10,6 @@ export * from "./CodeBlock";
export * from "./DonateButton";
export { default as ErrorBoundary } from "./ErrorBoundary";
export * from "./ErrorCard";
export * from "./ExpandableHeader";
export * from "./Flex";
export * from "./Heart";
export * from "./Icons";

View file

@ -27,13 +27,16 @@ export async function loadLazyChunks() {
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
let foundCssDebuggingLoad = false;
async function searchAndLoadLazyChunks(factoryCode: string) {
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
// the chunk containing the component
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
const shouldForceDefer = false;
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
@ -45,6 +48,16 @@ export async function loadLazyChunks() {
let invalidChunkGroup = false;
for (const id of chunkIds) {
if (hasCssDebuggingLoad) {
if (chunkIds.length > 1) {
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
}
invalidChunks.add(id);
invalidChunkGroup = true;
break;
}
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))

View file

@ -62,14 +62,21 @@ async function runReporter() {
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
} catch (e) {
let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
else if (method === "mapMangledModule") {
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
if (args[0].$$vencordProps != null) {
logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
} else {
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
}
} else if (method === "extractAndLoadChunks") {
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
} else if (method === "mapMangledModule") {
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
} else {
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
}
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
ReporterLogger.log("Webpack Find Fail:", logMessage);
}

View file

@ -71,13 +71,16 @@ export async function installExt(id: string) {
// React Devtools v4.25
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
// Unfortunately, Google does not serve old versions, so this is the only way
// This zip file is pinned to long commit hash so it cannot be changed remotely
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`;
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
const buf = await get(url, {
headers: {
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)"
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
}
});
await extract(crxToZip(buf), extDir).catch(console.error);
}

View file

@ -0,0 +1,5 @@
/* the profile popout badge container(s) */
[class*="biteSize_"] [class*="tags_"] [class*="container_"] {
/* Discord has padding set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */
padding: 0 1px;
}

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./fixDiscordBadgePadding.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary";
@ -26,7 +28,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc";
import { closeModal, Modals, openModal } from "@utils/modal";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { Forms, Toasts, UserStore } from "@webpack/common";
import { User } from "discord-types/general";
@ -77,7 +79,7 @@ export default definePlugin({
replace: "...$1.props,$& $1.image??"
},
{
match: /(?<=text:(\i)\.description,.{0,200})children:/,
match: /(?<="aria-label":(\i)\.description,.{0,200})children:/,
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
},
// conditionally override their onClick with badge.onClick if it exists
@ -100,8 +102,9 @@ export default definePlugin({
}
},
userProfileBadge: ContributorBadge,
async start() {
Vencord.Api.Badges.addBadge(ContributorBadge);
await loadBadges();
},
@ -141,8 +144,8 @@ export default definePlugin({
closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}>
<Modals.ModalRoot {...props}>
<Modals.ModalHeader>
<ModalRoot {...props}>
<ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
@ -156,8 +159,8 @@ export default definePlugin({
Vencord Donor
</Forms.FormTitle>
</Flex>
</Modals.ModalHeader>
<Modals.ModalContent>
</ModalHeader>
<ModalContent>
<Flex>
<img
role="presentation"
@ -180,13 +183,13 @@ export default definePlugin({
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText>
</div>
</Modals.ModalContent>
<Modals.ModalFooter>
</ModalContent>
<ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</Modals.ModalFooter>
</Modals.ModalRoot>
</ModalFooter>
</ModalRoot>
</ErrorBoundary>
));
},

View file

@ -12,11 +12,16 @@ export default definePlugin({
description: "API to add buttons to the chat input",
authors: [Devs.Ven],
patches: [{
find: '"sticker")',
replacement: {
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
patches: [
{
find: '"sticker")',
replacement: {
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
replace: (m, not, children) => not
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
}
}
}]
]
});

View file

@ -34,12 +34,22 @@ export default definePlugin({
}
},
{
find: ".Menu,{",
find: "navId:",
all: true,
replacement: {
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
}
noWarn: true,
replacement: [
{
match: /navId:(?=.+?([,}].*?\)))/g,
replace: (m, rest) => {
// Check if this navId: match is a destructuring statement, ignore it if it is
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) {
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
}
return m;
}
}
]
}
]
});

View file

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

View file

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

View file

@ -0,0 +1,68 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin from "@utils/types";
// duplicate values have multiple branches with different types. Just include all to be safe
const nameMap = {
radio: "MenuRadioItem",
separator: "MenuSeparator",
checkbox: "MenuCheckboxItem",
groupstart: "MenuGroup",
control: "MenuControlItem",
compositecontrol: "MenuControlItem",
item: "MenuItem",
customitem: "MenuItem",
};
export default definePlugin({
name: "MenuItemDemanglerAPI",
description: "Demangles Discord's Menu Item module",
authors: [Devs.Ven],
required: true,
patches: [
{
find: '"Menu API',
replacement: {
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
replace: (m, mod) => {
const nameAssignments = [] as string[];
// if (t.type === m.MenuItem)
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
// push({type:"item"})
const pushTypeRe = /type:"(\w+)"/g;
let typeMatch: RegExpExecArray | null;
// for each if (t.type === ...)
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
// extract the current menu item
const item = typeMatch[1];
// Set the starting index of the second regex to that of the first to start
// matching from after the if
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
// extract the first type: "..."
const type = pushTypeRe.exec(m)?.[1];
if (type && type in nameMap) {
const name = nameMap[type];
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
}
}
if (nameAssignments.length < 6) {
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
}
// Merge all our redefines with the actual module
return `${nameAssignments.join(";")};${m}`;
},
},
},
],
});

View file

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

View file

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

View file

@ -25,26 +25,23 @@ export default definePlugin({
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [
{
find: ".Messages.EDIT_TEXTAREA_HELP",
find: "#{intl::EDIT_TEXTAREA_HELP}",
replacement: {
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
replace: (match, args) => "" +
`async ${match}` +
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
"return Promise.resolve({shouldClear:false,shouldRefocus:true});"
}
},
{
find: ".handleSendMessage,onResize",
replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
`${rest1}async ${rest2}` +
// https://regex101.com/r/hBlXpl/1
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
"return{shoudClear:true,shouldRefocus:true};"
"return{shouldClear:false,shouldRefocus:true};"
}
},
{
@ -52,8 +49,7 @@ export default definePlugin({
replacement: {
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
replace: (m, message, channel, event) =>
// the message param is shadowed by the event param, so need to alias them
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
}
}
]

View file

@ -23,11 +23,14 @@ export default definePlugin({
name: "MessagePopoverAPI",
description: "API to add buttons to message popovers.",
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
patches: [{
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
replacement: {
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.Messages\.MESSAGE_ACTION_REPLY.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
patches: [
{
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
}
}
}],
]
});

View file

@ -33,8 +33,8 @@ export default definePlugin({
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
{
match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.*NOTICE_DISMISS:\1)/,
replace: "if($2.id==\"VencordNotice\")return($2=null,Vencord.Api.Notices.nextNotice(),true);$&"
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
}
]
}

View file

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

View file

@ -61,13 +61,13 @@ export default definePlugin({
]
},
{
find: ".installedLogHooks)",
find: ".BetterDiscord||null!=",
replacement: {
// if getDebugLogging() returns false, the hooks don't get installed.
match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
// Make hasClientMods return false
match: /(?=let \i=window;)/,
replace: "return false;"
}
},
}
],
startAt: StartAt.Init,

View file

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

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addAccessory } from "@api/MessageAccessories";
import { definePluginSettings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings";
import ErrorBoundary from "@components/ErrorBoundary";
@ -34,6 +33,7 @@ import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types";
import { checkForUpdates, isOutdated, update } from "@utils/updater";
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
import { JSX } from "react";
import gitHash from "~git-hash";
import plugins, { PluginMeta } from "~plugins";
@ -77,7 +77,7 @@ async function generateDebugInfoMessage() {
const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
// @ts-expect-error
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
@ -142,15 +142,15 @@ export default definePlugin({
required: true,
description: "Helps us provide support to you",
authors: [Devs.Ven],
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
dependencies: ["UserSettingsAPI"],
settings,
patches: [{
find: ".BEGINNING_DM.format",
find: "#{intl::BEGINNING_DM}",
replacement: {
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
}
}],
@ -235,7 +235,87 @@ export default definePlugin({
}
},
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
renderMessageAccessory(props) {
const buttons = [] as JSX.Element[];
const shouldAddUpdateButton =
!IS_UPDATER_DISABLED
&& (
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
)
&& props.message.content?.includes("update");
if (shouldAddUpdateButton) {
buttons.push(
<Button
key="vc-update"
color={Button.Colors.GREEN}
onClick={async () => {
try {
if (await forceUpdate())
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
else
showToast("Already up to date!", Toasts.Type.MESSAGE);
} catch (e) {
new Logger(this.name).error("Error while updating:", e);
showToast("Failed to update :(", Toasts.Type.FAILURE);
}
}}
>
Update Now
</Button>
);
}
if (props.channel.id === SUPPORT_CHANNEL_ID) {
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
buttons.push(
<Button
key="vc-dbg"
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
>
Run /vencord-debug
</Button>,
<Button
key="vc-plg-list"
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
>
Run /vencord-plugins
</Button>
);
}
if (props.message.author.id === VENBOT_USER_ID) {
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
if (match) {
buttons.push(
<Button
key="vc-run-snippet"
onClick={async () => {
try {
await AsyncFunction(match[1])();
showToast("Success!", Toasts.Type.SUCCESS);
} catch (e) {
new Logger(this.name).error("Error while running snippet:", e);
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
}
}}
>
Run Snippet
</Button>
);
}
}
}
return buttons.length
? <Flex>{buttons}</Flex>
: null;
},
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
const userId = channel.getRecipientId();
if (!isPluginDev(userId)) return null;
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
@ -248,85 +328,4 @@ export default definePlugin({
</Card>
);
}, { noop: true }),
start() {
addAccessory("vencord-debug", props => {
const buttons = [] as JSX.Element[];
const shouldAddUpdateButton =
!IS_UPDATER_DISABLED
&& (
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
)
&& props.message.content?.includes("update");
if (shouldAddUpdateButton) {
buttons.push(
<Button
key="vc-update"
color={Button.Colors.GREEN}
onClick={async () => {
try {
if (await forceUpdate())
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
else
showToast("Already up to date!", Toasts.Type.MESSAGE);
} catch (e) {
new Logger(this.name).error("Error while updating:", e);
showToast("Failed to update :(", Toasts.Type.FAILURE);
}
}}
>
Update Now
</Button>
);
}
if (props.channel.id === SUPPORT_CHANNEL_ID) {
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
buttons.push(
<Button
key="vc-dbg"
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
>
Run /vencord-debug
</Button>,
<Button
key="vc-plg-list"
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
>
Run /vencord-plugins
</Button>
);
}
if (props.message.author.id === VENBOT_USER_ID) {
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
if (match) {
buttons.push(
<Button
key="vc-run-snippet"
onClick={async () => {
try {
await AsyncFunction(match[1])();
showToast("Success!", Toasts.Type.SUCCESS);
} catch (e) {
new Logger(this.name).error("Error while running snippet:", e);
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
}
}}
>
Run Snippet
</Button>
);
}
}
}
return buttons.length
? <Flex>{buttons}</Flex>
: null;
});
},
});

View file

@ -16,14 +16,14 @@ import { User } from "discord-types/general";
interface UserProfileProps {
popoutProps: Record<string, any>;
currentUser: User;
originalPopout: () => React.ReactNode;
originalRenderPopout: () => 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 };
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
@ -69,23 +69,23 @@ export default definePlugin({
patches: [
{
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
group: true,
replacement: [
{
match: /(?<=\.SIZE_32\)}\);)/,
match: /(?<=\.AVATAR_SIZE\);)/,
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}}})`
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
},
{
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
replace: "$&onRequestClose:$self.onPopoutClose,"
},
{
match: /(?<=.avatarWrapper,)/,
match: /(?<=\.avatarWrapper,)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
}
]
@ -112,17 +112,17 @@ export default definePlugin({
openAlternatePopout = false;
},
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalRenderPopout }: UserProfileProps) => {
if (
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
) {
return originalPopout();
return originalRenderPopout();
}
const currentChannel = getCurrentChannel();
if (currentChannel?.getGuildId() == null) {
return originalPopout();
return originalRenderPopout();
}
return (

View file

@ -41,10 +41,10 @@ export default definePlugin({
},
{
// Status emojis
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}),children:",
replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/,
replace: "!0"
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
replace: (_, rest) => `${rest}!0`
}
},
{

View file

@ -28,10 +28,18 @@ export default definePlugin({
patches: [
{
find: 'action:"EXPAND_ROLES"',
replacement: {
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
}
replacement: [
{
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
},
{
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
// which makes the collapse button never show up and calculation never occur
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
replace: isExpanded => "false"
}
]
}
]
});

View file

@ -51,7 +51,7 @@ export default definePlugin({
{
find: "bitbucket.org",
replacement: {
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
match: /function \i\(\i\){(?=.{0,30}pathname:\i)/,
replace: "$&return null;"
},
predicate: () => settings.store.file

View file

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

View file

@ -0,0 +1,179 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { MessageExtra, MessageObject } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import { findLazy } from "@webpack";
const TesseractLogger = new Logger("Tesseract", "#ff9e64");
let worker!: Tesseract.Worker;
function reduceBboxGreatest(acc: Tesseract.Bbox, cur: Tesseract.Bbox): Tesseract.Bbox {
return {
x0: Math.min(acc.x0, cur.x0),
x1: Math.max(acc.x1, cur.x1),
y0: Math.min(acc.y0, cur.y0),
y1: Math.max(acc.y1, cur.y1)
};
}
function findWordLocation(text: Tesseract.Block[], regex: RegExp): Tesseract.Bbox[] {
const locs: Tesseract.Bbox[] = [];
for (let i = 0; i < text.length; i++) {
const block = text[i];
if (block.text.match(regex)) {
const bl = locs.length;
for (let j = 0; j < block.paragraphs.length; j++) {
const paragraph = block.paragraphs[j];
if (paragraph.text.match(regex)) {
const bl = locs.length;
for (let k = 0; k < paragraph.lines.length; k++) {
const line = paragraph.lines[k];
if (line.text.match(regex)) {
const bl = locs.length;
for (let l = 0; l < line.words.length; l++) {
const word = line.words[l];
let matches: RegExpExecArray[];
if ((matches = [...word.text.matchAll(new RegExp(regex, `${regex.flags.replace("g", "")}g`))]).length) {
for (const match of matches) {
const syms = word.symbols
.slice(match.index, match.index + match[0].length)
.map(x => x.bbox)
.reduce(reduceBboxGreatest);
locs.push(syms);
}
}
}
if (locs.length === bl) {
locs.push(line.bbox);
}
}
}
if (locs.length === bl) {
locs.push(paragraph.bbox);
}
}
}
if (locs.length === bl) {
locs.push(block.bbox);
}
}
}
return locs;
}
interface CloudUpload {
new(file: { file: File; isThumbnail: boolean; platform: number; }, channelId: string, showDiaglog: boolean, numCurAttachments: number): CloudUpload;
upload(): void;
}
const CloudUpload: CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
function getImage(file: File): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => resolve(img);
img.onerror = reject;
});
}
function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
return new Promise<Blob>(resolve => {
canvas.toBlob(blob => {
if (blob) {
resolve(blob);
} else {
throw new Error("Failed to create Blob");
}
}, "image/png");
});
}
const badRegex = /nix(?:os)?|This ?content ?is|blocked ?by ?this ?server/i;
export default definePlugin({
name: "AntiTessie",
authors: [Devs.sadan],
description: "Scans your messages with ocr for anything that matches the selected regex, and if found, blurs it",
start() {
if (!window?.Tesseract) {
fetch(
"https://unpkg.com/tesseract.js@6.0.0/dist/tesseract.min.js"
)
.then(async r => void (0, eval)(await r.text()))
.then(async () => {
worker = await Tesseract.createWorker("eng", Tesseract.OEM.TESSERACT_LSTM_COMBINED, {
corePath: "https://unpkg.com/tesseract.js-core@6.0.0/tesseract-core-simd-lstm.wasm.js",
workerPath: "https://unpkg.com/tesseract.js@6.0.0/dist/worker.min.js",
});
})
.then(() => {
worker.setParameters({
tessedit_pageseg_mode: Tesseract.PSM.AUTO
});
});
}
},
stop() {
worker.terminate();
},
async onBeforeMessageSend(channelId: string, message: MessageObject, extra: MessageExtra): Promise<void | { cancel: boolean; }> {
if (extra.channel.guild_id !== "1015060230222131221" && extra.channel.guild_id !== "1041012073603289109") {
return;
}
const uploads = extra?.uploads ?? [];
for (let i = 0; i < uploads.length; i++) {
async function convertToFile(canvas: HTMLCanvasElement): Promise<File> {
const blob = await canvasToBlob(canvas);
return new File([blob], `${upload.filename.substring(0, upload.filename.lastIndexOf("."))}.png`, {
type: "image/png"
});
}
const upload = uploads[i];
if (!upload.isImage) continue;
console.log(upload);
const ret = await worker.recognize(upload.item.file, {
}, {
text: true,
blocks: true,
});
if (ret.data.text.match(badRegex)) {
const toBlur = findWordLocation(ret.data.blocks!, badRegex);
const sourceImage = await getImage(upload.item.file);
const width = sourceImage.naturalWidth;
const height = sourceImage.naturalHeight;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
ctx.canvas.width = width;
ctx.canvas.height = height;
ctx.drawImage(sourceImage, 0, 0, width, height);
for (const { x0, x1, y0, y1 } of toBlur) {
ctx.fillStyle = "black";
ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
}
const newFile = await convertToFile(canvas);
const attachment = new CloudUpload({
file: newFile,
isThumbnail: false,
platform: 1
}, channelId, false, uploads.length);
attachment.upload();
extra.uploads![i] = attachment as any;
}
}
}
});

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { canonicalizeMatch } from "@utils/patches";
import { execFile } from "child_process";
import { promisify } from "util";
@ -26,7 +27,7 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
const APPLE_MUSIC_TOKEN_REGEX = canonicalizeMatch(/Promise.allSettled\(\i\)\}const \i="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)"/);
let cachedToken: string | undefined = undefined;

View file

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

View file

@ -36,7 +36,7 @@ export default definePlugin({
settings,
patches: [
{
find: "BAN_CONFIRM_TITLE.",
find: "#{intl::BAN_CONFIRM_TITLE}",
replacement: {
match: /src:\i\("?\d+"?\)/g,
replace: "src:$self.source"

View file

@ -17,14 +17,13 @@
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { useStateFromStores } from "@webpack/common";
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Animations, useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react";
import { ExpandedGuildFolderStore, settings } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const Animations = findByPropsLazy("a", "animated", "useTransition");
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
export default ErrorBoundary.wrap(guildsBarProps => {

View file

@ -18,9 +18,10 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
import { FluxDispatcher, useMemo } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
@ -122,7 +123,7 @@ export default definePlugin({
},
// 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\].+?}\)(?::null)?\](?=}\))/,
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
@ -158,7 +159,7 @@ export default definePlugin({
]
},
{
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
find: ".expandedFolderBackground,",
predicate: () => settings.store.sidebar,
replacement: [
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
@ -172,8 +173,8 @@ export default definePlugin({
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{
predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
replace: "$self.shouldShowTransition(arguments[0])&&"
match: /(?=,\{from:\{height)/,
replace: "&&$self.shouldShowTransition(arguments[0])"
},
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{
@ -184,7 +185,7 @@ export default definePlugin({
{
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/,
match: /(?<=\.isExpanded\),children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
},
{
@ -205,7 +206,7 @@ export default definePlugin({
}
},
{
find: ".Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
predicate: () => settings.store.closeAllHomeButton,
replacement: {
// Close all folders when clicking the home button
@ -275,19 +276,29 @@ export default definePlugin({
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
if (!isBetterFolders) return true;
try {
return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
} catch (e) {
console.error(e);
}
return true;
};
},
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return child?.props?.onScroll != null;
if (!isBetterFolders) return true;
if (child?.props?.className?.includes("itemsContainer") && child.props.children != null) {
// Filter out everything but the scroller for the guild list
child.props.children = child.props.children.filter(child => child?.props?.onScroll != null);
return true;
}
return true;
return false;
};
},

View file

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

View file

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

View file

@ -83,7 +83,7 @@ export default definePlugin({
if (!role) return;
if (role.colorString) {
children.push(
children.unshift(
<Menu.MenuItem
id="vc-copy-role-color"
label="Copy Role Color"
@ -93,22 +93,8 @@ export default definePlugin({
);
}
if (role.icon) {
children.push(
<Menu.MenuItem
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
}}
icon={ImageIcon}
/>
);
}
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.push(
children.unshift(
<Menu.MenuItem
id="vc-edit-role"
label="Edit Role"
@ -120,6 +106,24 @@ export default definePlugin({
/>
);
}
if (role.icon) {
children.push(
<Menu.MenuItem
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal({
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
height: 128,
width: 128
});
}}
icon={ImageIcon}
/>
);
}
}
}
});

View file

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

View file

@ -99,7 +99,7 @@ export const UnknownIcon = (props: React.PropsWithChildren<SVGProps<SVGSVGElemen
fill="currentColor"
viewBox="0 0 16 16"
>
<path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
<path fillRule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
</svg>
);

View file

@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
import { RenameButton } from "./components/RenameButton";
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
const SessionIconClasses = findByPropsLazy("sessionIcon");
const BlobMask = findExportedComponentLazy("BlobMask");
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
const settings = definePluginSettings({
backgroundCheck: {
@ -60,7 +60,7 @@ export default definePlugin({
patches: [
{
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT",
find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
replacement: [
// Replace children with a single label with state
{

View file

@ -5,8 +5,9 @@
*/
import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { getIntlMessage } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc";
import { Alerts, i18n, Menu, useMemo, useState } from "@webpack/common";
import { Alerts, Menu, useMemo, useState } from "@webpack/common";
import Plugins from "~plugins";
@ -48,7 +49,7 @@ export default function PluginsSubmenu() {
query={query}
onChange={setQuery}
ref={ref}
placeholder={i18n.Messages.SEARCH}
placeholder={getIntlMessage("SEARCH")}
/>
)}
/>

View file

@ -7,10 +7,11 @@
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react";
import PluginsSubmenu from "./PluginsSubmenu";
@ -100,8 +101,8 @@ export default definePlugin({
find: 'minimal:"contentColumnMinimal"',
replacement: [
{
match: /\(0,\i\.useTransition\)\((\i)/,
replace: "(_cb=>_cb(void 0,$1))||$&"
match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
replace: "(_cb=>_cb(void 0,$1))||"
},
{
match: /\i\.animated\.div/,
@ -111,15 +112,15 @@ export default definePlugin({
predicate: () => settings.store.disableFade
},
{ // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await [^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)(),"
},
predicate: () => settings.store.eagerLoad
},
{ // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: [
{
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
@ -159,7 +160,7 @@ export default definePlugin({
if (item.section === "HEADER") {
items.push({ label: item.label, items: [] });
} else if (item.section === "DIVIDER") {
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
} else {
items.at(-1)!.items.push(item);
}
@ -172,7 +173,7 @@ export default definePlugin({
}
return this;
},
map(render: (item: SettingsEntry) => ReactElement) {
map(render: (item: SettingsEntry) => ReactElement<any>) {
return items
.filter(a => a.items.length > 0)
.map(({ label, items }) => {
@ -180,11 +181,14 @@ export default definePlugin({
if (label) {
return (
<Menu.MenuItem
key={label}
id={label.replace(/\W/, "_")}
label={label}
children={children}
action={children[0].props.action}
/>);
>
{children}
</Menu.MenuItem>
);
} else {
return children;
}

View file

@ -27,7 +27,7 @@ export default definePlugin({
{
find: '"ChannelAttachButton"',
replacement: {
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
},
},

View file

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

View file

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

View file

@ -75,10 +75,11 @@ export default definePlugin({
patches: [{
find: "renderConnectionStatus(){",
replacement: {
match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i/,
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
}
}],
renderTimer(channelId: string) {
return <ErrorBoundary noop>
<this.Timer channelId={channelId} />

View file

@ -17,11 +17,7 @@
*/
import {
addPreEditListener,
addPreSendListener,
MessageObject,
removePreEditListener,
removePreSendListener
MessageObject
} from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -36,7 +32,18 @@ export default definePlugin({
name: "ClearURLs",
description: "Removes tracking garbage from URLs",
authors: [Devs.adryd],
dependencies: ["MessageEventsAPI"],
start() {
this.createRules();
},
onBeforeMessageSend(_, msg) {
return this.onSend(msg);
},
onBeforeMessageEdit(_cid, _mid, msg) {
return this.onSend(msg);
},
escapeRegExp(str: string) {
return (str && reHasRegExpChar.test(str))
@ -133,17 +140,4 @@ export default definePlugin({
);
}
},
start() {
this.createRules();
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
this.onSend(msg)
);
},
stop() {
removePreSendListener(this.preSend);
removePreEditListener(this.preEdit);
},
});

View file

@ -14,7 +14,7 @@ import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
@ -91,15 +91,12 @@ function ThemeSettings() {
const settings = definePluginSettings({
color: {
description: "Color your Discord client theme will be based around. Light mode isn't supported",
type: OptionType.COMPONENT,
default: "313338",
component: () => <ThemeSettings />
component: ThemeSettings
},
resetColor: {
description: "Reset Theme Color",
type: OptionType.COMPONENT,
default: "313338",
component: () => (
<Button onClick={() => onPickColor(0x313338)}>
Reset Theme Color
@ -110,7 +107,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ClientTheme",
authors: [Devs.F53, Devs.Nuckyz],
authors: [Devs.Nuckyz],
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
settings,

View file

@ -66,6 +66,20 @@ export default definePlugin({
},
patches: [
{
find: "https://github.com/highlightjs/highlight.js/issues/2277",
replacement: {
match: /\(console.log\(`Deprecated.+?`\),/,
replace: "("
}
},
{
find: 'The "interpolate" function is deprecated in v10 (use "to" instead)',
replacement: {
match: /,console.warn\(\i\+'The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
replace: ""
}
},
{
find: 'console.warn("Window state not initialized"',
replacement: {
@ -81,10 +95,9 @@ export default definePlugin({
}
},
{
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
all: true,
find: '"AppCrashedFatalReport: getLastCrash not supported."',
replacement: {
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
replace: ""
}
},
@ -140,5 +153,5 @@ export default definePlugin({
replace: "$self.NoopLogger()"
}
}
],
]
});

View file

@ -18,7 +18,9 @@
import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { ModalAPI } from "@utils/modal";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
import definePlugin, { PluginNative, StartAt } from "@utils/types";
@ -61,7 +63,7 @@ function makeShortcuts() {
default:
const uniqueMatches = [...new Set(matches)];
if (uniqueMatches.length > 1)
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
return matches[0];
}
@ -104,6 +106,7 @@ function makeShortcuts() {
canonicalizeMatch,
canonicalizeReplace,
canonicalizeReplacement,
runtimeHashMessageKey,
fakeRender: (component: ComponentType, props: any) => {
const prevWin = fakeRenderWin?.deref();
const win = prevWin?.closed === false
@ -130,7 +133,10 @@ function makeShortcuts() {
});
}
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div")));
root.render(Common.React.createElement(component, props));
doc.addEventListener("close", () => root.unmount(), { once: true });
},
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
@ -142,6 +148,8 @@ function makeShortcuts() {
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
openModal: { getter: () => ModalAPI.openModal },
openModalLazy: { getter: () => ModalAPI.openModalLazy },
Stores: {
getter: () => Object.fromEntries(
@ -157,11 +165,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
const currentVal = val.getter();
if (!currentVal || val.preload === false) return currentVal;
const value = currentVal[SYM_LAZY_GET]
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
: currentVal;
function unwrapProxy(value: any) {
if (value[SYM_LAZY_GET]) {
forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
} else if (value.$$vencordInternal) {
return forceLoad ? value.$$vencordInternal() : value;
}
if (value) define(window.shortcutList, key, { value });
return value;
}
const value = unwrapProxy(currentVal);
if (typeof value === "object" && value !== null) {
const descriptors = Object.getOwnPropertyDescriptors(value);
for (const propKey in descriptors) {
if (value[propKey] == null) continue;
const descriptor = descriptors[propKey];
if (descriptor.writable === true || descriptor.set != null) {
const currentValue = value[propKey];
const newValue = unwrapProxy(currentValue);
if (newValue != null && currentValue !== newValue) {
value[propKey] = newValue;
}
}
}
}
if (value != null) {
define(window.shortcutList, key, { value });
define(window, key, { value });
}
return value;
}
@ -171,6 +206,16 @@ export default definePlugin({
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
authors: [Devs.Ven],
patches: [
{
find: 'this,"_changeCallbacks",',
replacement: {
match: /\i\(this,"_changeCallbacks",/,
replace: "Reflect.defineProperty(this,Symbol.toStringTag,{value:this.getName(),configurable:!0,writable:!0,enumerable:!1}),$&"
}
}
],
startAt: StartAt.Init,
start() {
const shortcuts = makeShortcuts();

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Obsidian, Devs.Nuckyz],
patches: [
{
find: ".Messages.PREVIEW_BYTES_LEFT.format(",
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}),"

View file

@ -67,7 +67,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
find: "#{intl::ERRORS_UNEXPECTED_CRASH}",
replacement: {
match: /this\.setState\((.+?)\)/,
replace: "$self.handleCrash(this,$1);"

View file

@ -42,10 +42,11 @@ export default definePlugin({
// Only one of the two patches will be at effect; Discord often updates to switch between them.
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
{
find: ".ENTER&&(!",
find: ".selectPreviousCommandOption(",
replacement: {
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
replace: "$self.shouldSubmit($1, $2)"
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
}
},
{

View file

@ -19,6 +19,7 @@
import { definePluginSettings, Settings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards";
@ -27,15 +28,14 @@ import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common";
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
const ActivityView = findComponentByCodeLazy(".party?(0", ".card");
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
async function getApplicationAsset(key: string): Promise<string> {
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
}
@ -169,7 +169,7 @@ const settings = definePluginSettings({
value: TimestampMode.NOW
},
{
label: "Same as your current time",
label: "Same as your current time (not reset after 24h)",
value: TimestampMode.TIME
},
{
@ -269,6 +269,7 @@ function isStreamLinkDisabled() {
function isStreamLinkValid(value: string) {
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
if (value && value.length > 512) return "Streaming link must be not longer than 512 characters.";
return true;
}
@ -277,8 +278,9 @@ function isTimestampDisabled() {
}
function isImageKeyValid(value: string) {
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image. (e.g. https://i.imgur.com/...)";
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image. (e.g. https://media.tenor.com/...)";
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead.";
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'";
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image (e.g. https://media.tenor.com/...). Right click the GIF and click 'Copy image address'";
return true;
}
@ -390,13 +392,24 @@ async function setRpc(disable?: boolean) {
export default definePlugin({
name: "CustomRPC",
description: "Allows you to set a custom rich presence.",
description: "Add a fully customisable Rich Presence (Game status) to your Discord profile",
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
dependencies: ["UserSettingsAPI"],
start: setRpc,
stop: () => setRpc(true),
settings,
patches: [
{
find: ".party?(0",
all: true,
replacement: {
match: /\i\.id===\i\.id\?null:/,
replace: ""
}
}
],
settingsAboutComponent: () => {
const activity = useAwaiter(createActivity);
const gameActivityEnabled = ShowCurrentGame.useSetting();
@ -410,7 +423,7 @@ export default definePlugin({
style={{ padding: "1em" }}
>
<Forms.FormTitle>Notice</Forms.FormTitle>
<Forms.FormText>Game activity isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
<Forms.FormText>Activity Sharing isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
<Button
color={Button.Colors.TRANSPARENT}
@ -422,24 +435,33 @@ export default definePlugin({
</ErrorCard>
)}
<Forms.FormText>
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
get the application ID.
</Forms.FormText>
<Forms.FormText>
Upload images in the Rich Presence tab to get the image keys.
</Forms.FormText>
<Forms.FormText>
If you want to use image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and select "Copy image address".
</Forms.FormText>
<Flex flexDirection="column" style={{ gap: ".5em" }} className={Margins.top16}>
<Forms.FormText>
Go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
get the application ID.
</Forms.FormText>
<Forms.FormText>
Upload images in the Rich Presence tab to get the image keys.
</Forms.FormText>
<Forms.FormText>
If you want to use an image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and selecting "Copy image address".
</Forms.FormText>
<Forms.FormText>
You can't see your own buttons on your profile, but everyone else can see it fine.
</Forms.FormText>
<Forms.FormText>
Some weird unicode text ("fonts" 𝖑𝖎𝖐𝖊 𝖙𝖍𝖎𝖘) may cause the rich presence to not show up, try using normal letters instead.
</Forms.FormText>
</Flex>
<Forms.FormDivider className={Margins.top8} />
<div style={{ width: "284px", ...profileThemeStyle, padding: 8, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
{activity[0] && <ActivityComponent activity={activity[0]} channelId={SelectedChannelStore.getChannelId()}
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
application={{ id: settings.store.appID }}
user={UserStore.getCurrentUser()} />}
<div style={{ width: "284px", ...profileThemeStyle, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
{activity[0] && <ActivityView
activity={activity[0]}
user={UserStore.getCurrentUser()}
currentUser={UserStore.getCurrentUser()}
/>}
</div>
</>
);

View file

@ -41,7 +41,7 @@ export default definePlugin({
{
find: "DefaultCustomizationSections",
replacement: {
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/,
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
replace: "$self.DecorSection(),"
}
},

View file

@ -93,7 +93,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate(
} as AuthorizationState),
{
name: "decor-auth",
getStorage: () => indexedDBStorage,
storage: indexedDBStorage,
partialize: state => ({ tokens: state.tokens }),
onRehydrateStorage: () => state => state?.init()
}

View file

@ -95,24 +95,39 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate((set: any,
} as UsersDecorationsState)));
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
try {
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
useEffect(() => {
const destructor = useUsersDecorationsStore.subscribe(
state => {
if (!user) return;
const newDecorAvatarDecoration = state.getAsset(user.id);
if (!newDecorAvatarDecoration) return;
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
}
);
useEffect(() => {
const destructor = (() => {
try {
return useUsersDecorationsStore.subscribe(
state => {
if (!user) return;
const newDecorAvatarDecoration = state.getAsset(user.id);
if (!newDecorAvatarDecoration) return;
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
}
);
} catch {
return () => { };
}
})();
if (user) {
const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState();
fetchUserDecorAvatarDecoration(user.id);
}
return destructor;
}, []);
try {
if (user) {
const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState();
fetchUserDecorAvatarDecoration(user.id);
}
} catch { }
return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
return destructor;
}, []);
return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
} catch (e) {
console.error(e);
}
return null;
}

View file

@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection";
export const settings = definePluginSettings({
changeDecoration: {
type: OptionType.COMPONENT,
description: "Change your avatar decoration",
component() {
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
Enable Decor and restart your client to change your avatar decoration.

View file

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

View file

@ -5,7 +5,8 @@
*/
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 { DecorationGridItem } from ".";
@ -24,7 +25,7 @@ export default function DecorationGridNone(props: DecorationGridNoneProps) {
variant="text-xs/normal"
color="header-primary"
>
{i18n.Messages.NONE}
{getIntlMessage("NONE")}
</Text>
</DecorationGridItem >;
}

View file

@ -5,6 +5,7 @@
*/
import { React } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../";

View file

@ -7,6 +7,7 @@
import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack";
import { React } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../";
import Grid, { GridProps } from "./Grid";

View file

@ -10,6 +10,7 @@ import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Queue } from "@utils/Queue";
import { findComponentByCodeLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
@ -49,6 +50,8 @@ interface SectionHeaderProps {
section: Section;
}
const fetchAuthorsQueue = new Queue();
function SectionHeader({ section }: SectionHeaderProps) {
const hasSubtitle = typeof section.subtitle !== "undefined";
const hasAuthorIds = typeof section.authorIds !== "undefined";
@ -56,17 +59,18 @@ function SectionHeader({ section }: SectionHeaderProps) {
const [authors, setAuthors] = useState<User[]>([]);
useEffect(() => {
(async () => {
fetchAuthorsQueue.push(async () => {
if (!section.authorIds) return;
for (const authorId of section.authorIds) {
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId);
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId).catch(() => null);
if (author == null) continue;
setAuthors(authors => [...authors, author]);
}
})();
});
}, [section.authorIds]);
return <div>
<Flex>
<Forms.FormTitle style={{ flexGrow: 1 }}>{section.title}</Forms.FormTitle>

View file

@ -20,7 +20,7 @@ import { AvatarDecorationModalPreview } from "../components";
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"),
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"),
HelpMessage: filters.byCode(".iconDiv")
});
@ -119,8 +119,8 @@ function CreateDecorationModal(props: ModalProps) {
/>
</div>
</div>
<Forms.FormText type="description" className={Margins.bottom16}>
<br />You can receive updates on your decoration's review by joining <Link
<HelpMessage messageType={HelpMessageTypes.INFO} className={Margins.bottom8}>
To receive updates on your decoration's review, join <Link
href={`https://discord.gg/${INVITE_KEY}`}
onClick={async e => {
e.preventDefault();
@ -138,8 +138,8 @@ function CreateDecorationModal(props: ModalProps) {
}}
>
Decor's Discord server
</Link>.
</Forms.FormText>
</Link> and allow direct messages.
</HelpMessage>
</ErrorBoundary>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>

View file

@ -16,18 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
export default definePlugin({
name: "DisableCallIdle",
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
authors: [Devs.Nuckyz],
patches: [
{
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
find: "#{intl::BOT_CALL_IDLE_DISCONNECT_2}",
replacement: {
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
replace: ";return;"

View file

@ -211,7 +211,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
alignItems: "center"
}}>
{guilds.map(g => (
<Tooltip text={g.name}>
<Tooltip key={g.id} text={g.name}>
{({ onMouseLeave, onMouseEnter }) => (
<div
onMouseLeave={onMouseLeave}
@ -310,7 +310,8 @@ function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Om
}
function isGifUrl(url: string) {
return new URL(url).pathname.endsWith(".gif");
const u = new URL(url);
return u.pathname.endsWith(".gif") || u.searchParams.get("animated") === "true";
}
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {

View file

@ -16,15 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
import { getCurrentGuild, getEmojiURL } from "@utils/discord";
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 { 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, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
@ -194,6 +194,26 @@ const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId,
const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS);
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}:)\i`),
replace: "() => true",
predicate
}))
};
}
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
@ -203,6 +223,8 @@ export default definePlugin({
settings,
patches: [
// General bypass patches
makeBypassPatches(),
// Patch the emoji picker in voice calls to not be bypassed by fake nitro
{
find: "emojiItemDisabled]",
@ -213,7 +235,7 @@ export default definePlugin({
}
},
{
find: ".PREMIUM_LOCKED;",
find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
group: true,
predicate: () => settings.store.enableEmojiBypass,
replacement: [
@ -234,8 +256,11 @@ export default definePlugin({
},
{
// Disallow the emoji for premium locked if the intention doesn't allow it
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
replace: (m, not) => not
? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
: `(${m}||${IS_BYPASSEABLE_INTENTION})`
},
{
// Allow animated emojis to be used if the intention allows it
@ -252,15 +277,6 @@ export default definePlugin({
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
}
},
// Allow stickers to be sent everywhere
{
find: "canUseCustomStickersEverywhere:",
predicate: () => settings.store.enableStickerBypass,
replacement: {
match: /(?<=canUseCustomStickersEverywhere:)\i/,
replace: "()=>true"
},
},
// Make stickers always available
{
find: '"SENDABLE"',
@ -270,37 +286,15 @@ export default definePlugin({
replace: "true?"
}
},
// Allow streaming with high quality
{
find: "canUseHighVideoUploadQuality:",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: [
"canUseHighVideoUploadQuality",
"canStreamQuality",
].map(func => {
return {
match: new RegExp(`(?<=${func}:)\\i`, "g"),
replace: "()=>true"
};
})
},
// Remove boost requirements to stream with high quality
{
find: "STREAM_FPS_OPTION.format",
find: "#{intl::STREAM_FPS_OPTION}",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: {
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
replace: ""
}
},
// Allow client themes to be changeable
{
find: "canUseClientThemes:",
replacement: {
match: /(?<=canUseClientThemes:)\i/,
replace: "()=>true"
}
},
{
find: '"UserSettingsProtoStore"',
replacement: [
@ -359,13 +353,13 @@ export default definePlugin({
{
// Filter attachments to remove fake nitro stickers or emojis
predicate: () => settings.store.transformStickers,
match: /renderAttachments\(\i\){let{attachments:(\i).+?;/,
match: /renderAttachments\(\i\){.+?{attachments:(\i).+?;/,
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,
replacement: [
{
@ -390,7 +384,7 @@ export default definePlugin({
}
},
{
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
find: "#{intl::EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformEmojis,
replacement: {
// Add the fake nitro emoji notice
@ -398,17 +392,9 @@ export default definePlugin({
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)`
}
},
// Allow using custom app icons
{
find: "canUsePremiumAppIcons:",
replacement: {
match: /(?<=canUsePremiumAppIcons:)\i/,
replace: "()=>true"
}
},
// Separate patch for allowing using custom app icons
{
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
find: "?24:30,",
replacement: {
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
replace: "true"
@ -421,14 +407,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,
replace: "true"
}
},
// Allow using custom notification sounds
{
find: "canUseCustomNotificationSounds:",
replacement: {
match: /(?<=canUseCustomNotificationSounds:)\i/,
replace: "()=>true"
}
}
],
@ -539,7 +517,7 @@ export default definePlugin({
return array.filter(item => item != null);
},
ensureChildrenIsArray(child: ReactElement) {
ensureChildrenIsArray(child: ReactElement<any>) {
if (!Array.isArray(child.props.children)) child.props.children = [child.props.children];
},
@ -549,7 +527,7 @@ export default definePlugin({
let nextIndex = content.length;
const transformLinkChild = (child: ReactElement) => {
const transformLinkChild = (child: ReactElement<any>) => {
if (settings.store.transformEmojis) {
const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex);
if (fakeNitroMatch) {
@ -583,7 +561,7 @@ export default definePlugin({
return child;
};
const transformChild = (child: ReactElement) => {
const transformChild = (child: ReactElement<any>) => {
if (child?.props?.trusted != null) return transformLinkChild(child);
if (child?.props?.children != null) {
if (!Array.isArray(child.props.children)) {
@ -599,7 +577,7 @@ export default definePlugin({
return child;
};
const modifyChild = (child: ReactElement) => {
const modifyChild = (child: ReactElement<any>) => {
const newChild = transformChild(child);
if (newChild?.type === "ul" || newChild?.type === "ol") {
@ -626,7 +604,7 @@ export default definePlugin({
return newChild;
};
const modifyChildren = (children: Array<ReactElement>) => {
const modifyChildren = (children: Array<ReactElement<any>>) => {
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
children = this.clearEmptyArrayItems(children);
@ -878,7 +856,7 @@ export default definePlugin({
});
}
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
const { guildId } = this;
let hasBypass = false;
@ -945,7 +923,7 @@ export default definePlugin({
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);
@ -966,7 +944,7 @@ export default definePlugin({
return { cancel: false };
});
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => {
if (!s.enableEmojiBypass) return;
let hasBypass = false;
@ -978,7 +956,7 @@ export default definePlugin({
hasBypass = true;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);
@ -998,7 +976,7 @@ export default definePlugin({
},
stop() {
removePreSendListener(this.preSend);
removePreEditListener(this.preEdit);
removeMessagePreSendListener(this.preSend);
removeMessagePreEditListener(this.preEdit);
}
});

View file

@ -29,6 +29,7 @@ import definePlugin, { OptionType } from "@utils/types";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
import { User } from "discord-types/general";
import { ReactElement } from "react";
import virtualMerge from "virtual-merge";
interface UserProfile extends User {
@ -87,7 +88,7 @@ const settings = definePluginSettings({
interface ColorPickerProps {
color: number | null;
label: React.ReactElement;
label: ReactElement<any>;
showEyeDropper?: boolean;
suggestedColors?: string[];
onChange(value: number | null): void;
@ -108,10 +109,10 @@ interface ProfileModalProps {
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 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({
name: "FakeProfileThemes",
@ -126,9 +127,9 @@ export default definePlugin({
}
},
{
find: ".USER_SETTINGS_RESET_PROFILE_THEME",
find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}",
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})"
}
}

View file

@ -57,7 +57,7 @@ export default definePlugin({
{
// https://regex101.com/r/x2mobQ/1
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(\i-\i\.length)\)/,
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(Math\.max\(\i,\i(?:-\i\.length){2}\))\)/,
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
}

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