Compare commits

...

432 Commits

Author SHA1 Message Date
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
Vendicated
5d7ede34d8
bump to v1.5.7 2023-10-19 01:23:59 +02:00
Vendicated
cd61354998
ContributorModal: Fix vertical overflow on multi word names 2023-10-19 00:22:49 +02:00
AutumnVN
a452945ac8
feat(plugin): NoTypingAnimation (#1680) 2023-10-19 00:14:14 +02:00
Marocco2
b577660800
feat(VcNarrator): add {{NICKNAME}} as placeholder (#1792) 2023-10-19 00:05:47 +02:00
AutumnVN
4f57c7eded
betterNotes, userVoiceShow: fix padding issue (#1804) 2023-10-18 23:54:35 +02:00
Ryan Cao
e3e5da10a9
fix: Content Security Policy patching (#1814)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-10-18 23:44:29 +02:00
Vendicated
998ce72f3b
ForceOwnerCrown: Remove log spam 2023-10-14 02:46:57 +02:00
AutumnVN
188d12d1a3
roleColorEverywhere: role group color in thread/forum (#1778) 2023-10-13 04:26:18 +02:00
AutumnVN
a522eab40d
feat(plugin): NoMosaic (#1791) 2023-10-13 04:10:36 +02:00
AutumnVN
c2721f158f
imageZoom: fix again (#1793)
Co-authored-by: V <vendicated@riseup.net>
2023-10-13 04:07:21 +02:00
Vendicated
61cd7b4d99
arrpc: refactor for use in vesktop 2023-10-13 03:49:58 +02:00
Marocco2
926af0d1cd
feat(VcNarrator): add {{DISPLAY_NAME}} as placeholder (#1642)
Co-authored-by: V <vendicated@riseup.net>
2023-10-12 04:05:46 +02:00
sunnie
dcaf4aec97
fix moreUserTags (#1780) 2023-10-12 03:30:32 +02:00
Nuckyz
5a97adb435
Fix broken IgnoreActivities patch 2023-10-10 05:10:46 -03:00
V
925d709335
bump to v1.5.6 2023-10-09 03:57:44 +02:00
V
1a36dbbc9b
ci: cleanup publish workflow 2023-10-09 03:56:41 +02:00
V
b59db2f8c2
Drop Firefox extension support
Despite me already fixing all issues, mozilla is still giving me more
trouble. Now they are asking me to provide them with testing credentials
for discord. Not only do i not want to give them my account, it also
isn't even possible because of how discord's login from new location
verification works

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

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

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

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

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

* fix reviewdb bot tag
2023-09-29 00:46:33 +02:00
Vendicated
62277770a8
bump to v1.5.3 2023-09-28 02:42:55 +02:00
V
4facc3cad7
webContextMenus: support pasting images 2023-09-27 04:13:40 +02:00
TheKodeToad
837d1fc083
web: fix themes tab (#1756) 2023-09-26 21:47:12 +02:00
V
608a67c9ae
fix quick/searchReply & MessageClickactions not working in dms 2023-09-26 03:54:59 +02:00
AutumnVN
f32d25b641
viewRaw, viewIcons, permissionsViewer: fix some edge cases (#1745)
Co-authored-by: V <vendicated@riseup.net>
2023-09-26 01:48:09 +02:00
V
4c7a2ba340
Merge branch 'main' into dev 2023-09-26 01:45:38 +02:00
V
da7f0cfff6
PluginModal: Fix cancel button being white on white in light theme 2023-09-26 01:38:46 +02:00
V
ae6584da7c
add os-accent-color variable (following BetterDiscord) 2023-09-25 18:53:10 +02:00
V
c6b1b9463c
improve webpack find error messages 2023-09-25 18:27:18 +02:00
V
376aaf39ce
fix SearchReply filter 2023-09-25 18:07:43 +02:00
V
5454a41243
update issue templates 2023-09-25 04:27:16 +02:00
Hugo C
044f64e446
Improve permission checks on several plugins (#1746)
Co-authored-by: V <vendicated@riseup.net>
2023-09-24 16:42:53 +02:00
V
0b7fca864a
plugin READMEs: migrate from imgur to github assets 2023-09-24 16:17:33 +02:00
V
30ac256070
migrate all plugins to folders 2023-09-24 16:02:18 +02:00
Sam
d0e2a32471
VcNarrator: Ignore multiple underscores (#1748)
Co-authored-by: V <vendicated@riseup.net>
2023-09-24 15:55:23 +02:00
V
ec026ca34c
Update bug_report.yml 2023-09-24 15:45:22 +02:00
V
4baaa9bd91
Delete .github/ISSUE_TEMPLATE/feature_request.yml 2023-09-24 15:44:02 +02:00
V
d9933c5793
settingsSync: correctly use platform agnostic relaunch 2023-09-24 01:16:50 +02:00
V
2735037a67
[skip ci] bump to v1.5.2 2023-09-24 01:12:00 +02:00
Dziurwa
83bfe28fa4
FriendInvites: Add picking uses (1 or 5) (#1727)
Co-authored-by: V <vendicated@riseup.net>
2023-09-23 03:50:14 +02:00
V
08c5d23636
add max attempts to lazys 2023-09-23 03:25:19 +02:00
V
ac0f834155
Fix Plugin Settings on canary 2023-09-23 03:15:07 +02:00
V
fa16e1b56f
fix updater 2023-09-22 15:58:29 +02:00
V
cfca393f2b
ci(generatePluginList): add filePath 2023-09-22 04:48:54 +02:00
Nuckyz
eacc673bcc
Fix BetterRoleDot making reporter angry 2023-09-21 23:28:40 -03:00
Nuckyz
dba6c4cea6
Fix broken FakeNitro patch (again) 2023-09-21 22:41:25 -03:00
V
6b10947d06
Bump to v1.5.1 2023-09-21 18:58:43 +02:00
V
97b6699afe
Fuck you Mozilla 2023-09-21 18:56:58 +02:00
V
7e91edc757
browser: unhardcode rnnoise 2023-09-21 18:19:59 +02:00
V
a82544e93e
Delete FixInbox: Discord fixed this issue themselves now 2023-09-21 17:57:14 +02:00
AutumnVN
d8c8b74ed7
BetterRoleDot: Setting for copying role color in profile popout (#1698) 2023-09-21 17:40:37 +02:00
Daniel Foster
fadd1598f5
Settings: use nearest-neighbour for shiggy (#1739)
Co-authored-by: V <vendicated@riseup.net>
2023-09-21 17:18:41 +02:00
Manti
e5c0898dd6
[ReviewDB] add emojis, discord markdown & notifications (#1718)
Co-authored-by: V <vendicated@riseup.net>
2023-09-21 17:16:15 +02:00
Nuckyz
9550b74b2a
Fix broken FakeNitro patch 2023-09-20 02:44:31 -03:00
V
6cfb67a52e
fix ci 2023-09-19 04:15:34 +02:00
V
12fe367385
bump to v1.5.0 2023-09-19 04:12:54 +02:00
AutumnVN
fc0417eb80
moreUserTags: fix user tag in member list (#1730) 2023-09-19 04:11:30 +02:00
AutumnVN
1f87d14ab2
textReplace: pad space only in string rules (#1738) 2023-09-19 04:11:30 +02:00
XAlboX
7d8f3508a8
ClearURLs: Add si parameter on youtube.com (#1733)
Co-authored-by: V <vendicated@riseup.net>
2023-09-19 04:11:30 +02:00
V
41f5d71e38
Bundle dependencies with extensions for webstore rule compliance (#1740) 2023-09-19 04:11:27 +02:00
AutumnVN
efb88a4df8
favGifSearch: fix search bar (#1732) 2023-09-19 04:10:15 +02:00
Syncx
a73d09a2f0
PreviewMessage: Add attachments (& misc changes) (#1715) 2023-09-12 23:14:17 +02:00
AutumnVN
cf7c4d63b6
pictureInPicture: don't show PiP button on normal file (#1725) 2023-09-12 23:11:53 +02:00
Archer
a95311ef2c
lastfm: Add setting for artist name and song title only (#1726)
Co-authored-by: V <vendicated@riseup.net>
2023-09-12 23:11:25 +02:00
lovenginx
dd23f9802c
InvisibleChat: fixup decryption modal (#1720) 2023-09-12 23:04:50 +02:00
Nuckyz
f23ddf4cae
oopsies 2023-09-12 05:10:44 -03:00
Nuckyz
4222c7fd9f
Fix broken FakeNitro patch on canary 2023-09-12 05:01:18 -03:00
Nuckyz
09f65b401e
Fix broken MessageLogger patches 2023-09-12 04:28:55 -03:00
V
7364776715
Dearrow: Fix button part 2 2023-09-09 19:51:30 +02:00
V
3a5b70d410
Dearrow: Fix button 2023-09-09 19:49:11 +02:00
V
e08d49edac
New Plugin: Dearrow (#1723) 2023-09-09 19:17:50 +02:00
Mushrrom
17abbd3e3e
LastFM: Add setting for using name + artist as activity name (#1713)
Co-authored-by: V <vendicated@riseup.net>
2023-09-09 04:22:41 +02:00
V
25a1d934c6
bump to v1.4.7 2023-09-09 00:22:24 +02:00
Luna
620319a4bc blurnsfw: fix crash (#31)
Reviewed-on: https://codeberg.org/Ven/cord/pulls/31
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-08 21:55:27 +00:00
Hugo C
4c9996d620
feat(plugin): PictureInPicture (#1697)
Co-authored-by: V <vendicated@riseup.net>
2023-09-08 03:57:44 +02:00
V
885c75fdaa
add custom plugin author popouts (#1712) 2023-09-08 03:42:20 +02:00
Lewis Crichton
f2a22c5e57
feat: crash info in /vencord-debug (#1714)
Co-authored-by: V <vendicated@riseup.net>
2023-09-08 03:41:30 +02:00
V
5e3a485edc
ci: generate plugin readme map 2023-09-08 02:26:47 +02:00
Luna
452bf72e56 rnnoise: fix error on webcam (#29)
errored because there was no audio stream, luckily the patched function had a boolean indicating if it was audio or not so just ignore it if it isnt

Reviewed-on: https://codeberg.org/Ven/cord/pulls/29
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-06 17:10:33 +00:00
Luna
afa47addd7 usrbg: export the data to allow other plugins to access (#30)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Ven <ven@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/Ven/cord/pulls/30
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-06 17:10:04 +00:00
V
8ecee3d09f
fix ViewRaw & VoiceMessages context menu style 2023-09-06 19:08:48 +02:00
luk1337
d2aac5edc5
ClearURLs: Add share_id parameter on reddit.com (#1706) 2023-09-06 18:53:28 +02:00
lovenginx
fc45510053
LoadingQuotes: add a new quote (#1710) 2023-09-06 18:52:14 +02:00
AutumnVN
77bfaf38a5
HideAttachments: fix broken css (#1707) 2023-09-06 18:51:48 +02:00
AutumnVN
a4cadc03d8
viewRaw: dont add context menu to guild folders (#1708) 2023-09-06 18:51:18 +02:00
AutumnVN
315fcf1972
messageLogger: make ignoreChannels also ignore threads (#1709) 2023-09-06 18:50:20 +02:00
Nuckyz
f1b3b3c0a3
Uncanonicalize reported patches 2023-09-06 18:47:54 +02:00
V
4c805d08be
[skip ci] PatchHelper: Fix copy button style 2023-09-05 21:37:39 +02:00
V
9b987d1e56
Bump to v1.4.6 2023-09-05 21:25:16 +02:00
V
774318d583
ViewRaw: Update description 2023-09-05 21:24:43 +02:00
V
7d954f9ade
ViewRaw: Fix ugly copy icon & context menu position 2023-09-05 21:20:13 +02:00
Luna
860d6edc7b HideAttachments: allow hiding stickers (#13)
Self explanatory

Reviewed-on: https://codeberg.org/Ven/cord/pulls/13
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-05 18:56:28 +00:00
Rini
223b0366c6 pinDMs: fix alt+shift+up (#25)
applies the same fix as alt+up/down to the jump to unread one

confirmed non-explosion of shc

Reviewed-on: https://codeberg.org/Ven/cord/pulls/25
Co-authored-by: Rini <rini@rinici.de>
Co-committed-by: Rini <rini@rinici.de>
2023-09-05 18:51:22 +00:00
Luna
69cb7593eb View Raw on more objects (#27)
Shows view raw on guilds, channels, and users

Reviewed-on: https://codeberg.org/Ven/cord/pulls/27
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-09-05 18:49:57 +00:00
V
8614e17633
Fix some plugins wrongly displaying the settings cog 2023-09-05 20:10:42 +02:00
Ryan Cao
aecd9d8fda
feat(memberCount): format count according to user locale (#1679) 2023-09-05 19:46:33 +02:00
Hugo C
faeb4fb585
previewMessage: Hide the button once a message is sent (#1692)
Co-authored-by: V <vendicated@riseup.net>
2023-09-05 19:45:44 +02:00
dolfies
0d18b44ba7
feat(plugin): ShowTimeouts (#1687)
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Co-authored-by: V <vendicated@riseup.net>
2023-09-05 19:42:35 +02:00
omahs
d671bd65ac
fix typos (#1703) 2023-09-05 19:40:32 +02:00
AutumnVN
8e952c630b
MessageLogger: fix ignore by id doesn't ignore edited (#1705) 2023-09-05 19:39:22 +02:00
V
2c758ccdf8
new plugin: ServerProfile (#1704)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-09-05 18:36:14 +02:00
Nuckyz
c165725297
Fix OpenInApp patch 2023-09-02 03:03:50 -03:00
Nuckyz
29fbe3701a
Fix CrashHandler patch 2023-09-02 03:03:31 -03:00
V
0b7c0e9587
new plugin: themeAttributes (#1686) 2023-08-31 01:49:53 +02:00
Cynthia Foxwell
d88524e8cf
ShowMeYourName: Fix username being wrong on slash commands (#1690)
Co-authored-by: V <vendicated@riseup.net>
2023-08-31 01:01:29 +02:00
Hugo C
d6efd99849
messageLinkEmbeds: fix AutoModEmbed on compact mode (#1691)
Co-authored-by: V <vendicated@riseup.net>
2023-08-31 01:00:16 +02:00
ruukulada
fe6be987fd
gameActivityToggle: Fix disabled SVG look (#1696) 2023-08-31 00:53:05 +02:00
V
d688075c0a
Update Screenshot 2023-08-29 01:12:47 +02:00
V
c752be45b2
fix(security - ipc): do not expose sender 2023-08-25 14:57:04 +02:00
V
07c1f5eed1
Fix theme upload on crapOs 2023-08-25 14:37:50 +02:00
V
4df01b1e62
ci: fix generatePluginList 2023-08-25 14:33:33 +02:00
AutumnVN
ebe10d3fad
customRPC fixes (#1666)
Co-authored-by: V <vendicated@riseup.net>
2023-08-25 14:30:12 +02:00
Adrian Castro
eca4af829f
feat: add copyUserURLs plugin (#1683)
Co-authored-by: V <vendicated@riseup.net>
2023-08-25 14:24:58 +02:00
CanadaHonk
60458cdf1f
Win32Updater: use before-quit event instead of patching updater (#1668) 2023-08-25 14:21:27 +02:00
Sefa Eyeoglu
714d87241c
build: allow overriding git details & disabling updating (#1677)
Co-authored-by: V <vendicated@riseup.net>
2023-08-25 14:15:02 +02:00
Elliott Tallis
f628aa7a70
ClearURLs: Add si parameter on youtu.be (#1681) 2023-08-25 14:03:58 +02:00
megumin
0f0282551d
🗿 but HD (#1678) 2023-08-25 14:02:10 +02:00
V
0335e1ca59
new plugin FixSpotifyEmbeds: less loud embeds 2023-08-21 16:59:32 +02:00
V
817f0f7473
[skip ci] fix getPluginTarget 2023-08-20 15:14:28 +02:00
V
98a03c8862
bump to 1.4.5 2023-08-19 01:44:11 +02:00
V
72ce7a5ad1
themes: ignore non theme files 2023-08-19 01:33:39 +02:00
Syncx
e699ea63c7
feat(plugin): PreviewMessage (#1607)
Co-authored-by: V <vendicated@riseup.net>
2023-08-19 01:19:10 +02:00
VMGuy23
97e1e9eb7a
messageLogger: apply deleted style to ephemeral messages (#1608)
Co-authored-by: V <vendicated@riseup.net>
2023-08-19 01:14:51 +02:00
bb010g
4c4036546a
new plugin: NormalizeMessageLinks (#1641) 2023-08-19 01:10:59 +02:00
CanadaHonk
d582e61ec3
Fix patching Win32 updater with OpenAsar (#1667)
This is more generic rewrite allowing for more paths to be added in the future for whatever reason (like a rename in future Discord versions).

(The "OpenAsar" code previously was completely wrong)
2023-08-19 00:54:35 +02:00
V
ccd2ce8baf
Bump to 1.4.4 2023-08-16 02:10:06 +02:00
V
ede507e80c
chore: update for 'Vencord Desktop' -> 'Vesktop' rebrand 2023-08-16 01:55:56 +02:00
Justice Almanzar
ffdf63563b
feat(plugins): Web/Vesktop AI Noise Suppression powered by RNNoise (#1477)
Co-authored-by: V <vendicated@riseup.net>
2023-08-16 01:32:11 +02:00
Sammy
55b755b2df
InvisibleChat: improve encryption validator (#1646) 2023-08-16 00:57:56 +02:00
Nuckyz
ca439e9e9a
Fix ReviewDB buttons 2023-08-15 19:21:16 -03:00
Nuckyz
e02fcce3dc
Fix ServerListAPI 2023-08-15 18:48:50 -03:00
V
3e732646e5
Fix plugins broken by latest canary update (#1637)
* Fix WebContextMenus
* BetterGifAltText: futureproof find
* Fix pronoundb profile patch
* Fix NoScreensharePreview
* Fix FakeNitro emote text patch

---------

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-08-12 05:27:59 +02:00
V
d5b3b51050
bump to v1.4.3 2023-08-12 03:02:55 +02:00
V
725fb27e54
futureproof settings patches 2023-08-12 03:01:53 +02:00
V
243381fc91
Settings: Give vencord sections classes so themes can match them 2023-08-12 02:59:24 +02:00
V
54cbdfdad0
MemberCount: Remove negative margin, fixes issues with some themes 2023-08-12 02:46:38 +02:00
V
fe80b8cc85
AnonymiseFileNames: Properly keep .tar.* extensions 2023-08-12 02:45:11 +02:00
Andrew-DLO
742f5cf556
feat(plugin): SecretRingToneEnabler (#1570)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 02:23:00 +02:00
fawn
3b3da90c44
feat(VencordToolbox): add quickcss toggle (#1604)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 02:14:07 +02:00
TheKodeToad
c79e065d09
ShowMeYourName: Option to use display names instead of usernames (#1634)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 01:53:06 +02:00
V
3b8b43c7e0
ci: Fix reporter
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
2023-08-12 01:36:19 +02:00
AutumnVN
6e7996659f
messageLogger: fix ingore guild (#1632) 2023-08-11 06:10:49 +02:00
Arrow
abdf4ebb05
feat(plugin): NoSentryConsole (#1623)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-08-11 00:17:29 +02:00
Justice Almanzar
fa124d8877
build: split preload & renderer.css into D. desktop / vesktop (#1629) 2023-08-11 00:14:50 +02:00
im-h
135da2a5f3
Translate/SendTimestamps: Fix accessibility (#1628)
Co-authored-by: V <vendicated@riseup.net>
2023-08-10 02:31:28 +02:00
Manti
c96a1a9998
[ReviewDB] hide blocked users (#1626)
Co-authored-by: V <vendicated@riseup.net>
2023-08-10 00:28:46 +00:00
Rini
8b6c8bc0ec chore: transition to shorter license headers (#6)
Transition from the GPL jumpscare to a more concise header:

	/*
     * Vencord, a Discord client mod
	 * Copyright (c) 2023 Vendicated and contributors
     *
	 * SPDX-License-Identifier: GPL-3.0
     */

Old files still keep the old headers, while eslint will now autofix the new header

Reviewed-on: https://codeberg.org/Ven/cord/pulls/6
Co-authored-by: Rini <rini@rinici.de>
Co-committed-by: Rini <rini@rinici.de>
2023-08-10 00:17:49 +00:00
V
1a62249da6
bump to v1.4.2 2023-08-04 20:20:07 +02:00
Ryan Cao
21318850b1
fix: open links externally in Quick CSS editor (#1487)
Co-authored-by: V <vendicated@riseup.net>
2023-08-04 20:13:53 +02:00
Lewis Crichton
885ad134b3
fix(cloud): cross-client enabling without proper validation (#1531) 2023-08-04 20:10:12 +02:00
V
3e7d4e2623
make QuickCSS have priority over themes 2023-08-04 20:08:13 +02:00
AutumnVN
d3691f74c4
chore: Fix greetStickerPicker file name (#1610) 2023-08-04 20:01:49 +02:00
Justice Almanzar
268f3a1840
add bottom margin to settings section (#1616) 2023-08-04 19:56:40 +02:00
megumin
d6c43986fd
Add proper user-friendly theme manager (#635)
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2023-08-04 19:52:20 +02:00
V
bb7deeb09c
UserVoiceShow: Fix error 2023-08-03 00:13:04 +02:00
AutumnVN
0407be9847
textReplace: fix edge case (#1521)
Co-authored-by: V <vendicated@riseup.net>
2023-08-02 00:28:44 +00:00
Hugo C
645749b5ae
VoiceMessages: Improve permission check (#1544)
Co-authored-by: V <vendicated@riseup.net>
2023-08-02 00:27:10 +00:00
AutumnVN
2e002107a6
customRPC: add validation & some fixes (#1481)
Signed-off-by: V <vendicated@riseup.net>
2023-08-02 01:52:08 +02:00
Nickyux
cc07518a34
VoiceMessages: Support replies (#1565)
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
Co-authored-by: V <vendicated@riseup.net>
Signed-off-by: V <vendicated@riseup.net>
2023-08-02 01:51:39 +02:00
Lewis Crichton
ea64b33e24
star history :3 (#1528) 2023-08-01 05:35:45 +02:00
V
1a92d3ff8d
bump to v1.4.1 2023-08-01 05:34:43 +02:00
Rawir
45bb1af011
MuteNewGuild: Support lurked guilds (#1546)
Co-authored-by: Rawiros <45668076+Rawiros@users.noreply.github.com01~>
2023-08-01 05:32:29 +02:00
V
39ad88f433
Experiments: Fix canary 2023-08-01 05:23:52 +02:00
V
8cf4d2a2c0
Update 1_INSTALLING.md 2023-07-28 16:44:17 +02:00
V
fe5e041db8
VoiceMessages: Read file from dynamic path (fixes mac & linux support) 2023-07-27 02:06:18 +02:00
V
d18681c197
Delete blank.yml 2023-07-27 01:35:58 +02:00
V
c024db1bc4
Update config.yml 2023-07-27 01:35:44 +02:00
V
d8a0db8bee
Update blank.yml 2023-07-27 01:34:41 +02:00
V
f62efa5aa7
Update blank.yml 2023-07-27 01:33:55 +02:00
Hugo C
1d77ab0ade
MemberCount: fix family center crash (#1486) 2023-07-26 00:33:00 +00:00
V
9268cf3ffb
Bump to 1.4.0 2023-07-26 02:17:20 +02:00
Syncx
208371c471
feat(plugin): Favorite Gif Search (#1386)
Co-authored-by: V <vendicated@riseup.net>
2023-07-26 01:50:24 +02:00
Ryan Cao
c69c6f8cb7
feat(MessageLinkEmbeds): add whitelist/blacklist modes (#813)
Co-authored-by: V <vendicated@riseup.net>
2023-07-26 01:41:41 +02:00
alexia
f2c6fcaa3b
fix(PronounDB): don't use guild pronouns in global profile modal (#1462) 2023-07-26 01:34:51 +02:00
Aayush Shah
abf62f28db
Themes tab: Add QuickCss button (#1475) 2023-07-26 01:29:57 +02:00
V
8620a1d86d
New plugin: VoiceMessages (#1380)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
2023-07-26 01:27:04 +02:00
V
198b35ffdc
Merge branch 'main' into dev 2023-07-26 01:25:30 +02:00
Ryan Cao
b4d0d95731
fix vencord toolbox being unusable with drag region (#1480)
* fix(OpenInApp): Broken patch (#1434)

* fix: vencord toolbox unreachable with drag region

---------

Co-authored-by: whqwert <94757998+whqwert@users.noreply.github.com>
2023-07-25 15:34:57 +02:00
whqwert
f785aa1473
fix(OpenInApp): Broken patch (#1434) 2023-07-17 02:05:20 -03:00
Commandtechno
d56e6560e5
[chore] Update DisableDMCallIdle description (#1422) 2023-07-16 00:51:14 +02:00
Justice Almanzar
a7e74ee4d5
classNameFactory: Allow (& ignore) all sorts of falsy values (#1427) 2023-07-16 00:50:21 +02:00
Luca Zeuch
1340f023a3
feat(MessageLogger): add option to ignore channels and guilds (#1420) 2023-07-14 18:21:29 +02:00
V
2bf0c324d7
chore: Update dev ids 2023-07-14 01:12:07 +02:00
V
f621cdb50b
Bump monaco editor 2023-07-14 01:10:53 +02:00
echo
9717001783
delete uwuifier plugin (#1414)
Co-authored-by: exhq <exhq@exhq.dev>
2023-07-14 01:08:27 +02:00
MrDiamondDog
065ab75627
Add "Show New" option in plugin settings (#1416)
Co-authored-by: V <vendicated@riseup.net>
2023-07-13 19:35:40 +02:00
V
8aea72c1be
FakeNitro: Fix crash 2023-07-10 22:39:40 +02:00
V
bea7a1711e
Bump to v1.3.4 2023-07-08 03:40:31 +02:00
Lewis Crichton
e52ae62441
feat(cloud): support multiple user accounts (#1382)
Co-authored-by: V <vendicated@riseup.net>
2023-07-08 03:36:59 +02:00
V
7cd1d4c60f
translate: Add context menu item; fix MLE compatibility 2023-07-08 03:30:16 +02:00
V
2a318e390e
QuickCss: Fix wrongly applying quickcss when editing while disabled 2023-07-08 03:13:32 +02:00
V
7c7723bfb1
Plugin Settings: Use Switches for booleans 2023-07-08 03:04:58 +02:00
dolfies
2db0e71e5b
fix(RelationshipNotifier): Ignore user-actioned friend requests (#1390) 2023-07-08 02:37:32 +02:00
Ryan Cao
cde8074f44
feat(ClearURLs): add Threads share link tracking param (#1384) 2023-07-08 02:34:16 +02:00
Nuckyz
8b1630bc99
Fix ShowAllMessageButtons (#1392)
Co-authored-by: V <vendicated@riseup.net>
2023-07-08 02:33:37 +02:00
V
bf34b2ae43
Bump to v1.3.3 2023-07-06 01:26:31 +02:00
V
cb5f23d9b5
ProxyLazy: Limit attempts 2023-07-06 00:54:49 +02:00
V
cd2cbfa0ef
ShowHiddenChannels: Fix crashing on canary 2023-07-06 00:43:43 +02:00
Ryan Cao
232e340fab
fix: send notification when settings are manually synced (#1378)
Co-authored-by: V <vendicated@riseup.net>
2023-07-04 15:59:42 +00:00
V
8027daa2b0
Fix types 2023-07-04 17:58:32 +02:00
Ryan Cao
0f7b9f588e
perf(cloud sync): minify synced settings (#1377) 2023-07-04 17:57:28 +02:00
V
93482ac2a5
SpotifyControls: improve open in app capabilities & styles 2023-07-04 17:53:17 +02:00
V
994c3b3c92
OpenInApp: Fix bot buttons 2023-07-04 17:52:52 +02:00
V
c696c186e8
WebKeybinds: Fix scope 2023-07-02 01:23:51 +02:00
V
30d5e2108f
Bump to 1.3.2 2023-07-02 01:22:53 +02:00
V
1eabd1b701
WebKeybinds: Fix broken zoom on vesktop 2023-07-02 01:22:37 +02:00
V
1cbf2b43e1
Fix Badges 2023-07-02 01:17:02 +02:00
V
4c197d5d51
New Plugin: WebKeyBinds 2023-07-02 01:08:06 +02:00
V
f89027f46a
Bump to 1.3.1 2023-06-30 17:08:57 +02:00
V
07a0ebb1d2
PronounDB: Fix crash when having pronouns in profile disabled 2023-06-30 17:08:43 +02:00
V
f09b44b0d5
VcNarrator: Ignore stage channels 2023-06-30 15:55:20 +02:00
V
b607eebcb7
ImageZoom: Add square lens option 2023-06-30 15:50:56 +02:00
V
0936ca2985
ValidUser: Fix mentions with ! (<@!...>) 2023-06-30 15:43:21 +02:00
V
13bde79ec8
PronounDB: Fix profile patch, add pronoun source to tooltip 2023-06-30 15:39:33 +02:00
V
b592defaaf
OpenInApp: Add Epic Games; properly respect settings 2023-06-30 15:16:42 +02:00
V
73354973a3
OpenInApp: Support steam; integrate with SpotifyControls & ShowConnections 2023-06-29 16:15:07 +02:00
V
e12c0e546c
PronounDB: Fix crash 2023-06-29 14:28:16 +02:00
V
088a8bd1b6
OpenInApp: Support profile activity on web & profile connection 2023-06-29 05:02:13 +02:00
V
51adb26d01
New plugin: OpenInApp - Open spotify urls in spotify app 2023-06-29 04:33:35 +02:00
Nuckyz
cb980a1cad
Fix broken SHC patches (#1360) 2023-06-28 03:45:16 +00:00
V
69b10c1f07
Bump to 1.3.0 2023-06-27 23:12:56 +02:00
alexia
8e9ba7c7ee
PronounDB: fix caching not respecting user preference (#1344) 2023-06-27 22:46:52 +02:00
Commandtechno
12e3c9234d
ViewIcons: Fix animated server pfps & profile edit button (#1350)
fixes #1236
2023-06-27 22:41:52 +02:00
Sammy
1d8dcef394
InvisibleChat: fix indicator & crashes (#1356) 2023-06-27 22:41:33 +02:00
Syncx
4fe2845234
ImageZoom: add nearest neighbour (#1341) 2023-06-27 22:41:19 +02:00
V
5e71ed286e
PronounDB: Strip newlines from Discord Pronouns (Discord is stupid) 2023-06-25 19:08:18 +02:00
V
5edbd2391d
Fix build 2023-06-25 18:33:44 +02:00
ActuallyTheSun
8472c3823e
feat(🗿): ignore blocked users (#1327)
Co-authored-by: V <vendicated@riseup.net>
2023-06-25 18:31:39 +02:00
V
2103e52115
WebContextMenus: Support all text areas 2023-06-25 18:22:36 +02:00
Nuckyz
afbfb641e8
Fix IgnoreActivities broken patch (#1337) 2023-06-25 09:57:18 +00:00
338 changed files with 9171 additions and 4355 deletions

@ -4,7 +4,7 @@
"ignorePatterns": ["dist", "browser"], "ignorePatterns": ["dist", "browser"],
"plugins": [ "plugins": [
"@typescript-eslint", "@typescript-eslint",
"header", "simple-header",
"simple-import-sort", "simple-import-sort",
"unused-imports", "unused-imports",
"path-alias" "path-alias"
@ -26,35 +26,12 @@
// Since it's only been a month and Vencord has already been stolen // Since it's only been a month and Vencord has already been stolen
// by random skids who rebranded it to "AlphaCord" and erased all license // by random skids who rebranded it to "AlphaCord" and erased all license
// information // information
"header/header": [ "simple-header/header": [
2, "error",
"block", {
[ "files": ["scripts/header-new.txt", "scripts/header-old.txt"],
{ "templates": { "author": [".*", "Vendicated and contributors"] }
"pattern": "!?", }
"template": " "
},
" * Vencord, a modification for Discord's desktop app",
{
"pattern": " \\* Copyright \\(c\\) \\d{4}",
"template": " * 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/>.",
""
],
2
], ],
"quotes": ["error", "double", { "avoidEscape": true }], "quotes": ["error", "double", { "avoidEscape": true }],
"jsx-quotes": ["error", "prefer-double"], "jsx-quotes": ["error", "prefer-double"],
@ -74,7 +51,10 @@
"eqeqeq": ["error", "always", { "null": "ignore" }], "eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }], "spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error", "yoda": "error",
"prefer-destructuring": ["error", { "object": true, "array": false }], "prefer-destructuring": ["error", {
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}],
"operator-assignment": ["error", "always"], "operator-assignment": ["error", "always"],
"no-useless-computed-key": "error", "no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }], "no-unneeded-ternary": ["error", { "defaultAssignment": false }],

@ -1,14 +1,23 @@
name: Blank Template name: Blank Issue
description: Use this only if your issue does not fit into another template. **DO NOT ASK FOR SUPPORT OR REQUEST PLUGINS** description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
labels: []
body: body:
- type: textarea - type: markdown
id: info-sec
attributes: attributes:
label: Tell us all about it. value: |
description: Go nuts, let us know what you're wanting to bring attention to. # READ THIS BEFORE OPENING AN ISSUE
placeholder: ...
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
- you are filing a security related report
- type: textarea
id: content
attributes:
label: Content
validations: validations:
required: true required: true
@ -16,7 +25,6 @@ body:
id: agreement-check id: agreement-check
attributes: attributes:
label: Request Agreement label: Request Agreement
description: DO NOT USE THIS TEMPLATE FOR SUPPORT OR PLUGIN REQUESTS!!! For Support, **join our Discord**. For plugin requests, **use discussions**
options: options:
- label: This is not a support or plugin request - label: I have read the requirements for opening an issue above
required: true required: true

@ -1,9 +1,22 @@
name: Bug/Crash Report name: Bug/Crash Report
description: Create a bug or crash report for Vencord description: Create a bug or crash report for Vencord. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
labels: [bug] labels: [bug]
title: "[Bug] <title>" title: "[Bug] <title>"
body: body:
- type: markdown
attributes:
value: |
# READ THIS BEFORE OPENING AN ISSUE
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
DO NOT USE THIS FORM, unless
- you are a vencord contributor
- you were given explicit permission to use this form by a moderator in our support server
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
- type: input - type: input
id: discord id: discord
attributes: attributes:
@ -64,3 +77,5 @@ body:
options: options:
- label: I am using Discord Stable or tried on Stable and this bug happens there as well - label: I am using Discord Stable or tried on Stable and this bug happens there as well
required: true required: true
- label: I have read the requirements for opening an issue above
required: true

@ -1,32 +0,0 @@
name: Feature Request
description: Create a feature request for Vencord. To request new plugins, please use the Discussions tab
labels: [enhancement]
title: "[Feature Request] <title>"
body:
- type: input
id: discord
attributes:
label: Discord Account
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
placeholder: username#0000
validations:
required: false
- type: textarea
id: feature-basic-description
attributes:
label: What is it that you'd like to see?
description: Describe the feature you want added as detailed as possible
placeholder: I think ... would be a cool feature to add. This would be awesome, thanks!
validations:
required: true
- type: checkboxes
id: agreement-check
attributes:
label: Request Agreement
description: DO NOT USE THIS TEMPLATE FOR PLUGIN REQUESTS!!! For plugin requests, **use discussions**
options:
- label: This is not a plugin request
required: true

@ -38,11 +38,11 @@ jobs:
run: pnpm build --standalone run: pnpm build --standalone
- name: Generate plugin list - name: Generate plugin list
run: pnpm generatePluginJson dist/plugins.json run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
- name: Clean up obsolete files - name: Clean up obsolete files
run: | run: |
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
- name: Get some values needed for the release - name: Get some values needed for the release
id: release_values id: release_values

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

@ -65,7 +65,7 @@ Also pay attention to the following:
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`, - Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
`var .{1,2}=([^;]+);` `var .{1,2}=([^;]+);`
- If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters - If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters
- Additionally, as you might have noticed, all of the appove approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically - Additionally, as you might have noticed, all of the above approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
#### "replace" #### "replace"

@ -4,8 +4,7 @@
The cutest Discord client mod The cutest Discord client mod
![](https://user-images.githubusercontent.com/45497981/235015332-0453d3eb-1da6-4601-963e-ef5e454123a1.png) ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334)
*A screenshot of Vencord featuring the [ClearVision-v6 theme](https://github.com/ClearVision/ClearVision-v6) (Vencord does not come with it pre-installed, it is only an example)*
## Features ## Features
@ -23,34 +22,30 @@ The cutest Discord client mod
## Installing / Uninstalling ## Installing / Uninstalling
Click the below button to install Vencord to the Discord Desktop app Visit https://vencord.dev/download
[![Download and run the Installer](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#vencord-installer)
## Installing on Browser
[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
<details>
<summary>Alternative Downloads</summary>
## Vencord Desktop
> **Warning**
> This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
</details>
## Join our Support/Community Server ## Join our Support/Community Server
https://discord.gg/D9uwnFnqmd 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">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Vendicated/Vencord&type=Timeline&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Vendicated/Vencord&type=Timeline" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Vendicated/Vencord&type=Timeline" />
</picture>
</a>
## Disclaimer ## Disclaimer
Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity. Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity.

@ -19,10 +19,13 @@
/// <reference path="../src/modules.d.ts" /> /// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" /> /// <reference path="../src/globals.d.ts" />
import monacoHtml from "~fileContent/../src/components/monacoWin.html"; import monacoHtmlLocal from "~fileContent/monacoWin.html";
import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html";
import * as DataStore from "../src/api/DataStore"; import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils"; import { debounce } from "../src/utils";
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
import { getTheme, Theme } from "../src/utils/discord"; import { getTheme, Theme } from "../src/utils/discord";
import { getThemeInfo } from "../src/main/themes";
// Discord deletes this so need to store in variable // Discord deletes this so need to store in variable
const { localStorage } = window; const { localStorage } = window;
@ -34,8 +37,21 @@ const NOOP_ASYNC = async () => { };
const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css)); const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css));
const themeStore = DataStore.createStore("VencordThemes", "VencordThemeData");
// probably should make this less cursed at some point // probably should make this less cursed at some point
window.VencordNative = { window.VencordNative = {
themes: {
uploadTheme: (fileName: string, fileData: string) => DataStore.set(fileName, fileData, themeStore),
deleteTheme: (fileName: string) => DataStore.del(fileName, themeStore),
getThemesDir: async () => "",
getThemesList: () => DataStore.entries(themeStore).then(entries =>
entries.map(([name, css]) => getThemeInfo(css, name.toString()))
),
getThemeData: (fileName: string) => DataStore.get(fileName, themeStore),
getSystemValues: async () => ({}),
},
native: { native: {
getVersions: () => ({}), getVersions: () => ({}),
openExternal: async (url) => void open(url, "_blank") openExternal: async (url) => void open(url, "_blank")
@ -57,6 +73,7 @@ window.VencordNative = {
addChangeListener(cb) { addChangeListener(cb) {
cssListeners.add(cb); cssListeners.add(cb);
}, },
addThemeChangeListener: NOOP,
openFile: NOOP_ASYNC, openFile: NOOP_ASYNC,
async openEditor() { async openEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`; const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
@ -66,6 +83,7 @@ window.VencordNative = {
return; return;
} }
win.baseUrl = EXTENSION_BASE_URL;
win.setCss = setCssDebounced; win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get(); win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () => win.getTheme = () =>
@ -73,7 +91,7 @@ window.VencordNative = {
? "vs-light" ? "vs-light"
: "vs-dark"; : "vs-dark";
win.document.write(monacoHtml); win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn);
}, },
}, },
@ -81,5 +99,7 @@ window.VencordNative = {
get: () => localStorage.getItem("VencordSettings") || "{}", get: () => localStorage.getItem("VencordSettings") || "{}",
set: async (s: string) => localStorage.setItem("VencordSettings", s), set: async (s: string) => localStorage.setItem("VencordSettings", s),
getSettingsDir: async () => "LocalStorage" getSettingsDir: async () => "LocalStorage"
} },
pluginHelpers: {} as any,
}; };

@ -4,6 +4,11 @@ if (typeof browser === "undefined") {
const script = document.createElement("script"); const script = document.createElement("script");
script.src = browser.runtime.getURL("dist/Vencord.js"); script.src = browser.runtime.getURL("dist/Vencord.js");
script.id = "vencord-script";
Object.assign(script.dataset, {
extensionBaseUrl: browser.runtime.getURL(""),
version: browser.runtime.getManifest().version
});
const style = document.createElement("link"); const style = document.createElement("link");
style.type = "text/css"; style.type = "text/css";

@ -28,7 +28,7 @@
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": ["dist/Vencord.js", "dist/Vencord.css"], "resources": ["dist/*", "third-party/*"],
"matches": ["*://*.discord.com/*"] "matches": ["*://*.discord.com/*"]
} }
], ],

43
browser/monaco.ts Normal file

@ -0,0 +1,43 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./patch-worker";
import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js";
declare global {
const baseUrl: string;
const getCurrentCss: () => Promise<string>;
const setCss: (css: string) => void;
const getTheme: () => string;
}
const BASE = "/dist/monaco/vs";
self.MonacoEnvironment = {
getWorkerUrl(_moduleId: unknown, label: string) {
const path = label === "css" ? "/language/css/css.worker.js" : "/editor/editor.worker.js";
return new URL(BASE + path, baseUrl).toString();
}
};
getCurrentCss().then(css => {
const editor = monaco.editor.create(
document.getElementById("container")!,
{
value: css,
language: "css",
theme: getTheme(),
}
);
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
});

37
browser/monacoWin.html Normal file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vencord QuickCSS Editor</title>
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
const script = document.createElement("script");
script.src = new URL("/dist/monaco/index.js", baseUrl);
const style = document.createElement("link");
style.type = "text/css";
style.rel = "stylesheet";
style.href = new URL("/dist/monaco/index.css", baseUrl);
document.body.append(style, script);
</script>
</body>
</html>

135
browser/patch-worker.js Normal file

@ -0,0 +1,135 @@
/*
Copyright 2013 Rob Wu <gwnRob@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Target: Chrome 20+
// W3-compliant Worker proxy.
// This module replaces the global Worker object.
// When invoked, the default Worker object is called.
// If this call fails with SECURITY_ERR, the script is fetched
// using async XHR, and transparently proxies all calls and
// setters/getters to the new Worker object.
// Note: This script does not magically circumvent the Same origin policy.
(function () {
'use strict';
var Worker_ = window.Worker;
var URL = window.URL || window.webkitURL;
// Create dummy worker for the following purposes:
// 1. Don't override the global Worker object if the fallback isn't
// going to work (future API changes?)
// 2. Use it to trigger early validation of postMessage calls
// Note: Blob constructor is supported since Chrome 20, but since
// some of the used Chrome APIs are only supported as of Chrome 20,
// I don't bother adding a BlobBuilder fallback.
var dummyWorker = new Worker_(
URL.createObjectURL(new Blob([], { type: 'text/javascript' })));
window.Worker = function Worker(scriptURL) {
if (arguments.length === 0) {
throw new TypeError('Not enough arguments');
}
try {
return new Worker_(scriptURL);
} catch (e) {
if (e.code === 18/*DOMException.SECURITY_ERR*/) {
return new WorkerXHR(scriptURL);
} else {
throw e;
}
}
};
// Bind events and replay queued messages
function bindWorker(worker, workerURL) {
if (worker._terminated) {
return;
}
worker.Worker = new Worker_(workerURL);
worker.Worker.onerror = worker._onerror;
worker.Worker.onmessage = worker._onmessage;
var o;
while ((o = worker._replayQueue.shift())) {
worker.Worker[o.method].apply(worker.Worker, o.arguments);
}
while ((o = worker._messageQueue.shift())) {
worker.Worker.postMessage.apply(worker.Worker, o);
}
}
function WorkerXHR(scriptURL) {
var worker = this;
var x = new XMLHttpRequest();
x.responseType = 'blob';
x.onload = function () {
// http://stackoverflow.com/a/10372280/938089
var workerURL = URL.createObjectURL(x.response);
bindWorker(worker, workerURL);
};
x.open('GET', scriptURL);
x.send();
worker._replayQueue = [];
worker._messageQueue = [];
}
WorkerXHR.prototype = {
constructor: Worker_,
terminate: function () {
if (!this._terminated) {
this._terminated = true;
if (this.Worker)
this.Worker.terminate();
}
},
postMessage: function (message, transfer) {
if (!(this instanceof WorkerXHR))
throw new TypeError('Illegal invocation');
if (this.Worker) {
this.Worker.postMessage.apply(this.Worker, arguments);
} else {
// Trigger validation:
dummyWorker.postMessage(message);
// Alright, push the valid message to the queue.
this._messageQueue.push(arguments);
}
}
};
// Implement the EventTarget interface
[
'addEventListener',
'removeEventListener',
'dispatchEvent'
].forEach(function (method) {
WorkerXHR.prototype[method] = function () {
if (!(this instanceof WorkerXHR)) {
throw new TypeError('Illegal invocation');
}
if (this.Worker) {
this.Worker[method].apply(this.Worker, arguments);
} else {
this._replayQueue.push({ method: method, arguments: arguments });
}
};
});
Object.defineProperties(WorkerXHR.prototype, {
onmessage: {
get: function () { return this._onmessage || null; },
set: function (func) {
this._onmessage = typeof func === 'function' ? func : null;
}
},
onerror: {
get: function () { return this._onerror || null; },
set: function (func) {
this._onerror = typeof func === 'function' ? func : null;
}
}
});
})();

@ -1,5 +1,6 @@
> **Warning** > [!WARNING]
> These instructions are only for advanced users. If you're not a Developer, you should use our [graphical installer](https://github.com/Vendicated/VencordInstaller#usage) instead. > These instructions are only for advanced users. If you're not a Developer, you should use our [graphical installer](https://github.com/Vendicated/VencordInstaller#usage) instead.
> No support will be provided for installing in this fashion. If you cannot figure it out, you should just stick to a regular install.
# Installation Guide # Installation Guide
@ -62,7 +63,7 @@ Then fully close Discord from your taskbar or task manager, and restart it. Venc
If you're using Discord already, go into the `Updater` tab in settings. If you're using Discord already, go into the `Updater` tab in settings.
Sometimes it may be neccessary to manually update if the GUI updater fails. Sometimes it may be necessary to manually update if the GUI updater fails.
To pull latest changes: To pull latest changes:

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.2.9", "version": "1.6.3",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -11,7 +11,7 @@
"type": "git", "type": "git",
"url": "git+https://github.com/Vendicated/Vencord.git" "url": "git+https://github.com/Vendicated/Vencord.git"
}, },
"license": "GPL-3.0", "license": "GPL-3.0-or-later",
"author": "Vendicated", "author": "Vendicated",
"directories": { "directories": {
"doc": "docs" "doc": "docs"
@ -31,13 +31,18 @@
"watch": "node scripts/build/build.mjs --watch" "watch": "node scripts/build/build.mjs --watch"
}, },
"dependencies": { "dependencies": {
"@sapphi-red/web-noise-suppressor": "0.3.3",
"@vap/core": "0.0.12", "@vap/core": "0.0.12",
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
"eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4", "fflate": "^0.7.4",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.43.0",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.246",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.14.194",
"@types/node": "^18.16.3", "@types/node": "^18.16.3",
@ -49,9 +54,8 @@
"diff": "^5.1.0", "diff": "^5.1.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.15.18",
"eslint": "^8.28.0", "eslint": "^8.46.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
@ -63,13 +67,14 @@
"stylelint-config-standard": "^33.0.0", "stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.7", "tsx": "^3.12.7",
"type-fest": "^3.9.0", "type-fest": "^3.9.0",
"typescript": "^5.0.4" "typescript": "^5.0.4",
"zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@8.1.1", "packageManager": "pnpm@8.10.2",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch", "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
"eslint@8.28.0": "patches/eslint@8.28.0.patch" "eslint@8.46.0": "patches/eslint@8.46.0.patch"
}, },
"peerDependencyRules": { "peerDependencyRules": {
"ignoreMissing": [ "ignoreMissing": [
@ -89,7 +94,7 @@
"build": { "build": {
"overwriteDest": true "overwriteDest": true
}, },
"sourceDir": "./dist/extension-v2-unpacked" "sourceDir": "./dist/firefox-unpacked"
}, },
"engines": { "engines": {
"node": ">=18", "node": ">=18",

@ -1,8 +1,8 @@
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index 2046a148a17fd1d5f3a4bbc9f45f7700259d11fa..f4898c6b57355a4fd72c43a9f32bf1a36a6ccf4a 100644 index 0e0f6f09f2c35f3276173c08f832cde9f2cf56a0..7dc22851715f3574d935f513c1b5e35552985711 100644
--- a/lib/rules/no-useless-escape.js --- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js +++ b/lib/rules/no-useless-escape.js
@@ -97,12 +97,30 @@ module.exports = { @@ -65,13 +65,31 @@ module.exports = {
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
}, },
@ -25,21 +25,25 @@ index 2046a148a17fd1d5f3a4bbc9f45f7700259d11fa..f4898c6b57355a4fd72c43a9f32bf1a3
create(context) { create(context) {
+ const options = context.options[0] || {}; + const options = context.options[0] || {};
+ const { extra, extraCharClass } = options || '' + const { extra, extraCharClass } = options;
const sourceCode = context.getSourceCode(); const sourceCode = context.sourceCode;
const parser = new RegExpParser();
+ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra)) + const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra));
+ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass)) + const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass));
+ +
/** /**
* Reports a node * Reports a node
* @param {ASTNode} node The node to report * @param {ASTNode} node The node to report
@@ -238,7 +256,7 @@ module.exports = { @@ -200,9 +218,9 @@ module.exports = {
.filter(charInfo => charInfo.escaped) let allowedEscapes;
// Filter out characters that are valid to escape, based on their position in the regular expression. if (characterClassStack.length) {
- .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text)) - allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
+ .filter(charInfo => !(charInfo.inCharClass ? CHARCLASS_ESCAPES : NON_CHARCLASS_ESCAPES).has(charInfo.text)) + allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : CHARCLASS_ESCAPES;
} else {
// Report all the remaining characters. - allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
.forEach(charInfo => report(node, charInfo.index, charInfo.text)); + allowedEscapes = NON_CHARCLASS_ESCAPES;
}
if (allowedEscapes.has(escapedChar)) {
return;

330
pnpm-lock.yaml generated

@ -1,23 +1,39 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies: patchedDependencies:
eslint-plugin-path-alias@1.0.0: eslint-plugin-path-alias@1.0.0:
hash: m6sma4g6bh67km3q6igf6uxaja hash: m6sma4g6bh67km3q6igf6uxaja
path: patches/eslint-plugin-path-alias@1.0.0.patch path: patches/eslint-plugin-path-alias@1.0.0.patch
eslint@8.28.0: eslint@8.46.0:
hash: 7wc6icvgtg3uswirb5tpsbjnbe hash: xm46kqcmdgzlmm4aifkfpxaho4
path: patches/eslint@8.28.0.patch path: patches/eslint@8.46.0.patch
dependencies: dependencies:
'@sapphi-red/web-noise-suppressor':
specifier: 0.3.3
version: 0.3.3
'@vap/core': '@vap/core':
specifier: 0.0.12 specifier: 0.0.12
version: 0.0.12 version: 0.0.12
'@vap/shiki': '@vap/shiki':
specifier: 0.10.5 specifier: 0.10.5
version: 0.10.5 version: 0.10.5
eslint-plugin-simple-header:
specifier: ^1.0.2
version: 1.0.2
fflate: fflate:
specifier: ^0.7.4 specifier: ^0.7.4
version: 0.7.4 version: 0.7.4
gifenc:
specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3
version: github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3
monaco-editor:
specifier: ^0.43.0
version: 0.43.0
nanoid: nanoid:
specifier: ^4.0.2 specifier: ^4.0.2
version: 4.0.2 version: 4.0.2
@ -26,6 +42,9 @@ dependencies:
version: 1.0.1 version: 1.0.1
devDependencies: devDependencies:
'@types/chrome':
specifier: ^0.0.246
version: 0.0.246
'@types/diff': '@types/diff':
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3 version: 5.0.3
@ -46,10 +65,10 @@ devDependencies:
version: 2.4.2 version: 2.4.2
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^5.59.1 specifier: ^5.59.1
version: 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.28.0)(typescript@5.0.4) version: 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.46.0)(typescript@5.0.4)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^5.59.1 specifier: ^5.59.1
version: 5.59.1(eslint@8.28.0)(typescript@5.0.4) version: 5.59.1(eslint@8.46.0)(typescript@5.0.4)
diff: diff:
specifier: ^5.1.0 specifier: ^5.1.0
version: 5.1.0 version: 5.1.0
@ -60,23 +79,20 @@ devDependencies:
specifier: ^0.15.18 specifier: ^0.15.18
version: 0.15.18 version: 0.15.18
eslint: eslint:
specifier: ^8.28.0 specifier: ^8.46.0
version: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) version: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-import-resolver-alias: eslint-import-resolver-alias:
specifier: ^1.1.2 specifier: ^1.1.2
version: 1.1.2 version: 1.1.2
eslint-plugin-header:
specifier: ^3.1.1
version: 3.1.1(eslint@8.28.0)
eslint-plugin-path-alias: eslint-plugin-path-alias:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.28.0) version: 1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.46.0)
eslint-plugin-simple-import-sort: eslint-plugin-simple-import-sort:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.0.0(eslint@8.28.0) version: 10.0.0(eslint@8.46.0)
eslint-plugin-unused-imports: eslint-plugin-unused-imports:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.28.0) version: 2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.46.0)
highlight.js: highlight.js:
specifier: 10.6.0 specifier: 10.6.0
version: 10.6.0 version: 10.6.0
@ -104,9 +120,17 @@ devDependencies:
typescript: typescript:
specifier: ^5.0.4 specifier: ^5.0.4
version: 5.0.4 version: 5.0.4
zip-local:
specifier: ^0.3.5
version: 0.3.5
packages: packages:
/@aashutoshrathi/word-wrap@1.2.6:
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
engines: {node: '>=0.10.0'}
dev: true
/@babel/code-frame@7.21.4: /@babel/code-frame@7.21.4:
resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -399,7 +423,7 @@ packages:
dev: true dev: true
optional: true optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.28.0): /@eslint-community/eslint-utils@4.4.0(eslint@8.46.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -408,8 +432,8 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-visitor-keys: 3.4.0 eslint-visitor-keys: 3.4.2
dev: true dev: true
/@eslint-community/regexpp@4.5.1: /@eslint-community/regexpp@4.5.1:
@ -417,15 +441,20 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true dev: true
/@eslint/eslintrc@1.3.3: /@eslint-community/regexpp@4.6.2:
resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true
/@eslint/eslintrc@2.1.1:
resolution: {integrity: sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.3.4 debug: 4.3.4
espree: 9.4.1 espree: 9.6.1
globals: 13.17.0 globals: 13.20.0
ignore: 5.2.0 ignore: 5.2.4
import-fresh: 3.3.0 import-fresh: 3.3.0
js-yaml: 4.1.0 js-yaml: 4.1.0
minimatch: 3.1.2 minimatch: 3.1.2
@ -434,8 +463,13 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@humanwhocodes/config-array@0.11.7: /@eslint/js@8.46.0:
resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} resolution: {integrity: sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
dependencies: dependencies:
'@humanwhocodes/object-schema': 1.2.1 '@humanwhocodes/object-schema': 1.2.1
@ -498,10 +532,35 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sapphi-red/web-noise-suppressor@0.3.3:
resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==}
dev: false
/@types/chrome@0.0.246:
resolution: {integrity: sha512-MxGxEomGxsJiL9xe/7ZwVgwdn8XVKWbPvxpVQl3nWOjrS0Ce63JsfzxUc4aU3GvRcUPYsfufHmJ17BFyKxeA4g==}
dependencies:
'@types/filesystem': 0.0.33
'@types/har-format': 1.2.13
dev: true
/@types/diff@5.0.3: /@types/diff@5.0.3:
resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==} resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==}
dev: true dev: true
/@types/filesystem@0.0.33:
resolution: {integrity: sha512-2KedRPzwu2K528vFkoXnnWdsG0MtUwPjuA7pRy4vKxlxHEe8qUDZibYHXJKZZr2Cl/ELdCWYqyb/MKwsUuzBWw==}
dependencies:
'@types/filewriter': 0.0.30
dev: true
/@types/filewriter@0.0.30:
resolution: {integrity: sha512-lB98tui0uxc7erbj0serZfJlHKLNJHwBltPnbmO1WRpL5T325GOHRiQfr2E29V2q+S1brDO63Fpdt6vb3bES9Q==}
dev: true
/@types/har-format@1.2.13:
resolution: {integrity: sha512-PwBsCBD3lDODn4xpje3Y1di0aDJp4Ww7aSfMRVw6ysnxD4I7Wmq2mBkSKaDtN403hqH5sp6c9xQUvFYY3+lkBg==}
dev: true
/@types/json-schema@7.0.11: /@types/json-schema@7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
@ -569,7 +628,7 @@ packages:
'@types/node': 18.16.3 '@types/node': 18.16.3
dev: true dev: true
/@typescript-eslint/eslint-plugin@5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.28.0)(typescript@5.0.4): /@typescript-eslint/eslint-plugin@5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==} resolution: {integrity: sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -583,12 +642,12 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.5.1 '@eslint-community/regexpp': 4.5.1
'@typescript-eslint/parser': 5.59.1(eslint@8.28.0)(typescript@5.0.4) '@typescript-eslint/parser': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
'@typescript-eslint/scope-manager': 5.59.1 '@typescript-eslint/scope-manager': 5.59.1
'@typescript-eslint/type-utils': 5.59.1(eslint@8.28.0)(typescript@5.0.4) '@typescript-eslint/type-utils': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.1(eslint@8.28.0)(typescript@5.0.4) '@typescript-eslint/utils': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
debug: 4.3.4 debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
grapheme-splitter: 1.0.4 grapheme-splitter: 1.0.4
ignore: 5.2.4 ignore: 5.2.4
natural-compare-lite: 1.4.0 natural-compare-lite: 1.4.0
@ -599,7 +658,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser@5.59.1(eslint@8.28.0)(typescript@5.0.4): /@typescript-eslint/parser@5.59.1(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==} resolution: {integrity: sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -615,7 +674,7 @@ packages:
'@typescript-eslint/types': 5.59.1 '@typescript-eslint/types': 5.59.1
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4) '@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
debug: 4.3.4 debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -629,7 +688,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.59.1 '@typescript-eslint/visitor-keys': 5.59.1
dev: true dev: true
/@typescript-eslint/type-utils@5.59.1(eslint@8.28.0)(typescript@5.0.4): /@typescript-eslint/type-utils@5.59.1(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==} resolution: {integrity: sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -642,9 +701,9 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4) '@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.1(eslint@8.28.0)(typescript@5.0.4) '@typescript-eslint/utils': 5.59.1(eslint@8.46.0)(typescript@5.0.4)
debug: 4.3.4 debug: 4.3.4
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
tsutils: 3.21.0(typescript@5.0.4) tsutils: 3.21.0(typescript@5.0.4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
@ -677,7 +736,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils@5.59.1(eslint@8.28.0)(typescript@5.0.4): /@typescript-eslint/utils@5.59.1(eslint@8.46.0)(typescript@5.0.4):
resolution: {integrity: sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==} resolution: {integrity: sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -686,13 +745,13 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.28.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.46.0)
'@types/json-schema': 7.0.11 '@types/json-schema': 7.0.11
'@types/semver': 7.3.13 '@types/semver': 7.3.13
'@typescript-eslint/scope-manager': 5.59.1 '@typescript-eslint/scope-manager': 5.59.1
'@typescript-eslint/types': 5.59.1 '@typescript-eslint/types': 5.59.1
'@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4) '@typescript-eslint/typescript-estree': 5.59.1(typescript@5.0.4)
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-scope: 5.1.1 eslint-scope: 5.1.1
semver: 7.5.0 semver: 7.5.0
transitivePeerDependencies: transitivePeerDependencies:
@ -722,16 +781,16 @@ packages:
vscode-textmate: 5.2.0 vscode-textmate: 5.2.0
dev: false dev: false
/acorn-jsx@5.3.2(acorn@8.8.0): /acorn-jsx@5.3.2(acorn@8.10.0):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.10.0
dev: true dev: true
/acorn@8.8.0: /acorn@8.10.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
dev: true dev: true
@ -821,6 +880,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/async@1.5.2:
resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==}
dev: true
/atob@2.1.2: /atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'} engines: {node: '>= 4.5.0'}
@ -1446,18 +1509,7 @@ packages:
optional: true optional: true
dev: true dev: true
/eslint-plugin-header@3.1.1(eslint@8.28.0): /eslint-plugin-path-alias@1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.46.0):
resolution: {integrity: sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==}
peerDependencies:
eslint: '>=7.7.0'
peerDependenciesMeta:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
dev: true
/eslint-plugin-path-alias@1.0.0(patch_hash=m6sma4g6bh67km3q6igf6uxaja)(eslint@8.28.0):
resolution: {integrity: sha512-FXus57yC+Zd3sMv46pbloXYwFeNVNHJqlACr9V68FG/IzGFBBokGJpmjDbEjpt8ZCeVSndUubeDWWl2A8sCNVQ==} resolution: {integrity: sha512-FXus57yC+Zd3sMv46pbloXYwFeNVNHJqlACr9V68FG/IzGFBBokGJpmjDbEjpt8ZCeVSndUubeDWWl2A8sCNVQ==}
peerDependencies: peerDependencies:
eslint: ^7 eslint: ^7
@ -1465,14 +1517,18 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
nanomatch: 1.2.13 nanomatch: 1.2.13
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
patched: true patched: true
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.28.0): /eslint-plugin-simple-header@1.0.2:
resolution: {integrity: sha512-K1EJ/ueBIjPRA8qR44Ymo+GDmPYYmfoODtainGxVr7PSbX6QiaY+pTuGCrOhO+AtVsYJs8GLSVdGUTXyAxAtOA==}
dev: false
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.46.0):
resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==} resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
peerDependencies: peerDependencies:
eslint: '>=5.0.0' eslint: '>=5.0.0'
@ -1480,10 +1536,10 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
dev: true dev: true
/eslint-plugin-unused-imports@2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.28.0): /eslint-plugin-unused-imports@2.0.0(@typescript-eslint/eslint-plugin@5.59.1)(eslint@8.46.0):
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -1495,8 +1551,8 @@ packages:
eslint: eslint:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.28.0)(typescript@5.0.4) '@typescript-eslint/eslint-plugin': 5.59.1(@typescript-eslint/parser@5.59.1)(eslint@8.46.0)(typescript@5.0.4)
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe) eslint: 8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
eslint-rule-composer: 0.3.0 eslint-rule-composer: 0.3.0
dev: true dev: true
@ -1513,49 +1569,34 @@ packages:
estraverse: 4.3.0 estraverse: 4.3.0
dev: true dev: true
/eslint-scope@7.1.1: /eslint-scope@7.2.2:
resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
esrecurse: 4.3.0 esrecurse: 4.3.0
estraverse: 5.3.0 estraverse: 5.3.0
dev: true dev: true
/eslint-utils@3.0.0(eslint@8.28.0):
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
peerDependenciesMeta:
eslint:
optional: true
dependencies:
eslint: 8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe)
eslint-visitor-keys: 2.1.0
dev: true
/eslint-visitor-keys@2.1.0:
resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
engines: {node: '>=10'}
dev: true
/eslint-visitor-keys@3.3.0:
resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint-visitor-keys@3.4.0: /eslint-visitor-keys@3.4.0:
resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==} resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint@8.28.0(patch_hash=7wc6icvgtg3uswirb5tpsbjnbe): /eslint-visitor-keys@3.4.2:
resolution: {integrity: sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==} resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4):
resolution: {integrity: sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
'@eslint/eslintrc': 1.3.3 '@eslint-community/eslint-utils': 4.4.0(eslint@8.46.0)
'@humanwhocodes/config-array': 0.11.7 '@eslint-community/regexpp': 4.6.2
'@eslint/eslintrc': 2.1.1
'@eslint/js': 8.46.0
'@humanwhocodes/config-array': 0.11.10
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
ajv: 6.12.6 ajv: 6.12.6
@ -1564,60 +1605,46 @@ packages:
debug: 4.3.4 debug: 4.3.4
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.1.1 eslint-scope: 7.2.2
eslint-utils: 3.0.0(eslint@8.28.0) eslint-visitor-keys: 3.4.2
eslint-visitor-keys: 3.3.0 espree: 9.6.1
espree: 9.4.0 esquery: 1.5.0
esquery: 1.4.0
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1 file-entry-cache: 6.0.1
find-up: 5.0.0 find-up: 5.0.0
glob-parent: 6.0.2 glob-parent: 6.0.2
globals: 13.17.0 globals: 13.20.0
grapheme-splitter: 1.0.4 graphemer: 1.4.0
ignore: 5.2.0 ignore: 5.2.4
import-fresh: 3.3.0
imurmurhash: 0.1.4 imurmurhash: 0.1.4
is-glob: 4.0.3 is-glob: 4.0.3
is-path-inside: 3.0.3 is-path-inside: 3.0.3
js-sdsl: 4.1.5
js-yaml: 4.1.0 js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1 json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1 levn: 0.4.1
lodash.merge: 4.6.2 lodash.merge: 4.6.2
minimatch: 3.1.2 minimatch: 3.1.2
natural-compare: 1.4.0 natural-compare: 1.4.0
optionator: 0.9.1 optionator: 0.9.3
regexpp: 3.2.0
strip-ansi: 6.0.1 strip-ansi: 6.0.1
strip-json-comments: 3.1.1
text-table: 0.2.0 text-table: 0.2.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
patched: true patched: true
/espree@9.4.0: /espree@9.6.1:
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.10.0
acorn-jsx: 5.3.2(acorn@8.8.0) acorn-jsx: 5.3.2(acorn@8.10.0)
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.4.2
dev: true dev: true
/espree@9.4.1: /esquery@1.5.0:
resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
acorn: 8.8.0
acorn-jsx: 5.3.2(acorn@8.8.0)
eslint-visitor-keys: 3.3.0
dev: true
/esquery@1.4.0:
resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
dependencies: dependencies:
estraverse: 5.3.0 estraverse: 5.3.0
@ -1858,8 +1885,8 @@ packages:
which: 1.3.1 which: 1.3.1
dev: true dev: true
/globals@13.17.0: /globals@13.20.0:
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
type-fest: 0.20.2 type-fest: 0.20.2
@ -1881,10 +1908,18 @@ packages:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
dev: true dev: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: true
/grapheme-splitter@1.0.4: /grapheme-splitter@1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true dev: true
/graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
dev: true
/hard-rejection@2.1.0: /hard-rejection@2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1972,11 +2007,6 @@ packages:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: true dev: true
/ignore@5.2.0:
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
engines: {node: '>= 4'}
dev: true
/ignore@5.2.4: /ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@ -2168,10 +2198,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/js-sdsl@4.1.5:
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
dev: true
/js-tokens@4.0.0: /js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: true dev: true
@ -2203,6 +2229,12 @@ packages:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
dev: false dev: false
/jszip@2.7.0:
resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==}
dependencies:
pako: 1.0.11
dev: true
/kind-of@3.2.2: /kind-of@3.2.2:
resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2373,6 +2405,10 @@ packages:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: true dev: true
/monaco-editor@0.43.0:
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
dev: false
/ms@2.0.0: /ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: true dev: true
@ -2485,16 +2521,16 @@ packages:
wrappy: 1.0.2 wrappy: 1.0.2
dev: true dev: true
/optionator@0.9.1: /optionator@0.9.3:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dependencies: dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
deep-is: 0.1.4 deep-is: 0.1.4
fast-levenshtein: 2.0.6 fast-levenshtein: 2.0.6
levn: 0.4.1 levn: 0.4.1
prelude-ls: 1.2.1 prelude-ls: 1.2.1
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.3
dev: true dev: true
/p-limit@2.3.0: /p-limit@2.3.0:
@ -2530,6 +2566,10 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: true
/parent-module@1.0.1: /parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2681,6 +2721,11 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/q@1.5.1:
resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==}
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
@ -2734,11 +2779,6 @@ packages:
safe-regex: 1.1.0 safe-regex: 1.1.0
dev: true dev: true
/regexpp@3.2.0:
resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
engines: {node: '>=8'}
dev: true
/require-directory@2.1.1: /require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3323,11 +3363,6 @@ packages:
isexe: 2.0.0 isexe: 2.0.0
dev: true dev: true
/word-wrap@1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
engines: {node: '>=0.10.0'}
dev: true
/wrap-ansi@7.0.0: /wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -3405,3 +3440,18 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/zip-local@0.3.5:
resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==}
dependencies:
async: 1.5.2
graceful-fs: 4.2.11
jszip: 2.7.0
q: 1.5.1
dev: true
github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3:
resolution: {tarball: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3}
name: gifenc
version: 1.0.3
dev: false

@ -18,14 +18,19 @@
*/ */
import esbuild from "esbuild"; import esbuild from "esbuild";
import { readdir } from "fs/promises";
import { join } from "path";
import { commonOpts, globPlugins, isStandalone, VERSION, watch } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
const defines = { const defines = {
IS_STANDALONE: isStandalone, IS_STANDALONE: isStandalone,
IS_DEV: JSON.stringify(watch), IS_DEV: JSON.stringify(watch),
IS_UPDATER_DISABLED: updaterDisabled,
IS_WEB: false,
IS_EXTENSION: false,
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(), BUILD_TIMESTAMP,
}; };
if (defines.IS_STANDALONE === "false") if (defines.IS_STANDALONE === "false")
// If this is a local build (not standalone), optimise // If this is a local build (not standalone), optimise
@ -40,26 +45,61 @@ const nodeCommonOpts = {
format: "cjs", format: "cjs",
platform: "node", platform: "node",
target: ["esnext"], target: ["esnext"],
minify: true, external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
bundle: true,
external: ["electron", ...commonOpts.external],
define: defines, define: defines,
}; };
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`; const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
const sourcemap = watch ? "inline" : "external"; const sourcemap = watch ? "inline" : "external";
await Promise.all([ /**
// common preload * @type {import("esbuild").Plugin}
esbuild.build({ */
...nodeCommonOpts, const globNativesPlugin = {
entryPoints: ["src/preload.ts"], name: "glob-natives-plugin",
outfile: "dist/preload.js", setup: build => {
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") }, const filter = /^~pluginNatives$/;
sourcemap, build.onResolve({ filter }, args => {
}), return {
namespace: "import-natives",
path: args.path
};
});
// Discord Desktop main & renderer 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({ esbuild.build({
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
@ -69,8 +109,12 @@ await Promise.all([
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: true,
IS_VENCORD_DESKTOP: false IS_VESKTOP: false
} },
plugins: [
...nodeCommonOpts.plugins,
globNativesPlugin
]
}), }),
esbuild.build({ esbuild.build({
...commonOpts, ...commonOpts,
@ -87,13 +131,24 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: true,
IS_VENCORD_DESKTOP: false IS_VESKTOP: false
}
}),
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/preload.js",
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false
} }
}), }),
// Vencord Desktop main & renderer // Vencord Desktop main & renderer & preload
esbuild.build({ esbuild.build({
...nodeCommonOpts, ...nodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
@ -103,8 +158,12 @@ await Promise.all([
define: { define: {
...defines, ...defines,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: false,
IS_VENCORD_DESKTOP: true IS_VESKTOP: true
} },
plugins: [
...nodeCommonOpts.plugins,
globNativesPlugin
]
}), }),
esbuild.build({ esbuild.build({
...commonOpts, ...commonOpts,
@ -121,9 +180,20 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: false,
IS_VENCORD_DESKTOP: true IS_VESKTOP: true
}
}),
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/vencordDesktopPreload.js",
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("vencordDesktopPreload") },
sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true
} }
}), }),
]).catch(err => { ]).catch(err => {

@ -17,14 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import esbuild from "esbuild"; import esbuild from "esbuild";
import { zip } from "fflate";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises"; import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
import Zip from "zip-local";
import { commonOpts, globPlugins, VERSION, watch } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
/** /**
* @type {esbuild.BuildOptions} * @type {esbuild.BuildOptions}
@ -42,28 +41,69 @@ const commonOptions = {
target: ["esnext"], target: ["esnext"],
define: { define: {
IS_WEB: "true", IS_WEB: "true",
IS_EXTENSION: "false",
IS_STANDALONE: "true", IS_STANDALONE: "true",
IS_DEV: JSON.stringify(watch), IS_DEV: JSON.stringify(watch),
IS_DISCORD_DESKTOP: "false", IS_DISCORD_DESKTOP: "false",
IS_VENCORD_DESKTOP: "false", IS_VESKTOP: "false",
IS_UPDATER_DISABLED: "true",
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP: Date.now(), BUILD_TIMESTAMP,
} }
}; };
const MonacoWorkerEntryPoints = [
"vs/language/css/css.worker.js",
"vs/editor/editor.worker.js"
];
const RnNoiseFiles = [
"dist/rnnoise.wasm",
"dist/rnnoise_simd.wasm",
"dist/rnnoise/workletProcessor.js",
"LICENSE"
];
await Promise.all( await Promise.all(
[ [
esbuild.build({
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
bundle: true,
minify: true,
format: "iife",
outbase: "node_modules/monaco-editor/esm/",
outdir: "dist/monaco"
}),
esbuild.build({
entryPoints: ["browser/monaco.ts"],
bundle: true,
minify: true,
format: "iife",
outfile: "dist/monaco/index.js",
loader: {
".ttf": "file"
}
}),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
outfile: "dist/browser.js", outfile: "dist/browser.js",
footer: { js: "//# sourceURL=VencordWeb" }, footer: { js: "//# sourceURL=VencordWeb" },
}), }),
esbuild.build({
...commonOptions,
outfile: "dist/extension.js",
define: {
...commonOptions?.define,
IS_EXTENSION: "true",
},
footer: { js: "//# sourceURL=VencordWeb" },
}),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
define: { define: {
"window": "unsafeWindow", ...(commonOptions?.define),
...(commonOptions?.define) window: "unsafeWindow",
}, },
outfile: "dist/Vencord.user.js", outfile: "dist/Vencord.user.js",
banner: { banner: {
@ -78,12 +118,41 @@ await Promise.all(
); );
/** /**
* @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>} * @type {(dir: string) => Promise<string[]>}
*/ */
async function buildPluginZip(target, files, shouldZip) { async function globDir(dir) {
const files = [];
for (const child of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, child.name);
if (child.isDirectory())
files.push(...await globDir(p));
else
files.push(p);
}
return files;
}
/**
* @type {(dir: string, basePath?: string) => Promise<Record<string, string>>}
*/
async function loadDir(dir, basePath = "") {
const files = await globDir(dir);
return Object.fromEntries(await Promise.all(files.map(async f => [f.slice(basePath.length), await readFile(f)])));
}
/**
* @type {(target: string, files: string[]) => Promise<void>}
*/
async function buildExtension(target, files) {
const entries = { const entries = {
"dist/Vencord.js": await readFile("dist/browser.js"), "dist/Vencord.js": await readFile("dist/extension.js"),
"dist/Vencord.css": await readFile("dist/browser.css"), "dist/Vencord.css": await readFile("dist/extension.css"),
...await loadDir("dist/monaco"),
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
))),
...Object.fromEntries(await Promise.all(files.map(async f => { ...Object.fromEntries(await Promise.all(files.map(async f => {
let content = await readFile(join("browser", f)); let content = await readFile(join("browser", f));
if (f.startsWith("manifest")) { if (f.startsWith("manifest")) {
@ -99,31 +168,15 @@ async function buildPluginZip(target, files, shouldZip) {
}))), }))),
}; };
if (shouldZip) { await rm(target, { recursive: true, force: true });
return new Promise((resolve, reject) => { await Promise.all(Object.entries(entries).map(async ([file, content]) => {
zip(entries, {}, (err, data) => { const dest = join("dist", target, file);
if (err) { const parentDirectory = join(dest, "..");
reject(err); await mkdir(parentDirectory, { recursive: true });
} else { await writeFile(dest, content);
const out = join("dist", target); }));
writeFile(out, data).then(() => {
console.info("Extension written to " + out);
resolve();
}).catch(reject);
}
});
});
} else {
await rm(target, { recursive: true, force: true });
await Promise.all(Object.entries(entries).map(async ([file, content]) => {
const dest = join("dist", target, file);
const parentDirectory = join(dest, "..");
await mkdir(parentDirectory, { recursive: true });
await writeFile(dest, content);
}));
console.info("Unpacked Extension written to dist/" + target); console.info("Unpacked Extension written to dist/" + target);
}
} }
const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => {
@ -141,8 +194,12 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
await Promise.all([ await Promise.all([
appendCssRuntime, appendCssRuntime,
buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true), buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildPluginZip("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false), buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
buildPluginZip("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"], false),
]); ]);
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");

@ -20,29 +20,39 @@ import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js"; import "../checkNodeVersion.js";
import { exec, execSync } from "child_process"; import { exec, execSync } from "child_process";
import { existsSync, readFileSync } from "fs"; import { constants as FsConstants, readFileSync } from "fs";
import { readdir, readFile } from "fs/promises"; import { access, readdir, readFile } from "fs/promises";
import { join, relative } from "path"; import { join, relative } from "path";
import { promisify } from "util"; import { promisify } from "util";
// wtf is this assert syntax // wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" }; import PackageJSON from "../../package.json" assert { type: "json" };
import { getPluginTarget } from "../utils.mjs";
export const VERSION = PackageJSON.version; export const VERSION = PackageJSON.version;
export const BUILD_TIMESTAMP = Date.now(); // https://reproducible-builds.org/docs/source-date-epoch/
export const BUILD_TIMESTAMP = Number(process.env.SOURCE_DATE_EPOCH) || Date.now();
export const watch = process.argv.includes("--watch"); export const watch = process.argv.includes("--watch");
export const isStandalone = JSON.stringify(process.argv.includes("--standalone")); export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim(); export const updaterDisabled = JSON.stringify(process.argv.includes("--disable-updater"));
export const gitHash = process.env.VENCORD_HASH || execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
export const banner = { export const banner = {
js: ` js: `
// Vencord ${gitHash} // Vencord ${gitHash}
// Standalone: ${isStandalone} // Standalone: ${isStandalone}
// Platform: ${isStandalone === "false" ? process.platform : "Universal"} // Platform: ${isStandalone === "false" ? process.platform : "Universal"}
// Updater disabled: ${updaterDisabled}
`.trim() `.trim()
}; };
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs")); 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 // https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
/** /**
* @type {import("esbuild").Plugin} * @type {import("esbuild").Plugin}
@ -75,20 +85,19 @@ export const globPlugins = kind => ({
let plugins = "\n"; let plugins = "\n";
let i = 0; let i = 0;
for (const dir of pluginDirs) { for (const dir of pluginDirs) {
if (!existsSync(`./src/${dir}`)) continue; if (!await existsAsync(`./src/${dir}`)) continue;
const files = await readdir(`./src/${dir}`); const files = await readdir(`./src/${dir}`);
for (const file of files) { for (const file of files) {
if (file.startsWith("_") || file.startsWith(".")) continue; if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue; if (file === "index.ts") continue;
const fileBits = file.split("."); const target = getPluginTarget(file);
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1))) { if (target) {
const mod = fileBits.at(-2); if (target === "dev" && !watch) continue;
if (mod === "dev" && !watch) continue; if (target === "web" && kind === "discordDesktop") continue;
if (mod === "web" && kind === "discordDesktop") continue; if (target === "desktop" && kind === "web") continue;
if (mod === "desktop" && kind === "web") continue; if (target === "discordDesktop" && kind !== "discordDesktop") continue;
if (mod === "discordDesktop" && kind !== "discordDesktop") continue; if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
if (mod === "vencordDesktop" && kind !== "vencordDesktop") continue;
} }
const mod = `p${i}`; const mod = `p${i}`;
@ -133,11 +142,14 @@ export const gitRemotePlugin = {
namespace: "git-remote", path: args.path namespace: "git-remote", path: args.path
})); }));
build.onLoad({ filter, namespace: "git-remote" }, async () => { build.onLoad({ filter, namespace: "git-remote" }, async () => {
const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" }); let remote = process.env.VENCORD_REMOTE;
const remote = res.stdout.trim() if (!remote) {
.replace("https://github.com/", "") const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" });
.replace("git@github.com:", "") remote = res.stdout.trim()
.replace(/.git$/, ""); .replace("https://github.com/", "")
.replace("git@github.com:", "")
.replace(/.git$/, "");
}
return { contents: `export default "${remote}"` }; return { contents: `export default "${remote}"` };
}); });

@ -18,9 +18,12 @@
import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs";
import { access, readFile } from "fs/promises"; import { access, readFile } from "fs/promises";
import { join } from "path"; import { join, sep } from "path";
import { normalize as posixNormalize, sep as posixSep } from "path/posix";
import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript";
import { getPluginTarget } from "./utils.mjs";
interface Dev { interface Dev {
name: string; name: string;
id: string; id: string;
@ -37,6 +40,7 @@ interface PluginData {
required: boolean; required: boolean;
enabledByDefault: boolean; enabledByDefault: boolean;
target: "discordDesktop" | "vencordDesktop" | "web" | "dev"; target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
filePath: string;
} }
const devs = {} as Record<string, Dev>; const devs = {} as Record<string, Dev>;
@ -151,21 +155,29 @@ async function parseFile(fileName: string) {
case "required": case "required":
case "enabledByDefault": case "enabledByDefault":
data[key] = value.kind === SyntaxKind.TrueKeyword; data[key] = value.kind === SyntaxKind.TrueKeyword;
if (!data[key] && value.kind !== SyntaxKind.FalseKeyword) throw fail(`${key} is not a boolean literal`);
break; break;
} }
} }
if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing"); if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing");
const fileBits = fileName.split("."); const target = getPluginTarget(fileName);
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1)!)) { if (target) {
const mod = fileBits.at(-2)!; if (!["web", "discordDesktop", "vencordDesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`);
if (!["web", "discordDesktop", "vencordDesktop", "dev"].includes(mod)) throw fail(`invalid target ${fileBits.at(-2)}`); data.target = target as any;
data.target = mod as any;
} }
return data; data.filePath = posixNormalize(fileName)
.split(sep)
.join(posixSep)
.replace(/\/index\.([jt]sx?)$/, "")
.replace(/^src\/plugins\//, "");
let readme = "";
try {
readme = readFileSync(join(fileName, "..", "README.md"), "utf-8");
} catch { }
return [data, readme] as const;
} }
throw fail("no default export called 'definePlugin' found"); throw fail("no default export called 'definePlugin' found");
@ -194,18 +206,24 @@ function isPluginFile({ name }: { name: string; }) {
(async () => { (async () => {
parseDevs(); parseDevs();
const plugins = ["src/plugins", "src/plugins/_core"].flatMap(dir => const plugins = [] as PluginData[];
const readmes = {} as Record<string, string>;
await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir =>
readdirSync(dir, { withFileTypes: true }) readdirSync(dir, { withFileTypes: true })
.filter(isPluginFile) .filter(isPluginFile)
.map(async dirent => .map(async dirent => {
parseFile(await getEntryPoint(dir, dirent)) const [data, readme] = await parseFile(await getEntryPoint(dir, dirent));
) plugins.push(data);
); if (readme) readmes[data.name] = readme;
})
));
const data = JSON.stringify(await Promise.all(plugins)); const data = JSON.stringify(plugins);
if (process.argv.length > 2) { if (process.argv.length > 3) {
writeFileSync(process.argv[2], data); writeFileSync(process.argv[2], data);
writeFileSync(process.argv[3], JSON.stringify(readmes));
} else { } else {
console.log(data); console.log(data);
} }

@ -61,6 +61,13 @@ const report = {
otherErrors: [] as string[] otherErrors: [] as string[]
}; };
const IGNORED_DISCORD_ERRORS = [
"KeybindStore: Looking for callback action",
"Unable to process domain list delta: Client revision number is null",
"Downloading the full bad domains file",
/\[GatewaySocket\].{0,110}Cannot access '/
] as Array<string | RegExp>;
function toCodeBlock(s: string) { function toCodeBlock(s: string) {
s = s.replace(/```/g, "`\u200B`\u200B`"); s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```"; return "```" + s + " ```";
@ -86,6 +93,8 @@ async function printReport() {
console.log(` - Error: ${toCodeBlock(p.error)}`); console.log(` - Error: ${toCodeBlock(p.error)}`);
}); });
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
console.log("## Discord Errors"); console.log("## Discord Errors");
report.otherErrors.forEach(e => { report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e)}`);
@ -171,7 +180,7 @@ page.on("console", async e => {
plugin, plugin,
type, type,
id, id,
match: regex, match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
error: cause error: cause
}); });
break; break;
@ -259,11 +268,11 @@ function runTime(token: string) {
const { wreq } = Vencord.Webpack; const { wreq } = Vencord.Webpack;
console.error("[PUP_DEBUG]", "Loading all chunks..."); console.error("[PUP_DEBUG]", "Loading all chunks...");
const ids = Function("return" + wreq.u.toString().match(/\{.+\}/s)![0])(); const ids = Function("return" + wreq.u.toString().match(/(?<=\()\{.+?\}/s)![0])();
for (const id in ids) { for (const id in ids) {
const isWasm = await fetch(wreq.p + wreq.u(id)) const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text()) .then(r => r.text())
.then(t => t.includes(".module.wasm")); .then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
if (!isWasm) if (!isWasm)
await wreq.e(id as any); await wreq.e(id as any);
@ -280,7 +289,7 @@ function runTime(token: string) {
setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000); setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000);
}, 1000)); }, 1000));
} catch (e) { } catch (e) {
console.error("[PUP_DEBUG]", "A fatal error occured"); console.error("[PUP_DEBUG]", "A fatal error occurred");
console.error("[PUP_DEBUG]", e); console.error("[PUP_DEBUG]", e);
process.exit(1); process.exit(1);
} }

3
scripts/header-new.txt Normal file

@ -0,0 +1,3 @@
Vencord, a Discord client mod
Copyright (c) {year} {author}
SPDX-License-Identifier: GPL-3.0-or-later

17
scripts/header-old.txt Normal file

@ -0,0 +1,17 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) {year} {author}
*
* 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/>.
*/

30
scripts/utils.mjs Normal file

@ -0,0 +1,30 @@
/*
* 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/>.
*/
/**
* @param {string} filePath
* @returns {string | null}
*/
export function getPluginTarget(filePath) {
const pathParts = filePath.split(/[/\\]/);
if (/^index\.tsx?$/.test(pathParts.at(-1))) pathParts.pop();
const identifier = pathParts.at(-1).replace(/\.tsx?$/, "");
const identiferBits = identifier.split(".");
return identiferBits.length === 1 ? null : identiferBits.at(-1);
}

@ -27,6 +27,7 @@ export { PlainSettings, Settings };
import "./utils/quickCss"; import "./utils/quickCss";
import "./webpack/patchWebpack"; import "./webpack/patchWebpack";
import { get as dsGet } from "./api/DataStore";
import { showNotification } from "./api/Notifications"; import { showNotification } from "./api/Notifications";
import { PlainSettings, Settings } from "./api/Settings"; import { PlainSettings, Settings } from "./api/Settings";
import { patches, PMLogger, startAllPlugins } from "./plugins"; import { patches, PMLogger, startAllPlugins } from "./plugins";
@ -38,6 +39,22 @@ import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common"; import { SettingsRouter } from "./webpack/common";
async function syncSettings() { async function syncSettings() {
// pre-check for local shared settings
if (
Settings.cloud.authenticated &&
await dsGet("Vencord_cloudSecret") === null // this has been enabled due to local settings share or some other bug
) {
// show a notification letting them know and tell them how to fix it
showNotification({
title: "Cloud Integrations",
body: "We've noticed you have cloud integrations enabled in another client! Due to limitations, you will " +
"need to re-authenticate to continue using them. Click here to go to the settings page to do so!",
color: "var(--yellow-360)",
onClick: () => SettingsRouter.open("VencordCloud")
});
return;
}
if ( if (
Settings.cloud.settingsSync && // if it's enabled Settings.cloud.settingsSync && // if it's enabled
Settings.cloud.authenticated // if cloud integrations are enabled Settings.cloud.authenticated // if cloud integrations are enabled
@ -119,7 +136,7 @@ if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLow
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.head.append(Object.assign(document.createElement("style"), { document.head.append(Object.assign(document.createElement("style"), {
id: "vencord-native-titlebar-style", id: "vencord-native-titlebar-style",
textContent: "[class*=titleBar-]{display: none!important}" textContent: "[class*=titleBar]{display: none!important}"
})); }));
}, { once: true }); }, { once: true });
} }

@ -1,24 +1,14 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a Discord client mod
* Copyright (c) 2022 * Copyright (c) 2023 Vendicated and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* This program is free software: you can redistribute it and/or modify */
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IpcEvents } from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { IpcRes } from "@utils/types"; import { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { PluginIpcMappings } from "main/ipcPlugins";
import type { UserThemeHeader } from "main/themes";
function invoke<T = any>(event: IpcEvents, ...args: any[]) { function invoke<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.invoke(event, ...args) as Promise<T>; return ipcRenderer.invoke(event, ...args) as Promise<T>;
@ -28,7 +18,26 @@ export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.sendSync(event, ...args) as T; 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 { export default {
themes: {
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES),
},
updater: { updater: {
getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES), getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES),
update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE), update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE),
@ -50,6 +59,10 @@ export default {
ipcRenderer.on(IpcEvents.QUICK_CSS_UPDATE, (_, css) => cb(css)); ipcRenderer.on(IpcEvents.QUICK_CSS_UPDATE, (_, css) => cb(css));
}, },
addThemeChangeListener(cb: () => void) {
ipcRenderer.on(IpcEvents.THEME_UPDATE, () => cb());
},
openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS), openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS),
openEditor: () => invoke<void>(IpcEvents.OPEN_MONACO_EDITOR), openEditor: () => invoke<void>(IpcEvents.OPEN_MONACO_EDITOR),
}, },
@ -58,4 +71,6 @@ export default {
getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>, getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url) openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
}, },
pluginHelpers: PluginHelpers
}; };

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

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

@ -1,4 +1,4 @@
/* eslint-disable header/header */ /* eslint-disable simple-header/header */
/*! /*!
* idb-keyval v6.2.0 * idb-keyval v6.2.0

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

@ -34,6 +34,7 @@ export interface Settings {
useQuickCss: boolean; useQuickCss: boolean;
enableReactDevtools: boolean; enableReactDevtools: boolean;
themeLinks: string[]; themeLinks: string[];
enabledThemes: string[];
frameless: boolean; frameless: boolean;
transparent: boolean; transparent: boolean;
winCtrlQ: boolean; winCtrlQ: boolean;
@ -68,6 +69,7 @@ const DefaultSettings: Settings = {
autoUpdateNotification: true, autoUpdateNotification: true,
useQuickCss: true, useQuickCss: true,
themeLinks: [], themeLinks: [],
enabledThemes: [],
enableReactDevtools: false, enableReactDevtools: false,
frameless: false, frameless: false,
transparent: false, transparent: false,
@ -107,7 +109,7 @@ const saveSettingsOnFrequentAction = debounce(async () => {
} }
}, 60_000); }, 60_000);
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; }; type SubscriptionCallback = ((newValue: any, path: string) => void) & { _paths?: Array<string>; };
const subscriptions = new Set<SubscriptionCallback>(); const subscriptions = new Set<SubscriptionCallback>();
const proxyCache = {} as Record<string, any>; const proxyCache = {} as Record<string, any>;
@ -164,7 +166,7 @@ function makeProxy(settings: any, root = settings, path = ""): Settings {
const setPath = `${path}${path && "."}${p}`; const setPath = `${path}${path && "."}${p}`;
delete proxyCache[setPath]; delete proxyCache[setPath];
for (const subscription of subscriptions) { for (const subscription of subscriptions) {
if (!subscription._path || subscription._path === setPath) { if (!subscription._paths || subscription._paths.includes(setPath)) {
subscription(v, setPath); subscription(v, setPath);
} }
} }
@ -235,7 +237,8 @@ type ResolvePropDeep<T, P> = P extends "" ? T :
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void; export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void;
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void; export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void;
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) { export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
(onUpdate as SubscriptionCallback)._path = path; if (path)
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
subscriptions.add(onUpdate); subscriptions.add(onUpdate);
} }

@ -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));
}

@ -141,7 +141,7 @@ export const compileStyle = (style: Style) => {
*/ */
export const classNameToSelector = (name: string, prefix = "") => name.split(" ").map(n => `.${prefix}${n}`).join(""); export const classNameToSelector = (name: string, prefix = "") => name.split(" ").map(n => `.${prefix}${n}`).join("");
type ClassNameFactoryArg = string | string[] | Record<string, unknown>; type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
/** /**
* @param prefix The prefix to add to each class, defaults to `""` * @param prefix The prefix to add to each class, defaults to `""`
* @returns A classname generator function * @returns A classname generator function
@ -154,9 +154,9 @@ type ClassNameFactoryArg = string | string[] | Record<string, unknown>;
export const classNameFactory = (prefix: string = "") => (...args: ClassNameFactoryArg[]) => { export const classNameFactory = (prefix: string = "") => (...args: ClassNameFactoryArg[]) => {
const classNames = new Set<string>(); const classNames = new Set<string>();
for (const arg of args) { for (const arg of args) {
if (typeof arg === "string") classNames.add(arg); if (arg && typeof arg === "string") classNames.add(arg);
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name)); else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
else if (typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name)); else if (arg && typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
} }
return Array.from(classNames, name => prefix + name).join(" "); return Array.from(classNames, name => prefix + name).join(" ");
}; };

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

@ -0,0 +1,21 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByPropsLazy } from "@webpack";
import { Parser } from "@webpack/common";
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
/**
* Renders code in a Discord codeblock
*/
export function CodeBlock(props: { content?: string, lang: string; }) {
return (
<div className={CodeContainerClasses.markup}>
{Parser.defaultRules.codeBlock.react(props, null, {})}
</div>
);
}

@ -28,8 +28,8 @@ interface BaseIconProps extends IconProps {
interface IconProps extends SVGProps<SVGSVGElement> { interface IconProps extends SVGProps<SVGSVGElement> {
className?: string; className?: string;
height?: number; height?: string | number;
width?: number; width?: string | number;
} }
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) { function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
@ -97,7 +97,7 @@ export function OpenExternalIcon(props: IconProps) {
> >
<polygon <polygon
fill="currentColor" fill="currentColor"
fill-rule="nonzero" fillRule="nonzero"
points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8" points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8"
/> />
</Icon> </Icon>
@ -121,9 +121,13 @@ export function InfoIcon(props: IconProps) {
<Icon <Icon
{...props} {...props}
className={classes(props.className, "vc-info-icon")} className={classes(props.className, "vc-info-icon")}
viewBox="0 0 12 12" viewBox="0 0 24 24"
> >
<path fill="currentColor" d="M6 1C3.243 1 1 3.244 1 6c0 2.758 2.243 5 5 5s5-2.242 5-5c0-2.756-2.243-5-5-5zm0 2.376a.625.625 0 110 1.25.625.625 0 010-1.25zM7.5 8.5h-3v-1h1V6H5V5h1a.5.5 0 01.5.5v2h1v1z" /> <path
fill="currentColor"
transform="translate(2 2)"
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
/>
</Icon> </Icon>
); );
} }
@ -139,8 +143,8 @@ export function OwnerCrownIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="evenodd"
clip-rule="evenodd" clipRule="evenodd"
d="M13.6572 5.42868C13.8879 5.29002 14.1806 5.30402 14.3973 5.46468C14.6133 5.62602 14.7119 5.90068 14.6473 6.16202L13.3139 11.4954C13.2393 11.7927 12.9726 12.0007 12.6666 12.0007H3.33325C3.02725 12.0007 2.76058 11.792 2.68592 11.4954L1.35258 6.16202C1.28792 5.90068 1.38658 5.62602 1.60258 5.46468C1.81992 5.30468 2.11192 5.29068 2.34325 5.42868L5.13192 7.10202L7.44592 3.63068C7.46173 3.60697 7.48377 3.5913 7.50588 3.57559C7.5192 3.56612 7.53255 3.55663 7.54458 3.54535L6.90258 2.90268C6.77325 2.77335 6.77325 2.56068 6.90258 2.43135L7.76458 1.56935C7.89392 1.44002 8.10658 1.44002 8.23592 1.56935L9.09792 2.43135C9.22725 2.56068 9.22725 2.77335 9.09792 2.90268L8.45592 3.54535C8.46794 3.55686 8.48154 3.56651 8.49516 3.57618C8.51703 3.5917 8.53897 3.60727 8.55458 3.63068L10.8686 7.10202L13.6572 5.42868ZM2.66667 12.6673H13.3333V14.0007H2.66667V12.6673Z" d="M13.6572 5.42868C13.8879 5.29002 14.1806 5.30402 14.3973 5.46468C14.6133 5.62602 14.7119 5.90068 14.6473 6.16202L13.3139 11.4954C13.2393 11.7927 12.9726 12.0007 12.6666 12.0007H3.33325C3.02725 12.0007 2.76058 11.792 2.68592 11.4954L1.35258 6.16202C1.28792 5.90068 1.38658 5.62602 1.60258 5.46468C1.81992 5.30468 2.11192 5.29068 2.34325 5.42868L5.13192 7.10202L7.44592 3.63068C7.46173 3.60697 7.48377 3.5913 7.50588 3.57559C7.5192 3.56612 7.53255 3.55663 7.54458 3.54535L6.90258 2.90268C6.77325 2.77335 6.77325 2.56068 6.90258 2.43135L7.76458 1.56935C7.89392 1.44002 8.10658 1.44002 8.23592 1.56935L9.09792 2.43135C9.22725 2.56068 9.22725 2.77335 9.09792 2.90268L8.45592 3.54535C8.46794 3.55686 8.48154 3.56651 8.49516 3.57618C8.51703 3.5917 8.53897 3.60727 8.55458 3.63068L10.8686 7.10202L13.6572 5.42868ZM2.66667 12.6673H13.3333V14.0007H2.66667V12.6673Z"
/> />
</Icon> </Icon>
@ -159,8 +163,6 @@ export function ScreenshareIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z" d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z"
/> />
</Icon> </Icon>
@ -190,3 +192,66 @@ export function ImageInvisible(props: IconProps) {
</Icon> </Icon>
); );
} }
export function Microphone(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-microphone")}
viewBox="0 0 24 24"
>
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" />
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V22H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1Z" fill="currentColor" />
</Icon >
);
}
export function CogWheel(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-cog-wheel")}
viewBox="0 0 24 24"
>
<path
clipRule="evenodd"
fill="currentColor"
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
/>
</Icon>
);
}
export function ReplyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-reply-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10 8.26667V4L3 11.4667L10 18.9333V14.56C15 14.56 18.5 16.2667 21 20C20 14.6667 17 9.33333 10 8.26667Z"
/>
</Icon>
);
}
export function DeleteIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-delete-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"
/>
<path
fill="currentColor"
d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"
/>
</Icon>
);
}

@ -0,0 +1,113 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./contributorModal.css";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { DevsById } from "@utils/constants";
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general";
import Plugins from "~plugins";
import { PluginCard } from ".";
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
const cl = classNameFactory("vc-author-modal-");
export function openContributorModal(user: User) {
openModal(modalProps =>
<ModalRoot {...modalProps}>
<ErrorBoundary>
<ModalContent className={cl("root")}>
<ContributorModal user={user} />
</ModalContent>
</ErrorBoundary>
</ModalRoot>
);
}
function GithubIcon() {
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
return <img src={src} alt="GitHub" />;
}
function WebsiteIcon() {
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
return <img src={src} alt="Website" />;
}
function ContributorModal({ user }: { user: User; }) {
useSettings();
const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id));
useEffect(() => {
if (!profile && !user.bot && user.id)
fetchUserProfile(user.id);
}, [user.id]);
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
const plugins = useMemo(() => {
const allPlugins = Object.values(Plugins);
const pluginsByAuthor = DevsById[user.id]
? allPlugins.filter(p => p.authors.includes(DevsById[user.id]))
: allPlugins.filter(p => p.authors.some(a => a.name === user.username));
return pluginsByAuthor
.filter(p => !p.name.endsWith("API"))
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
}, [user.id, user.username]);
return (
<>
<div className={cl("header")}>
<img
className={cl("avatar")}
src={user.getAvatarURL(void 0, 512, true)}
alt=""
/>
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
<div className={cl("links")}>
{website && (
<MaskedLink
href={"https://" + website}
>
<WebsiteIcon />
</MaskedLink>
)}
{githubName && (
<MaskedLink href={`https://github.com/${githubName}`}>
<GithubIcon />
</MaskedLink>
)}
</div>
</div>
<div className={cl("plugins")}>
{plugins.map(p =>
<PluginCard
key={p.name}
plugin={p}
disabled={p.required ?? false}
onRestartNeeded={() => showToast("Restart to apply changes!")}
/>
)}
</div>
</>
);
}

@ -18,17 +18,16 @@
import { generateId } from "@api/Commands"; import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { OptionType, Plugin } from "@utils/types"; import { OptionType, Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByCode, findByPropsLazy } from "@webpack";
import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { Constructor } from "type-fest"; import { Constructor } from "type-fest";
@ -41,7 +40,7 @@ import {
SettingSliderComponent, SettingSliderComponent,
SettingTextComponent SettingTextComponent
} from "./components"; } from "./components";
import hideBotTagStyle from "./userPopoutHideBotTag.css?managed"; import { openContributorModal } from "./ContributorModal";
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
@ -89,30 +88,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const canSubmit = () => Object.values(errors).every(e => !e); const canSubmit = () => Object.values(errors).every(e => !e);
const hasSettings = Boolean(pluginSettings && plugin.options); const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
React.useEffect(() => { React.useEffect(() => {
enableStyle(hideBotTagStyle);
let originalUser: User;
(async () => { (async () => {
for (const user of plugin.authors.slice(0, 6)) { for (const user of plugin.authors.slice(0, 6)) {
const author = user.id const author = user.id
? await UserUtils.fetchUser(`${user.id}`) ? await UserUtils.getUser(`${user.id}`)
// only show name & pfp and no actions so users cannot harass plugin devs for support (send dms, add as friend, etc)
.then(u => (originalUser = u, makeDummyUser(u)))
.catch(() => makeDummyUser({ username: user.name })) .catch(() => makeDummyUser({ username: user.name }))
: makeDummyUser({ username: user.name }); : makeDummyUser({ username: user.name });
setAuthors(a => [...a, author]); setAuthors(a => [...a, author]);
} }
})(); })();
return () => {
disableStyle(hideBotTagStyle);
if (originalUser)
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: originalUser });
};
}, []); }, []);
async function saveAndClose() { async function saveAndClose() {
@ -214,6 +202,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
showDefaultAvatarsForNullUsers showDefaultAvatarsForNullUsers
showUserPopout showUserPopout
renderMoreUsers={renderMoreUsers} renderMoreUsers={renderMoreUsers}
renderUser={(user: User) => (
<Clickable
className={AvatarStyles.clickableAvatar}
onClick={() => openContributorModal(user)}
>
<img
className={AvatarStyles.avatar}
src={user.getAvatarURL(void 0, 80, true)}
alt={user.username}
title={user.username}
/>
</Clickable>
)}
/> />
</div> </div>
</Forms.FormSection> </Forms.FormSection>
@ -226,7 +227,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
</Forms.FormSection> </Forms.FormSection>
</div> </div>
)} )}
<Forms.FormSection> <Forms.FormSection className={Margins.bottom16}>
<Forms.FormTitle tag="h3">Settings</Forms.FormTitle> <Forms.FormTitle tag="h3">Settings</Forms.FormTitle>
{renderSettings()} {renderSettings()}
</Forms.FormSection> </Forms.FormSection>
@ -237,7 +238,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Button <Button
onClick={onClose} onClick={onClose}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
color={Button.Colors.WHITE} color={Button.Colors.PRIMARY}
look={Button.Looks.LINK} look={Button.Looks.LINK}
> >
Cancel Cancel

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionBoolean } from "@utils/types"; import { PluginOptionBoolean } from "@utils/types";
import { Forms, React, Select } from "@webpack/common"; import { Forms, React, Switch } from "@webpack/common";
import { ISettingElementProps } from "."; import { ISettingElementProps } from ".";
@ -31,11 +32,6 @@ export function SettingBooleanComponent({ option, pluginSettings, definedSetting
onError(error !== null); onError(error !== null);
}, [error]); }, [error]);
const options = [
{ label: "Enabled", value: true, default: def === true },
{ label: "Disabled", value: false, default: typeof def === "undefined" || def === false },
];
function handleChange(newValue: boolean): void { function handleChange(newValue: boolean): void {
const isValid = option.isValid?.call(definedSettings, newValue) ?? true; const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid); if (typeof isValid === "string") setError(isValid);
@ -49,18 +45,17 @@ export function SettingBooleanComponent({ option, pluginSettings, definedSetting
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle> <Switch
<Select value={state}
isDisabled={option.disabled?.call(definedSettings) ?? false} onChange={handleChange}
options={options} note={option.description}
placeholder={option.placeholder ?? "Select an option"} disabled={option.disabled?.call(definedSettings) ?? false}
maxVisibleItems={5}
closeOnSelect={true}
select={handleChange}
isSelected={v => v === state}
serialize={v => String(v)}
{...option.componentProps} {...option.componentProps}
/> hideBorder
style={{ marginBottom: "0.5em" }}
>
{wordsToTitle(wordsFromCamel(id))}
</Switch>
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</Forms.FormSection> </Forms.FormSection>
); );

@ -33,11 +33,10 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
const isValid = option.isValid?.call(definedSettings, newValue) ?? true; const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid); if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided."); else if (!isValid) setError("Invalid input provided.");
else { else setError(null);
setError(null);
setState(newValue); setState(newValue);
onChange(newValue); onChange(newValue);
}
} }
return ( return (

@ -0,0 +1,58 @@
.vc-author-modal-root {
padding: 1em;
}
.vc-author-modal-header {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.vc-author-modal-name {
text-transform: none;
flex-grow: 0;
background: var(--background-tertiary);
border-radius: 0 9999px 9999px 0;
padding: 6px 0.8em 6px 0.5em;
font-size: 20px;
height: 20px;
position: relative;
text-wrap: nowrap;
}
.vc-author-modal-name::before {
content: "";
display: block;
position: absolute;
height: 100%;
width: 16px;
background: var(--background-tertiary);
z-index: -1;
left: -16px;
top: 0;
}
.vc-author-modal-avatar {
height: 32px;
width: 32px;
border-radius: 50%;
}
.vc-author-modal-links {
margin-left: auto;
display: flex;
gap: 0.2em;
}
.vc-author-modal-links img {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-tertiary);
box-sizing: border-box
}
.vc-author-modal-plugins {
display: grid;
gap: 0.5em;
}

@ -22,20 +22,19 @@ import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices"; import { showNotice } from "@api/Notices";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex"; import { CogWheel, InfoIcon } from "@components/Icons";
import { Badge } from "@components/PluginSettings/components";
import PluginModal from "@components/PluginSettings/PluginModal"; import PluginModal from "@components/PluginSettings/PluginModal";
import { Switch } from "@components/Switch"; import { AddonCard } from "@components/VencordSettings/AddonCard";
import { SettingsTab } from "@components/VencordSettings/shared"; import { SettingsTab } from "@components/VencordSettings/shared";
import { ChangeList } from "@utils/ChangeList"; import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes, isObjectEmpty } from "@utils/misc";
import { openModalLazy } from "@utils/modal"; import { openModalLazy } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -48,8 +47,6 @@ const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
function showErrorToast(message: string) { function showErrorToast(message: string) {
Toasts.show({ Toasts.show({
@ -93,7 +90,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
isNew?: boolean; isNew?: boolean;
} }
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name]; const settings = Settings.plugins[plugin.name];
const isEnabled = () => settings.enabled ?? false; const isEnabled = () => settings.enabled ?? false;
@ -152,31 +149,31 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
} }
return ( return (
<Flex className={cl("card", { "card-disabled": disabled })} flexDirection="column" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}> <AddonCard
<div className={cl("card-header")}> name={plugin.name}
<Text variant="text-md/bold" className={cl("name")}> description={plugin.description}
{plugin.name}{isNew && <Badge text="NEW" color="#ED4245" />} isNew={isNew}
</Text> enabled={isEnabled()}
setEnabled={toggleEnabled}
disabled={disabled}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
infoButton={
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}> <button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
{plugin.options {plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel /> ? <CogWheel />
: <InfoIcon width="24" height="24" />} : <InfoIcon />}
</button> </button>
<Switch }
checked={isEnabled()} />
onChange={toggleEnabled}
disabled={disabled}
/>
</div>
<Text className={cl("note")} variant="text-sm/normal">{plugin.description}</Text>
</Flex >
); );
} }
const enum SearchStatus { const enum SearchStatus {
ALL, ALL,
ENABLED, ENABLED,
DISABLED DISABLED,
NEW
} }
export default function PluginSettings() { export default function PluginSettings() {
@ -229,6 +226,7 @@ export default function PluginSettings() {
const enabled = settings.plugins[plugin.name]?.enabled; const enabled = settings.plugins[plugin.name]?.enabled;
if (enabled && searchValue.status === SearchStatus.DISABLED) return false; if (enabled && searchValue.status === SearchStatus.DISABLED) return false;
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (!searchValue.value.length) return true; if (!searchValue.value.length) return true;
const v = searchValue.value.toLowerCase(); const v = searchValue.value.toLowerCase();
@ -253,7 +251,7 @@ export default function PluginSettings() {
} }
DataStore.set("Vencord_existingPlugins", existingTimestamps); DataStore.set("Vencord_existingPlugins", existingTimestamps);
return window._.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins; return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
})); }));
type P = JSX.Element | JSX.Element[]; type P = JSX.Element | JSX.Element[];
@ -321,7 +319,8 @@ export default function PluginSettings() {
options={[ options={[
{ label: "Show All", value: SearchStatus.ALL, default: true }, { label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Enabled", value: SearchStatus.ENABLED }, { label: "Show Enabled", value: SearchStatus.ENABLED },
{ label: "Show Disabled", value: SearchStatus.DISABLED } { label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show New", value: SearchStatus.NEW }
]} ]}
serialize={String} serialize={String}
select={onStatusChange} select={onStatusChange}

@ -23,38 +23,6 @@
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
} }
.vc-plugins-card {
background-color: var(--background-secondary-alt);
color: var(--interactive-active);
border-radius: 8px;
display: block;
height: 100%;
padding: 12px;
width: 100%;
transition: 0.1s ease-out;
transition-property: box-shadow, transform, background, opacity;
}
.vc-plugins-card-disabled {
opacity: 0.6;
}
.vc-plugins-card:hover {
background-color: var(--background-tertiary);
transform: translateY(-1px);
box-shadow: var(--elevation-high);
}
.vc-plugins-card-header {
margin-top: auto;
display: flex;
width: 100%;
justify-content: flex-end;
height: 1.5rem;
align-items: center;
gap: 8px;
}
.vc-plugins-info-button { .vc-plugins-info-button {
height: 24px; height: 24px;
width: 24px; width: 24px;
@ -86,27 +54,6 @@
text-align: center; text-align: center;
} }
.vc-plugins-note {
height: 36px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
/* stylelint-disable-next-line property-no-unknown */
box-orient: vertical;
}
.vc-plugins-name {
display: flex;
width: 100%;
align-items: center;
flex-grow: 1;
gap: 8px;
cursor: "default";
}
.vc-plugins-dep-name { .vc-plugins-dep-name {
margin: 0 auto; margin: 0 auto;
} }

@ -1,3 +0,0 @@
[class|="userPopoutOuter"] [class*="botTag"] {
display: none;
}

@ -0,0 +1,77 @@
/*
* 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 "./addonCard.css";
import { classNameFactory } from "@api/Styles";
import { Badge } from "@components/Badge";
import { Switch } from "@components/Switch";
import { Text } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";
const cl = classNameFactory("vc-addon-");
interface Props {
name: ReactNode;
description: ReactNode;
enabled: boolean;
setEnabled: (enabled: boolean) => void;
disabled?: boolean;
isNew?: boolean;
onMouseEnter?: MouseEventHandler<HTMLDivElement>;
onMouseLeave?: MouseEventHandler<HTMLDivElement>;
infoButton?: ReactNode;
footer?: ReactNode;
author?: ReactNode;
}
export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) {
return (
<div
className={cl("card", { "card-disabled": disabled })}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<div className={cl("header")}>
<div className={cl("name-author")}>
<Text variant="text-md/bold" className={cl("name")}>
{name}{isNew && <Badge text="NEW" color="#ED4245" />}
</Text>
{!!author && (
<Text variant="text-md/normal" className={cl("author")}>
{author}
</Text>
)}
</div>
{infoButton}
<Switch
checked={enabled}
onChange={setEnabled}
disabled={disabled}
/>
</div>
<Text className={cl("note")} variant="text-sm/normal">{description}</Text>
{footer}
</div>
);
}

@ -86,7 +86,7 @@ function SettingsSyncSection() {
<Button <Button
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
disabled={!sectionEnabled} disabled={!sectionEnabled}
onClick={() => putCloudSettings()} onClick={() => putCloudSettings(true)}
>Sync to Cloud</Button> >Sync to Cloud</Button>
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!"> <Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (

@ -17,6 +17,7 @@
*/ */
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { CodeBlock } from "@components/CodeBlock";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
@ -299,7 +300,7 @@ function PatchHelper() {
{!!(find && match && replacement) && ( {!!(find && match && replacement) && (
<> <>
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div> <CodeBlock lang="js" content={code} />
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button> <Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
</> </>
)} )}

@ -17,16 +17,35 @@
*/ */
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findLazy } from "@webpack"; import { findByPropsLazy, findLazy } from "@webpack";
import { Card, Forms, React, TextArea } from "@webpack/common"; 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";
import { AddonCard } from "./AddonCard";
import { SettingsTab, wrapTab } from "./shared"; import { SettingsTab, wrapTab } from "./shared";
type FileInput = ComponentType<{
ref: Ref<HTMLInputElement>;
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
multiple?: boolean;
filters?: { name?: string; extensions: string[]; }[];
}>;
const InviteActions = findByPropsLazy("resolveInvite");
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const TextAreaProps = findLazy(m => typeof m.textarea === "string");
const cl = classNameFactory("vc-settings-theme-");
function Validator({ link }: { link: string; }) { function Validator({ link }: { link: string; }) {
const [res, err, pending] = useAwaiter(() => fetch(link).then(res => { const [res, err, pending] = useAwaiter(() => fetch(link).then(res => {
if (res.status > 300) throw `${res.status} ${res.statusText}`; if (res.status > 300) throw `${res.status} ${res.statusText}`;
@ -75,10 +94,191 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
); );
} }
function ThemesTab() { interface ThemeCardProps {
const settings = useSettings(["themeLinks"]); theme: UserThemeHeader;
const [themeText, setThemeText] = React.useState(settings.themeLinks.join("\n")); enabled: boolean;
onChange: (enabled: boolean) => void;
onDelete: () => void;
}
function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
return (
<AddonCard
name={theme.name}
description={theme.description}
author={theme.author}
enabled={enabled}
setEnabled={onChange}
infoButton={
IS_WEB && (
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
<DeleteIcon />
</div>
)
}
footer={
<Flex flexDirection="row" style={{ gap: "0.2em" }}>
{!!theme.website && <Link href={theme.website}>Website</Link>}
{!!(theme.website && theme.invite) && " • "}
{!!theme.invite && (
<Link
href={`https://discord.gg/${theme.invite}`}
onClick={async e => {
e.preventDefault();
const { invite } = await InviteActions.resolveInvite(theme.invite, "Desktop Modal");
if (!invite) return showToast("Invalid or expired invite");
FluxDispatcher.dispatch({
type: "INVITE_MODAL_OPEN",
invite,
code: theme.invite,
context: "APP"
});
}}
>
Discord Server
</Link>
)}
</Flex>
}
/>
);
}
enum ThemeTab {
LOCAL,
ONLINE
}
function ThemesTab() {
const settings = useSettings(["themeLinks", "enabledThemes"]);
const fileInputRef = useRef<HTMLInputElement>(null);
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null);
const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir);
useEffect(() => {
refreshLocalThemes();
}, []);
async function refreshLocalThemes() {
const themes = await VencordNative.themes.getThemesList();
setUserThemes(themes);
}
// When a local theme is enabled/disabled, update the settings
function onLocalThemeChange(fileName: string, value: boolean) {
if (value) {
if (settings.enabledThemes.includes(fileName)) return;
settings.enabledThemes = [...settings.enabledThemes, fileName];
} else {
settings.enabledThemes = settings.enabledThemes.filter(f => f !== fileName);
}
}
async function onFileUpload(e: SyntheticEvent<HTMLInputElement>) {
e.stopPropagation();
e.preventDefault();
if (!e.currentTarget?.files?.length) return;
const { files } = e.currentTarget;
const uploads = Array.from(files, file => {
const { name } = file;
if (!name.endsWith(".css")) return;
return new Promise<void>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
VencordNative.themes.uploadTheme(name, reader.result as string)
.then(resolve)
.catch(reject);
};
reader.readAsText(file);
});
});
await Promise.all(uploads);
refreshLocalThemes();
}
function renderLocalThemes() {
return (
<>
<Card className="vc-settings-card">
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
<div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}>
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
BetterDiscord Themes
</Link>
<Link href="https://github.com/search?q=discord+theme">GitHub</Link>
</div>
<Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText>
</Card>
<Forms.FormSection title="Local Themes">
<Card className="vc-settings-quick-actions-card">
<>
{IS_WEB ?
(
<Button
size={Button.Sizes.SMALL}
disabled={themeDirPending}
>
Upload Theme
<FileInput
ref={fileInputRef}
onChange={onFileUpload}
multiple={true}
filters={[{ extensions: ["css"] }]}
/>
</Button>
) : (
<Button
onClick={() => showItemInFolder(themeDir!)}
size={Button.Sizes.SMALL}
disabled={themeDirPending}
>
Open Themes Folder
</Button>
)}
<Button
onClick={refreshLocalThemes}
size={Button.Sizes.SMALL}
>
Load missing Themes
</Button>
<Button
onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL}
>
Edit QuickCSS
</Button>
</>
</Card>
<div className={cl("grid")}>
{userThemes?.map(theme => (
<ThemeCard
key={theme.fileName}
enabled={settings.enabledThemes.includes(theme.fileName)}
onChange={enabled => onLocalThemeChange(theme.fileName, enabled)}
onDelete={async () => {
onLocalThemeChange(theme.fileName, false);
await VencordNative.themes.deleteTheme(theme.fileName);
refreshLocalThemes();
}}
theme={theme}
/>
))}
</div>
</Forms.FormSection>
</>
);
}
// When the user leaves the online theme textbox, update the settings
function onBlur() { function onBlur() {
settings.themeLinks = [...new Set( settings.themeLinks = [...new Set(
themeText themeText
@ -89,42 +289,56 @@ function ThemesTab() {
)]; )];
} }
function renderOnlineThemes() {
return (
<>
<Card className="vc-settings-card vc-text-selectable">
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
<Forms.FormText>One link per line</Forms.FormText>
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
</Card>
<Forms.FormSection title="Online Themes" tag="h5">
<TextArea
value={themeText}
onChange={setThemeText}
className={classes(TextAreaProps.textarea, "vc-settings-theme-links")}
placeholder="Theme Links"
spellCheck={false}
onBlur={onBlur}
rows={10}
/>
<Validators themeLinks={settings.themeLinks} />
</Forms.FormSection>
</>
);
}
return ( return (
<SettingsTab title="Themes"> <SettingsTab title="Themes">
<Card className="vc-settings-card vc-text-selectable"> <TabBar
<Forms.FormTitle tag="h5">Paste links to .theme.css files here</Forms.FormTitle> type="top"
<Forms.FormText>One link per line</Forms.FormText> look="brand"
<Forms.FormText><strong>Make sure to use the raw links or github.io links!</strong></Forms.FormText> className="vc-settings-tab-bar"
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} /> selectedItem={currentTab}
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle> onItemSelect={setCurrentTab}
<div style={{ marginBottom: ".5em" }}> >
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes"> <TabBar.Item
BetterDiscord Themes className="vc-settings-tab-bar-item"
</Link> id={ThemeTab.LOCAL}
<Link href="https://github.com/search?q=discord+theme">GitHub</Link> >
</div> Local Themes
<Forms.FormText>If using the BD site, click on "Source" somewhere below the Download button</Forms.FormText> </TabBar.Item>
<Forms.FormText>In the GitHub repository of your theme, find X.theme.css, click on it, then click the "Raw" button</Forms.FormText> <TabBar.Item
<Forms.FormText> className="vc-settings-tab-bar-item"
If the theme has configuration that requires you to edit the file: id={ThemeTab.ONLINE}
<ul> >
<li> Make a <Link href="https://github.com/signup">GitHub</Link> account</li> Online Themes
<li> Click the fork button on the top right</li> </TabBar.Item>
<li> Edit the file</li> </TabBar>
<li> Use the link to your own repository instead</li>
</ul> {currentTab === ThemeTab.LOCAL && renderLocalThemes()}
</Forms.FormText> {currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
</Card>
<Forms.FormTitle tag="h5">Themes</Forms.FormTitle>
<TextArea
value={themeText}
onChange={setThemeText}
className={`${TextAreaProps.textarea} vc-settings-theme-links`}
placeholder="Theme Links"
spellCheck={false}
onBlur={onBlur}
/>
<Validators themeLinks={settings.themeLinks} />
</SettingsTab> </SettingsTab>
); );
} }

@ -46,7 +46,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
if (code === "ENOENT") if (code === "ENOENT")
var err = `Command \`${path}\` not found.\nPlease install it and try again`; var err = `Command \`${path}\` not found.\nPlease install it and try again`;
else { 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`; err += stderr || `Code \`${code}\`. See the console for more info`;
} }
@ -249,4 +249,4 @@ function Updater() {
); );
} }
export default IS_WEB ? null : wrapTab(Updater, "Updater"); export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
@ -258,7 +257,11 @@ function DonateCard({ image }: DonateCardProps) {
src={image} src={image}
alt="" alt=""
height={128} height={128}
style={{ marginLeft: "auto", transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : "" }} style={{
imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0,
marginLeft: "auto",
transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0
}}
/> />
</Card> </Card>
); );

@ -0,0 +1,64 @@
.vc-addon-card {
background-color: var(--background-secondary-alt);
color: var(--interactive-active);
border-radius: 8px;
display: block;
height: 100%;
padding: 12px;
width: 100%;
transition: 0.1s ease-out;
transition-property: box-shadow, transform, background, opacity;
box-sizing: border-box;
}
.vc-addon-card-disabled {
opacity: 0.6;
}
.vc-addon-card:hover {
background-color: var(--background-tertiary);
transform: translateY(-1px);
box-shadow: var(--elevation-high);
}
.vc-addon-header {
margin-top: auto;
display: flex;
width: 100%;
justify-content: flex-end;
align-items: center;
gap: 8px;
margin-bottom: 0.5em;
}
.vc-addon-note {
height: 36px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
/* stylelint-disable-next-line property-no-unknown */
box-orient: vertical;
}
.vc-addon-name-author {
width: 100%;
}
.vc-addon-name {
display: flex;
width: 100%;
align-items: center;
flex-grow: 1;
gap: 8px;
}
.vc-addon-author {
font-size: 0.8em;
}
.vc-addon-author::before {
content: "by ";
}

@ -1,6 +1,6 @@
.vc-settings-tab-bar { .vc-settings-tab-bar {
margin-top: 20px; margin-top: 20px;
margin-bottom: -2px; margin-bottom: 10px;
border-bottom: 2px solid var(--background-modifier-accent); border-bottom: 2px solid var(--background-modifier-accent);
} }
@ -43,6 +43,7 @@
color: var(--text-normal) !important; color: var(--text-normal) !important;
padding: 0.5em; padding: 0.5em;
border: 1px solid var(--background-modifier-accent); border: 1px solid var(--background-modifier-accent);
max-height: unset;
} }
.vc-cloud-settings-sync-grid { .vc-cloud-settings-sync-grid {

@ -17,6 +17,7 @@
*/ */
import "./settingsStyles.css"; import "./settingsStyles.css";
import "./themesStyles.css";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { handleComponentFailed } from "@components/handleComponentFailed"; import { handleComponentFailed } from "@components/handleComponentFailed";

@ -0,0 +1,29 @@
.vc-settings-theme-grid {
display: grid;
grid-gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.vc-settings-theme-card {
display: flex;
flex-direction: column;
background-color: var(--background-secondary-alt);
color: var(--interactive-active);
border-radius: 8px;
padding: 1em;
width: 100%;
transition: 0.1s ease-out;
transition-property: box-shadow, transform, background, opacity;
}
.vc-settings-theme-card-text {
text-overflow: ellipsis;
height: 1.2em;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
}
.vc-settings-theme-author::before {
content: "by ";
}

8
src/globals.d.ts vendored

@ -33,10 +33,12 @@ declare global {
* replace: `${IS_WEB}?foo:bar` * replace: `${IS_WEB}?foo:bar`
*/ */
export var IS_WEB: boolean; export var IS_WEB: boolean;
export var IS_EXTENSION: boolean;
export var IS_DEV: boolean; export var IS_DEV: boolean;
export var IS_STANDALONE: boolean; export var IS_STANDALONE: boolean;
export var IS_UPDATER_DISABLED: boolean;
export var IS_DISCORD_DESKTOP: boolean; export var IS_DISCORD_DESKTOP: boolean;
export var IS_VENCORD_DESKTOP: boolean; export var IS_VESKTOP: boolean;
export var VERSION: string; export var VERSION: string;
export var BUILD_TIMESTAMP: number; export var BUILD_TIMESTAMP: number;
@ -58,8 +60,8 @@ declare global {
* If you really must use it, mark your plugin as Desktop App only by naming it Foo.desktop.ts(x) * If you really must use it, mark your plugin as Desktop App only by naming it Foo.desktop.ts(x)
*/ */
export var DiscordNative: any; export var DiscordNative: any;
export var VencordDesktop: any; export var Vesktop: any;
export var VencordDesktopNative: any; export var VesktopNative: any;
interface Window { interface Window {
webpackChunkdiscord_app: { webpackChunkdiscord_app: {

@ -19,21 +19,32 @@
import { app, protocol, session } from "electron"; import { app, protocol, session } from "electron";
import { join } from "path"; import { join } from "path";
import { getSettings } from "./ipcMain"; import { ensureSafePath, getSettings } from "./ipcMain";
import { IS_VANILLA } from "./utils/constants"; import { IS_VANILLA, THEMES_DIR } from "./utils/constants";
import { installExt } from "./utils/extensions"; import { installExt } from "./utils/extensions";
if (IS_VENCORD_DESKTOP || !IS_VANILLA) { if (IS_VESKTOP || !IS_VANILLA) {
app.whenReady().then(() => { app.whenReady().then(() => {
// Source Maps! Maybe there's a better way but since the renderer is executed // Source Maps! Maybe there's a better way but since the renderer is executed
// from a string I don't think any other form of sourcemaps would work // from a string I don't think any other form of sourcemaps would work
protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => { protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => {
let url = unsafeUrl.slice("vencord://".length); let url = unsafeUrl.slice("vencord://".length);
if (url.endsWith("/")) url = url.slice(0, -1); if (url.endsWith("/")) url = url.slice(0, -1);
if (url.startsWith("/themes/")) {
const theme = url.slice("/themes/".length);
const safeUrl = ensureSafePath(THEMES_DIR, theme);
if (!safeUrl) {
cb({ statusCode: 403 });
return;
}
cb(safeUrl.replace(/\?v=\d+$/, ""));
return;
}
switch (url) { switch (url) {
case "renderer.js.map": case "renderer.js.map":
case "vencordDesktopRenderer.js.map": case "vencordDesktopRenderer.js.map":
case "preload.js.map": case "preload.js.map":
case "vencordDesktopPreload.js.map":
case "patcher.js.map": case "patcher.js.map":
case "vencordDesktopMain.js.map": case "vencordDesktopMain.js.map":
cb(join(__dirname, url)); cb(join(__dirname, url));
@ -51,6 +62,10 @@ if (IS_VENCORD_DESKTOP || !IS_VANILLA) {
} catch { } } catch { }
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
};
// Remove CSP // Remove CSP
type PolicyResult = Record<string, string[]>; type PolicyResult = Record<string, string[]>;
@ -62,6 +77,7 @@ if (IS_VENCORD_DESKTOP || !IS_VANILLA) {
result[directiveKey] = directiveValue; result[directiveKey] = directiveValue;
} }
}); });
return result; return result;
}; };
const stringifyPolicy = (policy: PolicyResult): string => const stringifyPolicy = (policy: PolicyResult): string =>
@ -70,31 +86,39 @@ if (IS_VENCORD_DESKTOP || !IS_VANILLA) {
.map(directive => directive.flat().join(" ")) .map(directive => directive.flat().join(" "))
.join("; "); .join("; ");
function patchCsp(headers: Record<string, string[]>, header: string) { const patchCsp = (headers: Record<string, string[]>) => {
if (header in headers) { const header = findHeader(headers, "content-security-policy");
if (header) {
const csp = parsePolicy(headers[header][0]); const csp = parsePolicy(headers[header][0]);
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
csp[directive] = ["*", "blob:", "data:", "'unsafe-inline'"]; csp[directive] ??= [];
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
} }
// TODO: Restrict this to only imported packages with fixed version. // TODO: Restrict this to only imported packages with fixed version.
// Perhaps auto generate with esbuild // Perhaps auto generate with esbuild
csp["script-src"] ??= []; csp["script-src"] ??= [];
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com"); csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
headers[header] = [stringifyPolicy(csp)]; headers[header] = [stringifyPolicy(csp)];
} }
} };
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
if (responseHeaders) { if (responseHeaders) {
if (resourceType === "mainFrame") if (resourceType === "mainFrame")
patchCsp(responseHeaders, "content-security-policy"); patchCsp(responseHeaders);
// Fix hosts that don't properly set the css content type, such as // Fix hosts that don't properly set the css content type, such as
// raw.githubusercontent.com // raw.githubusercontent.com
if (resourceType === "stylesheet") if (resourceType === "stylesheet") {
responseHeaders["content-type"] = ["text/css"]; const header = findHeader(responseHeaders, "content-type");
if (header)
responseHeaders[header] = ["text/css"];
}
} }
cb({ cancel: false, responseHeaders }); cb({ cancel: false, responseHeaders });
}); });

@ -17,25 +17,60 @@
*/ */
import "./updater"; import "./updater";
import "./ipcPlugins";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { IpcEvents } from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { BrowserWindow, ipcMain, shell } from "electron"; import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
import { mkdirSync, readFileSync, watch } from "fs"; import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile, writeFile } from "fs/promises"; import { open, readdir, readFile, writeFile } from "fs/promises";
import { join } from "path"; import { join, normalize } from "path";
import monacoHtml from "~fileContent/../components/monacoWin.html;base64"; import monacoHtml from "~fileContent/monacoWin.html;base64";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants"; import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants";
import { makeLinksOpenExternally } from "./utils/externalLinks";
mkdirSync(SETTINGS_DIR, { recursive: true }); mkdirSync(SETTINGS_DIR, { recursive: true });
mkdirSync(THEMES_DIR, { recursive: true });
export function ensureSafePath(basePath: string, path: string) {
const normalizedBasePath = normalize(basePath);
const newPath = join(basePath, path);
const normalizedPath = normalize(newPath);
return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null;
}
function readCss() { function readCss() {
return readFile(QUICKCSS_PATH, "utf-8").catch(() => ""); return readFile(QUICKCSS_PATH, "utf-8").catch(() => "");
} }
async function listThemes(): Promise<UserThemeHeader[]> {
const files = await readdir(THEMES_DIR).catch(() => []);
const themeInfo: UserThemeHeader[] = [];
for (const fileName of files) {
if (!fileName.endsWith(".css")) continue;
const data = await getThemeData(fileName).then(stripBOM).catch(() => null);
if (data == null) continue;
themeInfo.push(getThemeInfo(data, fileName));
}
return themeInfo;
}
function getThemeData(fileName: string) {
fileName = fileName.replace(/\?v=\d+$/, "");
const safePath = ensureSafePath(THEMES_DIR, fileName);
if (!safePath) return Promise.reject(`Unsafe path ${fileName}`);
return readFile(safePath, "utf-8");
}
export function readSettings() { export function readSettings() {
try { try {
return readFileSync(SETTINGS_FILE, "utf-8"); return readFileSync(SETTINGS_FILE, "utf-8");
@ -74,6 +109,14 @@ ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
cssWriteQueue.push(() => writeFile(QUICKCSS_PATH, css)) cssWriteQueue.push(() => writeFile(QUICKCSS_PATH, css))
); );
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes());
ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName));
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
// win & mac only
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
}));
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR); ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings()); ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
@ -89,6 +132,10 @@ export function initIpc(mainWindow: BrowserWindow) {
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss()); mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
}, 50)); }, 50));
}); });
watch(THEMES_DIR, { persistent: false }, debounce(() => {
mainWindow.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0);
}));
} }
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => { ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
@ -97,11 +144,14 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
autoHideMenuBar: true, autoHideMenuBar: true,
darkTheme: true, darkTheme: true,
webPreferences: { webPreferences: {
preload: join(__dirname, "preload.js"), preload: join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js"),
contextIsolation: true, contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
sandbox: false sandbox: false
} }
}); });
makeLinksOpenExternally(win);
await win.loadURL(`data:text/html;base64,${monacoHtml}`); await win.loadURL(`data:text/html;base64,${monacoHtml}`);
}); });

42
src/main/ipcPlugins.ts Normal file

@ -0,0 +1,42 @@
/*
* 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 { IpcEvents } from "@utils/IpcEvents";
import { ipcMain } from "electron";
import PluginNatives from "~pluginNatives";
const PluginIpcMappings = {} as Record<string, Record<string, string>>;
export type PluginIpcMappings = typeof PluginIpcMappings;
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;
}
}
ipcMain.on(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP, e => {
e.returnValue = PluginIpcMappings;
});

@ -5,8 +5,8 @@
<title>Vencord QuickCSS Editor</title> <title>Vencord QuickCSS Editor</title>
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/editor/editor.main.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/editor/editor.main.min.css"
integrity="sha512-wB3xfL98hWg1bpkVYSyL0js/Jx9s7FsDg9aYO6nOMSJTgPuk/PFqxXQJKKSUjteZjeYrfgo9NFBOA1r9HwDuZw==" integrity="sha512-MOoQ02h80hklccfLrXFYkCzG+WVjORflOp9Zp8dltiaRP+35LYnO4LKOklR64oMGfGgJDLO8WJpkM1o5gZXYZQ=="
crossorigin="anonymous" crossorigin="anonymous"
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
/> />
@ -29,8 +29,8 @@
<body> <body>
<div id="container"></div> <div id="container"></div>
<script <script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/loader.min.js" src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/loader.min.js"
integrity="sha512-A+6SvPGkIN9Rf0mUXmW4xh7rDvALXf/f0VtOUiHlDUSPknu2kcfz1KzLpOJyL2pO+nZS13hhIjLqVgiQExLJrw==" integrity="sha512-QzMpXeCPciAHP4wbYlV2PYgrQcaEkDQUjzkPU4xnjyVSD9T36/udamxtNBqb4qK4/bMQMPZ8ayrBe9hrGdBFjQ=="
crossorigin="anonymous" crossorigin="anonymous"
referrerpolicy="no-referrer" referrerpolicy="no-referrer"
></script> ></script>
@ -38,7 +38,7 @@
<script> <script>
require.config({ require.config({
paths: { paths: {
vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs", vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs",
}, },
}); });

@ -16,22 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { app, autoUpdater } from "electron"; import { app } from "electron";
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs"; import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "original-fs";
import { basename, dirname, join } from "path"; import { basename, dirname, join } from "path";
const { setAppUserModelId } = app;
// Apparently requiring Discords updater too early leads into issues,
// copied this workaround from powerCord
app.setAppUserModelId = function (id: string) {
app.setAppUserModelId = setAppUserModelId;
setAppUserModelId.call(this, id);
patchUpdater();
};
function isNewer($new: string, old: string) { function isNewer($new: string, old: string) {
const newParts = $new.slice(4).split(".").map(Number); const newParts = $new.slice(4).split(".").map(Number);
const oldParts = old.slice(4).split(".").map(Number); const oldParts = old.slice(4).split(".").map(Number);
@ -77,23 +65,6 @@ function patchLatest() {
} }
} }
// Windows Host Updates install to a new folder app-{HOST_VERSION}, so we // Try to patch latest on before-quit
// need to reinject // Discord's Win32 updater will call app.quit() on restart and open new version on will-quit
function patchUpdater() { app.on("before-quit", patchLatest);
try {
const autoStartScript = join(require.main!.filename, "..", "autoStart", "win32.js");
const { update } = require(autoStartScript);
require.cache[autoStartScript]!.exports.update = function () {
update.apply(this, arguments);
patchLatest();
};
} catch {
// OpenAsar uses electrons autoUpdater on Windows
const { quitAndInstall } = autoUpdater;
autoUpdater.quitAndInstall = function () {
patchLatest();
quitAndInstall.call(this);
};
}
}

@ -71,7 +71,7 @@ if (!IS_VANILLA) {
constructor(options: BrowserWindowConstructorOptions) { constructor(options: BrowserWindowConstructorOptions) {
if (options?.webPreferences?.preload && options.title) { if (options?.webPreferences?.preload && options.title) {
const original = options.webPreferences.preload; const original = options.webPreferences.preload;
options.webPreferences.preload = join(__dirname, "preload.js"); options.webPreferences.preload = join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js");
options.webPreferences.sandbox = false; options.webPreferences.sandbox = false;
if (settings.frameless) { if (settings.frameless) {
options.frame = false; options.frame = false;

177
src/main/themes/LICENSE Normal file

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

81
src/main/themes/index.ts Normal file

@ -0,0 +1,81 @@
/* eslint-disable simple-header/header */
/*!
* BetterDiscord addon meta parser
* Copyright 2023 BetterDiscord contributors
* Copyright 2023 Vendicated and Vencord contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const splitRegex = /[^\S\r\n]*?\r?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;
export interface UserThemeHeader {
fileName: string;
name: string;
author: string;
description: string;
version?: string;
license?: string;
source?: string;
website?: string;
invite?: string;
}
function makeHeader(fileName: string, opts: Partial<UserThemeHeader> = {}): UserThemeHeader {
return {
fileName,
name: opts.name ?? fileName.replace(/\.css$/i, ""),
author: opts.author ?? "Unknown Author",
description: opts.description ?? "A Discord Theme.",
version: opts.version,
license: opts.license,
source: opts.source,
website: opts.website,
invite: opts.invite
};
}
export function stripBOM(fileContent: string) {
if (fileContent.charCodeAt(0) === 0xFEFF) {
fileContent = fileContent.slice(1);
}
return fileContent;
}
export function getThemeInfo(css: string, fileName: string): UserThemeHeader {
if (!css) return makeHeader(fileName);
const block = css.split("/**", 2)?.[1]?.split("*/", 1)?.[0];
if (!block) return makeHeader(fileName);
const header: Partial<UserThemeHeader> = {};
let field = "";
let accum = "";
for (const line of block.split(splitRegex)) {
if (line.length === 0) continue;
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
header[field] = accum.trim();
const l = line.indexOf(" ");
field = line.substring(1, l);
accum = line.substring(l + 1);
}
else {
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
}
}
header[field] = accum.trim();
delete header[""];
return makeHeader(fileName, header);
}

@ -16,12 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
export const VENCORD_FILES = [ export const VENCORD_FILES = [
IS_DISCORD_DESKTOP ? "patcher.js" : "vencordDesktopMain.js", IS_DISCORD_DESKTOP ? "patcher.js" : "vencordDesktopMain.js",
"preload.js", IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js",
IS_DISCORD_DESKTOP ? "renderer.js" : "vencordDesktopRenderer.js", IS_DISCORD_DESKTOP ? "renderer.js" : "vencordDesktopRenderer.js",
"renderer.css" IS_DISCORD_DESKTOP ? "renderer.css" : "vencordDesktopRenderer.css",
]; ];
export function serializeErrors(func: (...args: any[]) => any) { export function serializeErrors(func: (...args: any[]) => any) {

@ -49,7 +49,9 @@ async function getRepo() {
async function calculateGitChanges() { async function calculateGitChanges() {
await git("fetch"); 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(); const commits = res.stdout.trim();
return commits ? commits.split("\n").map(line => { return commits ? commits.split("\n").map(line => {

@ -16,4 +16,5 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import(IS_STANDALONE ? "./http" : "./git"); if (!IS_UPDATER_DISABLED)
import(IS_STANDALONE ? "./http" : "./git");

@ -25,13 +25,15 @@ export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? (
: join(app.getPath("userData"), "..", "Vencord") : join(app.getPath("userData"), "..", "Vencord")
); );
export const SETTINGS_DIR = join(DATA_DIR, "settings"); export const SETTINGS_DIR = join(DATA_DIR, "settings");
export const THEMES_DIR = join(DATA_DIR, "themes");
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css"); export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json"); export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
export const ALLOWED_PROTOCOLS = [ export const ALLOWED_PROTOCOLS = [
"https:", "https:",
"http:", "http:",
"steam:", "steam:",
"spotify:" "spotify:",
"com.epicgames.launcher:",
]; ];
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla"); export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");

@ -1,4 +1,4 @@
/* eslint-disable header/header */ /* eslint-disable simple-header/header */
/*! /*!
* crxToZip * crxToZip

@ -0,0 +1,48 @@
/*
* 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 { type BrowserWindow, shell } from "electron";
export function makeLinksOpenExternally(win: BrowserWindow) {
win.webContents.setWindowOpenHandler(({ url }) => {
switch (url) {
case "about:blank":
case "https://discord.com/popout":
case "https://ptb.discord.com/popout":
case "https://canary.discord.com/popout":
return { action: "allow" };
}
try {
var { protocol } = new URL(url);
} catch {
return { action: "deny" };
}
switch (protocol) {
case "http:":
case "https:":
case "mailto:":
case "steam:":
case "spotify:":
shell.openExternal(url);
}
return { action: "deny" };
});
}

5
src/modules.d.ts vendored

@ -24,6 +24,11 @@ declare module "~plugins" {
export default 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" { declare module "~git-hash" {
const hash: string; const hash: string;
export default hash; export default hash;

@ -80,22 +80,24 @@ export default definePlugin({
find: "Messages.PROFILE_USER_BADGES,role:", find: "Messages.PROFILE_USER_BADGES,role:",
replacement: [ replacement: [
{ {
match: /(?<=(\i)\.isTryItOutFlow,)(.{0,300})null==\i\?void 0:(\i)\.getBadges\(\)/, match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
replace: (_, props, restCode, badgesMod) => `vencordProps=${props},${restCode}Vencord.Api.Badges._getBadges(vencordProps).concat(${badgesMod}?.getBadges()??[])`, replace: "$&$1.unshift(...Vencord.Api.Badges._getBadges(arguments[0]));",
}, },
{ {
// alt: "", aria-hidden: false, src: originalSrc // alt: "", aria-hidden: false, src: originalSrc
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/g, match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
// ...badge.props, ..., src: badge.image ?? ... // ...badge.props, ..., src: badge.image ?? ...
replace: "...$1.props,$& $1.image??" replace: "...$1.props,$& $1.image??"
}, },
// replace their component with ours if applicable
{ {
match: /children:function(?<=(\i)\.(?:tooltip|description),spacing:\d.+?)/g, match: /(?<=text:(\i)\.description,spacing:12,)children:/,
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) : function" replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
}, },
// conditionally override their onClick with badge.onClick if it exists
{ {
match: /onClick:function(?=.{0,200}href:(\i)\.link)/, match: /href:(\i)\.link/,
replace: "onClick:$1.onClick??function" replace: "...($1.onClick && { onClick: $1.onClick }),$&"
} }
] ]
} }

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

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

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

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

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

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

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

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

@ -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]$&"
}
]
}
]
});

@ -22,11 +22,11 @@ import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "NoTrack", name: "NoTrack",
description: "Disable Discord's tracking ('science'), metrics and Sentry crash reporting", description: "Disable Discord's tracking ('science'), metrics and Sentry crash reporting",
authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz], authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz, Devs.Arrow],
required: true, required: true,
patches: [ patches: [
{ {
find: "TRACKING_URL:", find: "AnalyticsActionHandlers.handle",
replacement: { replacement: {
match: /^.+$/, match: /^.+$/,
replace: "()=>{}", replace: "()=>{}",
@ -43,14 +43,22 @@ export default definePlugin({
find: ".METRICS,", find: ".METRICS,",
replacement: [ replacement: [
{ {
match: /this\._intervalId.+?12e4\)/, match: /this\._intervalId=/,
replace: "" replace: "this._intervalId=undefined&&"
}, },
{ {
match: /(?<=increment=function\(\i\){)/, match: /(increment\(\i\){)/,
replace: "return;" replace: "$1return;"
} }
] ]
} },
{
find: ".installedLogHooks)",
replacement: {
// if getDebugLogging() returns false, the hooks don't get installed.
match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
}
},
] ]
}); });

@ -19,7 +19,6 @@
import { addContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { React, SettingsRouter } from "@webpack/common"; import { React, SettingsRouter } from "@webpack/common";
@ -40,8 +39,9 @@ export default definePlugin({
addContextMenuPatch("user-settings-cog", children => () => { addContextMenuPatch("user-settings-cog", children => () => {
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any; const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
section?.forEach(c => { section?.forEach(c => {
if (c?.props?.id?.startsWith("Vencord")) { const id = c?.props?.id;
c.props.action = () => SettingsRouter.open(c.props.id); if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
c.props.action = () => SettingsRouter.open(id);
} }
}); });
}); });
@ -63,77 +63,74 @@ export default definePlugin({
replacement: { replacement: {
get match() { get match() {
switch (Settings.plugins.Settings.settingsLocation) { switch (Settings.plugins.Settings.settingsLocation) {
case "top": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.USER_SETTINGS\}/; case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.BILLING_SETTINGS\}/; case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.APP_SETTINGS\}/; case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "aboveActivity": return /\{section:(.{1,2})\.ID\.HEADER,\s*label:(.{1,2})\..{1,2}\.Messages\.ACTIVITY_SETTINGS\}/; case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
case "belowActivity": return /(?<=\{section:(.{1,2})\.ID\.DIVIDER},)\{section:"changelog"/; case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
case "bottom": return /\{section:(.{1,2})\.ID\.CUSTOM,\s*element:.+?}/; case "aboveActivity":
default: { default:
new Logger("Settings").error( return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
new Error("No switch case matched????? Don't mess with the settings, silly")
);
// matches nothing
return /(?!a)a/;
}
} }
}, },
replace: "...$self.makeSettingsCategories($1),$&" replace: "...$self.makeSettingsCategories($1),$&"
} }
}], }],
customSections: [] as ((ID: Record<string, unknown>) => any)[], customSections: [] as ((SectionTypes: Record<string, unknown>) => any)[],
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) { makeSettingsCategories(SectionTypes: Record<string, unknown>) {
return [ return [
{ {
section: ID.HEADER, section: SectionTypes.HEADER,
label: "Vencord" label: "Vencord",
className: "vc-settings-header"
}, },
{ {
section: "VencordSettings", section: "VencordSettings",
label: "Vencord", label: "Vencord",
element: require("@components/VencordSettings/VencordTab").default element: require("@components/VencordSettings/VencordTab").default,
className: "vc-settings"
}, },
{ {
section: "VencordPlugins", section: "VencordPlugins",
label: "Plugins", label: "Plugins",
element: require("@components/VencordSettings/PluginsTab").default, element: require("@components/VencordSettings/PluginsTab").default,
className: "vc-plugins"
}, },
{ {
section: "VencordThemes", section: "VencordThemes",
label: "Themes", label: "Themes",
element: require("@components/VencordSettings/ThemesTab").default, element: require("@components/VencordSettings/ThemesTab").default,
className: "vc-themes"
}, },
!IS_WEB && { !IS_UPDATER_DISABLED && {
section: "VencordUpdater", section: "VencordUpdater",
label: "Updater", label: "Updater",
element: require("@components/VencordSettings/UpdaterTab").default, element: require("@components/VencordSettings/UpdaterTab").default,
className: "vc-updater"
}, },
{ {
section: "VencordCloud", section: "VencordCloud",
label: "Cloud", label: "Cloud",
element: require("@components/VencordSettings/CloudTab").default, element: require("@components/VencordSettings/CloudTab").default,
className: "vc-cloud"
}, },
{ {
section: "VencordSettingsSync", section: "VencordSettingsSync",
label: "Backup & Restore", label: "Backup & Restore",
element: require("@components/VencordSettings/BackupAndRestoreTab").default, element: require("@components/VencordSettings/BackupAndRestoreTab").default,
className: "vc-backup-restore"
}, },
IS_DEV && { IS_DEV && {
section: "VencordPatchHelper", section: "VencordPatchHelper",
label: "Patch Helper", label: "Patch Helper",
element: require("@components/VencordSettings/PatchHelperTab").default, element: require("@components/VencordSettings/PatchHelperTab").default,
className: "vc-patch-helper"
}, },
// TODO: make this use customSections ...this.customSections.map(func => func(SectionTypes)),
IS_VENCORD_DESKTOP && {
section: "VencordDesktop",
label: "Desktop Settings",
element: VencordDesktop.Components.Settings,
},
...this.customSections.map(func => func(ID)),
{ {
section: ID.DIVIDER section: SectionTypes.DIVIDER
} }
].filter(Boolean); ].filter(Boolean);
}, },
@ -172,7 +169,7 @@ export default definePlugin({
get additionalInfo() { get additionalInfo() {
if (IS_DEV) return " (Dev)"; if (IS_DEV) return " (Dev)";
if (IS_WEB) return " (Web)"; if (IS_WEB) return " (Web)";
if (IS_VENCORD_DESKTOP) return ` (VencordDesktop v${VencordDesktopNative.app.getVersion()})`; if (IS_VESKTOP) return ` (Vesktop v${VesktopNative.app.getVersion()})`;
if (IS_STANDALONE) return " (Standalone)"; if (IS_STANDALONE) return " (Standalone)";
return ""; return "";
}, },

@ -27,7 +27,7 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import plugins from "~plugins"; import plugins from "~plugins";
import settings from "./_core/settings"; import settings from "./settings";
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
@ -48,12 +48,12 @@ export default definePlugin({
name: "vencord-debug", name: "vencord-debug",
description: "Send Vencord Debug info", description: "Send Vencord Debug info",
predicate: ctx => AllowedChannelIds.includes(ctx.channel.id), predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),
execute() { async execute() {
const { RELEASE_CHANNEL } = window.GLOBAL_ENV; const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
const client = (() => { const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`; if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VENCORD_DESKTOP) return `Vencord Desktop v${VencordDesktopNative.app.getVersion()}`; if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
if ("armcord" in window) return `ArmCord v${window.armcord.version}`; if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
// @ts-expect-error // @ts-expect-error
@ -75,6 +75,10 @@ export default definePlugin({
OpenAsar: "openasar" in window, OpenAsar: "openasar" in window,
}; };
if (IS_DISCORD_DESKTOP) {
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
}
const debugInfo = ` const debugInfo = `
**Vencord Debug Info** **Vencord Debug Info**
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")} >>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}

@ -0,0 +1,51 @@
/*
* 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 { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "AlwaysAnimate",
description: "Animates anything that can be animated",
authors: [Devs.FieryFlames],
patches: [
{
find: "canAnimate:",
all: true,
// Some modules match the find but the replacement is returned untouched
noWarn: true,
replacement: {
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"
}
}
]
});

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

@ -26,6 +26,8 @@ const enum Methods {
Timestamp, Timestamp,
} }
const tarExtMatcher = /\.tar\.\w+$/;
export default definePlugin({ export default definePlugin({
name: "AnonymiseFileNames", name: "AnonymiseFileNames",
authors: [Devs.obscurity], authors: [Devs.obscurity],
@ -67,7 +69,8 @@ export default definePlugin({
anonymise(file: string) { anonymise(file: string) {
let name = "image"; let name = "image";
const extIdx = file.lastIndexOf("."); const tarMatch = tarExtMatcher.exec(file);
const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
const ext = extIdx !== -1 ? file.slice(extIdx) : ""; const ext = extIdx !== -1 ? file.slice(extIdx) : "";
switch (Settings.plugins.AnonymiseFileNames.method) { switch (Settings.plugins.AnonymiseFileNames.method) {

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

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

@ -16,56 +16,44 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { LazyComponent } from "@utils/react";
import { i18n, React, useStateFromStores } from "@webpack/common"; import { find, findByPropsLazy, findStoreLazy } from "@webpack";
import { useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react";
const cl = classNameFactory("vc-bf-"); import { ExpandedGuildFolderStore, settings } from ".";
const classes = findByPropsLazy("sidebar", "guilds");
const Animations = findByPropsLazy("a", "animated", "useTransition");
const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); 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: { export default ErrorBoundary.wrap(guildsBarProps => {
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(() => {
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
const fullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext()); const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const guilds = document.querySelector(`.${classes.guilds}`);
const visible = !!expandedFolders.size;
const className = cl("folder-sidebar", { fullscreen });
const Sidebar = ( const Sidebar = (
<Guilds <GuildsBar
className={classes.guilds} {...guildsBarProps}
bfGuildFolders={Array.from(expandedFolders)} 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 return visible
? <div className={className}>{Sidebar}</div> ? <div style={barStyle}>{Sidebar}</div>
: null; : null;
}
return ( return (
<Animations.Transition <Animations.Transition
@ -75,11 +63,13 @@ export default ErrorBoundary.wrap(() => {
leave={{ width: 0 }} leave={{ width: 0 }}
config={{ duration: 200 }} config={{ duration: 200 }}
> >
{(style, show) => show && ( {(animationStyle, show) =>
<Animations.animated.div style={style} className={className}> show && (
{Sidebar} <Animations.animated.div style={{ ...animationStyle, ...barStyle }}>
</Animations.animated.div> {Sidebar}
)} </Animations.animated.div>
)
}
</Animations.Transition> </Animations.Transition>
); );
}, { noop: true }); }, { noop: true });

@ -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;
}

@ -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);
},
});

@ -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
});

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -35,16 +34,18 @@ export default definePlugin({
}, },
}, },
{ {
find: 'preload:"none","aria', find: ".Messages.GIF,",
replacement: { replacement: {
match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/, match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
replace: replace:
"?($1.alt='GIF',$self.altify($1))", // rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2",
}, },
}, },
], ],
altify(props: any) { altify(props: any) {
props.alt ??= "GIF";
if (props.alt !== "GIF") return props.alt; if (props.alt !== "GIF") return props.alt;
let url: string = props.original || props.src; let url: string = props.original || props.src;

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

@ -23,7 +23,7 @@ import { Clipboard, Toasts } from "@webpack/common";
export default definePlugin({ export default definePlugin({
name: "BetterRoleDot", name: "BetterRoleDot",
authors: [Devs.Ven], authors: [Devs.Ven, Devs.AutumnVN],
description: description:
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously", "Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
@ -38,11 +38,31 @@ export default definePlugin({
{ {
find: '"dot"===', find: '"dot"===',
all: true, all: true,
noWarn: true,
predicate: () => Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
replacement: { replacement: {
match: /"(?:username|dot)"===\i(?!\.\i)/g, match: /"(?:username|dot)"===\i(?!\.\i)/g,
replace: "true", replace: "true",
}, },
},
{
find: ".ADD_ROLE_A11Y_LABEL",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,
replacement: {
match: /"dot"===\i/,
replace: "true"
}
},
{
find: ".roleVerifiedIcon",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,
replacement: {
match: /"dot"===\i/,
replace: "true"
}
} }
], ],
@ -50,7 +70,14 @@ export default definePlugin({
bothStyles: { bothStyles: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show both role dot and coloured names", description: "Show both role dot and coloured names",
restartNeeded: true,
default: false, default: false,
},
copyRoleColorInProfilePopout: {
type: OptionType.BOOLEAN,
description: "Allow click on role dot in profile popout to copy role color",
restartNeeded: true,
default: false
} }
}, },

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

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import * as t from "./types/stores"; import * as t from "./types/stores";

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

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