Compare commits

..

174 Commits

Author SHA1 Message Date
Vendicated
a9e67e2955 Bump to v1.6.1 2023-10-27 03:53:58 +02:00
Nuckyz
1676956f61 Fix hidden channels triggering the unread box 2023-10-26 20:59:48 -03:00
Nuckyz
8692109bc5 Add comments to some SHC patches 2023-10-26 18:13:25 -03:00
Nuckyz
0847f205b8 Fix some hidden channels not collapsing 2023-10-26 18:11:00 -03:00
AutumnVN
2f94e167c4 noProfileThemes: fix usrbg compatibility (#1905) 2023-10-26 22:52:48 +02:00
Vendicated
589c070773 fix not removing vencord 'chunks' from webpack 2023-10-26 22:51:07 +02:00
Nuckyz
8567ff6239 Little bit of SHC cleanup 2023-10-26 16:51:37 -03:00
Vendicated
e4701769a5 Fix entry webpack modules not being patched 2023-10-26 21:21:21 +02:00
Vendicated
25f101602d fix modules being patched multiple times 2023-10-26 21:03:05 +02:00
Nuckyz
85bfa1e719 Fix duplicated WebContextMenus find 2023-10-26 15:36:05 -03:00
Nuckyz
b48998d485 More accurate ShowAllMessageButtons patch 2023-10-26 15:25:29 -03:00
Nuckyz
6d605050e1 Fix BetterNoteBox 2023-10-26 15:07:44 -03:00
TheKodeToad
07c4a097e0 Fix EmoteCloner (#1907) 2023-10-26 13:49:18 -03:00
TheKodeToad
c1de41436a Fix plugins using promptToUpload (#1908) 2023-10-26 13:49:06 -03:00
Nuckyz
64c6f5740f Fix FakeNitro completely (#1903) 2023-10-26 03:19:26 +00:00
AutumnVN
03523446c1 silentTyping: fix showIcon toggle (#1898) 2023-10-26 00:50:33 +00:00
Vendicated
25dc25c707 Fix VoiceMessages 2023-10-26 02:28:17 +02:00
Hugo C
8ac8048845 fix: ImageZoom + PiP (#1894)
Co-authored-by: V <vendicated@riseup.net>
2023-10-26 02:17:31 +02:00
Vendicated
ffe6bb1c5d Bump to v1.6.0 2023-10-26 01:32:15 +02:00
Vendicated
74faaa216f Fix PlatformIndicators colorMobileIndicator 2023-10-26 01:29:52 +02:00
Luna
a6b8b59d5c fix: shikicodeblocks, betterroledot (#1895) 2023-10-26 01:03:11 +02:00
redstonekasi
9eaeb24196 fix: RoleColorEverywhere (#1884)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-10-26 00:56:39 +02:00
Nuckyz
6b28e5ad85 Partially fix FakeNitro 2023-10-25 19:38:35 -03:00
Vendicated
d852393cfb fix UserVoiceShow 2023-10-26 00:36:28 +02:00
IThundxr
0c12500c0a Fix: noUnblockToJump (#1893) 2023-10-26 00:29:26 +02:00
Vendicated
1502024440 Fix iLoveSpam 2023-10-26 00:29:03 +02:00
Vendicated
45fa4f89c6 Fix NoScreensharePreview 2023-10-26 00:20:56 +02:00
Vendicated
96dff84ed8 Fix PlatformIndicators 2023-10-26 00:03:51 +02:00
Vendicated
c1b8739104 Fix RevealAllSpoilers 2023-10-26 00:01:49 +02:00
Vendicated
2183d2d29d fix TimeBarAllActivities 2023-10-25 23:59:38 +02:00
Vendicated
722dcb033e Fix RelationShipNotifier 2023-10-25 23:50:33 +02:00
Ben Richeson
53cd14844f Fix MessageLogger (#1888)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-10-25 23:46:51 +02:00
nora
38daf6ec2b ImageZoom: fix typo (#1832) 2023-10-25 23:37:07 +02:00
Amia
635b80c58b fix: NoPendingCount (#1886) 2023-10-25 23:26:50 +02:00
Jack
ec9e111047 fix: (rewrite) SecretRingToneEnabler (#1887) 2023-10-25 23:25:39 +02:00
Jack
d54f8b5e8e fix: BANger (#1885) 2023-10-25 23:25:02 +02:00
Vendicated
09a922a01c Fix MuteNewGuild 2023-10-25 23:20:04 +02:00
Nuckyz
23963f40c2 Fix IgnoreActivities 2023-10-25 18:12:28 -03:00
Vendicated
a41abfef31 Fix MemberCount 2023-10-25 23:06:30 +02:00
Vendicated
ddee3e1264 Fix VencordToolbox 2023-10-25 23:00:11 +02:00
Aubrey/オーブリー
6c4afa52a3 Fix titlebar duplication with native Windows titlebar (#1890)
Co-authored-by: V <vendicated@riseup.net>
2023-10-25 22:49:41 +02:00
Vendicated
ce6081c39b Merge branch 'main' into dev 2023-10-25 22:49:09 +02:00
Nuckyz
123853e848 Fix ShowHiddenChannels 2023-10-25 17:25:44 -03:00
Vendicated
0c6445b66b fix reporter 2023-10-25 21:06:18 +02:00
Vendicated
7094345516 fix lintttttttttttttttttt :3 2023-10-25 21:01:20 +02:00
Vendicated
148a32096c Fix PronounDB in profile 2023-10-25 20:51:37 +02:00
TheKodeToad
ab1b002ed1 TypingTweaks: fix (#1883) 2023-10-25 20:43:48 +02:00
rini
c7a20769f9 fix things using lodash (#1882) 2023-10-25 20:29:32 +02:00
Amia
9c13befcb6 fix: ServerProfile (#1881) 2023-10-25 20:24:31 +02:00
Vendicated
3fdd0edf14 Fix InvisibleChat 2023-10-25 20:24:17 +02:00
Vendicated
2169090ce5 Fix SpotifyCrack 2023-10-25 20:20:46 +02:00
redstonekasi
5e13de72c2 fix: BlurNSFW (#1880) 2023-10-25 20:16:57 +02:00
Vendicated
3a7c27253b Fix PermissionFreeWill 2023-10-25 20:14:39 +02:00
Vendicated
465a87f66b Fix ColorSighted 2023-10-25 20:07:45 +02:00
Amia
13c59f47cb fix: MutualGroupDMs (#1879) 2023-10-25 20:01:25 +02:00
Vendicated
0d7157dd20 Fix BetterNoteBox 2023-10-25 19:59:20 +02:00
Vendicated
88c6e2a0e9 Fix PermissionViewer 2023-10-25 19:54:13 +02:00
Vendicated
535d510d3b Fix LoadingQuotes 2023-10-25 19:47:29 +02:00
Vendicated
cbc23b1bdd Fix BetterGifAltText 2023-10-25 19:41:18 +02:00
Vendicated
aa2a57b733 Fix Dearrow 2023-10-25 19:33:39 +02:00
Vendicated
c3ccbbfa99 Fix SpotifyControls 2023-10-25 19:28:07 +02:00
Vendicated
685f44d40f Fix OpenInApp 2023-10-25 19:22:57 +02:00
Vendicated
8f0009778a Fix ViewIcons 2023-10-25 19:09:59 +02:00
rini
f385dc380e fix whoreacted, betteruploadbutton (#1877) 2023-10-25 19:09:37 +02:00
redstonekasi
bb900785ed fix: ShowAllMessageButtons (#1876) 2023-10-25 18:52:21 +02:00
redstonekasi
61fac0a8f1 fix: NoPendingCount (#1871)
Co-authored-by: V <vendicated@riseup.net>
2023-10-25 18:46:32 +02:00
Jack
1a607639cc fix: greetStickerPicker (#1874) 2023-10-25 18:43:28 +02:00
Vendicated
c90440f031 Fix Settings Proto 2023-10-25 18:42:31 +02:00
Vendicated
baf952512c Fix InvisChat, PreviewMsg, SendTimestamps, SilentMsg, SilentType, Translate 2023-10-25 18:35:03 +02:00
rini
fbc5038306 fix: RPC plugins (#1873) 2023-10-25 18:20:32 +02:00
Vendicated
ddc39fe84d Fix SettingStores, GameActivityToggle 2023-10-25 18:15:18 +02:00
redstonekasi
4f8c75372c fix: NoBlockedMessages (#1870) 2023-10-25 18:09:04 +02:00
Jack Matthews
024a77c577 fix: typingIndicator 2023-10-25 18:07:41 +02:00
Vendicated
1130521e4b Fix WebContextMenus 2023-10-25 18:07:12 +02:00
redstonekasi
3bd657611c fix: StartupTimings (#1869) 2023-10-25 17:38:55 +02:00
Syncx
b659d4e9c1 fix: FavoriteEmojiFirst (#1844) 2023-10-25 17:38:55 +02:00
rini
9c60b38acc Fix PinDMs, SMYN, SilentTyping, ValidUser, PlatformIndicators (#1865) 2023-10-25 17:38:55 +02:00
sunnie
c5dd50ad8f fix messageLinkEmbeds, moreUserTags (#1859) 2023-10-25 17:38:55 +02:00
redstonekasi
131e91a37c fix: AlwaysTrust (#1868) 2023-10-25 17:38:55 +02:00
Nico
06b4dffa62 forceOwnerCrown: fix (#1858) 2023-10-25 17:38:55 +02:00
Syncx
3917193e8f fix: ImageZoom (#1864) 2023-10-25 17:38:55 +02:00
redstonekasi
8d1561aed4 fix: NoticesAPI (#1863) 2023-10-25 17:38:55 +02:00
Dea
cf3c28e1ff fix: onePingPerDM (#1867) 2023-10-25 17:38:55 +02:00
Vendicated
af1aa39647 Delete RNNoise - Krisp is now on web, so this is obsolete 2023-10-25 17:38:55 +02:00
megumin
7de1b5dcb6 fix: sortFriendRequests patches (#1848) 2023-10-25 17:38:55 +02:00
megumin
cd06980016 fix: pronoundb profile popout (#1860) 2023-10-25 17:38:55 +02:00
Nico
e69236c5f8 fix(vcDoubleClick): update for new discord build (#1862) 2023-10-25 17:38:55 +02:00
AutumnVN
2478ffb695 fix userutils (#1861) 2023-10-25 17:38:55 +02:00
Vendicated
788d22f9e9 MessageDecorationsAPI: fix 2023-10-25 17:38:55 +02:00
Vendicated
b69ac1cdad Fix PlatformIndicators 2023-10-25 17:38:55 +02:00
redstonekasi
892a79b2a7 fix: CommandsAPI 2023-10-25 17:38:55 +02:00
Vendicated
9895540943 MemberListDecoratorAPI: Fix 2023-10-25 17:38:55 +02:00
Vendicated
7e0fc1d0ea ServerListAPI: fix 2023-10-25 17:38:55 +02:00
Vendicated
bb771153e3 MessageEvents: Fix 2023-10-25 17:38:55 +02:00
TheKodeToad
65d39dcf21 ShowConnections, NoProfileThemes: Fix (#1854) 2023-10-25 17:38:55 +02:00
Jack
cf5e93ee52 fix: fakeProfileThemes (#1837) 2023-10-25 17:38:55 +02:00
megumin
4d8e4e62ca fix: notrack failing patches (#1857) 2023-10-25 17:38:55 +02:00
AutumnVN
cb2532d22c disableDMCallIdle: fix (#1856) 2023-10-25 17:38:55 +02:00
AutumnVN
8c998b9330 experiments: fix (#1855) 2023-10-25 17:38:55 +02:00
Vendicated
fb22c57da1 MessageAccessoriesAPI: Fix 2023-10-25 17:38:55 +02:00
Vendicated
554bb18e9b BadgeAPI: fix 2023-10-25 17:38:55 +02:00
Vendicated
44c9675795 ContextMenuApi: fix 2023-10-25 17:38:55 +02:00
AutumnVN
322ecc5e88 usrbg: fix (#1853) 2023-10-25 17:38:55 +02:00
Vendicated
7ee9a8bb99 SpotifyControls: Fix 2023-10-25 17:38:55 +02:00
Vendicated
69b54535c3 Fix NoTrack 2023-10-25 17:38:55 +02:00
AutumnVN
2a56081bc2 callTimer: fix (#1850) 2023-10-25 17:38:55 +02:00
AutumnVN
baa7d8c078 noDevToolsWarning: fix (#1851) 2023-10-25 17:38:55 +02:00
AutumnVN
940193c30b noMosaic: fix (#1849) 2023-10-25 17:38:55 +02:00
Hugo C
09b646b860 fix: PictureInPicture (#1839)
Co-authored-by: V <vendicated@riseup.net>
2023-10-25 17:38:55 +02:00
Syncx
922e3ce6fe fix: FavoriteGifSearch (#1842) 2023-10-25 17:38:55 +02:00
Erik
cb93c11e16 Fix CustomRPC (#1846) 2023-10-25 17:38:55 +02:00
megumin
4beef9f73b fix: ComponentDispatch and GifPaste plugin (#1843)
Co-authored-by: V <vendicated@riseup.net>
2023-10-25 17:38:55 +02:00
Vendicated
4da79abb21 Fix components filter 2023-10-25 17:38:55 +02:00
Vendicated
4e27722b54 fix webpack patching 2023-10-25 17:38:55 +02:00
Vendicated
97c0face2f PinDMs: Fix canary crash 2023-10-25 00:38:02 +02:00
V
1a1d9b07e8 Fix canary crashing (#1833) 2023-10-25 00:17:11 +02:00
Nuckyz
4a2def03e7 Fix MessageDecorationsAPI patch 2023-10-23 22:42:23 -03:00
Vendicated
544edce9f9 bump to v1.5.8 2023-10-21 19:26:29 +02:00
Dea
e4485165d0 onePingPerDM: add settings (#1802)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Dea <dea-banana@riseup.net>
2023-10-21 18:42:37 +02:00
Macintosh II
fada76ec81 PlatformIndicators: make size same as other memberlist icons (#1789)
Co-authored-by: V <vendicated@riseup.net>
2023-10-21 18:41:56 +02:00
zImPatrick
f659c46031 FakeNitro: Add app icon customization (#1822)
Co-authored-by: V <vendicated@riseup.net>
2023-10-21 17:53:00 +02:00
Nuckyz
ae1dc4eab0 Make reporter ignore useless Discord errors (#1829) 2023-10-21 17:51:07 +02:00
Nuckyz
fe60a72b80 Fix NoPendingCount patch 2023-10-21 12:26:04 -03:00
Nuckyz
5a0b2ee3f5 Fix FakeNitro patch 2023-10-21 12:26:04 -03:00
Nuckyz
6c1b8b0d8a Fix MessageDecorationsAPI 2023-10-21 12:25:57 -03:00
Nuckyz
b2a1410a96 Remove useless Experiments patch 2023-10-21 12:03:54 -03:00
Nuckyz
c25c95eecd Fix IgnoreActivities making reporter angry 2023-10-21 12:00:09 -03:00
ioj4
d94418f42f fix: windows host update patching (#1820) 2023-10-19 11:14:40 +02:00
Vendicated
da1a8cdd67 web: Fix themes tab 2023-10-19 10:13:05 +02:00
Vendicated
5d7ede34d8 bump to v1.5.7 2023-10-19 01:23:59 +02:00
Vendicated
cd61354998 ContributorModal: Fix vertical overflow on multi word names 2023-10-19 00:22:49 +02:00
AutumnVN
a452945ac8 feat(plugin): NoTypingAnimation (#1680) 2023-10-19 00:14:14 +02:00
Marocco2
b577660800 feat(VcNarrator): add {{NICKNAME}} as placeholder (#1792) 2023-10-19 00:05:47 +02:00
AutumnVN
4f57c7eded betterNotes, userVoiceShow: fix padding issue (#1804) 2023-10-18 23:54:35 +02:00
Ryan Cao
e3e5da10a9 fix: Content Security Policy patching (#1814)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-10-18 23:44:29 +02:00
Vendicated
998ce72f3b ForceOwnerCrown: Remove log spam 2023-10-14 02:46:57 +02:00
AutumnVN
188d12d1a3 roleColorEverywhere: role group color in thread/forum (#1778) 2023-10-13 04:26:18 +02:00
AutumnVN
a522eab40d feat(plugin): NoMosaic (#1791) 2023-10-13 04:10:36 +02:00
AutumnVN
c2721f158f imageZoom: fix again (#1793)
Co-authored-by: V <vendicated@riseup.net>
2023-10-13 04:07:21 +02:00
Vendicated
61cd7b4d99 arrpc: refactor for use in vesktop 2023-10-13 03:49:58 +02:00
Marocco2
926af0d1cd feat(VcNarrator): add {{DISPLAY_NAME}} as placeholder (#1642)
Co-authored-by: V <vendicated@riseup.net>
2023-10-12 04:05:46 +02:00
sunnie
dcaf4aec97 fix moreUserTags (#1780) 2023-10-12 03:30:32 +02:00
Nuckyz
5a97adb435 Fix broken IgnoreActivities patch 2023-10-10 05:10:46 -03:00
V
925d709335 bump to v1.5.6 2023-10-09 03:57:44 +02:00
V
1a36dbbc9b ci: cleanup publish workflow 2023-10-09 03:56:41 +02:00
V
b59db2f8c2 Drop Firefox extension support
Despite me already fixing all issues, mozilla is still giving me more
trouble. Now they are asking me to provide them with testing credentials
for discord. Not only do i not want to give them my account, it also
isn't even possible because of how discord's login from new location
verification works

i am very tired of having to fight mozilla and their stupid guidelines /
requests. publishing to amo is a nightmare. as such, official support
for the extension is hereby dropped

we cannot even distribute the extension ourselves because extensions
NEED TO BE SIGNED to install them (unless you use firefox nightly).
and guess how you sign? VIA THEIR STUPID STORE

Options for firefox users:
- use the UserScript
- grab extension-firefox.zip from releases and install it on firefox
  nightly
- make your own firefox developer account and manually sign the
  extension-firefox.zip and pray they sign it for you (they wouldn't
  sign my unlisted upload of it)
- use a chromium browser
2023-10-09 03:49:33 +02:00
V
d81302f64c Revert mozilla store compliance changes
This reverts commit 97b6699afefe373d510dda5589a0754a4b380153.

Vencord is dropping support for the firefox extension, so these changes
are now obsolete. revert so users can still install the extension
manually and enjoy the full experience
2023-10-09 03:15:43 +02:00
V
390987e4a9 Remove ReviewDB
abuse / harassment has gotten pretty bad.
i proposed the ability to allow users to delete comments from their own
profile, but there seems to be no interest among the reviewdb owners.

i don't want vencord to harbour harassment, so i'm removing the plugin
for now
2023-10-09 03:09:56 +02:00
V
377cf60055 fix global settings listeners 2023-10-09 03:01:54 +02:00
Nico
34ac718705 fix(forceOwnerCrown): update broken patch (#1777) 2023-10-07 23:20:36 -03:00
Nuckyz
e4659ed7c3 Rewrite IgnoreActivities (#1693) 2023-10-07 23:04:17 -03:00
Vendicated
c33d59b45d serverProfile: fix crash with lurked guilds
Co-authored-by: aamiaa <9750071+aamiaa@users.noreply.github.com>
2023-10-08 03:49:00 +02:00
Vendicated
ac1b67ccbd Experiments(isStaff): Fix search history not showing 2023-10-07 23:00:44 +02:00
Vendicated
c0f2c97458 ReviewDB: proper multi account support 2023-10-06 19:43:24 +02:00
Vendicated
664dd0a992 ReviewDB: allow deleting reviews on own profile 2023-10-06 18:44:22 +02:00
Vendicated
f66e35b658 fix(SendTimestamps): Do not add to ReviewDB input 2023-10-06 18:05:53 +02:00
Vendicated
df214e1e93 chore: remove legacy code 2023-10-06 18:01:19 +02:00
Vendicated
47a39a062e Fix Vesktop SettingsCog context menu 2023-10-06 17:54:46 +02:00
Vendicated
9e63da6d78 bump to v1.5.5 2023-10-06 04:08:49 +02:00
AutumnVN
79295683ee PictureInPicture: pip button hover styles (#1775)
Co-authored-by: V <vendicated@riseup.net>
2023-10-06 04:07:16 +02:00
Vendicated
5eb9dd04df Fix member list decorations api 2023-10-06 04:00:09 +02:00
Vendicated
03b5dc9c27 BetterRoleDot: Fix ci test false positives 2023-10-06 03:17:44 +02:00
wntiv-main
726a1b5d96 Fix command API (#1776)
Co-authored-by: V <vendicated@riseup.net>
2023-10-06 03:16:21 +02:00
AutumnVN
581fe252a4 fix imageZoom (#1772) 2023-10-03 02:39:34 +02:00
Vendicated
30b2e88e77 Bump to v1.5.4 2023-10-03 02:29:57 +02:00
TheKodeToad
1f38a8eeab Fix WhoReacted (#1769) 2023-10-03 02:26:38 +02:00
Vendicated
6db9721c06 ReviewDB: fix usericons appearing over modals 2023-10-03 02:17:28 +02:00
Dea
9891791fa7 feat(plugin): onePingPerDM (#1757)
Co-authored-by: V <vendicated@riseup.net>
2023-10-03 01:53:14 +02:00
Lewis Crichton
8dd5eeead2 feat(plugin): PermissionFreeWill (#1763)
Co-authored-by: V <vendicated@riseup.net>
2023-10-03 01:26:57 +02:00
whqwert
abf8667a5d fix(plugin): Party mode 🎉 2023-10-02 23:17:51 +02:00
Vendicated
e33ac900bc Merge remote-tracking branch 'origin/main' into dev 2023-10-02 23:10:07 +02:00
V
8a026060c7 Update blank.yml 2023-09-29 19:01:55 +02:00
V
c8b77bb187 Update bug_report.yml 2023-09-29 19:00:50 +02:00
AutumnVN
88b06191b9 fix modal image + reviewdb bot tag (#1761)
* fix modal image

* fix reviewdb bot tag
2023-09-29 00:46:33 +02:00
167 changed files with 1706 additions and 3000 deletions

View File

@ -51,7 +51,10 @@
"eqeqeq": ["error", "always", { "null": "ignore" }], "eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }], "spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error", "yoda": "error",
"prefer-destructuring": ["error", { "object": true, "array": false }], "prefer-destructuring": ["error", {
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}],
"operator-assignment": ["error", "always"], "operator-assignment": ["error", "always"],
"no-useless-computed-key": "error", "no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }], "no-unneeded-ternary": ["error", { "defaultAssignment": false }],

View File

@ -2,9 +2,29 @@ name: Blank Issue
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
body: body:
- type: markdown
attributes:
value: |
# READ THIS BEFORE OPENING AN ISSUE
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
- you are filing a security related report
- type: textarea - type: textarea
id: content id: content
attributes: attributes:
label: Content label: Content
validations: validations:
required: true required: true
- type: checkboxes
id: agreement-check
attributes:
label: Request Agreement
options:
- label: I have read the requirements for opening an issue above
required: true

View File

@ -4,6 +4,18 @@ labels: [bug]
title: "[Bug] <title>" title: "[Bug] <title>"
body: body:
- type: markdown
attributes:
value: |
# READ THIS BEFORE OPENING AN ISSUE
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
- you are filing a security related report
- type: input - type: input
id: discord id: discord
attributes: attributes:
@ -64,3 +76,5 @@ body:
options: options:
- label: I am using Discord Stable or tried on Stable and this bug happens there as well - label: I am using Discord Stable or tried on Stable and this bug happens there as well
required: true required: true
- label: I have read the requirements for opening an issue above
required: true

View File

@ -36,26 +36,10 @@ jobs:
- name: Publish extension - name: Publish extension
run: | run: |
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
EXIT_CODE=0
# Chrome
cd dist/chromium-unpacked cd dist/chromium-unpacked
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$? pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
# Firefox
cd ../firefox-unpacked
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
web-ext-submit || EXIT_CODE=$?
exit $EXIT_CODE
env: env:
# Chrome
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
# Firefox
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}

View File

@ -22,29 +22,7 @@ The cutest Discord client mod
## Installing / Uninstalling ## Installing / Uninstalling
Click the below button to install Vencord to the Discord Desktop app Visit https://vencord.dev/download
[![Download and run the Installer](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#vencord-installer)
## Installing on Browser
[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
<details>
<summary>Alternative Downloads</summary>
## Vencord Desktop
> **Warning**
> This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
</details>
## Join our Support/Community Server ## Join our Support/Community Server

32
browser/background.js Normal file
View File

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

View File

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

View File

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.5.3", "version": "1.6.1",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -94,7 +94,7 @@
"build": { "build": {
"overwriteDest": true "overwriteDest": true
}, },
"sourceDir": "./dist/extension-v2-unpacked" "sourceDir": "./dist/firefox-unpacked"
}, },
"engines": { "engines": {
"node": ">=18", "node": ">=18",

View File

@ -43,7 +43,7 @@ const nodeCommonOpts = {
format: "cjs", format: "cjs",
platform: "node", platform: "node",
target: ["esnext"], target: ["esnext"],
external: ["electron", ...commonOpts.external], external: ["electron", "original-fs", ...commonOpts.external],
define: defines, define: defines,
}; };

View File

@ -145,11 +145,11 @@ async function loadDir(dir, basePath = "") {
/** /**
* @type {(target: string, files: string[]) => Promise<void>} * @type {(target: string, files: string[]) => Promise<void>}
*/ */
async function buildExtension(target, files, noMonaco = false) { async function buildExtension(target, files) {
const entries = { const entries = {
"dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.js": await readFile("dist/extension.js"),
"dist/Vencord.css": await readFile("dist/extension.css"), "dist/Vencord.css": await readFile("dist/extension.css"),
...(noMonaco ? {} : await loadDir("dist/monaco")), ...await loadDir("dist/monaco"),
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file => ...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)] [`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
))), ))),
@ -195,8 +195,11 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
await Promise.all([ await Promise.all([
appendCssRuntime, appendCssRuntime,
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true), buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
]); ]);
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip"); Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
console.info("Packed Chromium Extension written to dist/extension.zip"); console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");

View File

@ -61,6 +61,13 @@ const report = {
otherErrors: [] as string[] otherErrors: [] as string[]
}; };
const IGNORED_DISCORD_ERRORS = [
"KeybindStore: Looking for callback action",
"Unable to process domain list delta: Client revision number is null",
"Downloading the full bad domains file",
/\[GatewaySocket\].{0,110}Cannot access '/
] as Array<string | RegExp>;
function toCodeBlock(s: string) { function toCodeBlock(s: string) {
s = s.replace(/```/g, "`\u200B`\u200B`"); s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```"; return "```" + s + " ```";
@ -86,6 +93,8 @@ async function printReport() {
console.log(` - Error: ${toCodeBlock(p.error)}`); console.log(` - Error: ${toCodeBlock(p.error)}`);
}); });
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
console.log("## Discord Errors"); console.log("## Discord Errors");
report.otherErrors.forEach(e => { report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e)}`);
@ -259,7 +268,7 @@ function runTime(token: string) {
const { wreq } = Vencord.Webpack; const { wreq } = Vencord.Webpack;
console.error("[PUP_DEBUG]", "Loading all chunks..."); console.error("[PUP_DEBUG]", "Loading all chunks...");
const ids = Function("return" + wreq.u.toString().match(/\{.+\}/s)![0])(); const ids = Function("return" + wreq.u.toString().match(/(?<=\()\{.+?\}/s)![0])();
for (const id in ids) { for (const id in ids) {
const isWasm = await fetch(wreq.p + wreq.u(id)) const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .then(r => r.text())

View File

@ -136,7 +136,7 @@ if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLow
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.head.append(Object.assign(document.createElement("style"), { document.head.append(Object.assign(document.createElement("style"), {
id: "vencord-native-titlebar-style", id: "vencord-native-titlebar-style",
textContent: "[class*=titleBar-]{display: none!important}" textContent: "[class*=titleBar]{display: none!important}"
})); }));
}, { once: true }); }, { once: true });
} }

View File

@ -17,14 +17,14 @@
*/ */
import { mergeDefaults } from "@utils/misc"; import { mergeDefaults } from "@utils/misc";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { SnowflakeUtils } from "@webpack/common"; import { SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
import type { PartialDeep } from "type-fest"; import type { PartialDeep } from "type-fest";
import { Argument } from "./types"; import { Argument } from "./types";
const createBotMessage = findByCodeLazy('username:"Clyde"'); const MessageCreator = findByPropsLazy("createBotMessage");
const MessageSender = findByPropsLazy("receiveMessage"); const MessageSender = findByPropsLazy("receiveMessage");
export function generateId() { export function generateId() {
@ -38,7 +38,7 @@ export function generateId() {
* @returns {Message} * @returns {Message}
*/ */
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message { export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
const botMessage = createBotMessage({ channelId, content: "", embeds: [] }); const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] });
MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage)); MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage));

View File

@ -20,7 +20,6 @@ import { Channel, User } from "discord-types/general/index.js";
interface DecoratorProps { interface DecoratorProps {
activities: any[]; activities: any[];
canUseAvatarDecorations: boolean;
channel: Channel; channel: Channel;
/** /**
* Only for DM members * Only for DM members
@ -52,9 +51,9 @@ export function removeDecorator(identifier: string) {
decorators.delete(identifier); decorators.delete(identifier);
} }
export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] { export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
const isInGuild = !!(props.guildId); const isInGuild = !!(props.guildId);
return [...decorators.values()].map(decoratorObj => { return Array.from(decorators.values(), decoratorObj => {
const { decorator, onlyIn } = decoratorObj; const { decorator, onlyIn } = decoratorObj;
// this can most likely be done cleaner // this can most likely be done cleaner
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) { if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {

View File

@ -237,7 +237,8 @@ type ResolvePropDeep<T, P> = P extends "" ? T :
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void; export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void;
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void; export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void;
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) { export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
((onUpdate as SubscriptionCallback)._paths ??= []).push(path); if (path)
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
subscriptions.add(onUpdate); subscriptions.add(onUpdate);
} }

View File

@ -1,69 +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 { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { findModuleId, wreq } from "@webpack";
import { Settings } from "./Settings";
interface Setting<T> {
/**
* Get the setting value
*/
getSetting(): T;
/**
* Update the setting value
* @param value The new value
*/
updateSetting(value: T | ((old: T) => T)): Promise<void>;
/**
* React hook for automatically updating components when the setting is updated
*/
useSetting(): T;
settingsStoreApiGroup: string;
settingsStoreApiName: string;
}
const SettingsStores: Array<Setting<any>> | undefined = proxyLazy(() => {
const modId = findModuleId('"textAndImages","renderSpoilers"');
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
const mod = wreq(modId);
if (mod == null) return;
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
});
/**
* Get the store for a setting
* @param group The setting group
* @param name The name of the setting
*/
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
}
/**
* getSettingStore but lazy
*/
export function getSettingStoreLazy<T = any>(group: string, name: string) {
return proxyLazy(() => getSettingStore<T>(group, name));
}

View File

@ -29,7 +29,6 @@ import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications"; import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList"; import * as $ServerList from "./ServerList";
import * as $Settings from "./Settings"; import * as $Settings from "./Settings";
import * as $SettingsStore from "./SettingsStore";
import * as $Styles from "./Styles"; import * as $Styles from "./Styles";
/** /**
@ -91,10 +90,6 @@ export const MemberListDecorators = $MemberListDecorators;
* An API allowing you to persist data * An API allowing you to persist data
*/ */
export const Settings = $Settings; export const Settings = $Settings;
/**
* An API allowing you to read, manipulate and automatically update components based on Discord settings
*/
export const SettingsStore = $SettingsStore;
/** /**
* An API allowing you to dynamically load styles * An API allowing you to dynamically load styles
* a * a

View File

@ -94,7 +94,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
(async () => { (async () => {
for (const user of plugin.authors.slice(0, 6)) { for (const user of plugin.authors.slice(0, 6)) {
const author = user.id const author = user.id
? await UserUtils.fetchUser(`${user.id}`) ? await UserUtils.getUser(`${user.id}`)
.catch(() => makeDummyUser({ username: user.name })) .catch(() => makeDummyUser({ username: user.name }))
: makeDummyUser({ username: user.name }); : makeDummyUser({ username: user.name });

View File

@ -17,6 +17,7 @@
font-size: 20px; font-size: 20px;
height: 20px; height: 20px;
position: relative; position: relative;
text-wrap: nowrap;
} }
.vc-author-modal-name::before { .vc-author-modal-name::before {

View File

@ -34,7 +34,7 @@ import { openModalLazy } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -251,7 +251,7 @@ export default function PluginSettings() {
} }
DataStore.set("Vencord_existingPlugins", existingTimestamps); DataStore.set("Vencord_existingPlugins", existingTimestamps);
return window._.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins; return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
})); }));
type P = JSX.Element | JSX.Element[]; type P = JSX.Element | JSX.Element[];

View File

@ -18,16 +18,14 @@
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons"; import { DeleteIcon } from "@components/Icons";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { IsFirefox } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native"; import { showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack"; import { findByPropsLazy, findLazy } from "@webpack";
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
import { UserThemeHeader } from "main/themes"; import { UserThemeHeader } from "main/themes";
import type { ComponentType, Ref, SyntheticEvent } from "react"; import type { ComponentType, Ref, SyntheticEvent } from "react";
@ -43,7 +41,7 @@ type FileInput = ComponentType<{
}>; }>;
const InviteActions = findByPropsLazy("resolveInvite"); const InviteActions = findByPropsLazy("resolveInvite");
const FileInput: FileInput = findByCodeLazy("activateUploadDialogue="); const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const TextAreaProps = findLazy(m => typeof m.textarea === "string");
const cl = classNameFactory("vc-settings-theme-"); const cl = classNameFactory("vc-settings-theme-");
@ -251,14 +249,12 @@ function ThemesTab() {
> >
Load missing Themes Load missing Themes
</Button> </Button>
{!IsFirefox && ( <Button
<Button onClick={() => VencordNative.quickCss.openEditor()}
onClick={() => VencordNative.quickCss.openEditor()} size={Button.Sizes.SMALL}
size={Button.Sizes.SMALL} >
> Edit QuickCSS
Edit QuickCSS </Button>
</Button>
)}
</> </>
</Card> </Card>
@ -320,15 +316,6 @@ function ThemesTab() {
return ( return (
<SettingsTab title="Themes"> <SettingsTab title="Themes">
{IsFirefox && (
<ErrorCard>
<Forms.FormTitle tag="h5">Warning</Forms.FormTitle>
<Forms.FormText>
You are using Firefox. Expect the vast majority of themes to not work.
If this is a problem, use a chromium browser or Discord Desktop / Vesktop.
</Forms.FormText>
</ErrorCard>
)}
<TabBar <TabBar
type="top" type="top"
look="brand" look="brand"

View File

@ -21,7 +21,6 @@ import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { IsFirefox } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity } from "@utils/misc"; import { identity } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native"; import { relaunch, showItemInFolder } from "@utils/native";
@ -110,14 +109,12 @@ function VencordSettings() {
Restart Client Restart Client
</Button> </Button>
)} )}
{!IsFirefox && ( <Button
<Button onClick={() => VencordNative.quickCss.openEditor()}
onClick={() => VencordNative.quickCss.openEditor()} size={Button.Sizes.SMALL}
size={Button.Sizes.SMALL} disabled={settingsDir === "Loading..."}>
disabled={settingsDir === "Loading..."}> Open QuickCSS File
Open QuickCSS File </Button>
</Button>
)}
{!IS_WEB && ( {!IS_WEB && (
<Button <Button
onClick={() => showItemInFolder(settingsDir)} onClick={() => showItemInFolder(settingsDir)}

View File

@ -62,6 +62,10 @@ if (IS_VESKTOP || !IS_VANILLA) {
} catch { } } catch { }
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
};
// Remove CSP // Remove CSP
type PolicyResult = Record<string, string[]>; type PolicyResult = Record<string, string[]>;
@ -73,6 +77,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
result[directiveKey] = directiveValue; result[directiveKey] = directiveValue;
} }
}); });
return result; return result;
}; };
const stringifyPolicy = (policy: PolicyResult): string => const stringifyPolicy = (policy: PolicyResult): string =>
@ -81,31 +86,39 @@ if (IS_VESKTOP || !IS_VANILLA) {
.map(directive => directive.flat().join(" ")) .map(directive => directive.flat().join(" "))
.join("; "); .join("; ");
function patchCsp(headers: Record<string, string[]>, header: string) { const patchCsp = (headers: Record<string, string[]>) => {
if (header in headers) { const header = findHeader(headers, "content-security-policy");
if (header) {
const csp = parsePolicy(headers[header][0]); const csp = parsePolicy(headers[header][0]);
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
csp[directive] = ["*", "blob:", "data:", "vencord:", "'unsafe-inline'"]; csp[directive] ??= [];
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
} }
// TODO: Restrict this to only imported packages with fixed version. // TODO: Restrict this to only imported packages with fixed version.
// Perhaps auto generate with esbuild // Perhaps auto generate with esbuild
csp["script-src"] ??= []; csp["script-src"] ??= [];
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com"); csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
headers[header] = [stringifyPolicy(csp)]; headers[header] = [stringifyPolicy(csp)];
} }
} };
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
if (responseHeaders) { if (responseHeaders) {
if (resourceType === "mainFrame") if (resourceType === "mainFrame")
patchCsp(responseHeaders, "content-security-policy"); patchCsp(responseHeaders);
// Fix hosts that don't properly set the css content type, such as // Fix hosts that don't properly set the css content type, such as
// raw.githubusercontent.com // raw.githubusercontent.com
if (resourceType === "stylesheet") if (resourceType === "stylesheet") {
responseHeaders["content-type"] = ["text/css"]; const header = findHeader(responseHeaders, "content-type");
if (header)
responseHeaders[header] = ["text/css"];
}
} }
cb({ cancel: false, responseHeaders }); cb({ cancel: false, responseHeaders });
}); });

View File

@ -17,7 +17,7 @@
*/ */
import { app } from "electron"; import { app } from "electron";
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs"; import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "original-fs";
import { basename, dirname, join } from "path"; import { basename, dirname, join } from "path";
function isNewer($new: string, old: string) { function isNewer($new: string, old: string) {

View File

@ -85,17 +85,19 @@ export default definePlugin({
}, },
{ {
// alt: "", aria-hidden: false, src: originalSrc // alt: "", aria-hidden: false, src: originalSrc
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/g, match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
// ...badge.props, ..., src: badge.image ?? ... // ...badge.props, ..., src: badge.image ?? ...
replace: "...$1.props,$& $1.image??" replace: "...$1.props,$& $1.image??"
}, },
// replace their component with ours if applicable
{ {
match: /children:function(?<=(\i)\.(?:tooltip|description),spacing:\d.+?)/g, match: /(?<=text:(\i)\.description,spacing:12,)children:/,
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) : function" replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
}, },
// conditionally override their onClick with badge.onClick if it exists
{ {
match: /onClick:function(?=.{0,200}href:(\i)\.link)/, match: /href:(\i)\.link/,
replace: "onClick:$1.onClick??function" replace: "...($1.onClick && { onClick: $1.onClick }),$&"
} }
] ]
} }

View File

@ -26,7 +26,7 @@ export default definePlugin({
patches: [ patches: [
// obtain BUILT_IN_COMMANDS instance // obtain BUILT_IN_COMMANDS instance
{ {
find: '"giphy","tenor"', find: ',"tenor"',
replacement: [ replacement: [
{ {
// Matches BUILT_IN_COMMANDS. This is not exported so this is // Matches BUILT_IN_COMMANDS. This is not exported so this is
@ -34,7 +34,7 @@ export default definePlugin({
// patch simpler // patch simpler
// textCommands = builtInCommands.filter(...) // textCommands = builtInCommands.filter(...)
match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/, match: /(?<=\w=)(\w)(\.filter\(.{0,60}tenor)/,
replace: "Vencord.Api.Commands._init($1)$2", replace: "Vencord.Api.Commands._init($1)$2",
} }
], ],
@ -44,8 +44,8 @@ export default definePlugin({
find: "Unexpected value for option", find: "Unexpected value for option",
replacement: { replacement: {
// return [2, cmd.execute(args, ctx)] // return [2, cmd.execute(args, ctx)]
match: /,(.{1,2})\.execute\((.{1,2}),(.{1,2})\)]/, match: /,(\i)\.execute\((\i),(\i)\)/,
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})]` replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})`
} }
}, },
// Show plugin name instead of "Built-In" // Show plugin name instead of "Built-In"

View File

@ -29,8 +29,8 @@ export default definePlugin({
{ {
find: "♫ (つ。◕‿‿◕。)つ ♪", find: "♫ (つ。◕‿‿◕。)つ ♪",
replacement: { replacement: {
match: /(?<=function \i\((\i)\){)(?=var \i,\i=\i\.navId)/, match: /let{navId:/,
replace: (_, props) => `Vencord.Api.ContextMenu._patchContextMenu(${props});` replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&"
} }
}, },
{ {

View File

@ -22,20 +22,25 @@ import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "MemberListDecoratorsAPI", name: "MemberListDecoratorsAPI",
description: "API to add decorators to member list (both in servers and DMs)", description: "API to add decorators to member list (both in servers and DMs)",
authors: [Devs.TheSun], authors: [Devs.TheSun, Devs.Ven],
patches: [ patches: [
{ {
find: "lostPermissionTooltipText,", find: ".lostPermission)",
replacement: { replacement: [
match: /Fragment,{children:\[(.{30,80})\]/, {
replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)" match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
} replace: "$&vencordProps=$1,"
}, {
match: /decorators:.{0,100}?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
}
]
}, },
{ {
find: "PrivateChannel.renderAvatar", find: "PrivateChannel.renderAvatar",
replacement: { replacement: {
match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/, match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)" replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]"
} }
} }
], ],

View File

@ -27,9 +27,8 @@ export default definePlugin({
{ {
find: ".Messages.REMOVE_ATTACHMENT_BODY", find: ".Messages.REMOVE_ATTACHMENT_BODY",
replacement: { replacement: {
match: /(.container\)?,children:)(\[[^\]]+\])(}\)\};return)/, match: /(?<=.container\)?,children:)(\[.+?\])/,
replace: (_, pre, accessories, post) => replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
`${pre}Vencord.Api.MessageAccessories._modifyAccessories(${accessories},this.props)${post}`,
}, },
}, },
], ],

View File

@ -25,10 +25,10 @@ export default definePlugin({
authors: [Devs.TheSun], authors: [Devs.TheSun],
patches: [ patches: [
{ {
find: ".withMentionPrefix", find: '"Message Username"',
replacement: { replacement: {
match: /(.roleDot.{10,50}{children:.{1,2})}\)/, match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/,
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})" replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))"
} }
} }
], ],

View File

@ -27,10 +27,8 @@ export default definePlugin({
{ {
find: '"MessageActionCreators"', find: '"MessageActionCreators"',
replacement: { replacement: {
// editMessage: function (...) { match: /async editMessage\(.+?\)\{/,
match: /\beditMessage:(function\(.+?\))\{/, replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
// editMessage: async function (...) { await handlePreEdit(...); ...
replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
} }
}, },
{ {
@ -38,7 +36,7 @@ export default definePlugin({
replacement: { replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); // 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) // Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
match: /(props\.chatInputType.+?\.then\(\()(function.+?var (\i)=\i\.\i\.parse\((\i),.+?var (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/, 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 }; // 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) => "" + replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
`${rest1}async ${rest2}` + `${rest1}async ${rest2}` +
@ -49,10 +47,10 @@ export default definePlugin({
{ {
find: '("interactionUsernameProfile', find: '("interactionUsernameProfile',
replacement: { replacement: {
match: /var \i=(\i)\.id,\i=(\i)\.id;return \i\.useCallback\(\(?function\((\i)\){/, match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
replace: (m, message, channel, event) => replace: (m, message, channel, event) =>
// the message param is shadowed by the event param, so need to alias them // the message param is shadowed by the event param, so need to alias them
`var _msg=${message},_chan=${channel};${m}Vencord.Api.MessageEvents._handleClick(_msg, _chan, ${event});` `const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
} }
} }
] ]

View File

@ -29,12 +29,12 @@ export default definePlugin({
find: 'displayName="NoticeStore"', find: 'displayName="NoticeStore"',
replacement: [ replacement: [
{ {
match: /(?=;\i=null;.{0,70}getPremiumSubscription)/g, match: /\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
replace: ";if(Vencord.Api.Notices.currentNotice)return false" replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
}, },
{ {
match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/, match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
replace: (_, notice) => `if(${notice}.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);` replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
} }
] ]
} }

View File

@ -27,15 +27,15 @@ export default definePlugin({
{ {
find: "Messages.DISCODO_DISABLED", find: "Messages.DISCODO_DISABLED",
replacement: { replacement: {
match: /(Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/, match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))" replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
} }
}, },
{ {
find: "Messages.SERVERS,children", find: "Messages.SERVERS,children",
replacement: { replacement: {
match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/, match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)" replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
} }
} }
] ]

View File

@ -1,38 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "SettingsStoreAPI",
description: "Patches Discord's SettingsStores to expose their group and name",
authors: [Devs.Nuckyz],
patches: [
{
find: '"textAndImages","renderSpoilers"',
replacement: [
{
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:function/,
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
}
]
}
]
});

View File

@ -26,7 +26,7 @@ export default definePlugin({
required: true, required: true,
patches: [ patches: [
{ {
find: "TRACKING_URL:", find: "AnalyticsActionHandlers.handle",
replacement: { replacement: {
match: /^.+$/, match: /^.+$/,
replace: "()=>{}", replace: "()=>{}",
@ -43,20 +43,21 @@ export default definePlugin({
find: ".METRICS,", find: ".METRICS,",
replacement: [ replacement: [
{ {
match: /this\._intervalId.+?12e4\)/, match: /this\._intervalId=/,
replace: "" replace: "this._intervalId=undefined&&"
}, },
{ {
match: /(?<=increment=function\(\i\){)/, match: /(increment\(\i\){)/,
replace: "return;" replace: "$1return;"
} }
] ]
}, },
{ {
find: ".installedLogHooks)", find: ".installedLogHooks)",
replacement: { replacement: {
match: /if\(\i\.getDebugLogging\(\)&&!\i\.installedLogHooks\)/, // if getDebugLogging() returns false, the hooks don't get installed.
replace: "if(false)" match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
} }
}, },
] ]

View File

@ -39,8 +39,9 @@ export default definePlugin({
addContextMenuPatch("user-settings-cog", children => () => { addContextMenuPatch("user-settings-cog", children => () => {
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any; const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
section?.forEach(c => { section?.forEach(c => {
if (c?.props?.id?.startsWith("Vencord")) { const id = c?.props?.id;
c.props.action = () => SettingsRouter.open(c.props.id); if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
c.props.action = () => SettingsRouter.open(id);
} }
}); });
}); });
@ -62,26 +63,26 @@ export default definePlugin({
replacement: { replacement: {
get match() { get match() {
switch (Settings.plugins.Settings.settingsLocation) { switch (Settings.plugins.Settings.settingsLocation) {
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/; case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/; case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/; case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/; case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/; case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
case "aboveActivity": case "aboveActivity":
default: default:
return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/; return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
} }
}, },
replace: "...$self.makeSettingsCategories($1),$&" replace: "...$self.makeSettingsCategories($1),$&"
} }
}], }],
customSections: [] as ((ID: Record<string, unknown>) => any)[], customSections: [] as ((SectionTypes: Record<string, unknown>) => any)[],
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) { makeSettingsCategories(SectionTypes: Record<string, unknown>) {
return [ return [
{ {
section: ID.HEADER, section: SectionTypes.HEADER,
label: "Vencord", label: "Vencord",
className: "vc-settings-header" className: "vc-settings-header"
}, },
@ -127,9 +128,9 @@ export default definePlugin({
element: require("@components/VencordSettings/PatchHelperTab").default, element: require("@components/VencordSettings/PatchHelperTab").default,
className: "vc-patch-helper" className: "vc-patch-helper"
}, },
...this.customSections.map(func => func(ID)), ...this.customSections.map(func => func(SectionTypes)),
{ {
section: ID.DIVIDER section: SectionTypes.DIVIDER
} }
].filter(Boolean); ].filter(Boolean);
}, },

View File

@ -17,7 +17,7 @@
*/ */
import { DataStore } from "@api/index"; import { DataStore } from "@api/index";
import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants"; import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
import { isPluginDev } from "@utils/misc"; import { isPluginDev } from "@utils/misc";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -30,7 +30,6 @@ import plugins from "~plugins";
import settings from "./settings"; import settings from "./settings";
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss";
const AllowedChannelIds = [ const AllowedChannelIds = [
SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_ID,
@ -116,22 +115,6 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "
onConfirm: rememberDismiss onConfirm: rememberDismiss
}); });
} }
if (IsFirefox) {
const rememberDismiss = () => DataStore.set(FIREFOX_DISMISS_KEY, true);
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>You are using Firefox.</Forms.FormText>
<Forms.FormText>Due to Firefox's stupid extension guidelines, most themes and many plugins will not function correctly.</Forms.FormText>
<Forms.FormText>Do not report bugs. Do not ask for help with broken plugins.</Forms.FormText>
<Forms.FormText>Instead, use a chromium browser, Discord Desktop, or Vesktop.</Forms.FormText>
</div>,
onCancel: rememberDismiss,
onConfirm: rememberDismiss
});
}
} }
} }
}); });

View File

@ -27,15 +27,15 @@ export default definePlugin({
{ {
find: ".displayName=\"MaskedLinkStore\"", find: ".displayName=\"MaskedLinkStore\"",
replacement: { replacement: {
match: /\.isTrustedDomain=function\(.\){return.+?};/, match: /(?<=isTrustedDomain\(\i\){)return \i\(\i\)/,
replace: ".isTrustedDomain=function(){return true};" replace: "return true"
} }
}, },
{ {
find: '"7z","ade","adp"', find: "isSuspiciousDownload:",
replacement: { replacement: {
match: /JSON\.parse\('\[.+?'\)/, match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
replace: "[]" replace: "$&return null;"
} }
} }
] ]

View File

@ -20,26 +20,19 @@ import { popNotice, showNotice } from "@api/Notices";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { filters, findByCodeLazy, mapMangledModuleLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, Forms, Toasts } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
const assetManager = mapMangledModuleLazy( const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL");
"getAssetImage: size must === [number, number] for Twitch",
{
getAsset: filters.byCode("apply("),
}
);
const lookupRpcApp = findByCodeLazy(".APPLICATION_RPC(");
async function lookupAsset(applicationId: string, key: string): Promise<string> { async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await assetManager.getAsset(applicationId, [key, undefined]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
} }
const apps: any = {}; const apps: any = {};
async function lookupApp(applicationId: string): Promise<string> { async function lookupApp(applicationId: string): Promise<string> {
const socket: any = {}; const socket: any = {};
await lookupRpcApp(socket, applicationId); await RpcUtils.fetchApplicationsRPC(socket, applicationId);
return socket.application; return socket.application;
} }
@ -58,6 +51,26 @@ export default definePlugin({
</> </>
), ),
async handleEvent(e: MessageEvent<any>) {
const data = JSON.parse(e.data);
const { activity } = data;
const assets = activity?.assets;
if (assets?.large_image) assets.large_image = await lookupAsset(activity.application_id, assets.large_image);
if (assets?.small_image) assets.small_image = await lookupAsset(activity.application_id, assets.small_image);
if (activity) {
const appId = activity.application_id;
apps[appId] ||= await lookupApp(appId);
const app = apps[appId];
activity.name ||= app.name;
}
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
},
async start() { async start() {
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users // ArmCord comes with its own arRPC implementation, so this plugin just confuses users
if ("armcord" in window) return; if ("armcord" in window) return;
@ -65,22 +78,7 @@ export default definePlugin({
if (ws) ws.close(); if (ws) ws.close();
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
ws.onmessage = async e => { // on message, set status to data ws.onmessage = this.handleEvent;
const data = JSON.parse(e.data);
if (data.activity?.assets?.large_image) data.activity.assets.large_image = await lookupAsset(data.activity.application_id, data.activity.assets.large_image);
if (data.activity?.assets?.small_image) data.activity.assets.small_image = await lookupAsset(data.activity.application_id, data.activity.assets.small_image);
if (data.activity) {
const appId = data.activity.application_id;
apps[appId] ||= await lookupApp(appId);
const app = apps[appId];
data.activity.name ||= app.name;
}
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
};
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s
if (!connectionSuccessful) { if (!connectionSuccessful) {

View File

@ -27,7 +27,7 @@ export default definePlugin({
{ {
find: "BAN_CONFIRM_TITLE.", find: "BAN_CONFIRM_TITLE.",
replacement: { replacement: {
match: /src:\w\(\d+\)/g, match: /src:\i\("\d+"\)/g,
replace: "src: Vencord.Settings.plugins.BANger.source" replace: "src: Vencord.Settings.plugins.BANger.source"
} }
} }

View File

@ -34,17 +34,18 @@ export default definePlugin({
}, },
}, },
{ {
find: ".embedGallerySide", find: ".Messages.GIF,",
replacement: { replacement: {
match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/, match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
replace: replace:
"?($1.alt='GIF',$self.altify($1))", // rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2",
}, },
}, },
], ],
altify(props: any) { altify(props: any) {
if (props.alt !== "GIF") return props.alt; if (props.alt && props.alt !== "GIF") return props.alt;
let url: string = props.original || props.src; let url: string = props.original || props.src;
try { try {

View File

@ -19,6 +19,9 @@
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
export default definePlugin({ export default definePlugin({
name: "BetterNotesBox", name: "BetterNotesBox",
@ -29,17 +32,31 @@ export default definePlugin({
{ {
find: "hideNote:", find: "hideNote:",
all: true, all: true,
// Some modules match the find but the replacement is returned untouched
noWarn: true,
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide, predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
replacement: { replacement: {
match: /hideNote:.+?(?=[,}])/g, match: /hideNote:.+?(?=([,}].*?\)))/g,
replace: "hideNote:true", replace: (m, rest) => {
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) return "hideNote:!0";
return m;
}
} }
}, { },
{
find: "Messages.NOTE_PLACEHOLDER", find: "Messages.NOTE_PLACEHOLDER",
replacement: { replacement: {
match: /\.NOTE_PLACEHOLDER,/, match: /\.NOTE_PLACEHOLDER,/,
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck," replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
} }
},
{
find: ".Messages.NOTE}",
replacement: {
match: /(?<=return \i\?)null(?=:\(0,\i\.jsxs)/,
replace: "$self.patchPadding(arguments[0])"
}
} }
], ],
@ -56,5 +73,12 @@ export default definePlugin({
disabled: () => Settings.plugins.BetterNotesBox.hide, disabled: () => Settings.plugins.BetterNotesBox.hide,
default: false default: false
} }
},
patchPadding(e: any) {
if (!e.lastSection) return;
return (
<div className={UserPopoutSectionCssClasses.lastSection}></div>
);
} }
}); });

View File

@ -38,6 +38,7 @@ export default definePlugin({
{ {
find: '"dot"===', find: '"dot"===',
all: true, all: true,
noWarn: true,
predicate: () => Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
replacement: { replacement: {
match: /"(?:username|dot)"===\i(?!\.\i)/g, match: /"(?:username|dot)"===\i(?!\.\i)/g,
@ -48,6 +49,7 @@ export default definePlugin({
{ {
find: ".ADD_ROLE_A11Y_LABEL", find: ".ADD_ROLE_A11Y_LABEL",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,
replacement: { replacement: {
match: /"dot"===\i/, match: /"dot"===\i/,
replace: "true" replace: "true"
@ -56,6 +58,7 @@ export default definePlugin({
{ {
find: ".roleVerifiedIcon", find: ".roleVerifiedIcon",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,
replacement: { replacement: {
match: /"dot"===\i/, match: /"dot"===\i/,
replace: "true" replace: "true"

View File

@ -29,9 +29,8 @@ export default definePlugin({
replacement: { replacement: {
// Discord merges multiple props here with Object.assign() // Discord merges multiple props here with Object.assign()
// This patch passes a third object to it with which we override onClick and onContextMenu // This patch passes a third object to it with which we override onClick and onContextMenu
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0)\},(.{1,3})\)/, match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
replace: (m, onDblClick, otherProps) => replace: "$&onClick:$1,onContextMenu:$2.onClick,",
`${m.slice(0, -1)},{onClick:${onDblClick},onContextMenu:${otherProps}.onClick})`,
}, },
}, },
], ],

View File

@ -45,11 +45,8 @@ export default definePlugin({
{ {
find: ".embedWrapper,embed", find: ".embedWrapper,embed",
replacement: [{ replacement: [{
match: /(\.renderEmbed=.+?(.)=.\.props)(.+?\.embedWrapper)/g, match: /\.embedWrapper/g,
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')" replace: "$&+(this.props.channel.nsfw?' vc-nsfw-img':'')"
}, {
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
replace: "$1,vcProps=$2$3+(vcProps.nsfw?' vc-nsfw-img':'')"
}] }]
} }
], ],

View File

@ -73,9 +73,9 @@ export default definePlugin({
}, },
patches: [{ patches: [{
find: ".renderConnectionStatus=", find: "renderConnectionStatus(){",
replacement: { replacement: {
match: /(?<=renderConnectionStatus=.+\.channel,children:)\w/, match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i/,
replace: "[$&, $self.renderTimer(this.props.channel.id)]" replace: "[$&, $self.renderTimer(this.props.channel.id)]"
} }
}], }],

View File

@ -34,8 +34,9 @@ export default definePlugin({
{ {
find: ".AVATAR_STATUS_MOBILE_16;", find: ".AVATAR_STATUS_MOBILE_16;",
replacement: { replacement: {
match: /(\.fromIsMobile,.+?)\i.status/, match: /(?<=fromIsMobile:\i=!0,.+?)status:(\i)/,
replace: (_, rest) => `${rest}"online"` // Rename field to force it to always use "online"
replace: 'status_$:$1="online"'
} }
} }
] ]

View File

@ -22,23 +22,15 @@ import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards"; import { isTruthy } from "@utils/guards";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const ActivityComponent = findByCodeLazy("onOpenGameProfile"); const ActivityComponent = findByCodeLazy("onOpenGameProfile");
const ActivityClassName = findByPropsLazy("activity", "buttonColor"); const ActivityClassName = findByPropsLazy("activity", "buttonColor");
const Colors = findByPropsLazy("profileColors"); const Colors = findByPropsLazy("profileColors");
const assetManager = mapMangledModuleLazy(
"getAssetImage: size must === [number, number] for Twitch",
{
getAsset: filters.byCode("apply("),
}
);
async function getApplicationAsset(key: string): Promise<string> { 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];
return (await assetManager.getAsset(settings.store.appID, [key, undefined]))[0];
} }
interface ActivityAssets { interface ActivityAssets {

View File

@ -147,8 +147,8 @@ export default definePlugin({
replacement: [ replacement: [
// patch componentDidMount to replace embed thumbnail and title // patch componentDidMount to replace embed thumbnail and title
{ {
match: /(\i).render=function.{0,50}\i\.embed/, match: /render\(\)\{let\{embed:/,
replace: "$1.componentDidMount=$self.embedDidMount,$&" replace: "componentDidMount=$self.embedDidMount;$&"
}, },
// add dearrow button // add dearrow button

View File

@ -27,7 +27,7 @@ export default definePlugin({
{ {
find: ".Messages.BOT_CALL_IDLE_DISCONNECT", find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
replacement: { replacement: {
match: /(?<=function \i\(\){)(?=.{1,100}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/, match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
replace: "return;" replace: "return;"
} }
} }

View File

@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
const StickersStore = findStoreLazy("StickersStore"); const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS("); const EmojiManager = findByPropsLazy("fetchEmoji", "uploadEmoji", "deleteEmoji");
interface Sticker { interface Sticker {
t: "Sticker"; t: "Sticker";
@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) {
reader.readAsDataURL(data); reader.readAsDataURL(data);
}); });
return uploadEmoji({ return EmojiManager.uploadEmoji({
guildId, guildId,
name: emoji.name.split("~")[0], name: emoji.name.split("~")[0],
image: dataUrl image: dataUrl

View File

@ -33,12 +33,6 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: false, default: false,
restartNeeded: true restartNeeded: true
},
forceStagingBanner: {
description: "Whether to force Staging banner under user area.",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true
} }
}); });
@ -58,7 +52,7 @@ export default definePlugin({
{ {
find: "Object.defineProperties(this,{isDeveloper", find: "Object.defineProperties(this,{isDeveloper",
replacement: { replacement: {
match: /(?<={isDeveloper:\{[^}]+?,get:function\(\)\{return )\w/, match: /(?<={isDeveloper:\{[^}]+?,get:\(\)=>)\i/,
replace: "true" replace: "true"
} }
}, },
@ -70,25 +64,26 @@ export default definePlugin({
} }
}, },
{ {
find: ".isStaff=function(){", find: ".isStaff=()",
predicate: () => settings.store.enableIsStaff, predicate: () => settings.store.enableIsStaff,
replacement: [ replacement: [
{ {
match: /return\s*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/, match: /=>*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
replace: (_, user, flags) => `return Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}` replace: (_, user, flags) => `=>Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
}, },
{ {
match: /hasFreePremium=function\(\){return this.isStaff\(\)\s*?\|\|/, match: /hasFreePremium\(\){return this.isStaff\(\)\s*?\|\|/,
replace: "hasFreePremium=function(){return ", replace: "hasFreePremium(){return ",
} }
] ]
}, },
// Fix search history being disabled / broken with isStaff
{ {
find: ".Messages.DEV_NOTICE_STAGING", find: '("showNewSearch")',
predicate: () => settings.store.forceStagingBanner, predicate: () => settings.store.enableIsStaff,
replacement: { replacement: {
match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/, match: /(?<=showNewSearch"\);return)\s?/,
replace: "true" replace: "!1&&"
} }
}, },
{ {

View File

@ -24,35 +24,33 @@ import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc"; import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react"; import type { ReactElement, ReactNode } from "react";
const DRAFT_TYPE = 0; const DRAFT_TYPE = 0;
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
const PreloadedUserSettingsProtoHandler = findLazy(m => m.ProtoClass?.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings");
const ReaderFactory = findByPropsLazy("readerFactory");
const StickerStore = findStoreLazy("StickersStore") as { const StickerStore = findStoreLazy("StickersStore") as {
getPremiumPacks(): StickerPack[]; getPremiumPacks(): StickerPack[];
getAllGuildStickers(): Map<string, Sticker[]>; getAllGuildStickers(): Map<string, Sticker[]>;
getStickerById(id: string): Sticker | undefined; getStickerById(id: string): Sticker | undefined;
}; };
function searchProtoClass(localName: string, parentProtoClass: any) { const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
if (!parentProtoClass) return; const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS");
const field = parentProtoClass.fields.find(field => field.localName === localName); function searchProtoClassField(localName: string, protoClass: any) {
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
if (!field) return; if (!field) return;
const getter: any = Object.values(field).find(value => typeof value === "function"); const fieldGetter = Object.values(field).find(value => typeof value === "function") as any;
return getter?.(); return fieldGetter?.();
} }
const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass)); const PreloadedUserSettingsActionCreators = proxyLazy(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators);
const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto)); const AppearanceSettingsActionCreators = proxyLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
const ClientThemeSettingsActionsCreators = proxyLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
const USE_EXTERNAL_EMOJIS = 1n << 18n; const USE_EXTERNAL_EMOJIS = 1n << 18n;
const USE_EXTERNAL_STICKERS = 1n << 37n; const USE_EXTERNAL_STICKERS = 1n << 37n;
@ -176,31 +174,37 @@ export default definePlugin({
predicate: () => settings.store.enableEmojiBypass, predicate: () => settings.store.enableEmojiBypass,
replacement: [ replacement: [
{ {
match: /(?<=(\i)=\i\.intention)/, // Create a variable for the intention of listing the emoji
replace: (_, intention) => `,fakeNitroIntention=${intention}` match: /(?<=,intention:(\i).+?;)/,
replace: (_, intention) => `let fakeNitroIntention=${intention};`
}, },
{ {
// Send the intention of listing the emoji to the nitro permission check functions
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g, match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
}, },
{ {
// Disallow the emoji if the intention doesn't allow it
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
}, },
{ {
// Make the emoji always available if the intention allows it
match: /if\(!\i\.available/, match: /if\(!\i\.available/,
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))` replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
} }
] ]
}, },
// Allow emojis and animated emojis to be sent everywhere
{ {
find: "canUseAnimatedEmojis:function", find: "canUseAnimatedEmojis:function",
predicate: () => settings.store.enableEmojiBypass, predicate: () => settings.store.enableEmojiBypass,
replacement: { replacement: {
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g, match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention!=null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
} }
}, },
// Allow stickers to be sent everywhere
{ {
find: "canUseStickersEverywhere:function", find: "canUseStickersEverywhere:function",
predicate: () => settings.store.enableStickerBypass, predicate: () => settings.store.enableStickerBypass,
@ -209,6 +213,7 @@ export default definePlugin({
replace: "$&return true;" replace: "$&return true;"
}, },
}, },
// Make stickers always available
{ {
find: "\"SENDABLE\"", find: "\"SENDABLE\"",
predicate: () => settings.store.enableStickerBypass, predicate: () => settings.store.enableStickerBypass,
@ -217,13 +222,13 @@ export default definePlugin({
replace: "true?" replace: "true?"
} }
}, },
// Allow streaming with high quality
{ {
find: "canUseHighVideoUploadQuality:function", find: "canUseHighVideoUploadQuality:function",
predicate: () => settings.store.enableStreamQualityBypass, predicate: () => settings.store.enableStreamQualityBypass,
replacement: [ replacement: [
"canUseHighVideoUploadQuality", "canUseHighVideoUploadQuality",
// TODO: Remove the last two when they get removed from stable "canStreamQuality",
"(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
].map(func => { ].map(func => {
return { return {
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"), match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
@ -231,14 +236,16 @@ export default definePlugin({
}; };
}) })
}, },
// Remove boost requirements to stream with high quality
{ {
find: "STREAM_FPS_OPTION.format", find: "STREAM_FPS_OPTION.format",
predicate: () => settings.store.enableStreamQualityBypass, predicate: () => settings.store.enableStreamQualityBypass,
replacement: { replacement: {
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g, match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
replace: "" replace: ""
} }
}, },
// Allow client themes to be changeable
{ {
find: "canUseClientThemes:function", find: "canUseClientThemes:function",
replacement: { replacement: {
@ -250,19 +257,22 @@ export default definePlugin({
find: '.displayName="UserSettingsProtoStore"', find: '.displayName="UserSettingsProtoStore"',
replacement: [ replacement: [
{ {
// Overwrite incoming connection settings proto with our local settings
match: /CONNECTION_OPEN:function\((\i)\){/, match: /CONNECTION_OPEN:function\((\i)\){/,
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);` replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
}, },
{ {
match: /=(\i)\.local;/, // Overwrite non local proto changes with our local settings
replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);` match: /let{settings:/,
replace: "arguments[0].local||$self.handleProtoChange(arguments[0].settings.proto);$&"
} }
] ]
}, },
// Call our function to handle changing the gradient theme when selecting a new one
{ {
find: "updateTheme:function", find: ",updateTheme(",
replacement: { replacement: {
match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/, match: /(function \i\(\i\){let{backgroundGradientPresetId:(\i).+?)(\i\.\i\.updateAsync.+?theme=(.+?),.+?},\i\))/,
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});` replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
} }
}, },
@ -270,11 +280,13 @@ export default definePlugin({
find: '["strong","em","u","text","inlineCode","s","spoiler"]', find: '["strong","em","u","text","inlineCode","s","spoiler"]',
replacement: [ replacement: [
{ {
// Call our function to decide whether the emoji link should be kept or not
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
match: /1!==(\i)\.length\|\|1!==\i\.length/, match: /1!==(\i)\.length\|\|1!==\i\.length/,
replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])` replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])`
}, },
{ {
// Patch the rendered message content to add fake nitro emojis or remove sticker links
predicate: () => settings.store.transformEmojis || settings.store.transformStickers, predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/, match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/,
replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);` replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);`
@ -282,36 +294,41 @@ export default definePlugin({
] ]
}, },
{ {
find: "renderEmbeds=function", find: "renderEmbeds(",
replacement: [ replacement: [
{ {
// Call our function to decide whether the embed should be ignored or not
predicate: () => settings.store.transformEmojis || settings.store.transformStickers, predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
match: /(renderEmbeds=function\((\i)\){)(.+?embeds\.map\(\(function\((\i)\){)/, match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/,
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;` replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
}, },
{ {
// Patch the stickers array to add fake nitro stickers
predicate: () => settings.store.transformStickers, predicate: () => settings.store.transformStickers,
match: /renderStickersAccessories=function\((\i)\){var (\i)=\(0,\i\.\i\)\(\i\),/, match: /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/,
replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message}),` replace: (_, message, stickers) => `${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
}, },
{ {
// Filter attachments to remove fake nitro stickers or emojis
predicate: () => settings.store.transformStickers, predicate: () => settings.store.transformStickers,
match: /renderAttachments=function\(\i\){var (\i)=\i.attachments.+?;/, match: /renderAttachments\(\i\){let{attachments:(\i).+?;/,
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});` replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
} }
] ]
}, },
{ {
find: ".STICKER_IN_MESSAGE_HOVER,", find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format",
predicate: () => settings.store.transformStickers, predicate: () => settings.store.transformStickers,
replacement: [ replacement: [
{ {
match: /var (\i)=\i\.renderableSticker,.{0,50}closePopout.+?channel:\i,closePopout:\i,/, // Export the renderable sticker to be used in the fake nitro sticker notice
replace: (m, renderableSticker) => `${m}renderableSticker:${renderableSticker},` match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/,
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
}, },
{ {
match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/, // Add the fake nitro sticker notice
replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!${props}.renderableSticker?.fake)` match: /(let \i,{sticker:\i,channel:\i,closePopout:\i.+?}=(\i).+?;)(.+?description:)(\i)(?=,sticker:\i)/,
replace: (_, rest, props, rest2, reactNode) => `${rest}let{fakeNitroRenderableSticker}=${props};${rest2}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!fakeNitroRenderableSticker?.fake)`
} }
] ]
}, },
@ -319,7 +336,8 @@ export default definePlugin({
find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,", find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,",
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<=(\i)=\i\.node.+?)/, // Export the emoji node to be used in the fake nitro emoji notice
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<={node:(\i),.+?)/,
replace: (m, node) => `${m}fakeNitroNode:${node},` replace: (m, node) => `${m}fakeNitroNode:${node},`
} }
}, },
@ -327,8 +345,25 @@ export default definePlugin({
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION", find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/, // Add the fake nitro emoji notice
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)` match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
}
},
// Allow using custom app icons
{
find: "canUsePremiumAppIcons:function",
replacement: {
match: /canUsePremiumAppIcons:function\(\i\){/,
replace: "$&return true;"
}
},
// Separate patch for allowing using custom app icons
{
find: "location:\"AppIconHome\"",
replacement: {
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
replace: "true"
} }
} }
], ],
@ -346,26 +381,30 @@ export default definePlugin({
}, },
handleProtoChange(proto: any, user: any) { handleProtoChange(proto: any, user: any) {
if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || (!proto.appearance && !AppearanceSettingsProto)) return; if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || !PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators) return;
const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0; const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0;
if (premiumType !== 2) { if (premiumType !== 2) {
proto.appearance ??= AppearanceSettingsProto.create(); proto.appearance ??= AppearanceSettingsActionCreators.create();
if (UserSettingsProtoStore.settings.appearance?.theme != null) { if (UserSettingsProtoStore.settings.appearance?.theme != null) {
proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme; const appearanceSettingsDummy = AppearanceSettingsActionCreators.create({
theme: UserSettingsProtoStore.settings.appearance.theme
});
proto.appearance.theme = appearanceSettingsDummy.theme;
} }
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null && ClientThemeSettingsProto) { if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({ const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
backgroundGradientPresetId: { backgroundGradientPresetId: {
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
} }
}); });
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto; proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy;
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId; proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
} }
} }
}, },
@ -374,26 +413,26 @@ export default definePlugin({
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0; const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
if (premiumType === 2 || backgroundGradientPresetId == null) return original(); if (premiumType === 2 || backgroundGradientPresetId == null) return original();
if (!AppearanceSettingsProto || !ClientThemeSettingsProto || !ReaderFactory) return; if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !ProtoUtils) return;
const currentAppearanceProto = PreloadedUserSettingsProtoHandler.getCurrentValue().appearance; const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance;
const newAppearanceProto = currentAppearanceProto != null const newAppearanceProto = currentAppearanceSettings != null
? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory) ? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS)
: AppearanceSettingsProto.create(); : AppearanceSettingsActionCreators.create();
newAppearanceProto.theme = theme; newAppearanceProto.theme = theme;
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({ const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
backgroundGradientPresetId: { backgroundGradientPresetId: {
value: backgroundGradientPresetId value: backgroundGradientPresetId
} }
}); });
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto; newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummy;
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId; newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create(); const proto = PreloadedUserSettingsActionCreators.ProtoClass.create();
proto.appearance = newAppearanceProto; proto.appearance = newAppearanceProto;
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
@ -519,7 +558,7 @@ export default definePlugin({
}; };
try { try {
return modifyChildren(window._.cloneDeep(content)); return modifyChildren(lodash.cloneDeep(content));
} catch (err) { } catch (err) {
new Logger("FakeNitro").error(err); new Logger("FakeNitro").error(err);
return content; return content;
@ -716,7 +755,7 @@ export default definePlugin({
gif.finish(); gif.finish();
const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" }); const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" });
promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE); UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
}, },
start() { start() {

View File

@ -87,15 +87,15 @@ export default definePlugin({
authors: [Devs.Alyxia, Devs.Remty], authors: [Devs.Alyxia, Devs.Remty],
patches: [ patches: [
{ {
find: "getUserProfile=", find: "UserProfileStore",
replacement: { replacement: {
match: /(?<=getUserProfile=function\(\i\){return )(\i\[\i\])/, match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/,
replace: "$self.colorDecodeHook($1)" replace: "$self.colorDecodeHook($1)"
} }
}, { }, {
find: ".USER_SETTINGS_PROFILE_THEME_ACCENT", find: ".USER_SETTINGS_PROFILE_THEME_ACCENT",
replacement: { replacement: {
match: /RESET_PROFILE_THEME}\)(?<=},color:(\i).+?},color:(\i).+?)/, match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
} }
} }

View File

@ -39,22 +39,27 @@ export default definePlugin({
description: "Puts your favorite emoji first in the emoji autocomplete.", description: "Puts your favorite emoji first in the emoji autocomplete.",
patches: [ patches: [
{ {
find: ".activeCommandOption", find: "renderResults({results:",
replacement: [ replacement: [
{ {
// = someFunc(a.selectedIndex); ...trackEmojiSearch({ state: theState, isInPopoutExperimental: someBool }) // https://regex101.com/r/N7kpLM/1
match: /=\i\(\i\.selectedIndex\);(?=.+?state:(\i),isInPopoutExperiment:\i)/, match: /let \i=.{1,100}renderResults\({results:(\i)\.query\.results,/,
// self.sortEmojis(theState) replace: "$self.sortEmojis($1);$&"
replace: "$&$self.sortEmojis($1);"
}, },
],
},
{
find: "MAX_AUTOCOMPLETE_RESULTS+",
replacement: [
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10 // set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later // and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
{ {
// https://regex101.com/r/x2mobQ/1
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length) // searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
match: /,maxCount:(\i)(.+?)=(\i)\.slice\(0,(\1-\i\.length)\)/, match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(\i-\i\.length)\)/,
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis) // ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
replace: ",maxCount:Infinity$2=($3.sliceTo=$4,$3)" replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
} }
] ]
} }

View File

@ -91,13 +91,13 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "renderCategoryExtras", find: "renderHeaderContent()",
replacement: [ replacement: [
{ {
// https://regex101.com/r/4uHtTE/1 // https://regex101.com/r/07gpzP/1
// ($1 renderHeaderContent=function { ... switch (x) ... case FAVORITES:return) ($2) ($3 case default:return r.jsx(($<searchComp>), {...props})) // ($1 renderHeaderContent=function { ... switch (x) ... case FAVORITES:return) ($2) ($3 case default:return r.jsx(($<searchComp>), {...props}))
match: /(renderHeaderContent=function.{1,150}FAVORITES:return)(.{1,150};)(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\.\i))/, match: /(renderHeaderContent\(\).{1,150}FAVORITES:return)(.{1,150});(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\..{1,10}),)/,
replace: "$1 this.state.resultType === \"Favorites\" ? $self.renderSearchBar(this, $<searchComp>) : $2; $3" replace: "$1 this.state.resultType === 'Favorites' ? $self.renderSearchBar(this, $<searchComp>) : $2;$3"
}, },
{ {
// to persist filtered favorites when component re-renders. // to persist filtered favorites when component re-renders.
@ -182,7 +182,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
ref={ref} ref={ref}
autoFocus={true} autoFocus={true}
className={containerClasses.searchBar} className={containerClasses.searchBar}
size={SearchBarComponent.Sizes.MEDIUM} size={SearchBarComponent.Sizes.SMALL}
onChange={onChange} onChange={onChange}
onClear={() => { onClear={() => {
setQuery(""); setQuery("");

View File

@ -19,6 +19,7 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { GuildStore } from "@webpack/common"; import { GuildStore } from "@webpack/common";
import { Channel, User } from "discord-types/general";
export default definePlugin({ export default definePlugin({
name: "ForceOwnerCrown", name: "ForceOwnerCrown",
@ -26,33 +27,22 @@ export default definePlugin({
authors: [Devs.D3SOX, Devs.Nickyux], authors: [Devs.D3SOX, Devs.Nickyux],
patches: [ patches: [
{ {
// This is the logic where it decides whether to render the owner crown or not find: "AVATAR_DECORATION_PADDING:",
find: ".renderOwner=",
replacement: { replacement: {
match: /isOwner;return null!=(\w+)?&&/g, match: /,isOwner:(\i),/,
replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&" replace: ",_isOwner:$1=$self.isGuildOwner(e),"
} }
},
],
isGuildOwner(props) {
// Check if channel is a Group DM, if so return false
if (props?.channel?.type === 3) {
return false;
} }
],
isGuildOwner(props: { user: User, channel: Channel, isOwner: boolean, guildId?: string; }) {
if (!props?.user?.id) return props.isOwner;
if (props.channel?.type === 3 /* GROUP_DM */)
return props.isOwner;
// guild id is in props twice, fallback if the first is undefined // guild id is in props twice, fallback if the first is undefined
const guildId = props?.guildId ?? props?.channel?.guild_id; const guildId = props.guildId ?? props.channel?.guild_id;
const userId = props?.user?.id; const userId = props.user.id;
if (guildId && userId) { return GuildStore.getGuild(guildId)?.ownerId === userId;
const guild = GuildStore.getGuild(guildId);
if (guild) {
return guild.ownerId === userId;
}
console.error("[ForceOwnerCrown] failed to get guild", { guildId, guild, props });
} else {
console.error("[ForceOwnerCrown] no guildId or userId", { guildId, userId, props });
}
return false;
}, },
}); });

View File

@ -16,16 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getSettingStoreLazy } from "@api/SettingsStore";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { StatusSettingsStores } from "@webpack/common";
import style from "./style.css?managed"; import style from "./style.css?managed";
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:"); const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
function makeIcon(showCurrentGame?: boolean) { function makeIcon(showCurrentGame?: boolean) {
@ -40,7 +39,7 @@ function makeIcon(showCurrentGame?: boolean) {
{!showCurrentGame && <> {!showCurrentGame && <>
<mask id="gameActivityMask" > <mask id="gameActivityMask" >
<rect fill="white" x="0" y="0" width="24" height="24" /> <rect fill="white" x="0" y="0" width="24" height="24" />
<path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z"/> <path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z" />
</mask> </mask>
<path fill="var(--status-danger)" d="M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z" /> <path fill="var(--status-danger)" d="M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z" />
</>} </>}
@ -50,7 +49,7 @@ function makeIcon(showCurrentGame?: boolean) {
} }
function GameActivityToggleButton() { function GameActivityToggleButton() {
const showCurrentGame = ShowCurrentGame?.useSetting(); const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting();
return ( return (
<Button <Button
@ -58,7 +57,7 @@ function GameActivityToggleButton() {
icon={makeIcon(showCurrentGame)} icon={makeIcon(showCurrentGame)}
role="switch" role="switch"
aria-checked={!showCurrentGame} aria-checked={!showCurrentGame}
onClick={() => ShowCurrentGame?.updateSetting(old => !old)} onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)}
/> />
); );
} }
@ -67,7 +66,6 @@ export default definePlugin({
name: "GameActivityToggle", name: "GameActivityToggle",
description: "Adds a button next to the mic and deafen button to toggle game activity.", description: "Adds a button next to the mic and deafen button to toggle game activity.",
authors: [Devs.Nuckyz, Devs.RuukuLada], authors: [Devs.Nuckyz, Devs.RuukuLada],
dependencies: ["SettingsStoreAPI"],
patches: [ patches: [
{ {

View File

@ -1,3 +1,3 @@
[class*="withTagAsButton"] { [class*="withTagAsButton"] {
min-width: 88px; min-width: 88px !important;
} }

View File

@ -33,7 +33,7 @@ export default definePlugin({
patches: [{ patches: [{
find: ".handleSelectGIF=", find: ".handleSelectGIF=",
replacement: { replacement: {
match: /\.handleSelectGIF=function.+?\{/, match: /\.handleSelectGIF=\i=>\{/,
replace: ".handleSelectGIF=function(gif){return $self.handleSelect(gif);" replace: ".handleSelectGIF=function(gif){return $self.handleSelect(gif);"
} }
}], }],

View File

@ -18,8 +18,9 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByProps, findByPropsLazy } from "@webpack";
import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common"; import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
@ -50,6 +51,7 @@ const settings = definePluginSettings({
}>(); }>();
const MessageActions = findByPropsLazy("sendGreetMessage"); const MessageActions = findByPropsLazy("sendGreetMessage");
const WELCOME_STICKERS = proxyLazy(() => findByProps("WELCOME_STICKERS")?.WELCOME_STICKERS);
function greet(channel: Channel, message: Message, stickers: string[]) { function greet(channel: Channel, message: Message, stickers: string[]) {
const options = MessageActions.getSendMessageOptionsForReply({ const options = MessageActions.getSendMessageOptionsForReply({
@ -75,7 +77,7 @@ function greet(channel: Channel, message: Message, stickers: string[]) {
} }
function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], message: Message, channel: Channel; }) { function GreetMenu({ channel, message }: { message: Message, channel: Channel; }) {
const s = settings.use(["greetMode", "multiGreetChoices"]); const s = settings.use(["greetMode", "multiGreetChoices"]);
const { greetMode, multiGreetChoices = [] } = s; const { greetMode, multiGreetChoices = [] } = s;
@ -105,7 +107,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
<Menu.MenuGroup <Menu.MenuGroup
label="Greet Stickers" label="Greet Stickers"
> >
{stickers.map(sticker => ( {WELCOME_STICKERS.map(sticker => (
<Menu.MenuItem <Menu.MenuItem
key={sticker.id} key={sticker.id}
id={"greet-" + sticker.id} id={"greet-" + sticker.id}
@ -123,7 +125,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
label="Unholy Multi-Greet" label="Unholy Multi-Greet"
id="unholy-multi-greet" id="unholy-multi-greet"
> >
{stickers.map(sticker => { {WELCOME_STICKERS.map(sticker => {
const checked = multiGreetChoices.some(s => s === sticker.id); const checked = multiGreetChoices.some(s => s === sticker.id);
return ( return (
@ -168,21 +170,20 @@ export default definePlugin({
{ {
find: "Messages.WELCOME_CTA_LABEL", find: "Messages.WELCOME_CTA_LABEL",
replacement: { replacement: {
match: /innerClassName:\i\(\).welcomeCTAButton,(?<=%\i\.length;return (\i)\[\i\].+?)/, match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
replace: "$&onContextMenu:(e)=>$self.pickSticker(e,$1,arguments[0])," replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"
} }
} }
], ],
pickSticker( pickSticker(
event: React.UIEvent, event: React.UIEvent,
stickers: Sticker[],
props: { props: {
channel: Channel, channel: Channel,
message: Message; message: Message;
} }
) { ) {
if (!(props.message as any).deleted) if (!(props.message as any).deleted)
ContextMenu.open(event, () => <GreetMenu stickers={stickers} {...props} />); ContextMenu.open(event, () => <GreetMenu {...props} />);
} }
}); });

View File

@ -25,10 +25,10 @@ export default definePlugin({
authors: [Devs.botato, Devs.Animal], authors: [Devs.botato, Devs.Animal],
patches: [ patches: [
{ {
find: "),{hasFlag:", find: "hasFlag:{writable",
replacement: { replacement: {
match: /(if\((.{1,2})<=1<<30\)return)/, match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
replace: "if($2===(1<<20)){return false};$1", replace: "if($1===(1<<20))return false;$&",
}, },
}, },
], ],

View File

@ -1,28 +1,17 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a Discord client mod
* Copyright (c) 2022 Vendicated and contributors * Copyright (c) 2023 Vendicated and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* This program is free software: you can redistribute it and/or modify */
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import { Tooltip } from "webpack/common"; import { StatusSettingsStores, Tooltip } from "webpack/common";
const enum ActivitiesTypes { const enum ActivitiesTypes {
Game, Game,
@ -31,203 +20,153 @@ const enum ActivitiesTypes {
interface IgnoredActivity { interface IgnoredActivity {
id: string; id: string;
name: string;
type: ActivitiesTypes; type: ActivitiesTypes;
} }
const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn");
const TryItOutClasses = findByPropsLazy("tryItOutBadge", "tryItOutBadgeIcon");
const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight");
const RunningGameStore = findStoreLazy("RunningGameStore"); const RunningGameStore = findStoreLazy("RunningGameStore");
function ToggleIconOff() { function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
return (
<svg
className={RegisteredGamesClasses.overlayToggleIconOff}
height="24"
width="24"
viewBox="0 2.2 32 26"
aria-hidden={true}
role="img"
>
<g
fill="none"
fillRule="evenodd"
>
<path
className={RegisteredGamesClasses.fill}
fill="currentColor"
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
/>
<rect
className={RegisteredGamesClasses.fill}
x="3"
y="26"
width="26"
height="2"
transform="rotate(-45 2 20)"
/>
</g>
</svg>
);
}
function ToggleIconOn({ forceWhite }: { forceWhite?: boolean; }) {
return (
<svg
className={RegisteredGamesClasses.overlayToggleIconOn}
height="24"
width="24"
viewBox="0 2.2 32 26"
>
<path
className={forceWhite ? "" : RegisteredGamesClasses.fill}
fill={forceWhite ? "var(--white-500)" : ""}
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
/>
</svg>
);
}
function ToggleActivityComponent({ activity, forceWhite, forceLeftMargin }: { activity: IgnoredActivity; forceWhite?: boolean; forceLeftMargin?: boolean; }) {
const forceUpdate = useForceUpdater(); const forceUpdate = useForceUpdater();
return ( return (
<Tooltip text="Toggle activity"> <Tooltip text={tooltipText}>
{({ onMouseLeave, onMouseEnter }) => ( {tooltipProps => (
<div <button
onMouseLeave={onMouseLeave} {...tooltipProps}
onMouseEnter={onMouseEnter}
className={RegisteredGamesClasses.overlayToggleIcon}
role="button"
aria-label="Toggle activity"
tabIndex={0}
style={forceLeftMargin ? { marginLeft: "2px" } : undefined}
onClick={e => handleActivityToggle(e, activity, forceUpdate)} onClick={e => handleActivityToggle(e, activity, forceUpdate)}
style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
> >
{ <svg
ignoredActivitiesCache.has(activity.id) width="24"
? <ToggleIconOff /> height="24"
: <ToggleIconOn forceWhite={forceWhite} /> viewBox="0 -960 960 960"
} >
</div> <path fill={fill} d={path} />
</svg>
</button>
)} )}
</Tooltip> </Tooltip>
); );
} }
function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) { const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
return ( const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
<div
className={`${TryItOutClasses.tryItOutBadge} ${BaseShapeRoundClasses.baseShapeRound}`} function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
style={{ padding: "0px 2px", height: 28 }} if (getIgnoredActivities().some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
> return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
<ToggleActivityComponent activity={activity} forceWhite={true} />
</div>
);
} }
function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) { function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity, forceUpdateButton: () => void) {
e.stopPropagation(); e.stopPropagation();
if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id);
else ignoredActivitiesCache.set(activity.id, activity); const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
forceUpdateComponent(); if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
saveCacheToDatastore(); else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
// Trigger activities recalculation
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
forceUpdateButton();
} }
async function saveCacheToDatastore() { const settings = definePluginSettings({}).withPrivateSettings<{
await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache); ignoredActivities: IgnoredActivity[];
} }>();
let ignoredActivitiesCache = new Map<IgnoredActivity["id"], IgnoredActivity>(); function getIgnoredActivities() {
return settings.store.ignoredActivities ??= [];
}
export default definePlugin({ export default definePlugin({
name: "IgnoreActivities", name: "IgnoreActivities",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.", description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.",
settings,
patches: [ patches: [
{ {
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", find: '.displayName="LocalActivityStore"',
replacement: {
match: /!(\i)(\)return null;var \i=(\i)\.overlay.+?children:)(\[.{0,70}overlayStatusText.+?\])(?=}\)}\(\))/,
replace: (_, platformCheck, restWithoutPlatformCheck, props, children) => "false"
+ `${restWithoutPlatformCheck}`
+ `(${platformCheck}?${children}:[])`
+ `.concat(Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(${props}))`
}
},
{
find: ".overlayBadge",
replacement: [ replacement: [
{ {
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i)\.name.+?null/, match: /LISTENING.+?}\),(?<=(\i)\.push.+?)/,
replace: (m, props) => `[${m},$self.renderToggleActivityButton(${props})]` replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored),`
},
{
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i\.application)\.name.+?null/,
replace: (m, props) => `${m},$self.renderToggleActivityButton(${props})`
} }
] ]
}, },
{ {
find: '.displayName="LocalActivityStore"', find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
replacement: { replacement: {
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/, match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
}
},
{
find: ".activityTitleText,variant",
replacement: {
match: /(?<=\i\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
},
},
{
find: ".activityCardDetails,children",
replacement: {
match: /(?<=\i\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
} }
} }
], ],
async start() { async start() {
const ignoredActivitiesData = await DataStore.get<string[] | Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities") ?? new Map<IgnoredActivity["id"], IgnoredActivity>(); const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
/** Migrate old data */
if (Array.isArray(ignoredActivitiesData)) {
for (const id of ignoredActivitiesData) {
ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game });
}
await saveCacheToDatastore(); if (oldIgnoredActivitiesData != null) {
} else ignoredActivitiesCache = ignoredActivitiesData; settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
.map(activity => ({ ...activity, name: "Unknown Name" }));
if (ignoredActivitiesCache.size !== 0) { DataStore.del("IgnoreActivities_ignoredActivities");
const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen(); }
for (const ignoredActivity of ignoredActivitiesCache.values()) { if (getIgnoredActivities().length !== 0) {
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
if (ignoredActivity.type !== ActivitiesTypes.Game) continue; if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) { if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
/** Custom added game which no longer exists */ getIgnoredActivities().splice(index, 1);
ignoredActivitiesCache.delete(ignoredActivity.id);
} }
} }
await saveCacheToDatastore();
} }
}, },
renderToggleGameActivityButton(props: { id?: string; exePath: string; }) {
return (
<ErrorBoundary noop>
<ToggleActivityComponent activity={{ id: props.id ?? props.exePath, type: ActivitiesTypes.Game }} forceLeftMargin={true} />
</ErrorBoundary>
);
},
renderToggleActivityButton(props: { id: string; }) {
return (
<ErrorBoundary noop>
<ToggleActivityComponentWithBackground activity={{ id: props.id, type: ActivitiesTypes.Embedded }} />
</ErrorBoundary>
);
},
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) { isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
if (props.type === 0) { if (props.type === 0 || props.type === 3) {
if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id); if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id);
else { else {
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
if (exePath) return !ignoredActivitiesCache.has(exePath); if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath);
} }
} }
return true; return true;
},
renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) {
return (
<ErrorBoundary noop>
<div style={{ marginLeft: 12, zIndex: 0 }}>
{ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)}
</div>
</ErrorBoundary>
);
},
renderToggleActivityButton(props: { id: string; name: string; }) {
return (
<ErrorBoundary noop>
{ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })}
</ErrorBoundary>
);
} }
}); });

View File

@ -37,13 +37,6 @@ export const settings = definePluginSettings({
default: true, default: true,
}, },
preventCarouselFromClosingOnClick: {
type: OptionType.BOOLEAN,
// Thanks chat gpt
description: "Allow the image modal in the image slideshow thing / carousel to remain open when clicking on the image",
default: true,
},
invertScroll: { invertScroll: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Invert scroll", description: "Invert scroll",
@ -101,7 +94,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
/> />
<Menu.MenuCheckboxItem <Menu.MenuCheckboxItem
id="vc-nearest-neighbour" id="vc-nearest-neighbour"
label="Nearset Neighbour" label="Nearest Neighbour"
checked={settings.store.nearestNeighbour} checked={settings.store.nearestNeighbour}
action={() => { action={() => {
settings.store.nearestNeighbour = !settings.store.nearestNeighbour; settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
@ -163,10 +156,14 @@ export default definePlugin({
patches: [ patches: [
{ {
find: '"renderLinkComponent","maxWidth"', find: "Messages.OPEN_IN_BROWSER",
replacement: { replacement: {
match: /(return\(.{1,100}\(\)\.wrapper.{1,100})(src)/, // there are 2 image thingies. one for carosuel and one for the single image.
replace: `$1id: '${ELEMENT_ID}',$2` // so thats why i added global flag.
// also idk if this patch is good, should it be more specific?
// https://regex101.com/r/xfvNvV/1
match: /return.{1,200}\.wrapper.{1,200}src:\i,/g,
replace: `$&id: '${ELEMENT_ID}',`
} }
}, },
@ -174,29 +171,21 @@ export default definePlugin({
find: "handleImageLoad=", find: "handleImageLoad=",
replacement: [ replacement: [
{ {
match: /(render=function\(\){.{1,500}limitResponsiveWidth.{1,600})onMouseEnter:/, match: /showThumbhashPlaceholder:\i,/,
replace: "$1...$self.makeProps(this),onMouseEnter:" replace: "...$self.makeProps(this),$&"
}, },
{ {
match: /componentDidMount=function\(\){/, match: /componentDidMount\(\){/,
replace: "$&$self.renderMagnifier(this);", replace: "$&$self.renderMagnifier(this);",
}, },
{ {
match: /componentWillUnmount=function\(\){/, match: /componentWillUnmount\(\){/,
replace: "$&$self.unMountMagnifier();" replace: "$&$self.unMountMagnifier();"
} }
] ]
}, },
{
find: ".carouselModal,",
replacement: {
match: /onClick:(\i),/,
replace: "onClick:$self.settings.store.preventCarouselFromClosingOnClick ? () => {} : $1,"
}
}
], ],
settings, settings,

View File

@ -25,12 +25,6 @@
box-shadow: none; box-shadow: none;
} }
[class*="modalCarouselWrapper"] {
height: fit-content;
top: 50%;
transform: translateY(-50%);
}
[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) { [class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) {
position: absolute; position: absolute;
left: 50%; left: 50%;

View File

@ -131,15 +131,15 @@ export default definePlugin({
// Indicator // Indicator
find: ".Messages.MESSAGE_EDITED,", find: ".Messages.MESSAGE_EDITED,",
replacement: { replacement: {
match: /var .,.,.=(.)\.className,.=.\.message,.=.\.children,.=.\.content,.=.\.onUpdate/gm, match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&" replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
} }
}, },
{ {
find: ".activeCommandOption", find: "ChannelTextAreaButtons",
replacement: { replacement: {
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}", replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
} }
}, },
], ],

View File

@ -21,8 +21,8 @@ import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, Forms } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
interface ActivityAssets { interface ActivityAssets {
large_image?: string; large_image?: string;
@ -86,15 +86,9 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
const logger = new Logger("LastFMRichPresence"); const logger = new Logger("LastFMRichPresence");
const presenceStore = findByPropsLazy("getLocalPresence"); const presenceStore = findByPropsLazy("getLocalPresence");
const assetManager = mapMangledModuleLazy(
"getAssetImage: size must === [number, number] for Twitch",
{
getAsset: filters.byCode("apply("),
}
);
async function getApplicationAsset(key: string): Promise<string> { async function getApplicationAsset(key: string): Promise<string> {
return (await assetManager.getAsset(applicationId, [key, undefined]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
} }
function setActivity(activity: Activity | null) { function setActivity(activity: Activity | null) {

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
// These are Xor encrypted to prevent you from spoiling yourself when you read the source code. // These are Xor encrypted to prevent you from spoiling yourself when you read the source code.
// don't worry about it :P // don't worry about it :P
@ -60,17 +61,35 @@ const quotes = [
"Wdn`khc'|f*eghl{%" "Wdn`khc'|f*eghl{%"
]; ];
const settings = definePluginSettings({
replaceEvents: {
description: "Replace Event Quotes too",
type: OptionType.BOOLEAN,
default: true
}
});
export default definePlugin({ export default definePlugin({
name: "LoadingQuotes", name: "LoadingQuotes",
description: "Replace Discords loading quotes", description: "Replace Discords loading quotes",
authors: [Devs.Ven, Devs.KraXen72], authors: [Devs.Ven, Devs.KraXen72],
settings,
patches: [ patches: [
{ {
find: ".LOADING_DID_YOU_KNOW", find: ".LOADING_DID_YOU_KNOW}",
replacement: { replacement: [
match: /\._loadingText=.+?random\(.+?;/s, {
replace: "._loadingText=$self.quote;", match: /\._loadingText=function\(\)\{/,
}, replace: "$&return $self.quote;",
},
{
match: /\._eventLoadingText=function\(\)\{/,
replace: "$&return $self.quote;",
predicate: () => settings.store.replaceEvents
}
],
}, },
], ],

View File

@ -105,10 +105,10 @@ export default definePlugin({
authors: [Devs.Ven, Devs.Commandtechno], authors: [Devs.Ven, Devs.Commandtechno],
patches: [{ patches: [{
find: ".isSidebarVisible,", find: "{isSidebarVisible:",
replacement: { replacement: {
match: /(var (\i)=\i\.className.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: "$1:[$2?.startsWith('members')?$self.render():null,$3" replace: ":[$1?.startsWith('members')?$self.render():null,$2"
} }
}], }],

View File

@ -18,7 +18,6 @@
import { addAccessory } from "@api/MessageAccessories"; import { addAccessory } from "@api/MessageAccessories";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStore";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js"; import { Devs } from "@utils/constants.js";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
@ -36,6 +35,7 @@ import {
PermissionStore, PermissionStore,
RestAPI, RestAPI,
Text, Text,
TextAndImagesSettingsStores,
UserStore UserStore
} from "@webpack/common"; } from "@webpack/common";
import { Channel, Guild, Message } from "discord-types/general"; import { Channel, Guild, Message } from "discord-types/general";
@ -46,12 +46,11 @@ const messageCache = new Map<string, {
}>(); }>();
const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed")); const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed"));
const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes('["message","compact","className",'))); const AutoModEmbed = LazyComponent(() => findByCode(".withFooter]:", "childrenMessageContent:"));
const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes("renderSimpleAccessories)")));
const SearchResultClasses = findByPropsLazy("message", "searchResult"); const SearchResultClasses = findByPropsLazy("message", "searchResult");
let AutoModEmbed: React.ComponentType<any> = () => null;
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g; const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//; const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
@ -319,10 +318,9 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe
/>; />;
} }
const compactModeEnabled = getSettingStoreLazy<boolean>("textAndImages", "messageDisplayCompact")!;
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
const { message, channel, guildID } = props; const { message, channel, guildID } = props;
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
const isDM = guildID === "@me"; const isDM = guildID === "@me";
const images = getImages(message); const images = getImages(message);
const { parse } = Parser; const { parse } = Parser;
@ -338,7 +336,7 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span> <span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
</Text> </Text>
} }
compact={compactModeEnabled.getSetting()} compact={compact}
content={ content={
<> <>
{message.content || message.attachments.length <= images.length {message.content || message.attachments.length <= images.length
@ -365,20 +363,7 @@ export default definePlugin({
name: "MessageLinkEmbeds", name: "MessageLinkEmbeds",
description: "Adds a preview to messages that link another message", description: "Adds a preview to messages that link another message",
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev], authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
dependencies: ["MessageAccessoriesAPI", "SettingsStoreAPI"], dependencies: ["MessageAccessoriesAPI"],
patches: [
{
find: ".embedCard",
replacement: [{
match: /function (\i)\(\i\){var \i=\i\.message,\i=\i\.channel.{0,200}\.hideTimestamp/,
replace: "$self.AutoModEmbed=$1;$&"
}]
}
],
set AutoModEmbed(e: any) {
AutoModEmbed = e;
},
settings, settings,

View File

@ -1,10 +1,10 @@
/* Message content highlighting */ /* Message content highlighting */
.messagelogger-deleted [class*="contents-"] > :is(div, h1, h2, h3, p) { .messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) {
color: #f04747 !important; color: #f04747 !important;
} }
/* Bot "thinking" text highlighting */ /* Bot "thinking" text highlighting */
.messagelogger-deleted [class*="colorStandard-"] { .messagelogger-deleted [class*="colorStandard"] {
color: #f04747 !important; color: #f04747 !important;
} }

View File

@ -210,7 +210,7 @@ export default definePlugin({
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id); ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
}, },
// Based on canary 9ab8626bcebceaea6da570b9c586172d02b9c996 // Based on canary 63b8f1b4f2025213c5cf62f0966625bee3d53136
patches: [ patches: [
{ {
// MessageStore // MessageStore
@ -219,7 +219,7 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Add deleted=true to all target messages in the MESSAGE_DELETE event // Add deleted=true to all target messages in the MESSAGE_DELETE event
match: /MESSAGE_DELETE:function\((\w)\){var .+?((?:\w{1,2}\.){2})getOrCreate.+?},/, match: /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
replace: replace:
"MESSAGE_DELETE:function($1){" + "MESSAGE_DELETE:function($1){" +
" var cache = $2getOrCreate($1.channelId);" + " var cache = $2getOrCreate($1.channelId);" +
@ -229,7 +229,7 @@ export default definePlugin({
}, },
{ {
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event // Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
match: /MESSAGE_DELETE_BULK:function\((\w)\){var .+?((?:\w{1,2}\.){2})getOrCreate.+?},/, match: /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
replace: replace:
"MESSAGE_DELETE_BULK:function($1){" + "MESSAGE_DELETE_BULK:function($1){" +
" var cache = $2getOrCreate($1.channelId);" + " var cache = $2getOrCreate($1.channelId);" +
@ -239,7 +239,7 @@ export default definePlugin({
}, },
{ {
// Add current cached content + new edit time to cached message's editHistory // Add current cached content + new edit time to cached message's editHistory
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/, match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/,
replace: "$1" + replace: "$1" +
".update($3,m =>" + ".update($3,m =>" +
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" + " (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
@ -251,8 +251,8 @@ export default definePlugin({
}, },
{ {
// fix up key (edit last message) attempting to edit a deleted message // fix up key (edit last message) attempting to edit a deleted message
match: /(?<=getLastEditableMessage=.{0,200}\.find\(\(function\((\i)\)\{)return/, match: /(?<=getLastEditableMessage\(\i\)\{.{0,200}\.find\((\i)=>)/,
replace: "return !$1.deleted &&" replace: "!$1.deleted &&"
} }
] ]
}, },
@ -260,13 +260,13 @@ export default definePlugin({
{ {
// Message domain model // Message domain model
// Module 451 // Module 451
find: "isFirstMessageInForumPost=function", find: "}addReaction(",
replacement: [ replacement: [
{ {
match: /(\w)\.customRenderedContent=(\w)\.customRenderedContent;/, match: /this\.customRenderedContent=(\i)\.customRenderedContent,/,
replace: "$1.customRenderedContent = $2.customRenderedContent;" + replace: "this.customRenderedContent = $1.customRenderedContent," +
"$1.deleted = $2.deleted || false;" + "this.deleted = $1.deleted || false," +
"$1.editHistory = $2.editHistory || [];" "this.editHistory = $1.editHistory || [],"
} }
] ]
}, },
@ -283,7 +283,7 @@ export default definePlugin({
// }, // },
{ {
// Pass through editHistory & deleted & original attachments to the "edited message" transformer // Pass through editHistory & deleted & original attachments to the "edited message" transformer
match: /interactionData:(\w)\.interactionData/, match: /interactionData:(\i)\.interactionData/,
replace: replace:
"interactionData:$1.interactionData," + "interactionData:$1.interactionData," +
"deleted:$1.deleted," + "deleted:$1.deleted," +
@ -299,7 +299,7 @@ export default definePlugin({
{ {
// Construct new edited message and add editHistory & deleted (ref above) // Construct new edited message and add editHistory & deleted (ref above)
// Pass in custom data to attachment parser to mark attachments deleted as well // Pass in custom data to attachment parser to mark attachments deleted as well
match: /attachments:(\w{1,2})\((\w)\)/, match: /attachments:(\i)\((\i)\)/,
replace: replace:
"attachments: $1((() => {" + "attachments: $1((() => {" +
" let old = arguments[1]?.attachments;" + " let old = arguments[1]?.attachments;" +
@ -315,7 +315,7 @@ export default definePlugin({
}, },
{ {
// Preserve deleted attribute on attachments // Preserve deleted attribute on attachments
match: /(\((\w)\){return null==\2\.attachments.+?)spoiler:/, match: /(\((\i)\){return null==\2\.attachments.+?)spoiler:/,
replace: replace:
"$1deleted: arguments[0]?.deleted," + "$1deleted: arguments[0]?.deleted," +
"spoiler:" "spoiler:"
@ -326,15 +326,15 @@ export default definePlugin({
{ {
// Attachment renderer // Attachment renderer
// Module 96063 // Module 96063
find: "().removeAttachmentHoverButton", find: ".removeAttachmentHoverButton",
replacement: [ replacement: [
{ {
match: /((\w)\.className,\w=\2\.attachment),/, match: /(className:\i,attachment:\i),/,
replace: "$1,deleted=$2.attachment?.deleted," replace: "$1,attachment: {deleted},"
}, },
{ {
match: /\["className","attachment".+?className:/, match: /\[\i\.obscured\]:.+?,/,
replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +" replace: "$& 'messagelogger-deleted-attachment': deleted,"
} }
] ]
}, },
@ -360,7 +360,7 @@ export default definePlugin({
{ {
// Render editHistory in the deepest div for message content // Render editHistory in the deepest div for message content
match: /(\)\("div",\{id:.+?children:\[)/, match: /(\)\("div",\{id:.+?children:\[)/,
replace: "$1 (arguments[0].message.editHistory.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), " replace: "$1 (arguments[0].message.editHistory?.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
} }
] ]
}, },
@ -371,11 +371,11 @@ export default definePlugin({
find: "displayName=\"ReferencedMessageStore\"", find: "displayName=\"ReferencedMessageStore\"",
replacement: [ replacement: [
{ {
match: /MESSAGE_DELETE:function\((\w)\).+?},/, match: /MESSAGE_DELETE:function\((\i)\).+?},/,
replace: "MESSAGE_DELETE:function($1){}," replace: "MESSAGE_DELETE:function($1){},"
}, },
{ {
match: /MESSAGE_DELETE_BULK:function\((\w)\).+?},/, match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/,
replace: "MESSAGE_DELETE_BULK:function($1){}," replace: "MESSAGE_DELETE_BULK:function($1){},"
} }
] ]
@ -384,7 +384,7 @@ export default definePlugin({
{ {
// Message context base menu // Message context base menu
// Module 600300 // Module 600300
find: "id:\"remove-reactions\"", find: "useMessageMenu:",
replacement: [ replacement: [
{ {
// Remove the first section if message is deleted // Remove the first section if message is deleted

View File

@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy } from "@webpack"; import { findByPropsLazy, findLazy } from "@webpack";
import { Card, ChannelStore, Forms, GuildStore, Switch, TextInput, Tooltip, useState } from "@webpack/common"; import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
import { RC } from "@webpack/types"; import { RC } from "@webpack/types";
import { Channel, Message, User } from "discord-types/general"; import { Channel, Message, User } from "discord-types/general";
@ -53,14 +53,11 @@ interface TagSettings {
[k: string]: TagSetting; [k: string]: TagSetting;
} }
const CLYDE_ID = "1081004946872352958";
// PermissionStore.computePermissions is not the same function and doesn't work here // PermissionStore.computePermissions is not the same function and doesn't work here
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as { const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
computePermissions({ ...args }): bigint; computePermissions({ ...args }): bigint;
}; };
const Permissions = findByPropsLazy("SEND_MESSAGES", "VIEW_CREATOR_MONETIZATION_ANALYTICS") as Record<PermissionName, bigint>;
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; }; const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot(); const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
@ -190,17 +187,14 @@ export default definePlugin({
patches: [ patches: [
// add tags to the tag list // add tags to the tag list
{ {
find: '.BOT=0]="BOT"', find: "BotTagTypes:",
replacement: [ replacement: {
// add tags to the exported tags list (Tag.Types) match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
{ replace: "($1=$self.getTagTypes()))[$2.BOT"
match: /(\i)\[.\.BOT=0\]="BOT";/, }
replace: "$&$1=$self.addTagVariants($1);"
}
]
}, },
{ {
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP;", find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP,",
replacement: [ replacement: [
// make the tag show the right text // make the tag show the right text
{ {
@ -215,25 +209,25 @@ export default definePlugin({
}, },
// add HTML data attributes (for easier theming) // add HTML data attributes (for easier theming)
{ {
match: /children:\[(?=\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/, match: /.botText,children:(\i)}\)]/,
replace: "'data-tag':$1.toLowerCase(),children:[" replace: "$&,'data-tag':$1.toLowerCase()"
} }
], ],
}, },
// in messages // in messages
{ {
find: ".Types.ORIGINAL_POSTER", find: "renderSystemTag:",
replacement: { replacement: {
match: /return null==(\i)\?null:\(0,/, match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
replace: "$1=$self.getTag({...arguments[0],origType:$1,location:'chat'});$&" replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
} }
}, },
// in the member list // in the member list
{ {
find: ".renderBot=function(){", find: ".Messages.GUILD_OWNER,",
replacement: { replacement: {
match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/, match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type" replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
} }
}, },
// pass channel id down props to be used in profiles // pass channel id down props to be used in profiles
@ -253,11 +247,18 @@ export default definePlugin({
}, },
// in profiles // in profiles
{ {
find: ",botType:", find: ",overrideDiscriminator:",
replacement: { replacement: [
match: /,botType:(\i\((\i)\)),/g, {
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'})," // prevent channel id from getting ghosted
} // it's either this or extremely long lookbehind
match: /user:\i,nick:\i,/,
replace: "$&moreTags_channelId,"
}, {
match: /,botType:(\i\((\i)\)),/g,
replace: ",botType:$self.getTag({user:$2,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),"
}
]
}, },
], ],
@ -297,24 +298,25 @@ export default definePlugin({
if (!guild) return []; if (!guild) return [];
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites }); const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
return Object.entries(Permissions) return Object.entries(PermissionsBits)
.map(([perm, permInt]) => .map(([perm, permInt]) =>
permissions & permInt ? perm : "" permissions & permInt ? perm : ""
) )
.filter(Boolean); .filter(Boolean);
}, },
addTagVariants(tagConstant) { getTagTypes() {
const obj = {};
let i = 100; let i = 100;
tags.forEach(({ name }) => { tags.forEach(({ name }) => {
tagConstant[name] = ++i; obj[name] = ++i;
tagConstant[i] = name; obj[i] = name;
tagConstant[`${name}-BOT`] = ++i; obj[`${name}-BOT`] = ++i;
tagConstant[i] = `${name}-BOT`; obj[i] = `${name}-BOT`;
tagConstant[`${name}-OP`] = ++i; obj[`${name}-OP`] = ++i;
tagConstant[i] = `${name}-OP`; obj[i] = `${name}-OP`;
}); });
return tagConstant; return obj;
}, },
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]), isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
@ -341,15 +343,17 @@ export default definePlugin({
message, user, channelId, origType, location, channel message, user, channelId, origType, location, channel
}: { }: {
message?: Message, message?: Message,
user: User, user: User & { isClyde(): boolean; },
channel?: Channel & { isForumPost(): boolean; }, channel?: Channel & { isForumPost(): boolean; },
channelId?: string; channelId?: string;
origType?: number; origType?: number;
location: "chat" | "not-chat"; location: "chat" | "not-chat";
}): number | null { }): number | null {
if (!user)
return null;
if (location === "chat" && user.id === "1") if (location === "chat" && user.id === "1")
return Tag.Types.OFFICIAL; return Tag.Types.OFFICIAL;
if (user.id === CLYDE_ID) if (user.isClyde())
return Tag.Types.AI; return Tag.Types.AI;
let type = typeof origType === "number" ? origType : null; let type = typeof origType === "number" ? origType : null;
@ -377,7 +381,6 @@ export default definePlugin({
break; break;
} }
} }
return type; return type;
} }
}); });

View File

@ -45,16 +45,16 @@ export default definePlugin({
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince], authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
patches: [ patches: [
{ {
find: ",acceptInvite:function", find: ",acceptInvite(",
replacement: { replacement: {
match: /INVITE_ACCEPT_SUCCESS.+?;(\i)=null.+?;/, match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/,
replace: (m, guildId) => `${m}$self.handleMute(${guildId});` replace: (m, guildId) => `${m}$self.handleMute(${guildId});`
} }
}, },
{ {
find: "{joinGuild:function", find: "{joinGuild:",
replacement: { replacement: {
match: /guildId:(\w+),lurker:(\w+).{0,20}\)}\)\);/, match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});` replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});`
} }
} }

View File

@ -47,16 +47,17 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
replacement: [ replacement: {
{ match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/, replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
replace: '$1?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),' }
}, },
{ {
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, find: ".UserProfileSections.USER_INFO_CONNECTIONS:",
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);" replacement: {
} match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
] replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
}
} }
], ],

View File

@ -29,11 +29,11 @@ export default definePlugin({
authors: [Devs.rushii, Devs.Samu], authors: [Devs.rushii, Devs.Samu],
patches: [ patches: [
{ {
find: 'safety_prompt:"DMSpamExperiment",response:"show_redacted_messages"', find: "Messages.BLOCKED_MESSAGES_HIDE",
replacement: [ replacement: [
{ {
match: /\.collapsedReason;return/, match: /let\{[^}]*collapsedReason[^}]*\}/,
replace: ".collapsedReason;return null;return;" replace: "return null;$&"
} }
] ]
}, },

View File

@ -26,8 +26,8 @@ export default definePlugin({
patches: [{ patches: [{
find: "setDevtoolsCallbacks", find: "setDevtoolsCallbacks",
replacement: { replacement: {
match: /if\(.{0,10}\|\|"0.0.0"!==.{0,2}\.remoteApp\.getVersion\(\)\)/, match: /if\(null!=\i&&"0.0.0"===\i\.remoteApp\.getVersion\(\)\)/,
replace: "if(false)" replace: "if(true)"
} }
}] }]
}); });

View File

@ -0,0 +1,43 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import style from "./styles.css?managed";
export default definePlugin({
name: "NoMosaic",
authors: [Devs.AutumnVN],
description: "Removes Discord new image mosaic",
tags: ["image", "mosaic", "media"],
patches: [
{
find: ".oneByTwoLayoutThreeGrid",
replacement: [{
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
replace: 'mediaLayoutType:"RESPONSIVE"'
},
{
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
replace: '"INVALID"',
},]
},
{
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
replacement: {
match: /\i===\i\.\i\.MOSAIC/,
replace: "true"
}
}],
start() {
enableStyle(style);
},
stop() {
disableStyle(style);
}
});

View File

@ -0,0 +1,3 @@
[class^="nonMediaAttachmentsContainer_"] [class*="messageAttachment_"] {
position: relative;
}

View File

@ -55,18 +55,18 @@ export default definePlugin({
// or by searching for "showProgressBadge:" // or by searching for "showProgressBadge:"
patches: [ patches: [
{ {
find: ".getPendingCount=", find: "getPendingCount(){",
predicate: () => settings.store.hideFriendRequestsCount, predicate: () => settings.store.hideFriendRequestsCount,
replacement: { replacement: {
match: /(?<=\.getPendingCount=function\(\)\{)/, match: /(?<=getPendingCount\(\)\{)/,
replace: "return 0;" replace: "return 0;"
} }
}, },
{ {
find: ".getMessageRequestsCount=", find: "getMessageRequestsCount(){",
predicate: () => settings.store.hideMessageRequestsCount, predicate: () => settings.store.hideMessageRequestsCount,
replacement: { replacement: {
match: /(?<=\.getMessageRequestsCount=function\(\)\{)/, match: /(?<=getMessageRequestsCount\(\)\{)/,
replace: "return 0;" replace: "return 0;"
} }
}, },
@ -84,8 +84,10 @@ export default definePlugin({
find: "showProgressBadge:", find: "showProgressBadge:",
predicate: () => settings.store.hidePremiumOffersCount, predicate: () => settings.store.hidePremiumOffersCount,
replacement: { replacement: {
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/, // The two groups inside the first group grab the minified names of the variables,
replace: "(function(){return 0})" // they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,200}\i=)\1\+\2/,
replace: "0"
} }
} }
], ],

View File

@ -30,23 +30,23 @@ export default definePlugin({
// = isPremiumAtLeast(user.premiumType, TIER_2) // = isPremiumAtLeast(user.premiumType, TIER_2)
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/, match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2) // = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
replace: "=$1?.banner&&" replace: "=(arguments[0]?.bannerSrc||$1?.banner)&&"
} }
}, },
{ {
find: "().avatarPositionPremiumNoBanner,default:", find: ".avatarPositionPremiumNoBanner,default:",
replacement: { replacement: {
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal // premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\(\)\.(\i))/, match: /\.avatarPositionPremiumNoBanner(?=,default:\i\.(\i))/,
// premiumUserWithoutBanner: foo().avatarPositionNormal... // premiumUserWithoutBanner: foo().avatarPositionNormal...
replace: ".$1" replace: ".$1"
} }
}, },
{ {
find: ".hasThemeColors=function(){", find: "hasThemeColors(){",
replacement: { replacement: {
match: /(?<=key:"canUsePremiumProfileCustomization",get:function\(\){return)/, match: /get canUsePremiumProfileCustomization\(\){return /,
replace: " false;" replace: "$&false &&"
} }
} }
] ]

View File

@ -25,14 +25,11 @@ export default definePlugin({
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
patches: [ patches: [
{ {
find: '("ApplicationStreamPreviewUploadManager")', find: '"ApplicationStreamPreviewUploadManager"',
replacement: [ replacement: {
String.raw`\i\.\i\.makeChunkedRequest\(`, match: /await \i\.\i\.(makeChunkedRequest\(|post\(\{url:)\i\.\i\.STREAM_PREVIEW.+?\}\)/g,
String.raw`\i\.\i\.post\({url:` replace: "0"
].map(match => ({ }
match: new RegExp(String.raw`(?=return\[(\d),${match}\i\.\i\.STREAM_PREVIEW.+?}\)\];)`),
replace: (_, code) => `return[${code},Promise.resolve({body:"",status:204})];`
}))
} }
] ]
}); });

View File

@ -0,0 +1,21 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoTypingAnimation",
authors: [Devs.AutumnVN],
description: "Disables the CPU-intensive typing dots animation",
patches: [{
find: "dotCycle",
replacement: {
match: /document.hasFocus\(\)/,
replace: "false"
}
}]
});

View File

@ -33,7 +33,7 @@ export default definePlugin({
} }
}, },
{ {
find: "renderJumpButton=function()", find: "renderJumpButton()",
replacement: { replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/, match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
replace: "if(false)$1" replace: "if(false)$1"

View File

@ -0,0 +1,7 @@
# OnePingPerDM
If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit
## Purpose
- Prevents ping audio spam in DMs
- Be able to distinguish more than one ping as multiple users
- Be less annoyed while gaming

View File

@ -0,0 +1,71 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
import { MessageJSON } from "discord-types/general";
const enum ChannelType {
DM = 1,
GROUP_DM = 3
}
const settings = definePluginSettings({
channelToAffect: {
type: OptionType.SELECT,
description: "Select the type of DM for the plugin to affect",
options: [
{ label: "Both", value: "both_dms", default: true },
{ label: "User DMs", value: "user_dm" },
{ label: "Group DMs", value: "group_dm" },
]
},
allowMentions: {
type: OptionType.BOOLEAN,
description: "Receive audio pings for @mentions",
default: false,
},
allowEveryone: {
type: OptionType.BOOLEAN,
description: "Receive audio pings for @everyone and @here in group DMs",
default: false,
},
});
export default definePlugin({
name: "OnePingPerDM",
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
authors: [Devs.ProffDea],
settings,
patches: [{
find: ".getDesktopType()===",
replacement: [{
match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
},
{
match: /sound:(\i\?\i:void 0,volume:\i,onClick)/,
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
}]
}],
isPrivateChannelRead(message: MessageJSON) {
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
return false;
}
if (
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
(settings.store.allowEveryone && message.mention_everyone)
) {
return true;
}
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
},
});

View File

@ -53,10 +53,10 @@ export default definePlugin({
patches: [ patches: [
{ {
find: '"MaskedLinkStore"', find: "trackAnnouncementMessageLinkClicked({",
replacement: { replacement: {
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,250}\.trusted)/, match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)async function \1\(.+?\)\{/,
replace: "return $self.handleLink(...arguments).then(handled => handled||$1)" replace: "$& if(await $self.handleLink(...arguments)) return;"
} }
}, },
// Make Spotify profile activity links open in app on web // Make Spotify profile activity links open in app on web
@ -71,7 +71,7 @@ export default definePlugin({
{ {
find: ".CONNECTED_ACCOUNT_VIEWED,", find: ".CONNECTED_ACCOUNT_VIEWED,",
replacement: { replacement: {
match: /(?<=href:\i,onClick:function\(\i\)\{)(?=\i=(\i)\.type,.{0,50}CONNECTED_ACCOUNT_VIEWED)/, match: /(?<=href:\i,onClick:\i=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/,
replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);" replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);"
} }
} }

View File

@ -19,10 +19,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findStoreLazy } from "@webpack"; import { FluxDispatcher } from "@webpack/common";
import { GenericStore } from "@webpack/common";
const PoggerModeSettingsStore: GenericStore = findStoreLazy("PoggermodeSettingsStore");
const enum Intensity { const enum Intensity {
Normal, Normal,
@ -61,9 +58,12 @@ export default definePlugin({
}); });
function setPoggerState(state: boolean) { function setPoggerState(state: boolean) {
Object.assign(PoggerModeSettingsStore.__getLocalVars().state, { FluxDispatcher.dispatch({
enabled: state, type: "POGGERMODE_SETTINGS_UPDATE",
settingsVisible: state settings: {
enabled: state,
settingsVisible: state
}
}); });
} }
@ -101,5 +101,8 @@ function setSettings(intensity: Intensity) {
} }
} }
Object.assign(PoggerModeSettingsStore.__getLocalVars().state, state); FluxDispatcher.dispatch({
type: "POGGERMODE_SETTINGS_UPDATE",
settings: state
});
} }

View File

@ -0,0 +1,9 @@
# PermissionFreeWill
Removes the client-side restrictions that prevent editing channel permissions, such as permission lockouts ("Pretty sure
you don't want to do this") and onboarding requirements ("Making this change will make your server incompatible [...]")
## Warning
This plugin will let you create permissions in servers that **WILL** lock you out of channels until an administrator
can resolve it for you. Please be careful with the overwrites you are making and check carefully.

View File

@ -0,0 +1,57 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({
lockout: {
type: OptionType.BOOLEAN,
default: true,
description: 'Bypass the permission lockout prevention ("Pretty sure you don\'t want to do this")',
restartNeeded: true
},
onboarding: {
type: OptionType.BOOLEAN,
default: true,
description: 'Bypass the onboarding requirements ("Making this change will make your server incompatible [...]")',
restartNeeded: true
}
});
export default definePlugin({
name: "PermissionFreeWill",
description: "Disables the client-side restrictions for channel permission management.",
authors: [Devs.lewisakura],
patches: [
// Permission lockout, just set the check to true
{
find: ".showPermissionLockoutModal(",
replacement: [
{
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
replace: "$&true||"
}
],
predicate: () => settings.store.lockout
},
// Onboarding, same thing but we need to prevent the check
{
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
replacement: [
{
// are we java yet?
match: /(?<=(?:isDefaultChannelThresholdMetAfterDelete|checkDefaultChannelThresholdMetAfterChannelPermissionDeny):function\(\)\{)return \i(?=\})/g,
replace: "return () => true"
}
],
predicate: () => settings.store.onboarding
}
],
settings
});

View File

@ -161,7 +161,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS", find: ".popularApplicationCommandIds,",
replacement: { replacement: {
match: /showBorder:.{0,60}}\),(?<=guild:(\i),guildMember:(\i),.+?)/, match: /showBorder:.{0,60}}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
replace: (m, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember}),` replace: (m, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember}),`

View File

@ -20,7 +20,8 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, Co
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { makeLazy } from "@utils/lazy"; import { makeLazy } from "@utils/lazy";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { UploadHandler, UserUtils } from "@webpack/common";
import { applyPalette, GIFEncoder, quantize } from "gifenc"; import { applyPalette, GIFEncoder, quantize } from "gifenc";
const DRAFT_TYPE = 0; const DRAFT_TYPE = 0;
@ -35,8 +36,6 @@ const getFrames = makeLazy(() => Promise.all(
)) ))
); );
const fetchUser = findByCodeLazy(".USER(");
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
const UploadStore = findByPropsLazy("getUploads"); const UploadStore = findByPropsLazy("getUploads");
function loadImage(source: File | string) { function loadImage(source: File | string) {
@ -70,7 +69,7 @@ async function resolveImage(options: Argument[], ctx: CommandContext, noServerPf
return opt.value; return opt.value;
case "user": case "user":
try { try {
const user = await fetchUser(opt.value); const user = await UserUtils.getUser(opt.value);
return user.getAvatarURL(noServerPfp ? void 0 : ctx.guild?.id, 2048).replace(/\?size=\d+$/, "?size=2048"); return user.getAvatarURL(noServerPfp ? void 0 : ctx.guild?.id, 2048).replace(/\?size=\d+$/, "?size=2048");
} catch (err) { } catch (err) {
console.error("[petpet] Failed to fetch user\n", err); console.error("[petpet] Failed to fetch user\n", err);
@ -175,7 +174,7 @@ export default definePlugin({
const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" }); const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" });
// Immediately after the command finishes, Discord clears all input, including pending attachments. // Immediately after the command finishes, Discord clears all input, including pending attachments.
// Thus, setTimeout is needed to make this execute after Discord cleared the input // Thus, setTimeout is needed to make this execute after Discord cleared the input
setTimeout(() => promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10); setTimeout(() => UploadHandler.promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10);
}, },
}, },
] ]

View File

@ -24,13 +24,12 @@ export default definePlugin({
description: "Adds picture in picture to videos (next to the Download button)", description: "Adds picture in picture to videos (next to the Download button)",
authors: [Devs.Lumap], authors: [Devs.Lumap],
settings, settings,
patches: [ patches: [
{ {
find: ".onRemoveAttachment,", find: ".nonMediaAttachment]",
replacement: { replacement: {
match: /\.nonMediaAttachment,!(\i).{0,7}children:\[(\i),/, match: /\.nonMediaAttachment\].{0,10}children:\[\S/,
replace: "$&$1&&$2&&$self.renderPiPButton()," replace: "$&&&$self.renderPiPButton(),"
}, },
}, },
], ],

View File

@ -0,0 +1,8 @@
.vc-pip-button {
color: var(--interactive-normal);
}
.vc-pip-button:hover {
background-color: var(--background-modifier-hover);
color: var(--interactive-hover);
}

View File

@ -66,7 +66,7 @@ export default definePlugin({
// filter Discord's privateChannelIds list to remove pins, and pass // filter Discord's privateChannelIds list to remove pins, and pass
// pinCount as prop. This needs to be here so that the entire DM list receives // pinCount as prop. This needs to be here so that the entire DM list receives
// updates on pin/unpin // updates on pin/unpin
match: /privateChannelIds:(\i),/, match: /(?<=\i,{channels:\i,)privateChannelIds:(\i),/,
replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c)),pinCount:$self.usePinCount($1)," replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c)),pinCount:$self.usePinCount($1),"
}, },
{ {
@ -75,39 +75,39 @@ export default definePlugin({
// - Section 1: buttons for pages like Friends & Library // - Section 1: buttons for pages like Friends & Library
// - Section 2: our pinned dms // - Section 2: our pinned dms
// - Section 3: the normal dm list // - Section 3: the normal dm list
match: /(?<=renderRow:(\i)\.renderRow,)sections:\[\i,/, match: /(?<=renderRow:this\.renderRow,)sections:\[\i,/,
// For some reason, adding our sections when no private channels are ready yet // For some reason, adding our sections when no private channels are ready yet
// makes DMs infinitely load. Thus usePinCount returns either a single element // makes DMs infinitely load. Thus usePinCount returns either a single element
// array with the count, or an empty array. Due to spreading, only in the former // array with the count, or an empty array. Due to spreading, only in the former
// case will an element be added to the outer array // case will an element be added to the outer array
// Thanks for the fix, Strencher! // Thanks for the fix, Strencher!
replace: "$&...$1.props.pinCount," replace: "$&...this.props.pinCount??[],"
}, },
{ {
// Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages" // Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages"
// lookbehind is used to lookup parameter name. We could use arguments[0], but // lookbehind is used to lookup parameter name. We could use arguments[0], but
// if children ever is wrapped in an iife, it will break // if children ever is wrapped in an iife, it will break
match: /children:(\i\.\i\.Messages.DIRECT_MESSAGES)(?<=renderSection=function\((\i)\).+?)/, match: /children:(\i\.\i\.Messages.DIRECT_MESSAGES)(?<=renderSection=(\i)=>{.+?)/,
replace: "children:$2.section===1?'Pinned DMs':$1" replace: "children:$2.section===1?'Pinned DMs':$1"
}, },
{ {
// Patch channel lookup inside renderDM // Patch channel lookup inside renderDM
// channel=channels[channelIds[row]]; // channel=channels[channelIds[row]];
match: /(?<=preRenderedChildren,(\i)=)((\i)\[\i\[\i\]\]);/, match: /(?<=renderDM=\((\i),(\i)\)=>{.*?this.state,\i=\i\[\i\],\i=)((\i)\[\i\]);/,
// section 1 is us, manually get our own channel // section 1 is us, manually get our own channel
// section === 1 ? getChannel(channels, row) : channels[channelIds[row]]; // section === 1 ? getChannel(channels, row) : channels[channelIds[row]];
replace: "arguments[0]===1?$self.getChannel($3,arguments[1]):$2;" replace: "$1===1?$self.getChannel($4,$2):$3;"
}, },
{ {
// Fix getRowHeight's check for whether this is the DMs section // Fix getRowHeight's check for whether this is the DMs section
// section === DMS // section === DMS
match: /===\i.DMS&&0/, match: /===\i\.DMS&&0/,
// section -1 === DMS // section -1 === DMS
replace: "-1$&" replace: "-1$&"
}, },
{ {
// Override scrollToChannel to properly account for pinned channels // Override scrollToChannel to properly account for pinned channels
match: /(?<=else\{\i\+=)(\i)\*\(.+?(?=;)/, match: /(?<=scrollTo\(\{to:\i\}\):\(\i\+=)(\d+)\*\(.+?(?=,)/,
replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)" replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)"
} }
] ]
@ -115,19 +115,19 @@ export default definePlugin({
// Fix Alt Up/Down navigation // Fix Alt Up/Down navigation
{ {
find: '"mod+alt+right"', find: ".Routes.APPLICATION_STORE&&",
replacement: { replacement: {
// channelIds = __OVERLAY__ ? stuff : toArray(getStaticPaths()).concat(toArray(channelIds)) // channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
match: /(?<=(\i)=__OVERLAY__\?\i:.{0,10})\.concat\((.{0,10})\)/, match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,
// ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c))) // ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
replace: ".concat($self.getSnapshot()).concat($2.filter(c=>!$self.isPinned(c)))" replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
} }
}, },
// fix alt+shift+up/down // fix alt+shift+up/down
{ {
find: '"alt+shift+down"', find: ".getFlattenedGuildIds()],",
replacement: { replacement: {
match: /(?<=return \i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/, match: /(?<=\i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/,
replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))" replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
} }
}, },

View File

@ -23,20 +23,20 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";
import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
const SessionsStore = findStoreLazy("SessionsStore"); const SessionsStore = findStoreLazy("SessionsStore");
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) { function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
return ({ color, tooltip }: { color: string; tooltip: string; }) => ( return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => (
<Tooltip text={tooltip} > <Tooltip text={tooltip} >
{(tooltipProps: any) => ( {(tooltipProps: any) => (
<svg <svg
{...tooltipProps} {...tooltipProps}
height={opts?.height ?? 20} height={(opts?.height ?? 20) - (small ? 3 : 0)}
width={opts?.width ?? 20} width={(opts?.width ?? 20) - (small ? 3 : 0)}
viewBox={opts?.viewBox ?? "0 0 24 24"} viewBox={opts?.viewBox ?? "0 0 24 24"}
fill={color} fill={color}
> >
@ -55,18 +55,18 @@ const Icons = {
}; };
type Platform = keyof typeof Icons; type Platform = keyof typeof Icons;
const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE"); const StatusUtils = findByPropsLazy("getStatusColor", "StatusTypes");
const PlatformIcon = ({ platform, status }: { platform: Platform, status: string; }) => { const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
const tooltip = platform[0].toUpperCase() + platform.slice(1); const tooltip = platform[0].toUpperCase() + platform.slice(1);
const Icon = Icons[platform] ?? Icons.desktop; const Icon = Icons[platform] ?? Icons.desktop;
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} />; return <Icon color={`var(--${StatusUtils.getStatusColor(status)}`} tooltip={tooltip} small={small} />;
}; };
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id]; const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; }) => { const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
if (!user || user.bot) return null; if (!user || user.bot) return null;
if (user.id === UserStore.getCurrentUser().id) { if (user.id === UserStore.getCurrentUser().id) {
@ -99,6 +99,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: {
key={platform} key={platform}
platform={platform as Platform} platform={platform as Platform}
status={status} status={status}
small={small}
/> />
)); ));
@ -137,7 +138,7 @@ const indicatorLocations = {
description: "In the member list", description: "In the member list",
onEnable: () => addDecorator("platform-indicator", props => onEnable: () => addDecorator("platform-indicator", props =>
<ErrorBoundary noop> <ErrorBoundary noop>
<PlatformIndicator user={props.user} /> <PlatformIndicator user={props.user} small={true} />
</ErrorBoundary> </ErrorBoundary>
), ),
onDisable: () => removeDecorator("platform-indicator") onDisable: () => removeDecorator("platform-indicator")
@ -197,13 +198,13 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status // Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
match: /(?<=return \i\.\i\.Masks\.STATUS_TYPING;)(.+?)(\i)\?(\i\.\i\.Masks\.STATUS_ONLINE_MOBILE):/, match: /\.STATUS_TYPING;switch(?=.+?(if\(\i\)return \i\.\i\.Masks\.STATUS_ONLINE_MOBILE))/,
replace: (_, rest, isMobile, mobileMask) => `if(${isMobile})return ${mobileMask};${rest}` replace: ".STATUS_TYPING;$1;switch"
}, },
{ {
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status // Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
match: /(switch\(\i\){case \i\.\i\.ONLINE:return )(\i)\?({.+?}):/, match: /switch\(\i\)\{case \i\.\i\.ONLINE:(if\(\i\)return\{[^}]+\})/,
replace: (_, rest, isMobile, component) => `if(${isMobile})return${component};${rest}` replace: "$1;$&"
} }
] ]
}, },
@ -229,7 +230,7 @@ export default definePlugin({
] ]
}, },
{ {
find: "isMobileOnline=function", find: "}isMobileOnline(",
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator, predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
replacement: { replacement: {
// Make isMobileOnline return true no matter what is the user status // Make isMobileOnline return true no matter what is the user status

View File

@ -129,10 +129,10 @@ export default definePlugin({
authors: [Devs.Aria], authors: [Devs.Aria],
patches: [ patches: [
{ {
find: ".activeCommandOption", find: "ChannelTextAreaButtons",
replacement: { replacement: {
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
replace: "$&;try{$2||$1.unshift($self.previewIcon(arguments[0]))}catch{}", replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
} }
}, },
], ],

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
@ -39,17 +40,17 @@ function shouldShow(message: Message): boolean {
return true; return true;
} }
export function PronounsChatComponentWrapper({ message }: { message: Message; }) { export const PronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
return shouldShow(message) return shouldShow(message)
? <PronounsChatComponent message={message} /> ? <PronounsChatComponent message={message} />
: null; : null;
} }, { noop: true });
export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) { export const CompactPronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
return shouldShow(message) return shouldShow(message)
? <CompactPronounsChatComponent message={message} /> ? <CompactPronounsChatComponent message={message} />
: null; : null;
} }, { noop: true });
function PronounsChatComponent({ message }: { message: Message; }) { function PronounsChatComponent({ message }: { message: Message; }) {
const [result] = useFormattedPronouns(message.author.id); const [result] = useFormattedPronouns(message.author.id);
@ -63,7 +64,7 @@ function PronounsChatComponent({ message }: { message: Message; }) {
: null; : null;
} }
export function CompactPronounsChatComponent({ message }: { message: Message; }) { export const CompactPronounsChatComponent = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
const [result] = useFormattedPronouns(message.author.id); const [result] = useFormattedPronouns(message.author.id);
return result return result
@ -73,4 +74,4 @@ export function CompactPronounsChatComponent({ message }: { message: Message; })
> {result}</span> > {result}</span>
) )
: null; : null;
} }, { noop: true });

View File

@ -41,7 +41,7 @@ export default definePlugin({
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
replacement: { replacement: {
match: /("span",{id:\i,className:\i,children:\i}\))/, match: /("span",{id:\i,className:\i,children:\i}\))/,
replace: "$1, $self.CompactPronounsChatComponentWrapper(e)" replace: "$1, $self.CompactPronounsChatComponentWrapper(arguments[0])"
} }
}, },
// Patch the chat timestamp element (normal mode) // Patch the chat timestamp element (normal mode)
@ -49,7 +49,7 @@ export default definePlugin({
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
replacement: { replacement: {
match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/, match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/,
replace: "[$1, $self.PronounsChatComponentWrapper(e)]" replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]"
} }
}, },
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns // Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
@ -57,19 +57,18 @@ export default definePlugin({
find: ".userTagNoNickname", find: ".userTagNoNickname",
replacement: [ replacement: [
{ {
match: /,(\i)=(\i)\.pronouns/, match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i;/,
replace: ",[$1,vcPronounSource]=$self.useProfilePronouns($2.user.id)" replace: "$&let vcPronounSource;[$2,vcPronounSource]=$self.useProfilePronouns($1.id);"
}, },
PRONOUN_TOOLTIP_PATCH PRONOUN_TOOLTIP_PATCH
] ]
}, },
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns // Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
{ {
find: ".USER_PROFILE_ACTIVITY", find: ".nameTagSmall)",
replacement: [ replacement: [
{ {
/* FIXME: old name is getGlobalName, new name is getName. Remove optional Global once stable has also migrated */ match: /\.getName\(\i\);(?<=displayProfile.{0,200})/,
match: /\.get(?:Global)?Name\(\i\);(?<=displayProfile.{0,200})/,
replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;" replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;"
}, },
PRONOUN_TOOLTIP_PATCH PRONOUN_TOOLTIP_PATCH

View File

@ -37,7 +37,7 @@ export async function onRelationshipRemove({ relationship: { type, id } }: Relat
return; return;
} }
const user = await UserUtils.fetchUser(id) const user = await UserUtils.getUser(id)
.catch(() => null); .catch(() => null);
if (!user) return; if (!user) return;

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