Compare commits

...

188 Commits
v1.5.7 ... dev

Author SHA1 Message Date
V
4a5371a746 Update README.md 2023-11-16 05:40:50 +01:00
adryd
77749ed5e1 oneko: update script version (#1994) 2023-11-16 01:26:35 +01:00
Justice Almanzar
45aa9fbb6d Fix hljs find (#1983) 2023-11-15 15:30:31 -03:00
megumin
4a2657f928 fix(channeltags): message author should be clyde (#644) (#1986) 2023-11-15 15:15:43 -03:00
AutumnVN
6578eb487e MessageLogger: fix attachment ignore (#1989) 2023-11-15 15:13:19 -03:00
Thoth
c080a0eaac shikiCodeblocks: transform lang to lower case to avoid failing detection (#1990) 2023-11-15 15:09:26 -03:00
Jack
3ea6a96715 chore: Fix PinDMs patch (#1981) 2023-11-15 15:01:50 -03:00
Nuckyz
af614465a4 Remove obsolete experiments patch 2023-11-15 14:54:48 -03:00
Nuckyz
7b248ee309 Fix SHC patches 2023-11-15 14:50:52 -03:00
Ajay Ramachandran
fd25b5f296 fix(dearrow): support DeArrow thumbnail submissions at 0 seconds (#1979) 2023-11-15 14:30:48 -03:00
megumin
77d08c5c28 feat: Add Environment variable to disable updater auto-patching (#1971) 2023-11-15 14:30:48 -03:00
V
ea11f2244f README: Add sponsors 2023-11-13 01:37:15 +01:00
V
96126fa39f remove pipebomb from github actions (#1968) 2023-11-09 07:15:49 +01:00
AM
9bd82943e3 [Plugin] Super Reaction Tweaks (#1958)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Jack Matthews <jm5112356@gmail.com>
2023-11-09 04:42:35 +01:00
V
5edc94062c make packageManager key less specific 2023-11-09 02:42:34 +01:00
AutumnVN
394d2060eb searchReply: fix (#1961) 2023-11-09 02:34:40 +01:00
V
119b628f33 feat: simple plugin natives (#1965) 2023-11-09 02:32:34 +01:00
zImPatrick
32f2043193 Fix FakeNitro sticker bypass (#1964) 2023-11-07 22:24:17 -03:00
Marvin Witt
04d2dd26c4 fix(dearrow): don't replace thumbnail if only original available (#1959) 2023-11-07 22:24:08 -03:00
Vendicated
86e94343cc bump to v1.6.3 2023-11-05 02:07:33 +01:00
Vendicated
dd44ac1ad2 OpenInApp: Support podcasts 2023-11-04 19:08:44 +01:00
Vendicated
370b3d366d OpenInApp: Fix links in messages 2023-11-04 18:59:16 +01:00
Vendicated
a67c7f841d Fix ViewIcons correctly 2023-11-04 18:54:29 +01:00
Vendicated
2c9793202d Revert "Fix ViewIcons"
This reverts commit 098da8c3fd95cdd1f299b86d0b919b001009a1df.
2023-11-04 18:54:02 +01:00
AutumnVN
a257926609 customRpc: fix discord attachment link (#1949) 2023-11-04 18:45:17 +01:00
AutumnVN
77659be4f0 fakeNitro: disallow emoji in add reaction (#1954) 2023-11-04 18:44:53 +01:00
AutumnVN
37b9a62460 favGifSearch: fix search bar (#1955) 2023-11-04 18:44:29 +01:00
Lewis Crichton
7f73e13364 docs: point people to advisories for security bugs (#1957) 2023-11-04 18:41:38 +01:00
Vendicated
fcf2bdda70 fix TypingTweaks 2023-11-03 02:03:58 +01:00
AutumnVN
fa9da2d693 noMosaic: play video inline + optional media layout type (#1946) 2023-11-03 01:57:39 +01:00
AutumnVN
44b21394b3 gifPaste: fix unable to use gif picker in profile customization (#1947) 2023-11-03 01:56:31 +01:00
Nuckyz
27fffc8bc3 Fix ShowMeYourName 2023-11-01 23:28:52 -03:00
Nuckyz
098da8c3fd Fix ViewIcons 2023-11-01 23:26:13 -03:00
Nuckyz
9cf88d4232 Fix MessageDecorationsAPI 2023-11-01 22:46:06 -03:00
Vendicated
dd61b0c999 bump to v1.6.2 2023-11-01 02:25:13 +01:00
Haruka
5dc0d06be1 EmoteCloner: make the error toasts useful (#1938)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-11-01 02:23:45 +01:00
AutumnVN
9de6c2d4ff previewMessage: fix button (#1919) 2023-11-01 02:19:26 +01:00
AutumnVN
e37f62ac0a imageZoom: dont close carousel modal on image click (#1926)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-11-01 02:19:02 +01:00
AutumnVN
56a9d79f85 PiP: fix issues / styles (#1927) 2023-11-01 02:14:32 +01:00
Nuckyz
9d78233afa Fix displaying BetterFolders sidebar when watching streams in fullscreen 2023-11-01 02:10:58 +01:00
Vendicated
9af2ec65ae OnePingPerDM: fix server pings 2023-11-01 02:09:43 +01:00
Vendicated
584885acf5 [skip ci] Revert "add react linting"
doesnt work properly :(

This reverts commit 18fdc33ee7d1f60d58645c2a98f402988b97e996.
2023-10-31 23:56:13 +01:00
Vendicated
18fdc33ee7 [skip ci] add react linting 2023-10-31 23:50:55 +01:00
V
522fdcd15d WebKeyBinds: Fix & make available on ArmCord 2023-10-28 23:51:04 +02:00
Nuckyz
af135b9245 Fix AlwaysAnimate 2023-10-28 17:43:27 -03:00
Nuckyz
4b958d17fd Fix BetterFolders freeze and add new options (#1923) 2023-10-28 20:18:00 +00:00
Nuckyz
bfb48b4faf BetterFolders: Option to choose whether to keep guild icons (#1918) 2023-10-28 04:00:17 +00:00
Hugo C
9dd8e72245 fix: PiP replacing video download button (#1910) 2023-10-28 02:22:04 +02:00
AutumnVN
aae790f1c1 vencordToolbox: correct hover color + oneko (#1913) 2023-10-28 02:21:35 +02:00
AutumnVN
7f17e70697 noSystemBadge: fix (#1914) 2023-10-28 02:20:29 +02:00
Vendicated
b3311c6f12 BetterGifAltText: Fix displaying undefined 2023-10-28 02:19:43 +02:00
Nuckyz
bc09225258 Add missing patches predicates to BetterFolders 2023-10-27 20:10:44 -03:00
Nuckyz
9ce923d4d7 Fix BetterFolders 2023-10-27 19:56:11 -03:00
Nuckyz
7845af0802 Remove hacks to support no longer active versions of Discord 2023-10-27 19:55:39 -03:00
Nuckyz
89672882b9 PermViewer: Fix incorrectly displaying some role names as Unknown Role 2023-10-27 14:33:18 -03:00
Nuckyz
e05c630a54 SHC: Make Chat Input Bar channel list include hidden channels 2023-10-27 14:29:25 -03:00
Nuckyz
38834ef7ac Fix git updater 2023-10-27 13:03:52 -03:00
Luna
98d49af728 Fix git updater for other branches (#1915) 2023-10-27 12:09:39 -03:00
Susheel Thapa
0afe319141 fix typo in multiple files (#1911) 2023-10-27 12:09:38 -03:00
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
164 changed files with 1972 additions and 1839 deletions

View File

@ -51,7 +51,10 @@
"eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }],
"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"],
"no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],

View File

@ -14,7 +14,8 @@ body:
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
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
- type: input
id: discord

View File

@ -4,7 +4,9 @@
The cutest Discord client mod
![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334)
| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) |
|:--:|
| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
## Features
@ -28,6 +30,14 @@ Visit https://vencord.dev/download
https://discord.gg/D9uwnFnqmd
## Sponsors
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|:--:|
| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) |
| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |
## Star History
<a href="https://star-history.com/#Vendicated/Vencord&Timeline">

View File

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.5.7",
"version": "1.6.3",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -70,7 +70,7 @@
"typescript": "^5.0.4",
"zip-local": "^0.3.5"
},
"packageManager": "pnpm@8.1.1",
"packageManager": "pnpm@8.10.2",
"pnpm": {
"patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",

View File

@ -18,8 +18,10 @@
*/
import esbuild from "esbuild";
import { readdir } from "fs/promises";
import { join } from "path";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
const defines = {
IS_STANDALONE: isStandalone,
@ -43,13 +45,59 @@ const nodeCommonOpts = {
format: "cjs",
platform: "node",
target: ["esnext"],
external: ["electron", ...commonOpts.external],
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
define: defines,
};
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
const sourcemap = watch ? "inline" : "external";
/**
* @type {import("esbuild").Plugin}
*/
const globNativesPlugin = {
name: "glob-natives-plugin",
setup: build => {
const filter = /^~pluginNatives$/;
build.onResolve({ filter }, args => {
return {
namespace: "import-natives",
path: args.path
};
});
build.onLoad({ filter, namespace: "import-natives" }, async () => {
const pluginDirs = ["plugins", "userplugins"];
let code = "";
let natives = "\n";
let i = 0;
for (const dir of pluginDirs) {
const dirPath = join("src", dir);
if (!await existsAsync(dirPath)) continue;
const plugins = await readdir(dirPath);
for (const p of plugins) {
if (!await existsAsync(join(dirPath, p, "native.ts"))) continue;
const nameParts = p.split(".");
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
// pluginName.thing.desktop -> PluginName.thing
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
const mod = `p${i}`;
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
i++;
}
}
code += `export default {${natives}};`;
return {
contents: code,
resolveDir: "./src"
};
});
}
};
await Promise.all([
// Discord Desktop main & renderer & preload
esbuild.build({
@ -62,7 +110,11 @@ await Promise.all([
...defines,
IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false
}
},
plugins: [
...nodeCommonOpts.plugins,
globNativesPlugin
]
}),
esbuild.build({
...commonOpts,
@ -107,7 +159,11 @@ await Promise.all([
...defines,
IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true
}
},
plugins: [
...nodeCommonOpts.plugins,
globNativesPlugin
]
}),
esbuild.build({
...commonOpts,

View File

@ -20,8 +20,8 @@ import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js";
import { exec, execSync } from "child_process";
import { existsSync, readFileSync } from "fs";
import { readdir, readFile } from "fs/promises";
import { constants as FsConstants, readFileSync } from "fs";
import { access, readdir, readFile } from "fs/promises";
import { join, relative } from "path";
import { promisify } from "util";
@ -47,6 +47,12 @@ export const banner = {
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
export function existsAsync(path) {
return access(path, FsConstants.F_OK)
.then(() => true)
.catch(() => false);
}
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
/**
* @type {import("esbuild").Plugin}
@ -79,7 +85,7 @@ export const globPlugins = kind => ({
let plugins = "\n";
let i = 0;
for (const dir of pluginDirs) {
if (!existsSync(`./src/${dir}`)) continue;
if (!await existsAsync(`./src/${dir}`)) continue;
const files = await readdir(`./src/${dir}`);
for (const file of files) {
if (file.startsWith("_") || file.startsWith(".")) continue;

View File

@ -61,6 +61,13 @@ const report = {
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) {
s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```";
@ -86,6 +93,8 @@ async function printReport() {
console.log(` - Error: ${toCodeBlock(p.error)}`);
});
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
console.log("## Discord Errors");
report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`);
@ -259,7 +268,7 @@ function runTime(token: string) {
const { wreq } = Vencord.Webpack;
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) {
const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text())
@ -280,7 +289,7 @@ function runTime(token: string) {
setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000);
}, 1000));
} catch (e) {
console.error("[PUP_DEBUG]", "A fatal error occured");
console.error("[PUP_DEBUG]", "A fatal error occurred");
console.error("[PUP_DEBUG]", e);
process.exit(1);
}

View File

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

View File

@ -7,6 +7,7 @@
import { IpcEvents } from "@utils/IpcEvents";
import { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron";
import { PluginIpcMappings } from "main/ipcPlugins";
import type { UserThemeHeader } from "main/themes";
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
@ -17,6 +18,16 @@ export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.sendSync(event, ...args) as T;
}
const PluginHelpers = {} as Record<string, Record<string, (...args: any[]) => Promise<any>>>;
const pluginIpcMap = sendSync<PluginIpcMappings>(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP);
for (const [plugin, methods] of Object.entries(pluginIpcMap)) {
const map = PluginHelpers[plugin] = {};
for (const [methodName, method] of Object.entries(methods)) {
map[methodName] = (...args: any[]) => invoke(method as IpcEvents, ...args);
}
}
export default {
themes: {
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
@ -61,12 +72,5 @@ export default {
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
},
pluginHelpers: {
OpenInApp: {
resolveRedirect: (url: string) => invoke<string>(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, url),
},
VoiceMessages: {
readRecording: (path: string) => invoke<Uint8Array | null>(IpcEvents.VOICE_MESSAGES_READ_RECORDING, path),
}
}
pluginHelpers: PluginHelpers
};

View File

@ -17,14 +17,14 @@
*/
import { mergeDefaults } from "@utils/misc";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { findByPropsLazy } from "@webpack";
import { SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general";
import type { PartialDeep } from "type-fest";
import { Argument } from "./types";
const createBotMessage = findByCodeLazy('username:"Clyde"');
const MessageCreator = findByPropsLazy("createBotMessage");
const MessageSender = findByPropsLazy("receiveMessage");
export function generateId() {
@ -38,7 +38,7 @@ export function generateId() {
* @returns {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));

View File

@ -69,7 +69,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
* Remove a context menu patch
* @param navId The navId(s) for the context menu(s) to remove the patch
* @param patch The patch to be removed
* @returns Wheter the patch was sucessfully removed from the context menu(s)
* @returns Whether the patch was successfully removed from the context menu(s)
*/
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
const navIds = Array.isArray(navId) ? navId : [navId as string];
@ -82,7 +82,7 @@ export function removeContextMenuPatch<T extends string | Array<string>>(navId:
/**
* Remove a global context menu patch
* @param patch The patch to be removed
* @returns Wheter the patch was sucessfully removed
* @returns Whether the patch was successfully removed
*/
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
return globalPatches.delete(patch);

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 $ServerList from "./ServerList";
import * as $Settings from "./Settings";
import * as $SettingsStore from "./SettingsStore";
import * as $Styles from "./Styles";
/**
@ -91,10 +90,6 @@ export const MemberListDecorators = $MemberListDecorators;
* An API allowing you to persist data
*/
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
* a

View File

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

View File

@ -34,7 +34,7 @@ import { openModalLazy } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
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";
@ -251,7 +251,7 @@ export default function PluginSettings() {
}
DataStore.set("Vencord_existingPlugins", existingTimestamps);
return window._.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
}));
type P = JSX.Element | JSX.Element[];

View File

@ -25,7 +25,7 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native";
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 { UserThemeHeader } from "main/themes";
import type { ComponentType, Ref, SyntheticEvent } from "react";
@ -41,7 +41,7 @@ type FileInput = ComponentType<{
}>;
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 cl = classNameFactory("vc-settings-theme-");

View File

@ -46,7 +46,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
if (code === "ENOENT")
var err = `Command \`${path}\` not found.\nPlease install it and try again`;
else {
var err = `An error occured while running \`${cmd}\`:\n`;
var err = `An error occurred while running \`${cmd}\`:\n`;
err += stderr || `Code \`${code}\`. See the console for more info`;
}

View File

@ -17,73 +17,26 @@
*/
import { IpcEvents } from "@utils/IpcEvents";
import { app, ipcMain } from "electron";
import { readFile } from "fs/promises";
import { request } from "https";
import { basename, normalize } from "path";
import { ipcMain } from "electron";
import { getSettings } from "./ipcMain";
import PluginNatives from "~pluginNatives";
// FixSpotifyEmbeds
app.on("browser-window-created", (_, win) => {
win.webContents.on("frame-created", (_, { frame }) => {
frame.once("dom-ready", () => {
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
const settings = getSettings().plugins?.FixSpotifyEmbeds;
if (!settings?.enabled) return;
const PluginIpcMappings = {} as Record<string, Record<string, string>>;
export type PluginIpcMappings = typeof PluginIpcMappings;
frame.executeJavaScript(`
const original = Audio.prototype.play;
Audio.prototype.play = function() {
this.volume = ${(settings.volume / 100) || 0.1};
return original.apply(this, arguments);
for (const [plugin, methods] of Object.entries(PluginNatives)) {
const entries = Object.entries(methods);
if (!entries.length) continue;
const mappings = PluginIpcMappings[plugin] = {};
for (const [methodName, method] of entries) {
const key = `VencordPluginNative_${plugin}_${methodName}`;
ipcMain.handle(key, method);
mappings[methodName] = key;
}
`);
}
});
});
});
// #region OpenInApp
// These links don't support CORS, so this has to be native
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
function getRedirect(url: string) {
return new Promise<string>((resolve, reject) => {
const req = request(new URL(url), { method: "HEAD" }, res => {
resolve(
res.headers.location
? getRedirect(res.headers.location)
: url
);
});
req.on("error", reject);
req.end();
});
}
ipcMain.handle(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, async (_, url: string) => {
if (!validRedirectUrls.test(url)) return url;
return getRedirect(url);
ipcMain.on(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP, e => {
e.returnValue = PluginIpcMappings;
});
// #endregion
// #region VoiceMessages
ipcMain.handle(IpcEvents.VOICE_MESSAGES_READ_RECORDING, async (_, filePath: string) => {
filePath = normalize(filePath);
const filename = basename(filePath);
const discordBaseDirWithTrailingSlash = normalize(app.getPath("userData") + "/");
console.log(filename, discordBaseDirWithTrailingSlash, filePath);
if (filename !== "recording.ogg" || !filePath.startsWith(discordBaseDirWithTrailingSlash)) return null;
try {
const buf = await readFile(filePath);
return new Uint8Array(buf.buffer);
} catch {
return null;
}
});
// #endregion

View File

@ -17,7 +17,7 @@
*/
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";
function isNewer($new: string, old: string) {
@ -32,6 +32,8 @@ function isNewer($new: string, old: string) {
}
function patchLatest() {
if (process.env.DISABLE_UPDATER_AUTO_PATCHING) return;
try {
const currentAppPath = dirname(process.execPath);
const currentVersion = basename(currentAppPath);

View File

@ -49,7 +49,9 @@ async function getRepo() {
async function calculateGitChanges() {
await git("fetch");
const res = await git("log", "HEAD...origin/main", "--pretty=format:%an/%h/%s");
const branch = await git("branch", "--show-current");
const res = await git("log", `HEAD...origin/${branch.stdout.trim()}`, "--pretty=format:%an/%h/%s");
const commits = res.stdout.trim();
return commits ? commits.split("\n").map(line => {

5
src/modules.d.ts vendored
View File

@ -24,6 +24,11 @@ declare module "~plugins" {
export default plugins;
}
declare module "~pluginNatives" {
const pluginNatives: Record<string, Record<string, (event: Electron.IpcMainInvokeEvent, ...args: unknown[]) => unknown>>;
export default pluginNatives;
}
declare module "~git-hash" {
const hash: string;
export default hash;

View File

@ -85,17 +85,19 @@ export default definePlugin({
},
{
// 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 ?? ...
replace: "...$1.props,$& $1.image??"
},
// replace their component with ours if applicable
{
match: /children:function(?<=(\i)\.(?:tooltip|description),spacing:\d.+?)/g,
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) : function"
match: /(?<=text:(\i)\.description,spacing:12,)children:/,
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)/,
replace: "onClick:$1.onClick??function"
match: /href:(\i)\.link/,
replace: "...($1.onClick && { onClick: $1.onClick }),$&"
}
]
}

View File

@ -44,8 +44,8 @@ export default definePlugin({
find: "Unexpected value for option",
replacement: {
// return [2, cmd.execute(args, ctx)]
match: /,(.{1,2})\.execute\((.{1,2}),(.{1,2})\)]/,
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})]`
match: /,(\i)\.execute\((\i),(\i)\)/,
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})`
}
},
// Show plugin name instead of "Built-In"

View File

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

View File

@ -25,25 +25,23 @@ export default definePlugin({
authors: [Devs.TheSun, Devs.Ven],
patches: [
{
find: "lostPermissionTooltipText,",
replacement: {
match: /decorators:.{0,100}?children:\[(?<=(\i)\.lostPermissionTooltipText.+?)/,
replace: "$&...Vencord.Api.MemberListDecorators.__getDecorators($1),"
find: ".lostPermission)",
replacement: [
{
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",
replacement: [
// props are shadowed by nested props so we have to do this
{
match: /\i=(\i)\.applicationStream,/,
replace: "$&vencordProps=$1,"
},
{
replacement: {
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
replace: "decorators:[...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)), $1?$2:null]"
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",
replacement: {
match: /(.container\)?,children:)(\[[^\]]+\])(}\)\};return)/,
replace: (_, pre, accessories, post) =>
`${pre}Vencord.Api.MessageAccessories._modifyAccessories(${accessories},this.props)${post}`,
match: /(?<=.container\)?,children:)(\[.+?\])/,
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
},
},
],

View File

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

View File

@ -27,10 +27,8 @@ export default definePlugin({
{
find: '"MessageActionCreators"',
replacement: {
// editMessage: function (...) {
match: /\beditMessage:(function\(.+?\))\{/,
// editMessage: async function (...) { await handlePreEdit(...); ...
replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
match: /async editMessage\(.+?\)\{/,
replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
}
},
{
@ -38,7 +36,7 @@ export default definePlugin({
replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
match: /(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 };
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
`${rest1}async ${rest2}` +
@ -49,10 +47,10 @@ export default definePlugin({
{
find: '("interactionUsernameProfile',
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) =>
// 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"',
replacement: [
{
match: /(?=;\i=null;.{0,70}getPremiumSubscription)/g,
replace: ";if(Vencord.Api.Notices.currentNotice)return false"
match: /\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
{
match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/,
replace: (_, notice) => `if(${notice}.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);`
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
}
]
}

View File

@ -27,15 +27,15 @@ export default definePlugin({
{
find: "Messages.DISCODO_DISABLED",
replacement: {
match: /(Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
}
},
{
find: "Messages.SERVERS,children",
replacement: {
match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/,
replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)"
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
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,
patches: [
{
find: "TRACKING_URL:",
find: "AnalyticsActionHandlers.handle",
replacement: {
match: /^.+$/,
replace: "()=>{}",
@ -43,20 +43,21 @@ export default definePlugin({
find: ".METRICS,",
replacement: [
{
match: /this\._intervalId.+?12e4\)/,
replace: ""
match: /this\._intervalId=/,
replace: "this._intervalId=undefined&&"
},
{
match: /(?<=increment=function\(\i\){)/,
replace: "return;"
match: /(increment\(\i\){)/,
replace: "$1return;"
}
]
},
{
find: ".installedLogHooks)",
replacement: {
match: /if\(\i\.getDebugLogging\(\)&&!\i\.installedLogHooks\)/,
replace: "if(false)"
// if getDebugLogging() returns false, the hooks don't get installed.
match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
}
},
]

View File

@ -63,26 +63,26 @@ export default definePlugin({
replacement: {
get match() {
switch (Settings.plugins.Settings.settingsLocation) {
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/;
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
case "aboveActivity":
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),$&"
}
}],
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 [
{
section: ID.HEADER,
section: SectionTypes.HEADER,
label: "Vencord",
className: "vc-settings-header"
},
@ -128,9 +128,9 @@ export default definePlugin({
element: require("@components/VencordSettings/PatchHelperTab").default,
className: "vc-patch-helper"
},
...this.customSections.map(func => func(ID)),
...this.customSections.map(func => func(SectionTypes)),
{
section: ID.DIVIDER
section: SectionTypes.DIVIDER
}
].filter(Boolean);
},

View File

@ -21,16 +21,30 @@ import definePlugin from "@utils/types";
export default definePlugin({
name: "AlwaysAnimate",
description: "Animates anything that can be animated, besides status emojis.",
description: "Animates anything that can be animated",
authors: [Devs.FieryFlames],
patches: [
{
find: ".canAnimate",
find: "canAnimate:",
all: true,
// Some modules match the find but the replacement is returned untouched
noWarn: true,
replacement: {
match: /\.canAnimate\b/g,
replace: ".canAnimate || true"
match: /canAnimate:.+?(?=([,}].*?\)))/g,
replace: (m, rest) => {
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) return "canAnimate:!0";
return m;
}
}
},
{
// Status emojis
find: ".Messages.GUILD_OWNER,",
replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/,
replace: "!0"
}
}
]

View File

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

View File

@ -20,26 +20,19 @@ import { popNotice, showNotice } from "@api/Notices";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { filters, findByCodeLazy, mapMangledModuleLazy } from "@webpack";
import { FluxDispatcher, Forms, Toasts } from "@webpack/common";
import { findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
const assetManager = mapMangledModuleLazy(
"getAssetImage: size must === [number, number] for Twitch",
{
getAsset: filters.byCode("apply("),
}
);
const lookupRpcApp = findByCodeLazy(".APPLICATION_RPC(");
const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL");
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 = {};
async function lookupApp(applicationId: string): Promise<string> {
const socket: any = {};
await lookupRpcApp(socket, applicationId);
await RpcUtils.fetchApplicationsRPC(socket, applicationId);
return socket.application;
}

View File

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

View File

@ -16,56 +16,44 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { i18n, React, useStateFromStores } from "@webpack/common";
import { LazyComponent } from "@utils/react";
import { find, findByPropsLazy, findStoreLazy } from "@webpack";
import { useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react";
const cl = classNameFactory("vc-bf-");
const classes = findByPropsLazy("sidebar", "guilds");
import { ExpandedGuildFolderStore, settings } from ".";
const Animations = findByPropsLazy("a", "animated", "useTransition");
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const Animations = findByPropsLazy("a", "animated", "useTransition");
const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")')));
function Guilds(props: {
className: string;
bfGuildFolders: any[];
}) {
// @ts-expect-error
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
// TODO: Make this better
const scrollerProps = res.props.children?.props?.children?.props?.children?.[1]?.props;
if (scrollerProps?.children) {
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
if (servers) scrollerProps.children = servers;
}
return res;
}
export default ErrorBoundary.wrap(() => {
export default ErrorBoundary.wrap(guildsBarProps => {
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
const fullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const guilds = document.querySelector(`.${classes.guilds}`);
const visible = !!expandedFolders.size;
const className = cl("folder-sidebar", { fullscreen });
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const Sidebar = (
<Guilds
className={classes.guilds}
bfGuildFolders={Array.from(expandedFolders)}
<GuildsBar
{...guildsBarProps}
isBetterFolders={true}
betterFoldersExpandedIds={expandedFolders}
/>
);
if (!guilds || !Settings.plugins.BetterFolders.sidebarAnim)
const visible = !!expandedFolders.size;
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.
// Also display flex otherwise to fix scrolling
const barStyle = {
display: isFullscreen ? "none" : "flex",
} as CSSProperties;
if (!guilds || !settings.store.sidebarAnim) {
return visible
? <div className={className}>{Sidebar}</div>
? <div style={barStyle}>{Sidebar}</div>
: null;
}
return (
<Animations.Transition
@ -75,11 +63,13 @@ export default ErrorBoundary.wrap(() => {
leave={{ width: 0 }}
config={{ duration: 200 }}
>
{(style, show) => show && (
<Animations.animated.div style={style} className={className}>
{(animationStyle, show) =>
show && (
<Animations.animated.div style={{ ...animationStyle, ...barStyle }}>
{Sidebar}
</Animations.animated.div>
)}
)
}
</Animations.Transition>
);
}, { noop: true });

View File

@ -1,17 +0,0 @@
.vc-bf-folder-sidebar [class*="wrapper-"] > [class*="listItem-"]:first-of-type,
.vc-bf-folder-sidebar [class*="unreadMentionsIndicator"] {
display: none;
}
.vc-bf-folder-sidebar [class*="expandedFolderBackground-"] {
background-color: transparent;
}
.vc-bf-folder-sidebar {
display: flex;
}
.vc-bf-fullscreen {
width: 0 !important;
visibility: hidden;
}

View File

@ -1,177 +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 "./betterFolders.css";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
const GuildsTree = findLazy(m => m.prototype?.convertToFolder);
const GuildFolderStore = findStoreLazy("SortedGuildStore");
const ExpandedFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
const settings = definePluginSettings({
sidebar: {
type: OptionType.BOOLEAN,
description: "Display servers from folder on dedicated sidebar",
default: true,
},
sidebarAnim: {
type: OptionType.BOOLEAN,
description: "Animate opening the folder sidebar",
default: true,
},
closeAllFolders: {
type: OptionType.BOOLEAN,
description: "Close all folders when selecting a server not in a folder",
default: false,
},
closeAllHomeButton: {
type: OptionType.BOOLEAN,
description: "Close all folders when clicking on the home button",
default: false,
},
closeOthers: {
type: OptionType.BOOLEAN,
description: "Close other folders when opening a folder",
default: false,
},
forceOpen: {
type: OptionType.BOOLEAN,
description: "Force a folder to open when switching to a server of that folder",
default: false,
},
});
export default definePlugin({
name: "BetterFolders",
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
authors: [Devs.juby, Devs.AutumnVN],
patches: [
{
find: '("guildsnav")',
predicate: () => settings.store.sidebar,
replacement: [
{
match: /(\i)\(\){return \i\(\(0,\i\.jsx\)\("div",{className:\i\(\)\.guildSeparator}\)\)}/,
replace: "$&$self.Separator=$1;"
},
// Folder component patch
{
match: /\i\(\(function\(\i,\i,\i\){var \i=\i\.key;return.+\(\i\)},\i\)}\)\)/,
replace: "arguments[0].bfHideServers?null:$&"
},
// BEGIN Guilds component patch
{
match: /(\i)\.themeOverride,(.{15,25}\(function\(\){var \i=)(\i\.\i\.getGuildsTree\(\))/,
replace: "$1.themeOverride,bfPatch=$1.bfGuildFolders,$2bfPatch?$self.getGuildsTree(bfPatch,$3):$3"
},
{
match: /return(\(0,\i\.jsx\))(\(\i,{)(folderNode:\i,setNodeRef:\i\.setNodeRef,draggable:!0,.+},\i\.id\));case/,
replace: "var bfHideServers=typeof bfPatch==='undefined',folder=$1$2bfHideServers,$3;return !bfHideServers&&arguments[1]?[$1($self.Separator,{}),folder]:folder;case"
},
// END
{
match: /\("guildsnav"\);return\(0,\i\.jsx\)\(.{1,6},{navigator:\i,children:\(0,\i\.jsx\)\(/,
replace: "$&$self.Guilds="
}
]
},
{
find: "APPLICATION_LIBRARY,render",
predicate: () => settings.store.sidebar,
replacement: {
match: /(\(0,\i\.jsx\))\(\i\..,{className:\i\(\)\.guilds,themeOverride:\i}\)/,
replace: "$&,$1($self.FolderSideBar,{})"
}
},
{
find: '("guildsnav")',
predicate: () => settings.store.closeAllHomeButton,
replacement: {
match: ",onClick:function(){if(!__OVERLAY__){",
replace: "$&$self.closeFolders();"
}
}
],
settings,
start() {
const getGuildFolder = (id: string) => GuildFolderStore.getGuildFolders().find(f => f.guildIds.includes(id));
FluxDispatcher.subscribe("CHANNEL_SELECT", this.onSwitch = data => {
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
return;
if (this.lastGuildId !== data.guildId) {
this.lastGuildId = data.guildId;
const guildFolder = getGuildFolder(data.guildId);
if (guildFolder?.folderId) {
if (settings.store.forceOpen && !ExpandedFolderStore.isFolderExpanded(guildFolder.folderId))
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
} else if (settings.store.closeAllFolders)
this.closeFolders();
}
});
FluxDispatcher.subscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder = e => {
if (settings.store.closeOthers && !this.dispatching)
FluxDispatcher.wait(() => {
const expandedFolders = ExpandedFolderStore.getExpandedFolders();
if (expandedFolders.size > 1) {
this.dispatching = true;
for (const id of expandedFolders) if (id !== e.folderId)
FolderUtils.toggleGuildFolderExpand(id);
this.dispatching = false;
}
});
});
},
stop() {
FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onSwitch);
FluxDispatcher.unsubscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder);
},
FolderSideBar,
getGuildsTree(folders, oldTree) {
const tree = new GuildsTree();
tree.root.children = oldTree.root.children.filter(e => folders.includes(e.id));
tree.nodes = folders.map(id => oldTree.nodes[id]);
return tree;
},
closeFolders() {
for (const id of ExpandedFolderStore.getExpandedFolders())
FolderUtils.toggleGuildFolderExpand(id);
},
});

View File

@ -0,0 +1,307 @@
/*
* 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 { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
enum FolderIconDisplay {
Never,
Always,
MoreThanOneFolderExpanded
}
const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree);
const SortedGuildStore = findStoreLazy("SortedGuildStore");
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null;
let dispatchingFoldersClose = false;
function getGuildFolder(id: string) {
return SortedGuildStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
}
function closeFolders() {
for (const id of ExpandedGuildFolderStore.getExpandedFolders())
FolderUtils.toggleGuildFolderExpand(id);
}
export const settings = definePluginSettings({
sidebar: {
type: OptionType.BOOLEAN,
description: "Display servers from folder on dedicated sidebar",
restartNeeded: true,
default: true
},
sidebarAnim: {
type: OptionType.BOOLEAN,
description: "Animate opening the folder sidebar",
default: true
},
closeAllFolders: {
type: OptionType.BOOLEAN,
description: "Close all folders when selecting a server not in a folder",
default: false
},
closeAllHomeButton: {
type: OptionType.BOOLEAN,
description: "Close all folders when clicking on the home button",
restartNeeded: true,
default: false
},
closeOthers: {
type: OptionType.BOOLEAN,
description: "Close other folders when opening a folder",
default: false
},
forceOpen: {
type: OptionType.BOOLEAN,
description: "Force a folder to open when switching to a server of that folder",
default: false
},
keepIcons: {
type: OptionType.BOOLEAN,
description: "Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar",
restartNeeded: true,
default: false
},
showFolderIcon: {
type: OptionType.SELECT,
description: "Show the folder icon above the folder guilds in the BetterFolders sidebar",
options: [
{ label: "Never", value: FolderIconDisplay.Never },
{ label: "Always", value: FolderIconDisplay.Always, default: true },
{ label: "When more than one folder is expanded", value: FolderIconDisplay.MoreThanOneFolderExpanded }
],
restartNeeded: true
}
});
export default definePlugin({
name: "BetterFolders",
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
authors: [Devs.juby, Devs.AutumnVN, Devs.Nuckyz],
settings,
patches: [
{
find: '("guildsnav")',
predicate: () => settings.store.sidebar,
replacement: [
// Create the isBetterFolders variable in the GuildsBar component
{
match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
replace: ",isBetterFolders"
},
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{
match: /(useStateFromStoresArray\).{0,25}let \i)=(\i\.\i.getGuildsTree\(\))/,
replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`
},
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
{
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
},
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
{
match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/,
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
},
// Export the isBetterFolders variable to the folders component
{
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
}
]
},
{
// This is the parent folder component
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
replacement: [
{
// Modify the expanded state to instead return the list of expanded folders
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
},
{
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
// Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined
match: /(?<=folderNode:(\i),expanded:)\i(?=,)/,
replace: (isExpandedOrExpandedIds, folderNote) => ""
+ `typeof ${isExpandedOrExpandedIds}==="boolean"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),`
+ `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0`
}
]
},
{
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
predicate: () => settings.store.sidebar,
replacement: [
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
// If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions
{
predicate: () => settings.store.keepIcons,
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
},
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{
predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
replace: "!!arguments[0].isBetterFolders&&"
},
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{
predicate: () => !settings.store.keepIcons,
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
},
{
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
},
{
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
}
]
},
{
find: "APPLICATION_LIBRARY,render",
predicate: () => settings.store.sidebar,
replacement: {
// Render the Better Folders sidebar
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
replace: ",$self.FolderSideBar($1)"
}
},
{
find: ".Messages.DISCODO_DISABLED",
predicate: () => settings.store.closeAllHomeButton,
replacement: {
// Close all folders when clicking the home button
match: /(?<=onClick:\(\)=>{)(?=.{0,200}"discodo")/,
replace: "$self.closeFolders();"
}
}
],
flux: {
CHANNEL_SELECT(data) {
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
return;
if (lastGuildId !== data.guildId) {
lastGuildId = data.guildId;
const guildFolder = getGuildFolder(data.guildId);
if (guildFolder?.folderId) {
if (settings.store.forceOpen && !ExpandedGuildFolderStore.isFolderExpanded(guildFolder.folderId)) {
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
}
} else if (settings.store.closeAllFolders) {
closeFolders();
}
}
},
TOGGLE_GUILD_FOLDER_EXPAND(data) {
if (settings.store.closeOthers && !dispatchingFoldersClose) {
dispatchingFoldersClose = true;
FluxDispatcher.wait(() => {
const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();
if (expandedFolders.size > 1) {
for (const id of expandedFolders) if (id !== data.folderId)
FolderUtils.toggleGuildFolderExpand(id);
}
dispatchingFoldersClose = false;
});
}
}
},
getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set<any>) {
if (!isBetterFolders || expandedFolderIds == null) return oldTree;
const newTree = new GuildsTree();
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
newTree.nodes = Object.fromEntries(
Object.entries(oldTree.nodes)
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
);
return newTree;
},
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
}
return true;
};
},
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return "onScroll" in child.props;
}
return true;
};
},
shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set<any>) {
if (!isBetterFolders) return true;
switch (settings.store.showFolderIcon) {
case FolderIconDisplay.Never:
return false;
case FolderIconDisplay.Always:
return true;
case FolderIconDisplay.MoreThanOneFolderExpanded:
return (expandedFolderIds?.size ?? 0) > 1;
default:
return true;
}
},
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
closeFolders
});

View File

@ -34,16 +34,18 @@ export default definePlugin({
},
},
{
find: ".embedGallerySide",
find: ".Messages.GIF,",
replacement: {
match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/,
match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
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) {
props.alt ??= "GIF";
if (props.alt !== "GIF") return props.alt;
let url: string = props.original || props.src;

View File

@ -32,10 +32,16 @@ export default definePlugin({
{
find: "hideNote:",
all: true,
// Some modules match the find but the replacement is returned untouched
noWarn: true,
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
replacement: {
match: /hideNote:.+?(?=[,}])/g,
replace: "hideNote:true",
match: /hideNote:.+?(?=([,}].*?\)))/g,
replace: (m, rest) => {
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) return "hideNote:!0";
return m;
}
}
},
{
@ -48,8 +54,8 @@ export default definePlugin({
{
find: ".Messages.NOTE}",
replacement: {
match: /(\i)\.hideNote\?null/,
replace: "$1.hideNote?$self.patchPadding($1)"
match: /(?<=return \i\?)null(?=:\(0,\i\.jsxs)/,
replace: "$self.patchPadding(arguments[0])"
}
}
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,23 +22,16 @@ import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const ActivityComponent = findByCodeLazy("onOpenGameProfile");
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
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> {
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
return (await assetManager.getAsset(settings.store.appID, [key, undefined]))[0];
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
}
interface ActivityAssets {
@ -403,7 +396,7 @@ export default definePlugin({
return (
<>
<Forms.FormText>
Go to <Link href="https://discord.com/developers/applications">Discord Deverloper Portal</Link> to create an application and
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
get the application ID.
</Forms.FormText>
<Forms.FormText>

View File

@ -50,7 +50,7 @@ async function embedDidMount(this: Component<Props>) {
const { titles, thumbnails } = await res.json();
const hasTitle = titles[0]?.votes >= 0;
const hasThumb = thumbnails[0]?.votes >= 0;
const hasThumb = thumbnails[0]?.votes >= 0 && !thumbnails[0].original;
if (!hasTitle && !hasThumb) return;
@ -58,12 +58,12 @@ async function embedDidMount(this: Component<Props>) {
enabled: true
};
if (titles[0]?.votes >= 0) {
if (hasTitle) {
embed.dearrow.oldTitle = embed.rawTitle;
embed.rawTitle = titles[0].title;
}
if (thumbnails[0]?.votes >= 0) {
if (hasThumb) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
}
@ -147,8 +147,8 @@ export default definePlugin({
replacement: [
// patch componentDidMount to replace embed thumbnail and title
{
match: /(\i).render=function.{0,50}\i\.embed/,
replace: "$1.componentDidMount=$self.embedDidMount,$&"
match: /render\(\)\{let\{embed:/,
replace: "componentDidMount=$self.embedDidMount;$&"
},
// add dearrow button

View File

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

View File

@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
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 { Promisable } from "type-fest";
const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
const EmojiManager = findByPropsLazy("fetchEmoji", "uploadEmoji", "deleteEmoji");
interface Sticker {
t: "Sticker";
@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) {
reader.readAsDataURL(data);
});
return uploadEmoji({
return EmojiManager.uploadEmoji({
guildId,
name: emoji.name.split("~")[0],
image: dataUrl
@ -155,10 +155,15 @@ async function doClone(guildId: string, data: Sticker | Emoji) {
type: Toasts.Type.SUCCESS,
id: Toasts.genId()
});
} catch (e) {
} catch (e: any) {
let message = "Something went wrong (check console!)";
try {
message = JSON.parse(e.text).message;
} catch { }
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
Toasts.show({
message: "Oopsie something went wrong :( Check console!!!",
message: "Failed to clone: " + message,
type: Toasts.Type.FAILURE,
id: Toasts.genId()
});

View File

@ -52,7 +52,7 @@ export default definePlugin({
{
find: "Object.defineProperties(this,{isDeveloper",
replacement: {
match: /(?<={isDeveloper:\{[^}]+?,get:function\(\)\{return )\w/,
match: /(?<={isDeveloper:\{[^}]+?,get:\(\)=>)\i/,
replace: "true"
}
},
@ -64,28 +64,19 @@ export default definePlugin({
}
},
{
find: ".isStaff=function(){",
find: ".isStaff=()",
predicate: () => settings.store.enableIsStaff,
replacement: [
{
match: /return\s*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
replace: (_, user, flags) => `return Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
match: /=>*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
replace: (_, user, flags) => `=>Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
},
{
match: /hasFreePremium=function\(\){return this.isStaff\(\)\s*?\|\|/,
replace: "hasFreePremium=function(){return ",
match: /hasFreePremium\(\){return this.isStaff\(\)\s*?\|\|/,
replace: "hasFreePremium(){return ",
}
]
},
// Fix search history being disabled / broken with isStaff
{
find: 'get("disable_new_search")',
predicate: () => settings.store.enableIsStaff,
replacement: {
match: /(?<=showNewSearch"\);return)\s?!/,
replace: "!1&&!"
}
},
{
find: 'H1,title:"Experiments"',
replacement: {

View File

@ -24,35 +24,33 @@ import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react";
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 {
getPremiumPacks(): StickerPack[];
getAllGuildStickers(): Map<string, Sticker[]>;
getStickerById(id: string): Sticker | undefined;
};
function searchProtoClass(localName: string, parentProtoClass: any) {
if (!parentProtoClass) return;
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
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;
const getter: any = Object.values(field).find(value => typeof value === "function");
return getter?.();
const fieldGetter = Object.values(field).find(value => typeof value === "function") as any;
return fieldGetter?.();
}
const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass));
const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto));
const PreloadedUserSettingsActionCreators = proxyLazy(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators);
const AppearanceSettingsActionCreators = proxyLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
const ClientThemeSettingsActionsCreators = proxyLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
const USE_EXTERNAL_EMOJIS = 1n << 18n;
const USE_EXTERNAL_STICKERS = 1n << 37n;
@ -176,39 +174,46 @@ export default definePlugin({
predicate: () => settings.store.enableEmojiBypass,
replacement: [
{
match: /(?<=(\i)=\i\.intention)/,
replace: (_, intention) => `,fakeNitroIntention=${intention}`
// Create a variable for the intention of listing the emoji
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,
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;)/,
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/,
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",
predicate: () => settings.store.enableEmojiBypass,
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)`
}
},
// Allow stickers to be sent everywhere
{
find: "canUseStickersEverywhere:function",
find: "canUseCustomStickersEverywhere:function",
predicate: () => settings.store.enableStickerBypass,
replacement: {
match: /canUseStickersEverywhere:function\(\i\){/,
match: /canUseCustomStickersEverywhere:function\(\i\){/,
replace: "$&return true;"
},
},
// Make stickers always available
{
find: "\"SENDABLE\"",
predicate: () => settings.store.enableStickerBypass,
@ -217,6 +222,7 @@ export default definePlugin({
replace: "true?"
}
},
// Allow streaming with high quality
{
find: "canUseHighVideoUploadQuality:function",
predicate: () => settings.store.enableStreamQualityBypass,
@ -230,14 +236,16 @@ export default definePlugin({
};
})
},
// Remove boost requirements to stream with high quality
{
find: "STREAM_FPS_OPTION.format",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: {
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
replace: ""
}
},
// Allow client themes to be changeable
{
find: "canUseClientThemes:function",
replacement: {
@ -249,19 +257,22 @@ export default definePlugin({
find: '.displayName="UserSettingsProtoStore"',
replacement: [
{
// Overwrite incoming connection settings proto with our local settings
match: /CONNECTION_OPEN:function\((\i)\){/,
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
},
{
match: /=(\i)\.local;/,
replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);`
// Overwrite non local proto changes with our local settings
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: {
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});`
}
},
@ -269,11 +280,13 @@ export default definePlugin({
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
replacement: [
{
// Call our function to decide whether the emoji link should be kept or not
predicate: () => settings.store.transformEmojis,
match: /1!==(\i)\.length\|\|1!==\i\.length/,
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,
match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/,
replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);`
@ -281,36 +294,41 @@ export default definePlugin({
]
},
{
find: "renderEmbeds=function",
find: "renderEmbeds(",
replacement: [
{
// Call our function to decide whether the embed should be ignored or not
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;`
},
{
// Patch the stickers array to add fake nitro stickers
predicate: () => settings.store.transformStickers,
match: /renderStickersAccessories=function\((\i)\){var (\i)=\(0,\i\.\i\)\(\i\),/,
replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message}),`
match: /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/,
replace: (_, message, stickers) => `${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
},
{
// Filter attachments to remove fake nitro stickers or emojis
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});`
}
]
},
{
find: ".STICKER_IN_MESSAGE_HOVER,",
find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format",
predicate: () => settings.store.transformStickers,
replacement: [
{
match: /var (\i)=\i\.renderableSticker,.{0,50}closePopout.+?channel:\i,closePopout:\i,/,
replace: (m, renderableSticker) => `${m}renderableSticker:${renderableSticker},`
// Export the renderable sticker to be used in the fake nitro sticker notice
match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/,
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
},
{
match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/,
replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!${props}.renderableSticker?.fake)`
// Add the fake nitro sticker notice
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)`
}
]
},
@ -318,7 +336,8 @@ export default definePlugin({
find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,",
predicate: () => settings.store.transformEmojis,
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},`
}
},
@ -326,8 +345,25 @@ export default definePlugin({
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
predicate: () => settings.store.transformEmojis,
replacement: {
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
// Add the fake nitro emoji notice
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"
}
}
],
@ -345,26 +381,30 @@ export default definePlugin({
},
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;
if (premiumType !== 2) {
proto.appearance ??= AppearanceSettingsProto.create();
proto.appearance ??= AppearanceSettingsActionCreators.create();
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) {
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
backgroundGradientPresetId: {
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
}
});
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto;
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy;
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
}
}
},
@ -373,26 +413,26 @@ export default definePlugin({
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
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
? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory)
: AppearanceSettingsProto.create();
const newAppearanceProto = currentAppearanceSettings != null
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS)
: AppearanceSettingsActionCreators.create();
newAppearanceProto.theme = theme;
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
backgroundGradientPresetId: {
value: backgroundGradientPresetId
}
});
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto;
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummy;
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create();
const proto = PreloadedUserSettingsActionCreators.ProtoClass.create();
proto.appearance = newAppearanceProto;
FluxDispatcher.dispatch({
@ -518,7 +558,7 @@ export default definePlugin({
};
try {
return modifyChildren(window._.cloneDeep(content));
return modifyChildren(lodash.cloneDeep(content));
} catch (err) {
new Logger("FakeNitro").error(err);
return content;
@ -715,7 +755,7 @@ export default definePlugin({
gif.finish();
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() {

View File

@ -87,15 +87,15 @@ export default definePlugin({
authors: [Devs.Alyxia, Devs.Remty],
patches: [
{
find: "getUserProfile=",
find: "UserProfileStore",
replacement: {
match: /(?<=getUserProfile=function\(\i\){return )(\i\[\i\])/,
match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/,
replace: "$self.colorDecodeHook($1)"
}
}, {
find: ".USER_SETTINGS_PROFILE_THEME_ACCENT",
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})"
}
}

View File

@ -39,22 +39,27 @@ export default definePlugin({
description: "Puts your favorite emoji first in the emoji autocomplete.",
patches: [
{
find: ".activeCommandOption",
find: "renderResults({results:",
replacement: [
{
// = someFunc(a.selectedIndex); ...trackEmojiSearch({ state: theState, isInPopoutExperimental: someBool })
match: /=\i\(\i\.selectedIndex\);(?=.+?state:(\i),isInPopoutExperiment:\i)/,
// self.sortEmojis(theState)
replace: "$&$self.sortEmojis($1);"
// https://regex101.com/r/N7kpLM/1
match: /let \i=.{1,100}renderResults\({results:(\i)\.query\.results,/,
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
// 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)
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)
replace: ",maxCount:Infinity$2=($3.sliceTo=$4,$3)"
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
}
]
}

View File

@ -60,7 +60,7 @@ interface Instance {
}
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput");
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow");
export const settings = definePluginSettings({
searchOption: {
@ -91,13 +91,13 @@ export default definePlugin({
patches: [
{
find: "renderCategoryExtras",
find: "renderHeaderContent()",
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}))
match: /(renderHeaderContent=function.{1,150}FAVORITES:return)(.{1,150};)(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\.\i))/,
replace: "$1 this.state.resultType === \"Favorites\" ? $self.renderSearchBar(this, $<searchComp>) : $2; $3"
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"
},
{
// to persist filtered favorites when component re-renders.

View File

@ -0,0 +1,27 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { app } from "electron";
import { getSettings } from "main/ipcMain";
app.on("browser-window-created", (_, win) => {
win.webContents.on("frame-created", (_, { frame }) => {
frame.once("dom-ready", () => {
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
const settings = getSettings().plugins?.FixSpotifyEmbeds;
if (!settings?.enabled) return;
frame.executeJavaScript(`
const original = Audio.prototype.play;
Audio.prototype.play = function() {
this.volume = ${(settings.volume / 100) || 0.1};
return original.apply(this, arguments);
}
`);
}
});
});
});

View File

@ -27,18 +27,17 @@ export default definePlugin({
authors: [Devs.D3SOX, Devs.Nickyux],
patches: [
{
// This is the logic where it decides whether to render the owner crown or not
find: ".MULTIPLE_AVATAR",
find: "AVATAR_DECORATION_PADDING:",
replacement: {
match: /(\i)=(\i)\.isOwner,/,
replace: "$1=$self.isGuildOwner($2),"
match: /,isOwner:(\i),/,
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
}
}
],
isGuildOwner(props: { user: User, channel: Channel, guildId?: string; }) {
if (!props?.user?.id) 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 false;
return props.isOwner;
// guild id is in props twice, fallback if the first is undefined
const guildId = props.guildId ?? props.channel?.guild_id;

View File

@ -16,16 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getSettingStoreLazy } from "@api/SettingsStore";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByCodeLazy } from "@webpack";
import { StatusSettingsStores } from "@webpack/common";
import style from "./style.css?managed";
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
function makeIcon(showCurrentGame?: boolean) {
@ -40,7 +39,7 @@ function makeIcon(showCurrentGame?: boolean) {
{!showCurrentGame && <>
<mask id="gameActivityMask" >
<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>
<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() {
const showCurrentGame = ShowCurrentGame?.useSetting();
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting();
return (
<Button
@ -58,7 +57,7 @@ function GameActivityToggleButton() {
icon={makeIcon(showCurrentGame)}
role="switch"
aria-checked={!showCurrentGame}
onClick={() => ShowCurrentGame?.updateSetting(old => !old)}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)}
/>
);
}
@ -67,7 +66,6 @@ export default definePlugin({
name: "GameActivityToggle",
description: "Adds a button next to the mic and deafen button to toggle game activity.",
authors: [Devs.Nuckyz, Devs.RuukuLada],
dependencies: ["SettingsStoreAPI"],
patches: [
{

View File

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

View File

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

View File

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

View File

@ -6,13 +6,12 @@
import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStore";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { Tooltip } from "webpack/common";
import { StatusSettingsStores, Tooltip } from "webpack/common";
const enum ActivitiesTypes {
Game,
@ -26,7 +25,6 @@ interface IgnoredActivity {
}
const RunningGameStore = findStoreLazy("RunningGameStore");
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
const forceUpdate = useForceUpdater();
@ -68,7 +66,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
// Trigger activities recalculation
ShowCurrentGame?.updateSetting(old => old);
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
forceUpdateButton();
}
@ -85,7 +83,6 @@ export default definePlugin({
authors: [Devs.Nuckyz],
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.",
dependencies: ["SettingsStoreAPI"],
settings,
patches: [
@ -93,30 +90,31 @@ export default definePlugin({
find: '.displayName="LocalActivityStore"',
replacement: [
{
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
match: /LISTENING.+?}\),(?<=(\i)\.push.+?)/,
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored),`
}
]
},
{
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
replacement: {
match: /\(\)\.removeGame.+?null(?<=(\i)\?\i=\i\.\i\.Messages\.SETTINGS_GAMES_NOW_PLAYING_STATE.+?=(\i)\.overlay.+?)/,
replace: (m, nowPlaying, props) => `${m},$self.renderToggleGameActivityButton(${props},${nowPlaying})`
match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
}
},
{
find: ".Messages.EMBEDDED_ACTIVITIES_HAVE_PLAYED_ONE_KNOWN",
replacement: [
{
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
find: ".activityTitleText,variant",
replacement: {
match: /(?<=\i\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
},
},
{
match: /(?<=\(\)\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
find: ".activityCardDetails,children",
replacement: {
match: /(?<=\i\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
}
]
}
],

View File

@ -37,13 +37,6 @@ export const settings = definePluginSettings({
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: {
type: OptionType.BOOLEAN,
description: "Invert scroll",
@ -101,7 +94,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
/>
<Menu.MenuCheckboxItem
id="vc-nearest-neighbour"
label="Nearset Neighbour"
label="Nearest Neighbour"
checked={settings.store.nearestNeighbour}
action={() => {
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
@ -163,10 +156,14 @@ export default definePlugin({
patches: [
{
find: '"renderLinkComponent","maxWidth"',
find: "Messages.OPEN_IN_BROWSER",
replacement: {
match: /(return\(.{1,100}\(\)\.wrapper.{1,200})(src)/,
replace: `$1id: '${ELEMENT_ID}',$2`
// there are 2 image thingies. one for carosuel and one for the single image.
// 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,26 +171,26 @@ export default definePlugin({
find: "handleImageLoad=",
replacement: [
{
match: /showThumbhashPlaceholder:/,
match: /showThumbhashPlaceholder:\i,/,
replace: "...$self.makeProps(this),$&"
},
{
match: /componentDidMount=function\(\){/,
match: /componentDidMount\(\){/,
replace: "$&$self.renderMagnifier(this);",
},
{
match: /componentWillUnmount=function\(\){/,
match: /componentWillUnmount\(\){/,
replace: "$&$self.unMountMagnifier();"
}
]
},
{
find: ".carouselModal,",
find: ".carouselModal",
replacement: {
match: /onClick:(\i),/,
replace: "onClick:$self.settings.store.preventCarouselFromClosingOnClick ? () => {} : $1,"
match: /(?<=\.carouselModal.{0,100}onClick:)\i,/,
replace: "()=>{},"
}
}
],

View File

@ -15,25 +15,17 @@
border-radius: 0;
}
.vc-imgzoom-nearest-neighbor > img {
image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
.vc-imgzoom-nearest-neighbor>img {
image-rendering: pixelated;
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
}
/* make the carousel take up less space so we can click the backdrop and exit out of it */
[class|="carouselModal"] {
height: fit-content;
box-shadow: none;
[class*="modalCarouselWrapper_"] {
top: 0 !important;
}
[class*="modalCarouselWrapper"] {
height: fit-content;
top: 50%;
transform: translateY(-50%);
}
[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
[class*="carouselModal_"] {
height: 0 !important;
}

View File

@ -131,15 +131,15 @@ export default definePlugin({
// Indicator
find: ".Messages.MESSAGE_EDITED,",
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 {};$&"
}
},
{
find: ".activeCommandOption",
find: "ChannelTextAreaButtons",
replacement: {
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}",
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
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 { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { FluxDispatcher, Forms } from "@webpack/common";
import { findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
interface ActivityAssets {
large_image?: string;
@ -86,15 +86,9 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
const logger = new Logger("LastFMRichPresence");
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> {
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
}
function setActivity(activity: Activity | null) {

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
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.
// don't worry about it :P
@ -60,17 +61,35 @@ const quotes = [
"Wdn`khc'|f*eghl{%"
];
const settings = definePluginSettings({
replaceEvents: {
description: "Replace Event Quotes too",
type: OptionType.BOOLEAN,
default: true
}
});
export default definePlugin({
name: "LoadingQuotes",
description: "Replace Discords loading quotes",
authors: [Devs.Ven, Devs.KraXen72],
settings,
patches: [
{
find: ".LOADING_DID_YOU_KNOW",
replacement: {
match: /\._loadingText=.+?random\(.+?;/s,
replace: "._loadingText=$self.quote;",
find: ".LOADING_DID_YOU_KNOW}",
replacement: [
{
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],
patches: [{
find: ".isSidebarVisible,",
find: "{isSidebarVisible:",
replacement: {
match: /(var (\i)=\i\.className.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: "$1:[$2?.startsWith('members')?$self.render():null,$3"
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
}
}],

View File

@ -18,7 +18,6 @@
import { addAccessory } from "@api/MessageAccessories";
import { definePluginSettings } from "@api/Settings";
import { getSettingStoreLazy } from "@api/SettingsStore";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js";
import { classes } from "@utils/misc";
@ -36,6 +35,7 @@ import {
PermissionStore,
RestAPI,
Text,
TextAndImagesSettingsStores,
UserStore
} from "@webpack/common";
import { Channel, Guild, Message } from "discord-types/general";
@ -46,12 +46,11 @@ const messageCache = new Map<string, {
}>();
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");
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 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 {
const { message, channel, guildID } = props;
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
const isDM = guildID === "@me";
const images = getImages(message);
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>
</Text>
}
compact={compactModeEnabled.getSetting()}
compact={compact}
content={
<>
{message.content || message.attachments.length <= images.length
@ -365,20 +363,7 @@ export default definePlugin({
name: "MessageLinkEmbeds",
description: "Adds a preview to messages that link another message",
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
dependencies: ["MessageAccessoriesAPI", "SettingsStoreAPI"],
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;
},
dependencies: ["MessageAccessoriesAPI"],
settings,

View File

@ -1,10 +1,10 @@
/* 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;
}
/* Bot "thinking" text highlighting */
.messagelogger-deleted [class*="colorStandard-"] {
.messagelogger-deleted [class*="colorStandard"] {
color: #f04747 !important;
}

View File

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

View File

@ -25,10 +25,6 @@ import definePlugin, { OptionType } from "@utils/types";
const EMOTE = "<:luna:1035316192220553236>";
const DATA_KEY = "MessageTags_TAGS";
const MessageTagsMarker = Symbol("MessageTags");
const author = {
id: "821472922140803112",
bot: false
};
interface Tag {
name: string;
@ -59,14 +55,12 @@ function createTagCommand(tag: Tag) {
execute: async (_, ctx) => {
if (!await getTag(tag.name)) {
sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
});
return { content: `/${tag.name}` };
}
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} The tag **${tag.name}** has been sent!`
});
return { content: tag.message.replaceAll("\\n", "\n") };
@ -162,7 +156,6 @@ export default definePlugin({
if (await getTag(name))
return sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} A Tag with the name **${name}** already exists!`
});
@ -176,7 +169,6 @@ export default definePlugin({
await addTag(tag);
sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} Successfully created the tag **${name}**!`
});
break; // end 'create'
@ -186,7 +178,6 @@ export default definePlugin({
if (!await getTag(name))
return sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
});
@ -194,14 +185,12 @@ export default definePlugin({
await removeTag(name);
sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} Successfully deleted the tag **${name}**!`
});
break; // end 'delete'
}
case "list": {
sendBotMessage(ctx.channel.id, {
author,
embeds: [
{
// @ts-ignore
@ -224,12 +213,10 @@ export default definePlugin({
if (!tag)
return sendBotMessage(ctx.channel.id, {
author,
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
});
sendBotMessage(ctx.channel.id, {
author,
content: tag.message.replaceAll("\\n", "\n")
});
break; // end 'preview'
@ -237,7 +224,6 @@ export default definePlugin({
default: {
sendBotMessage(ctx.channel.id, {
author,
content: "Invalid sub-command"
});
break;

View File

@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
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 { Channel, Message, User } from "discord-types/general";
@ -58,7 +58,6 @@ const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole")
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 isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
@ -188,17 +187,14 @@ export default definePlugin({
patches: [
// add tags to the tag list
{
find: '.BOT=0]="BOT"',
replacement: [
// add tags to the exported tags list (Tag.Types)
{
match: /(\i)\[.\.BOT=0\]="BOT";/,
replace: "$&$1=$self.addTagVariants($1);"
find: "BotTagTypes:",
replacement: {
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
replace: "($1=$self.getTagTypes()))[$2.BOT"
}
]
},
{
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP;",
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP,",
replacement: [
// make the tag show the right text
{
@ -213,25 +209,25 @@ export default definePlugin({
},
// add HTML data attributes (for easier theming)
{
match: /children:\[(?=\i\?null:\i,\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
replace: "'data-tag':$1.toLowerCase(),children:["
match: /.botText,children:(\i)}\)]/,
replace: "$&,'data-tag':$1.toLowerCase()"
}
],
},
// in messages
{
find: ".Types.ORIGINAL_POSTER",
find: "renderSystemTag:",
replacement: {
match: /return null==(\i)\?null:\(0,/,
replace: "$1=$self.getTag({...arguments[0],origType:$1,location:'chat'});$&"
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
}
},
// in the member list
{
find: ".Messages.GUILD_OWNER,",
replacement: {
match: /(?<type>\i)=\(null==.{0,50}\.BOT,null!=(?<user>\i)&&\i\.bot/,
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }), typeof $<type> === 'number'"
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
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
@ -251,11 +247,18 @@ export default definePlugin({
},
// in profiles
{
find: "showStreamerModeTooltip:",
replacement: {
find: ",overrideDiscriminator:",
replacement: [
{
// 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:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
replace: ",botType:$self.getTag({user:$2,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),"
}
]
},
],
@ -295,24 +298,25 @@ export default definePlugin({
if (!guild) return [];
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
return Object.entries(Permissions)
return Object.entries(PermissionsBits)
.map(([perm, permInt]) =>
permissions & permInt ? perm : ""
)
.filter(Boolean);
},
addTagVariants(tagConstant) {
getTagTypes() {
const obj = {};
let i = 100;
tags.forEach(({ name }) => {
tagConstant[name] = ++i;
tagConstant[i] = name;
tagConstant[`${name}-BOT`] = ++i;
tagConstant[i] = `${name}-BOT`;
tagConstant[`${name}-OP`] = ++i;
tagConstant[i] = `${name}-OP`;
obj[name] = ++i;
obj[i] = name;
obj[`${name}-BOT`] = ++i;
obj[i] = `${name}-BOT`;
obj[`${name}-OP`] = ++i;
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`]),
@ -377,7 +381,6 @@ export default definePlugin({
break;
}
}
return type;
}
});

View File

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

View File

@ -47,16 +47,17 @@ export default definePlugin({
patches: [
{
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:))/,
replace: '$1?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
}
},
{
find: ".UserProfileSections.USER_INFO_CONNECTIONS:",
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],
patches: [
{
find: 'safety_prompt:"DMSpamExperiment",response:"show_redacted_messages"',
find: "Messages.BLOCKED_MESSAGES_HIDE",
replacement: [
{
match: /\.collapsedReason;return/,
replace: ".collapsedReason;return null;return;"
match: /let\{[^}]*collapsedReason[^}]*\}/,
replace: "return null;$&"
}
]
},

View File

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

View File

@ -4,37 +4,76 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import style from "./styles.css?managed";
const settings = definePluginSettings({
inlineVideo: {
description: "Play videos without carousel modal",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true
},
mediaLayoutType: {
description: "Choose media layout type",
type: OptionType.SELECT,
restartNeeded: true,
options: [
{ label: "STATIC, render loading image but image isn't resposive, no problem unless discord window width is too small", value: "STATIC", default: true },
{ label: "RESPONSIVE, image is responsive but not render loading image, cause messages shift when loaded", value: "RESPONSIVE" },
]
}
});
export default definePlugin({
name: "NoMosaic",
authors: [Devs.AutumnVN],
description: "Removes Discord new image mosaic",
tags: ["image", "mosaic", "media"],
patches: [{
find: "Media Mosaic",
replacement: [
settings,
patches: [
{
find: ".oneByTwoLayoutThreeGrid",
replacement: [{
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
replace: 'mediaLayoutType:"RESPONSIVE"',
},
{
match: /\i===\i\.\i\.MOSAIC/,
replace: "true",
replace: "mediaLayoutType:$self.mediaLayoutType()",
},
{
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
replace: '"INVALID"',
}]
},
{
find: "renderAttachments(",
predicate: () => settings.store.inlineVideo,
replacement: {
match: /url:(\i)\.url\}\);return /,
replace: "$&$1.content_type?.startsWith('image/')&&"
}
},
{
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
replacement: {
match: /\i===\i\.\i\.MOSAIC/,
replace: "true"
}
}
],
}],
mediaLayoutType() {
return settings.store.mediaLayoutType;
},
start() {
enableStyle(style);
},
stop() {
disableStyle(style);
}

View File

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

View File

@ -55,18 +55,18 @@ export default definePlugin({
// or by searching for "showProgressBadge:"
patches: [
{
find: ".getPendingCount=",
find: "getPendingCount(){",
predicate: () => settings.store.hideFriendRequestsCount,
replacement: {
match: /(?<=\.getPendingCount=function\(\)\{)/,
match: /(?<=getPendingCount\(\)\{)/,
replace: "return 0;"
}
},
{
find: ".getMessageRequestsCount=",
find: "getMessageRequestsCount(){",
predicate: () => settings.store.hideMessageRequestsCount,
replacement: {
match: /(?<=\.getMessageRequestsCount=function\(\)\{)/,
match: /(?<=getMessageRequestsCount\(\)\{)/,
replace: "return 0;"
}
},
@ -84,8 +84,10 @@ export default definePlugin({
find: "showProgressBadge:",
predicate: () => settings.store.hidePremiumOffersCount,
replacement: {
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
replace: "(function(){return 0})"
// The two groups inside the first group grab the minified names of the variables,
// 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)
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
replace: "=$1?.banner&&"
replace: "=(arguments[0]?.bannerSrc||$1?.banner)&&"
}
},
{
find: "().avatarPositionPremiumNoBanner,default:",
find: ".avatarPositionPremiumNoBanner,default:",
replacement: {
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\(\)\.(\i))/,
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\.(\i))/,
// premiumUserWithoutBanner: foo().avatarPositionNormal...
replace: ".$1"
}
},
{
find: ".hasThemeColors=function(){",
find: "hasThemeColors(){",
replacement: {
match: /(?<=key:"canUsePremiumProfileCustomization",get:function\(\){return)/,
replace: " false;"
match: /get canUsePremiumProfileCustomization\(\){return /,
replace: "$&false &&"
}
}
]

View File

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

View File

@ -25,15 +25,15 @@ export default definePlugin({
authors: [Devs.rushii],
patches: [
{
find: "setSystemTrayApplications:function",
find: ",setSystemTrayApplications",
replacement: [
{
match: /setBadge:function.+?},/,
replace: "setBadge:function(){},"
match: /setBadge\(\i\).+?},/,
replace: "setBadge(){},"
},
{
match: /setSystemTrayIcon:function.+?},/,
replace: "setSystemTrayIcon:function(){},"
match: /setSystemTrayIcon\(\i\).+?},/,
replace: "setSystemTrayIcon(){},"
}
]
}

View File

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

View File

@ -4,35 +4,65 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore, ReadStateStore } from "@webpack/common";
import { Message } from "discord-types/general";
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: /if\((\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\){/,
replace: "if($1){if(!$self.isPrivateChannelRead(arguments[0]?.message))return;"
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:)/,
match: /sound:(\i\?\i:void 0,volume:\i,onClick)/,
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
}]
}],
isPrivateChannelRead(message: Message) {
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 && channelType !== ChannelType.GROUP_DM) ||
(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

@ -26,15 +26,13 @@ export default definePlugin({
authors: [Devs.Ven, Devs.adryd],
start() {
fetch("https://raw.githubusercontent.com/adryd325/oneko.js/5977144dce83e4d71af1de005d16e38eebeb7b72/oneko.js")
fetch("https://raw.githubusercontent.com/adryd325/oneko.js/8fa8a1864aa71cd7a794d58bc139e755e96a236c/oneko.js")
.then(x => x.text())
.then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif"))
.then(eval);
},
stop() {
clearInterval(window.onekoInterval);
delete window.onekoInterval;
document.getElementById("oneko")?.remove();
}
});

View File

@ -18,12 +18,12 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { OptionType, PluginNative } from "@utils/types";
import { showToast, Toasts } from "@webpack/common";
import type { MouseEvent } from "react";
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user)\/(.+)(?:\?.+?)?$/;
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/;
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
@ -45,6 +45,8 @@ const settings = definePluginSettings({
}
});
const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative<typeof import("./native")>;
export default definePlugin({
name: "OpenInApp",
description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser",
@ -53,10 +55,10 @@ export default definePlugin({
patches: [
{
find: '"MaskedLinkStore"',
find: "trackAnnouncementMessageLinkClicked({",
replacement: {
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,250}\.trusted)/,
replace: "return $self.handleLink(...arguments).then(handled => handled||$1)"
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/,
replace: "async $& if(await $self.handleLink(...arguments)) return;"
}
},
// Make Spotify profile activity links open in app on web
@ -71,7 +73,7 @@ export default definePlugin({
{
find: ".CONNECTED_ACCOUNT_VIEWED,",
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);"
}
}
@ -84,7 +86,7 @@ export default definePlugin({
if (!IS_WEB && ShortUrlMatcher.test(url)) {
event?.preventDefault();
// CORS jumpscare
url = await VencordNative.pluginHelpers.OpenInApp.resolveRedirect(url);
url = await Native.resolveRedirect(url);
}
spotify: {

View File

@ -0,0 +1,31 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcMainInvokeEvent } from "electron";
import { request } from "https";
// These links don't support CORS, so this has to be native
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
function getRedirect(url: string) {
return new Promise<string>((resolve, reject) => {
const req = request(new URL(url), { method: "HEAD" }, res => {
resolve(
res.headers.location
? getRedirect(res.headers.location)
: url
);
});
req.on("error", reject);
req.end();
});
}
export async function resolveRedirect(_: IpcMainInvokeEvent, url: string) {
if (!validRedirectUrls.test(url)) return url;
return getRedirect(url);
}

View File

@ -31,7 +31,7 @@ export default definePlugin({
patches: [
// Permission lockout, just set the check to true
{
find: "Messages.SELF_DENY_PERMISSION_BODY",
find: ".showPermissionLockoutModal(",
replacement: [
{
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
@ -42,11 +42,12 @@ export default definePlugin({
},
// Onboarding, same thing but we need to prevent the check
{
find: "Messages.ONBOARDING_CHANNEL_THRESHOLD_WARNING",
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
replacement: [
{
match: /case 1:if\((?=!\i\.sent.{20,30}Messages\.CANNOT_CHANGE_CHANNEL_PERMS)/,
replace: "$&false&&"
// are we java yet?
match: /(?<=(?:isDefaultChannelThresholdMetAfterDelete|checkDefaultChannelThresholdMetAfterChannelPermissionDeny):function\(\)\{)return \i(?=\})/g,
replace: "return () => true"
}
],
predicate: () => settings.store.onboarding

View File

@ -135,9 +135,9 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
<Text variant="text-md/normal">
{
permission.type === PermissionType.Role
? role?.name || "Unknown Role"
? role?.name ?? "Unknown Role"
: permission.type === PermissionType.User
? (user && getUniqueUsername(user)) || "Unknown User"
? (user && getUniqueUsername(user)) ?? "Unknown User"
: (
<Flex style={{ gap: "0.2em", justifyItems: "center" }}>
@owner

View File

@ -161,7 +161,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS",
find: ".popularApplicationCommandIds,",
replacement: {
match: /showBorder:.{0,60}}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
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 { makeLazy } from "@utils/lazy";
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";
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");
function loadImage(source: File | string) {
@ -70,7 +69,7 @@ async function resolveImage(options: Argument[], ctx: CommandContext, noServerPf
return opt.value;
case "user":
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");
} catch (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" });
// 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
setTimeout(() => promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10);
setTimeout(() => UploadHandler.promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10);
},
},
]

View File

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

View File

@ -66,7 +66,7 @@ export default definePlugin({
// 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
// 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),"
},
{
@ -75,39 +75,39 @@ export default definePlugin({
// - Section 1: buttons for pages like Friends & Library
// - Section 2: our pinned dms
// - 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
// 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
// case will an element be added to the outer array
// 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"
// lookbehind is used to lookup parameter name. We could use arguments[0], but
// 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"
},
{
// Patch channel lookup inside renderDM
// 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 ? 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
// section === DMS
match: /===\i.DMS&&0/,
// section -1 === DMS
replace: "-1$&"
// DMS (inlined) === section
match: /(?<=getRowHeight=\(.{2,50}?)1===\i/,
// DMS (inlined) === section - 1
replace: "$&-1"
},
{
// 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,$&)"
}
]
@ -115,19 +115,19 @@ export default definePlugin({
// Fix Alt Up/Down navigation
{
find: '"mod+alt+right"',
find: ".Routes.APPLICATION_STORE&&",
replacement: {
// channelIds = __OVERLAY__ ? stuff : toArray(getStaticPaths()).concat(toArray(channelIds))
match: /(?<=(\i)=__OVERLAY__\?\i:.{0,10})\.concat\((.{0,10})\)/,
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,
// ....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
{
find: '"alt+shift+down"',
find: ".getFlattenedGuildIds()],",
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)))"
}
},

View File

@ -23,20 +23,20 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
import { User } from "discord-types/general";
const SessionsStore = findStoreLazy("SessionsStore");
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} >
{(tooltipProps: any) => (
<svg
{...tooltipProps}
height={opts?.height ?? 20}
width={opts?.width ?? 20}
height={(opts?.height ?? 20) - (small ? 3 : 0)}
width={(opts?.width ?? 20) - (small ? 3 : 0)}
viewBox={opts?.viewBox ?? "0 0 24 24"}
fill={color}
>
@ -55,18 +55,18 @@ const 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 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 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.id === UserStore.getCurrentUser().id) {
@ -99,6 +99,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: {
key={platform}
platform={platform as Platform}
status={status}
small={small}
/>
));
@ -137,7 +138,7 @@ const indicatorLocations = {
description: "In the member list",
onEnable: () => addDecorator("platform-indicator", props =>
<ErrorBoundary noop>
<PlatformIndicator user={props.user} />
<PlatformIndicator user={props.user} small={true} />
</ErrorBoundary>
),
onDisable: () => removeDecorator("platform-indicator")
@ -197,13 +198,13 @@ export default definePlugin({
replacement: [
{
// 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):/,
replace: (_, rest, isMobile, mobileMask) => `if(${isMobile})return ${mobileMask};${rest}`
match: /\.STATUS_TYPING;switch(?=.+?(if\(\i\)return \i\.\i\.Masks\.STATUS_ONLINE_MOBILE))/,
replace: ".STATUS_TYPING;$1;switch"
},
{
// 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)\?({.+?}):/,
replace: (_, rest, isMobile, component) => `if(${isMobile})return${component};${rest}`
match: /switch\(\i\)\{case \i\.\i\.ONLINE:(if\(\i\)return\{[^}]+\})/,
replace: "$1;$&"
}
]
},
@ -229,7 +230,7 @@ export default definePlugin({
]
},
{
find: "isMobileOnline=function",
find: "}isMobileOnline(",
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
replacement: {
// Make isMobileOnline return true no matter what is the user status

View File

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

View File

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

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