Compare commits

..

923 Commits

Author SHA1 Message Date
Rie Takahashi
9bc373d87a fix: merge tti components, use css file 2023-09-17 20:45:04 +01:00
Rie Takahashi
fb7e1c64fd fix missing $ in regex 2023-09-17 19:42:59 +01:00
Rie Takahashi
1128e8f3ad feat(startup timings): add Time-To-Interactive info 2023-09-17 19:37:03 +01: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
V
d7ac418e05 Fix some plugins displaying legacy discriminators (username#0000) 2023-06-23 18:09:43 +02:00
V
214c101740 Bump to v1.2.9 2023-06-22 22:45:28 +02:00
V
5a0e501829 Fix ValidUser, BetterFolders & MutualGroupDms
Co-authored-by: Juby210 <31005896+Juby210@users.noreply.github.com>
Co-authored-by: Amia <9750071+aamiaa@users.noreply.github.com>
2023-06-22 22:35:59 +02:00
V
92113da7c0 Fix Vencord Web 2023-06-21 21:50:28 +02:00
V
96f30a5359 Update README.md 2023-06-21 21:07:48 +02:00
V
ceb1f15188 utils: export missing members 2023-06-21 21:04:04 +02:00
V
626eb3613e [skip ci] WelcomeStickerPicker: Clean up settings types 2023-06-21 02:05:01 +02:00
V
3020fcc9bb [skip ci] Improve typings for settings.withPrivateSettings 2023-06-21 02:00:59 +02:00
heckeroncrack
bc0de3926c Improve uwuify (#1295)
Co-authored-by: V <vendicated@riseup.net>
2023-06-20 22:11:59 +02:00
Nuckyz
9820b79dfe SHC: show active now voice members; PermViewer: improve styling (#1314) 2023-06-20 22:11:50 +02:00
V
ab811470fc Update README.md 2023-06-18 21:27:52 +02:00
V
e4162e7bd5 PronounDB: Source pronouns from both PronounDB & Discord (#1301)
* PronounDB: Source pronouns from both PronounDB & Discord

* jdsjdsajjasiofigvjodsjigfdjiogegjnegjnersjn
2023-06-17 03:50:59 +02:00
V
7e8397a4da Bump to 1.2.8 2023-06-16 19:47:01 +02:00
V
555cf64080 BiggerStreamPreview: Hide element if no stream is found 2023-06-16 19:46:39 +02:00
V
2039e10fd5 Codeberg Mirror: Hopefully fix race condition 2023-06-16 19:45:01 +02:00
Phil
e8d90d2b45 feat(plugin): BiggerStreamPreview (#1222)
Co-authored-by: V <vendicated@riseup.net>
2023-06-16 19:35:50 +02:00
kb
55af40ee74 MessageLogger: Add user ignore list (#1275)
Co-authored-by: V <vendicated@riseup.net>
2023-06-16 19:35:35 +02:00
V
a1fabcdf0a UnsuppressEmbeds: Support dms 2023-06-16 19:28:30 +02:00
V
eaeb60308e [skip ci] Add version to /vencord-debug 2023-06-16 19:07:22 +02:00
Amia
662c0227eb New Plugin: MutualGroupDMs (#1239)
Co-authored-by: V <vendicated@riseup.net>
2023-06-15 03:39:15 +02:00
Dominik
543fdf4943 New Plugin: UnsuppressEmbeds (#1262)
Co-authored-by: V <vendicated@riseup.net>
2023-06-15 03:18:34 +02:00
Nuckyz
1225383723 Fix broken SHC patch (#1290) 2023-06-15 01:05:15 +00:00
V
07a9adbce2 🧹🧹 2023-06-13 03:45:05 +02:00
V
42d8211871 Fix disabling plugins whose stop() errors 2023-06-13 02:29:29 +02:00
V
ab3e993274 ViewRaw: Remove less properties from author 2023-06-13 02:23:06 +02:00
rad
386dfe363a ViewRaw: Add setting for swapping left/right click (#1263)
Co-authored-by: V <vendicated@riseup.net>
2023-06-13 01:05:04 +02:00
AAGaming
a4191c9f6c Settings: add custom sections support (#1270)
Co-authored-by: V <vendicated@riseup.net>
2023-06-11 23:11:56 +02:00
V
f1349a2787 Remove Cloud Sync notification 2023-06-11 22:57:07 +02:00
V
3680c26f72 Bump to 1.2.7 2023-06-09 22:18:32 +02:00
Axu
683c92f904 fix: do not highlight new member leaf in red in message logger (#1071)
Co-authored-by: V <vendicated@riseup.net>
2023-06-09 22:17:44 +02:00
Rendundakat
3410ed024f QuickReply: respect users in userList of NoReplyMention (#1191)
Co-authored-by: V <vendicated@riseup.net>
2023-06-09 22:17:32 +02:00
Luna
dbad10984a [skip ci] Don't run ci on forks; Fix interaction nicks in SMYN (#4)
The following actions are disabled unless run on the main repo: codeberg, publish, build/upload, reportBrokenPlugins

Reviewed-on: https://codeberg.org/Ven/cord/pulls/4
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-06-09 19:48:18 +00:00
whqwert
55543d8640 Fix(RevealAllSpoilers): New spoiler class name (#1229) 2023-06-04 17:22:21 -03:00
Manti
263fbc377e Fix ReviewDB auth (#1227) 2023-06-03 20:07:04 +00:00
AutumnVN
c9c0ab5aca Fix cloud auth [object%20Object] (#1226) 2023-06-03 16:57:31 -03:00
V
7b2bf08b8f [skip ci] ci: fix inconsistent formatting 2023-06-01 22:49:15 +02:00
TheKodeToad
43011825af Add NoProfileThemes plugin (#1193)
Co-authored-by: V <vendicated@riseup.net>
2023-05-31 19:14:18 +02:00
fawn
4abcea61f8 feat(MessageClickAction): delete message logger history (#1204) 2023-05-31 19:14:17 +02:00
V
cba810cab5 Improve settings selectable text style 2023-05-31 19:14:17 +02:00
Amia
5938c7d67c Plugin: NoPendingCount (#1216)
Co-authored-by: V <vendicated@riseup.net>
2023-05-31 19:14:17 +02:00
V
99d8b8b75f VcNarrator: Fix always saying 'someone' 2023-05-31 19:14:14 +02:00
Luna
503d49d295 Add codeberg to gitlens and add build tasks (#3)
It needed to be added inorder for gitlens to recognise the remote as a gitea instance
without, it wouldnt be able to open branches/specific commits correctly

and build tasks are just for ease of development

(teeny weeny pr bc im not gonna do anything else with this branch)

Co-authored-by: Luna R <imlvnaa@gmail.com>
Reviewed-on: https://codeberg.org/Ven/cord/pulls/3
Co-authored-by: Luna <imlvnaa@gmail.com>
Co-committed-by: Luna <imlvnaa@gmail.com>
2023-05-31 17:12:37 +00:00
V
137b79d95b README: Add codeberg mirror 2023-05-30 16:27:59 +02:00
V
3c02d6e1b4 Bump to 1.2.6 2023-05-30 15:24:21 +02:00
V
a2a33ca62d Fix occasional freezing on firefox (cache related) 2023-05-30 15:23:33 +02:00
V
d8cd557fb2 Clean up build scripts 2023-05-30 15:23:32 +02:00
V
7568bbaed0 VcNarrator: Improve username cleaning to support non ascii chars 2023-05-30 15:23:32 +02:00
V
9023d45d9e Improve /vencord-debug 2023-05-30 15:23:32 +02:00
V
bee70390a9 Add codeberg mirror sync workflow 2023-05-29 23:50:45 +02:00
Manti
3e3d05fc26 ReviewDB: Add Review Modal & Pagination (#1174)
Co-authored-by: V <vendicated@riseup.net>
2023-05-28 22:03:06 +02:00
V
6300198a54 MessageLinkEmbeds: Fix niche unclaimed account bug
This plugin fires MESSAGE_UPDATE events for messages containing message
links (to rerender them). If the updated message is an interaction, it
contains message.interaction.user. If the one who ran the command is
you, message.interaction.user will be you and the email in this data is
always set to null. Discord seems to update the local user data with
this user. So essentially, in the above described edge case it would
update the current user to have no email (only locally, in memory. There
is 0 risk for your account, it was just a temporary visual bug) which
would cause the unclaimed account banner to appear.

This commit fixes this by simply omitting the interaction field from the
MESSAGE_UPDATE event
2023-05-28 22:02:47 +02:00
Nuckyz
458c7ed4c5 Make Fake Nitro transformations support the new markdown (#911) 2023-05-23 00:25:48 -03:00
V
d888a0a291 [skip ci] Fix plugin json generation 2023-05-23 04:42:06 +02:00
V
a94787a9f3 Bump to 1.2.5 2023-05-23 03:50:21 +02:00
V
368d2bcdbb DiscordUtils: Add sendMessage 2023-05-23 03:47:09 +02:00
UwU
bc46bfa467 New plugin : Party mode 🎉 (#1161)
Co-authored-by: ArjixWasTaken <53124886+ArjixWasTaken@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2023-05-23 03:32:27 +02:00
V
dab48288a8 TypingTweaks: Fix type error 2023-05-23 03:22:48 +02:00
alexia
9aef97c771 fix(TypingTweaks): use global displayName over username (#1165)
Co-authored-by: V <vendicated@riseup.net>
2023-05-23 03:19:26 +02:00
PandaNinjas
9d62dec6b9 Uwufy: Add option to uwufy all messages (#1036)
Co-authored-by: V <vendicated@riseup.net>
2023-05-23 03:13:21 +02:00
V
6bf6583e7d FakeNitro: Fix unavailable emotes; Discord Stickers are now free (#1184)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-05-23 03:02:48 +02:00
Nuckyz
5219fb700f PermViewer: Add ability to change sort order; Properly center (#1182) 2023-05-23 02:22:25 +02:00
V
184c03b28e PluginModal: Anonymise authors (#1176) 2023-05-23 01:55:39 +02:00
Nuckyz
ec091a7959 Fix SHC broken patches; Sort PermViewer channel overwrites roles (#1166) 2023-05-20 02:24:56 +02:00
V
89a6c575c9 lastfm: Fix discord application 2023-05-18 05:11:06 +02:00
V
60325c6aa5 bump to 1.2.4 2023-05-17 04:42:50 +02:00
Hunter
c2a1c4cbf6 Urban Dictionary: better embed (#1127)
Co-authored-by: V <vendicated@riseup.net>
2023-05-17 04:40:40 +02:00
Syncx
1d6b78f6c6 feat(plugin): FavoriteEmojiFirst (#1110)
Co-authored-by: V <vendicated@riseup.net>
2023-05-17 04:38:15 +02:00
Sammy
341151a718 feat(InvisibleChat): use discords embed api (#1162) 2023-05-17 04:38:01 +02:00
Nuckyz
f6fd7cf37a PlatformIndicators: make mobile icon match Discord's (#1160) 2023-05-17 04:37:47 +02:00
Kay Alizer
d53476a32a VcNarrator: Add 'Narrate Self' option (#1120)
* Add 'Narrate Self' option

if anyone wishes to hear their own name on join this should work

* Apply suggestions from code review

Co-authored-by: V <vendicated@riseup.net>

* Update vcNarrator.tsx

---------

Co-authored-by: V <vendicated@riseup.net>
2023-05-16 19:23:10 +02:00
Animal
fc943b7778 emoteCloner: allow other characters in sticker names (#1140)
* allow spaces and other characters in cloning sticker names

* fix

* stickers have a different character limit
2023-05-16 19:22:46 +02:00
Nuckyz
3f2bcd2cab SHC: Permissions viewer integration (#475) 2023-05-16 17:15:56 +00:00
Nuckyz
235000cf41 Fix handleProtoChange erroring when userSettingsProto is undefined (#1150) 2023-05-16 14:53:17 +00:00
V
263884cbd8 PermViewer: Fix context menu for roleless users & muted channels (#1138)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-05-16 00:19:20 +02:00
V
bb83c0b672 [skip ci] PronounDB: Do not add pronouns to automod messages 2023-05-15 18:20:34 +02:00
V
2815509c00 UserScript: Fix fetch().res.ok 2023-05-15 02:49:34 +02:00
V
53ff2532f4 bump to 1.2.3 2023-05-15 02:34:01 +02:00
Nuckyz
64b38348d4 feat(plugins): Permissions Viewer (#477)
Co-authored-by: V <vendicated@riseup.net>
2023-05-15 02:33:04 +02:00
V
9c1b3a9afd Update README.md (#1123) 2023-05-14 13:16:18 +02:00
outfoxxed
caf77a3d7f NoReplyMention: add option to only exclude specific users from pings (#1107)
Co-authored-by: V <vendicated@riseup.net>
2023-05-14 00:01:10 +00:00
Manti
7a27de8927 [ReviewDB] Improve UI & Use new RewviewDB api endpoints (#1104) 2023-05-14 01:29:13 +02:00
LordElias
1bc0678422 MoreUserTags: Fix & Add ability to customize text inside tag (#1077)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: ActuallyTheSun <78964224+ActuallyTheSun@users.noreply.github.com>
2023-05-13 22:38:49 +00:00
V
cd53cf38fe ReverseImageSearch: Add engine icons 2023-05-14 00:10:01 +02:00
V
f13f9e80a9 ViewIcons: Add context menu icons 2023-05-13 23:49:47 +02:00
V
c062f9bdeb SpotifyControls: Add context menu icons 2023-05-13 23:47:13 +02:00
V
f2ef96a420 PlatformIndicators: Fix profile spacing 2023-05-13 22:56:46 +02:00
V
16365d3ea1 bump to 1.2.2 2023-05-13 18:48:40 +02:00
V
1ec28a345b EmoteCloner: Add Sticker cloning (#1118) 2023-05-13 18:47:46 +02:00
V
2fdc00b11e BadgesAPI: Fix canary crash (new pomelo badge, the horror) 2023-05-12 23:34:30 +02:00
V
3da112680d oops, add missing else 2023-05-12 04:17:12 +02:00
V
1d93162036 MessageClickActions: Add double click to reply 2023-05-12 04:15:35 +02:00
V
7dcd32e838 PlatformIndicators: Fix weird spacing in badges 2023-05-12 03:54:57 +02:00
V
ade31f993b Implement plugin tags 2023-05-12 03:41:15 +02:00
AutumnVN
3c7496ac6d TextReplace: Visible Linebreak in settings (#1063)
Co-authored-by: V <vendicated@riseup.net>
2023-05-12 02:59:51 +02:00
Supertiger
63387a48ee silentMessageToggle: Add setting to disable auto disable (#1062)
Co-authored-by: V <vendicated@riseup.net>
2023-05-12 00:58:53 +00:00
V
3bb68467bb Update README.md 2023-05-12 02:40:31 +02:00
V
2b337eace1 [skip ci] ShowHiddenChannel: Fix broken patch 2023-05-12 02:30:51 +02:00
V
5c5b009c41 Settings: Fix resetting scroll/search when getting a ping (#1106) 2023-05-12 01:40:43 +02:00
Nuckyz
0c54b1fa1d [skip ci] Fix InvisibleChat button being added to wrong chat box (#1100) 2023-05-11 17:44:33 +00:00
Kode
393f76749a USRBG: Hide Nitro badge if banner's source is USRBG (#1096)
* Hide Nitro badge if banner's source is USRBG, tweaks to description

* Rename function

* Update src/plugins/usrbg/index.tsx

---------

Co-authored-by: V <vendicated@riseup.net>
2023-05-11 19:33:08 +02:00
V
1fe7f3c297 ViewIcons: More consistent context menu position 2023-05-11 19:26:55 +02:00
V
622e8dc3e0 [skip ci] Translate: Shift/Right click shortcut 2023-05-11 19:00:18 +02:00
V
a8b6aea26f bump to 1.2.1 2023-05-11 02:39:59 +02:00
V
e50c2fafa5 Settings > Plugins: Hide API plugins 2023-05-11 02:38:59 +02:00
V
6e3cafce42 Translate: Only add button in chat box 2023-05-11 02:35:06 +02:00
V
4d0a064425 WebContextMenus: Fix slate menu 2023-05-11 02:35:05 +02:00
V
d1ad6c47a7 Fix ServerListAPI 2023-05-11 02:35:05 +02:00
V
d5c35055f3 experiments: Add a warning card to experiments page (#1097) 2023-05-11 02:34:39 +02:00
V
cb385d1b28 New Plugin: Translate (#1089)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-05-10 23:14:04 +02:00
V
195f1a032f ShowConnections: Add verified & copy/link icons in tooltip (#1092) 2023-05-10 23:13:47 +02:00
V
dfda9e7f84 New plugin: ValidUser (#1081) 2023-05-10 23:13:28 +02:00
Vendicated
0d5e2d0696 [skip ci] Refactor utils 2023-05-06 01:36:00 +02:00
Vendicated
2834bed518 MemberCount: Do not count offline group lol 2023-05-06 01:06:48 +02:00
Vendicated
0b2c3c834a MemberCount: Fix not properly updating on channel switch 2023-05-05 18:41:04 +02:00
AutumnVN
3a54a24c70 VencordToolbox: Change img icon to svg (#1059)
Co-authored-by: V <vendicated@riseup.net>
2023-05-05 16:41:01 +00:00
Vendicated
244d10dc9e [skip ci] Fix handleComponentFailed spam 2023-05-05 16:57:58 +02:00
Đỗ Văn Hoài Tuân
c25bc0ff4b USRBG: Add VoiceBackground (#1038)
Co-authored-by: V <vendicated@riseup.net>
2023-05-05 02:54:33 +02:00
Kode
22334663cf TextReplace: Test rules section (#1039) 2023-05-05 02:51:01 +02:00
Đỗ Văn Hoài Tuân
8813f81bde ShowConnections: Remove "(click to copy/open)" from tooltip (#1041) 2023-05-05 02:47:59 +02:00
Đỗ Văn Hoài Tuân
84371ed456 TextReplace: Fix Linebreak (#1057) 2023-05-05 02:47:31 +02:00
Nuckyz
8f61119b99 ShowAllMessageButtons plugin (#1029) 2023-05-05 02:47:26 +02:00
JSEverything
0a89d09727 style(Settings): Make the text about raw theme links bold (#1052) 2023-05-05 02:47:18 +02:00
Nuckyz
474932161f Fix FakeNitro Stickers (#1048) 2023-05-05 02:47:08 +02:00
Vendicated
bd95a25f4c TextReplace: Do not apply rules in #textreplace-rules channel 2023-05-02 04:39:27 +02:00
Vendicated
6a57ecc22b Delete FxTwitter & NoCanaryMessageLinks: Use TextReplace 2023-05-02 04:37:16 +02:00
Vendicated
0d665b7e0b Badge duplication glitch was too good to be true 2023-05-02 03:11:40 +02:00
Vendicated
d94b28fb8e Fix VencordToolbox 2023-05-02 02:57:25 +02:00
V
bc1d8694d4 new plugin: VencordToolbox (#998) 2023-05-02 02:55:38 +02:00
V
7bc1362cbd MessageEvents: Support sendMessage with attachments (#1023) 2023-05-02 02:55:21 +02:00
Vendicated
4dce836ff7 RelationshipNotifier: Fix false positives when using AccountSwitcher 2023-05-02 02:52:09 +02:00
Vendicated
9f534c0685 SupportHelper: Add OpenAsar info 2023-05-02 02:52:03 +02:00
V
c62d05e1b3 Refactor ipc to be strongly typed and hide impl details (#1018) 2023-05-02 02:50:51 +02:00
Nuckyz
6a1cb133cd Fix blank errors on plugin reports (#1021) 2023-05-01 07:26:27 +00:00
Vendicated
c6196dff81 [skip ci] PlatformIndicator: Fix 1px misalignment 2023-04-30 21:47:33 +02:00
Vendicated
acdb390302 [skip ci] Bump deps 2023-04-30 17:11:38 +02:00
Nuckyz
08d88b326d Fix Plugin Reporting false dependency errors and other stuff (#1017) 2023-04-30 13:34:38 +00:00
LordElias
a73858d131 UserVoiceShow: Fix lack of bottom padding for popout on current user and broken modal patch (#873) 2023-04-30 10:29:45 -03:00
Đỗ Văn Hoài Tuân
b0caa6f4db feat(plugin): TextReplace (#994)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-30 01:19:08 +00:00
Vendicated
168d4b4cd9 PlatformIndicators: Fix layout reflows - The trilogy 2023-04-30 02:07:57 +02:00
Vendicated
06cee75a56 PlatformIndicators: Fix layout reflows 2: Electric Boogaloo 2023-04-30 01:56:12 +02:00
Vendicated
d589d22a0b bump to 1.2.0 2023-04-30 01:27:08 +02:00
Vendicated
4c13521a30 PlatformIndicators: Fix layout reflows 2023-04-30 01:25:17 +02:00
Manti
043381963b ReviewDB: make warning review disableable; add timestamps (#948) 2023-04-30 00:53:37 +02:00
LordElias
5b485806ea Plugin Settings: Better margin for plugin settings modal (#1008) 2023-04-30 00:52:20 +02:00
Kode
29c994648b ShowConnections plugin (#923)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-30 00:49:27 +02:00
Vendicated
a35b417194 BetterRoleDot: Fix copying colour 2023-04-30 00:09:15 +02:00
Vendicated
070aa343ef Remove migratePluginSettings calls 2023-04-30 00:09:15 +02:00
Đỗ Văn Hoài Tuân
b95c5c6619 Fix USRBG (#1000)
Co-authored-by: V <vendicated@riseup.net>
2023-04-30 00:08:04 +02:00
Vendicated
bf795c49df [skip ci] Update Monaco; Fix code scanning errors 2023-04-29 04:16:28 +02:00
Vendicated
a2e03084b0 BadgeAPI: Add support for multiple donor badges 2023-04-29 02:06:52 +02:00
Vendicated
ec72b4c91d PronounDB: Oop, conditional hooks are bad :> 2023-04-29 02:04:00 +02:00
Vendicated
d70d7c7b49 [skip ci] reporter: reduce sleep time 2023-04-28 19:34:21 +02:00
Vendicated
e7d0fc258d [skip ci] WebContextMenus: fix false ci test positives 2023-04-28 19:32:29 +02:00
Vendicated
7b13b9a53e PronounDB: Fix not working in profiles 2023-04-28 19:15:07 +02:00
Vendicated
1b2cb52dac ViewIcons: Fix viewing banners 2023-04-28 18:36:22 +02:00
Vendicated
0fe0fecba2 MessageLogger: Nicer context menu 2023-04-28 04:23:42 +02:00
Vendicated
c1fca76f94 SilentMessageToggle: Add missing dependency on MessageEventsApi 2023-04-28 04:03:49 +02:00
carince
0cc3901e4e MuteNewGuilds: add toggles for guild, everyone, and roles. (#979)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Norikiru <99774021+Norikiru@users.noreply.github.com>
2023-04-28 02:03:38 +00:00
Vendicated
f8ace5b53a [skip ci] definePluginSettings: Add missing BigInt type 2023-04-28 02:57:09 +02:00
V
c0954a1844 Specify that theme in screenshot is only an example 2023-04-28 02:51:18 +02:00
V
6548163d3e Update README.md 2023-04-28 02:49:34 +02:00
V
24f161d6e9 Update README.md 2023-04-28 02:48:39 +02:00
V
7d00b6a842 README: Add screenshot & more info 2023-04-28 02:17:33 +02:00
Vendicated
5f5d4b8961 [skip ci] UserScript: Fix cors check pt 2 2023-04-28 01:13:42 +02:00
Hazel Summer
5be86f9bd1 WhoReacted: Fix clicking user pfps also adding reaction (#977) 2023-04-24 01:42:38 +02:00
Vendicated
dfc3f05834 Fix RoleColorEverywhere crash 2023-04-23 23:33:55 +02:00
V
63fc354d48 feat: auto-managed flux subscriptions via plugin.flux (#959) 2023-04-22 01:18:19 +00:00
V
c6f0c84935 PinDMs: Fix alt up/down being weird (#957) 2023-04-22 03:17:00 +02:00
Vendicated
a8d017811d TypingTweaks: Fix filter 2023-04-22 03:16:08 +02:00
Vendicated
8dd70f5d1a Fix inserting text when markdown preview is off 2023-04-18 23:13:10 +02:00
Amsyar Rasyiq
8be6c6e3ce fix(ReviewDB): fix margin in self-profile preview (#935)
Co-authored-by: V <vendicated@riseup.net>
2023-04-18 18:53:11 +02:00
V
7e96b5dcfb RelationShipNotifier: Support multiple users (#944) 2023-04-18 16:52:46 +00:00
Đỗ Văn Hoài Tuân
99a7d78e9b [skip ci] USRBG: Update README link to a more accurate one (#943) 2023-04-18 16:04:17 +02:00
Nuckyz
e70d00d008 BadgesAPI: Don't depend on getBadges module not being undefined (#942) 2023-04-18 05:25:14 +00:00
Nuckyz
c0ac6a4b86 SilentMessageToggle: Option to persist state (#941) 2023-04-18 04:54:21 +00:00
Vendicated
29749e93c7 CI Reporter fixes 2023-04-18 02:54:22 +02:00
Vendicated
993c6be219 Fix badges 2023-04-18 02:47:48 +02:00
Đỗ Văn Hoài Tuân
e2e1cf2bfd feat(plugin): USRBG (#844)
Co-authored-by: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-18 00:18:18 +02:00
Vendicated
59e3c2c609 ci: fix cding into wrong folder 2023-04-17 04:09:20 +02:00
Nuckyz
43d7ca4c30 Make chat bar buttons have a consistent size (#927)
Co-authored-by: V <vendicated@riseup.net>
2023-04-17 02:09:11 +00:00
V
5305447f44 firefox: Fix csp (QuickCss, themes, some plugins) (#554) 2023-04-17 04:05:01 +02:00
Vendicated
76e74b3e40 SendTimestamps: Fix chatbox having a scrollbar 2023-04-17 02:55:08 +02:00
Vendicated
e767da4b08 Fix errors on setups with no SpeechSynthesis support (part 2) 2023-04-17 01:52:46 +02:00
Vendicated
e4f3f57a28 bump to v1.1.7 2023-04-17 01:48:53 +02:00
Lily • Lylythii
72f6dd84ee SilentTyping: Make Tooltip text casing consistent w other plugins (#918) 2023-04-17 01:46:51 +02:00
Mufaro
9c929a4d98 messageTags: Fix duplicate entries & replies (#922)
Co-authored-by: V <vendicated@riseup.net>
2023-04-16 23:44:33 +00:00
Luca LeBlanc
dac9cad873 Improve FakeNitro emoji popup message (#924)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: V <vendicated@riseup.net>
2023-04-17 01:43:11 +02:00
Vendicated
6fd5c7874f Remove ContextMenuAPIs from plugin dependencies (now required) 2023-04-17 01:39:16 +02:00
Vendicated
a56dfe269c Improve unintuitive plugin descriptions 2023-04-17 01:37:55 +02:00
Vendicated
7d55a81bac MessageLogger: Add toggle deleted highlight rightclick button 2023-04-17 01:27:25 +02:00
Vendicated
ce64631310 MessageLogger: Apply styles to gifs, stickers & new markdown 2023-04-17 01:18:45 +02:00
Vendicated
1caaa78490 PinDMs: Add option to sort by most recent message 2023-04-17 01:06:54 +02:00
Vendicated
d35654b887 Improve themes tab instructions 2023-04-17 00:23:22 +02:00
Vendicated
ca5d24385f Fix errors on setups with no Notifications/SpeechSynthesis support 2023-04-17 00:21:49 +02:00
V
cb3bd4b881 ci: Also upload extension zips 2023-04-15 15:02:31 +02:00
Lewis Crichton
ff3589d157 CloudSync: fix accidently applying stale settings (#915
* modify the cloud timestamp before importing

* use util/native relaunch

* lambda why

* "should work!" - Vendicated, 15th April 2023
2023-04-15 14:50:00 +02:00
dimden
7a98f1dfcb Make Vencord Extension run in iframes (#914)
Fixes support for https://github.com/NeverDecaf/discord-PWA
2023-04-15 14:49:48 +02:00
fawn
9e6d3459e3 fix(smyn): reversed settings (#913) 2023-04-15 12:20:49 +02:00
Jack
ea30ca418f feat: Add AlwaysAnimate plugin (#908)
Co-authored-by: V <vendicated@riseup.net>
2023-04-15 05:10:03 +00:00
Nuckyz
1f7ec93a24 SHC: Small improvements (#907) 2023-04-15 03:40:42 +00:00
Nuckyz
336c7bdd5e SHC: Fix emoji rendering & allowed users/roles edge cases (#895)
Co-authored-by: V <vendicated@riseup.net>
2023-04-15 05:02:08 +02:00
V
88ad4f1b05 SendTimestamps (#891)
Co-authored-by: Tyler Flowers <contact@ggtylerr.dev>
2023-04-15 04:42:18 +02:00
Sofia Lima
f75f887861 feat(plugin): ShowMeYourName (#901) 2023-04-15 03:58:22 +02:00
Nuckyz
96f640da67 Make Context Menu API support hooks (#902)
Co-authored-by: V <vendicated@riseup.net>
2023-04-15 02:47:03 +02:00
Manti
e8809fc57b add reviewdb bacc (#898) 2023-04-15 02:34:53 +02:00
Ryan Cao
ca91ef4e39 feat(moreUserTags): add HTML data attributes to user tags (#883)
Co-authored-by: V <vendicated@riseup.net>
2023-04-15 02:31:36 +02:00
V
db7fc3769b Fix settings on Vencord Mobile (#905) 2023-04-15 02:27:11 +02:00
V
6c719f5ee9 PinDMs (#879) 2023-04-15 02:26:46 +02:00
Nuckyz
c6fd8cae16 Fix MuteNewGuild (#896) 2023-04-14 18:02:03 +02:00
V
1adbf9e41a Delete muteNewGuild.ts
This plugin has a bad bug that breaks all DM notifications. Removed for now to prevent more damage, will be back once fixed!

I will try to make this future update fix the problem for any users affected by it.
For now, if you're affected, see https://gist.github.com/Vendicated/c86016b7fa68a754885ecd82a6ac0f2a for a quick fix
2023-04-14 08:53:40 +02:00
Nuckyz
aee6bed48c Revert "Delete DisableDMCallIdle: Discord removed this 'feature'" (#889)
THEY ADDED IT BACK WHYYYY
2023-04-14 03:37:14 +02:00
Vendicated
c8817e805f Fix badges 2023-04-13 21:04:19 +02:00
Vendicated
c6f0d0763c Remove noisy notifications from notification log 2023-04-13 19:15:36 +02:00
Vendicated
3bd3012aa9 Delete DisableDMCallIdle: Discord removed this 'feature' 2023-04-13 19:12:09 +02:00
Vendicated
694a693a8e MemberCount: Fall back to approx member count if necessary 2023-04-13 19:10:20 +02:00
Elliott Tallis
ed827c2d81 Shiki: Make CodeBlock button texts not copyable (#864) 2023-04-13 04:26:43 +02:00
Kode
71849cac9a Fix ViewIcons on webhooks and default avatars! (#880)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-13 04:25:38 +02:00
Nuckyz
e34da54271 Option to transform emotes/stickers in compound messages (#876)
+ ContextMenu refactor to not call callbacks for same children multiple times

Co-authored-by: V <vendicated@riseup.net>
2023-04-13 04:22:38 +02:00
Vendicated
cfe41ef656 ViewIcons: Add format setting & user context menu + cleanup 2023-04-12 03:27:31 +02:00
V
4d836524c1 GreetStickerPicker: greet with stickers of your choice (#866)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-04-12 02:33:51 +02:00
Dziurwa
edc96387f5 Fix FriendInvites (#874) 2023-04-12 02:33:05 +02:00
Vendicated
358eb6ad8e ImageZoom Fixes 2023-04-11 02:00:42 +02:00
Syncx
c997cb4958 ImageZoom: Fix not being able to close carousels on sides (#857)
Co-authored-by: V <vendicated@riseup.net>
2023-04-10 22:38:37 +00:00
Kode
83dab24fb9 PronounDB: Fix pronouns flickering (#862)
Co-authored-by: V <vendicated@riseup.net>
2023-04-11 00:37:24 +02:00
Vendicated
8a305d2d11 clean up spotify controls css 2023-04-11 00:32:11 +02:00
Ezzud
7eb12f0fb7 SpotifyControls: Fix flashing button row when using show on hover (#850)
Co-authored-by: V <vendicated@riseup.net>
2023-04-10 22:21:30 +00:00
Luna
0a3dc5c6e8 VoiceUserShow: Fix lack of bottom margin (#853)
Co-authored-by: V <vendicated@riseup.net>
2023-04-10 22:10:21 +00:00
Cat
b21516d44e Make QuickCss window title consistent with other windows (#859)
Co-authored-by: V <vendicated@riseup.net>
2023-04-11 00:07:36 +02:00
Nuckyz
65f7cf9503 Nicer GameActivity & SilentMsg UX; Fix [object Object] jumpscare (#863) 2023-04-10 23:59:48 +02:00
V
40a7aa5079 UserScript: Fix cors check 2023-04-09 06:58:29 +02:00
Amsyar Rasyiq
c4a3d25d37 feat(NoAutoReplyMention): Inverse shift reply behaviour (#839)
Co-authored-by: V <vendicated@riseup.net>
2023-04-09 06:55:04 +02:00
Ryan Cao
613fa9a57b feat: add translucency option for macOS (#849)
Co-authored-by: V <vendicated@riseup.net>
2023-04-09 02:06:09 +00:00
V
08822dd190 Improvements for VencordDesktop (#847) 2023-04-09 04:04:02 +02:00
Đỗ Văn Hoài Tuân
bfa20f2634 feat(setting): Disable minimum window size #834 (#848) 2023-04-09 03:41:55 +02:00
Vendicated
840da146b9 UX: Make possibly copy-relevant text in settings copyable 2023-04-08 23:28:12 +02:00
Vendicated
acc874c34f WebContextMenus: Fix jpegs being uncopyable 2023-04-08 23:05:38 +02:00
Vendicated
0dee968e98 WebContextMenus: Don't include queryparams in filename on save 2023-04-08 22:57:34 +02:00
Vendicated
09e919f0c6 bump to 1.1.6 2023-04-08 03:53:32 +02:00
V
eaf1af75bd WebContextMenus: Port more menus (#818)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-04-08 03:51:37 +02:00
exit
7c514e4b1d SupportHelper: Add missing dependency - CommandsAPI (#823)
Co-authored-by: V <vendicated@riseup.net>
2023-04-07 19:19:12 +00:00
LordElias
1432baa28b ignore userplugins when linting (#822)
Co-authored-by: V <vendicated@riseup.net>
2023-04-07 19:17:54 +00:00
exit
f1f61195c3 InvisibleChat: Add missing dependency on MessagePopoverAPI (#817)
Co-authored-by: V <vendicated@riseup.net>
2023-04-07 19:16:18 +00:00
Đỗ Văn Hoài Tuân
8fefa2b716 FakeNitro: Fix stickers with space in name #819 (#820) 2023-04-07 21:15:11 +02:00
Ryan Cao
2a0c30b66d feat(moreusertags): add option to not show more tags for bots (#812)
Co-authored-by: V <vendicated@riseup.net>
2023-04-07 00:31:21 +00:00
Lewis Crichton
97f8d4d515 feat: Cloud settings sync (#505)
Co-authored-by: Ven <vendicated@riseup.net>
2023-04-07 02:27:18 +02:00
Vendicated
2672dea8e3 ci: bump action 2023-04-06 03:34:02 +02:00
Vendicated
63f5b0a663 bump pnpm to v8 2023-04-06 03:34:02 +02:00
ActuallyTheSun
e40ebacc5b feat(plugin): WebhookTags -> MoreUserTags (#378)
Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-06 03:28:38 +02:00
LordElias
e261c93563 feat(plugin): User Voice Show (#694)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2023-04-06 03:22:54 +02:00
Syncx
df7357b357 feat(plugin): Image Zoom (#510)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Ven <vendicated@riseup.net>
2023-04-06 01:06:11 +00:00
Đỗ Văn Hoài Tuân
2e6c5eacf7 BetterFolders: Fix Close all not working (#808) 2023-04-06 03:02:53 +02:00
Dziurwa
c9fd404012 Fix FriendInvites (#802)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-04-05 23:01:11 +02:00
V
814302e272 Fix Badges (#801) 2023-04-05 22:45:14 +02:00
Anubis
72ba83924c SpotifyControls: add album art hover transition (#797) 2023-04-05 22:45:03 +02:00
Nuckyz
9d742094cb ShowHiddenChannels: Use Discord's new overlay vars (#795)
* Fix SHC css for new Discord vars

* I'm dumb

* improvements to work with themes

---------

Co-authored-by: V <vendicated@riseup.net>
2023-04-05 20:44:03 +00:00
Nuckyz
38f3aac98d Fix VolumeBooster and improve ContextMenuAPI patch (#793)
Co-authored-by: V <vendicated@riseup.net>
2023-04-05 03:07:17 +00:00
Nuckyz
12ffb9d642 Fake Nitro Transform Stickers option and other stuff (#683)
Co-authored-by: V <vendicated@riseup.net>
2023-04-05 05:06:04 +02:00
Vendicated
99391a4f0e fix generatePluginList 2023-04-05 04:54:54 +02:00
Vendicated
6492908a62 VencordDesktop: Fix Updater 2023-04-05 04:34:39 +02:00
Vendicated
676bc612d9 VencordDesktop: Include web plugins & use proper showItemInFolder 2023-04-05 04:09:42 +02:00
Vendicated
d8a5e43034 Fix Themes Tab 2023-04-04 22:24:16 +02:00
Vendicated
8ad710abca Fix ContextMenuAPI 2023-04-04 22:19:52 +02:00
Vendicated
368cb7bc6b Fix Toasts 2023-04-04 21:51:03 +02:00
Vendicated
4aa7a052d0 Bump to v1.1.5 2023-04-04 21:29:39 +02:00
Vendicated
f088f17a0a Remove accidently introduced patch 2023-04-04 21:28:38 +02:00
Vendicated
a55c758b0e Fix SpotifyControls 2023-04-04 21:27:44 +02:00
Vendicated
f092f434fe Fix Vencord 2023-04-04 21:14:55 +02:00
Remty
2e6dfaa879 FakeProfileThemes: add usage guide (#778)
Co-authored-by: V <vendicated@riseup.net>
2023-04-04 13:28:41 +00:00
Nuckyz
96dc2e12d0 Fix Web & Game Activity Toggle (#777) 2023-04-04 15:26:53 +02:00
Đỗ Văn Hoài Tuân
d931790ed0 BetterFolders: Fix unread indicator & read all buttons being duplicated (#776) 2023-04-04 05:33:11 +02:00
V
6b26c12bfa Add additional build flavours for Vencord Desktop (#765) 2023-04-04 01:16:29 +02:00
Vendicated
5bb08bdb64 SpotifyControls: Fix crashing on canary
Vencord is still pretty broken on Canary and likely will be for a bit,
but this should at least fix instantly crashing
2023-04-03 21:25:14 +02:00
Vendicated
405be7ef13 Fix weird style on username sheet 2023-04-03 03:13:54 +02:00
Vendicated
a7e2fb48ba fix oopsie 2023-04-03 02:36:54 +02:00
Nuckyz
ae80749dd8 Game Activity Toggle and SettingsStoreAPI (#587) 2023-04-03 02:13:44 +02:00
Vendicated
8c47b7080d QuickReply & Up Key: Do not attempt to edit/reply to logged deleted message 2023-04-02 22:14:58 +02:00
Juby210
8378638ee4 BetterFolders: fix mentions display (#761)
closes #759
2023-04-02 20:31:10 +02:00
V
7c563471f6 Fix typo 2023-04-02 18:31:23 +02:00
Juby210
29382d2781 Add BetterFolders plugin (#530)
Co-authored-by: Ven <vendicated@riseup.net>
2023-04-02 17:43:06 +02:00
Vendicated
6226672ee8 Web: Update extension icon from trolley to Vencord logo 2023-04-02 16:55:36 +02:00
V
5b5ee82f27 Update Contributor Badge to new logo 2023-04-02 16:16:15 +02:00
Remty
62f74f5917 feat(plugin): FakeProfileThemes (#710) 2023-04-02 16:12:19 +02:00
V
265c7a18a7 Delete corruptMp4s.ts
Discord/Electron fixed this bug, so mp4s created by this plugin just look normal on Electron 22, not fixable
2023-04-02 04:33:17 +02:00
Vendicated
462f191051 Bump to v1.1.4 2023-04-02 04:26:05 +02:00
V
6960a439c9 Add Notification log (#745) 2023-04-01 02:47:49 +02:00
Vendicated
4dff1c5bd5 RelationShipNotifier: Delay by 5s to fix false positives 2023-03-31 17:17:50 +02:00
nick
2c8ebdce7d feat(plugin): RelationshipNotifier (#450)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-31 05:07:35 +00:00
Nuckyz
dae7cb67ef Fix IgnoreActivities broken patch (#743) 2023-03-31 04:11:15 +00:00
Berlin
081b01b667 feat(plugin): Wikisearch (#585)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-31 04:09:19 +00:00
Vendicated
5340ea7ba0 Add back window transparency with temporary unsafe settings key 2023-03-31 05:59:45 +02:00
Vendicated
84a649a671 docs: fix ToC 2023-03-31 05:56:08 +02:00
Vendicated
efd9927696 Fix broken plugins 2023-03-31 05:55:25 +02:00
V
c86a34a15d Update 1_INSTALLING.md 2023-03-31 05:30:45 +02:00
Vendicated
ff16513f21 Fix onHeadersReceived clashes when using OpenAsar (fix github raw styles) 2023-03-31 01:18:57 +02:00
Vendicated
906c265aea FakeNitro: Fix fake emote rendering incorrectly in thread previews 2023-03-31 00:15:51 +02:00
Vendicated
708c16176b Remove transparency feature
This not only causes incredibly confusion among users because they
expect it to work without themes, it also causes freezes/whitescreens
for some users. Thus, this feature is disabled for now until someone
contributes a fix!
2023-03-30 23:48:26 +02:00
whqwert
035d1e24b2 feat(SpotifyControls): Fix background color for built-in themes (#731)
Co-authored-by: V <vendicated@riseup.net>
2023-03-30 17:09:04 +02:00
Vendicated
48e9b1be7a new Plugin: GifPaste - Insert Gif links instead of sending 2023-03-30 15:58:20 +02:00
Vendicated
6acdaf207d NoTrack: Update description & authors 2023-03-30 01:41:18 +02:00
Vendicated
9d41b360c9 Fix NoTrack 2023-03-30 01:35:42 +02:00
Vendicated
12cbd73e7f SpotifyControls: Add right click menus to title/album/artists 2023-03-30 01:29:34 +02:00
Phil
420b068094 Fix makeProxy returning stale proxies after assigning objects (#722) 2023-03-28 18:26:57 +00:00
Vendicated
ee943c4284 Bump to v1.1.3 2023-03-28 19:09:48 +02:00
Vendicated
337b3709d6 types: Make ErrorBoundary.wrap explicitly return Function 2023-03-28 19:06:58 +02:00
Elliott Tallis
eb318c678f feat(ViewRaw): Improve View Raw action icon (#720)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-28 16:59:30 +00:00
Vendicated
081df6beb7 Fix SilentMessage/SilentTyping toggles showing in wrong sections
Closes #656
2023-03-28 18:56:12 +02:00
Vendicated
ab911b48b5 TypingTweaks: Make names open profile on click
Closes #718
2023-03-28 18:43:45 +02:00
Skye
8cb3491086 feat(uwuify): improve uwuification algorithm (#706)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-28 16:23:51 +00:00
Lewis Crichton
ee794d140f fix: no more theme box obliteration (#707)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-28 16:20:06 +00:00
Vendicated
a00542b61b MessageLinkEmbeds: Fix weird commas in title 2023-03-26 01:27:30 +01:00
Vendicated
041a13c9d3 DevCompanion: Always use original 2023-03-26 01:27:01 +01:00
Lewis Crichton
24aa90bd9c fix API plugins being force enabled unconditionally (#704)
* only enable dependencies if required

* fixme note
2023-03-25 15:20:00 +00:00
Ven
c574f53417 Add Code of Conduct (#680) 2023-03-25 12:41:39 +00:00
Nuckyz
92b84a9e94 Fix broken patches (#701) 2023-03-25 08:42:26 +00:00
RuiNtD
bbf3c74cb2 Update LastFM plugin (#483)
Co-authored-by: Ven <vendicated@riseup.net>
Co-authored-by: Sofia Lima <me@dzshn.xyz>
2023-03-25 04:00:27 +01:00
hunter
93cb51a975 feat(MessageEvents): Promisable send/edit listeners (#514)
* promisable send/edit listeners

* added self

* Apply suggestions from code review

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>

* fix patches

---------

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-25 03:54:20 +01:00
Syncx
0b4ae729a3 feat(plugin): SearchReply (#551)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-03-25 02:37:29 +00:00
TheKodeToad
b90392576e PronounDB: Add support for compact mode & clean up (#604) 2023-03-25 01:30:24 +00:00
Ven
e143260891 MessageLogger: Add context menu entry to remove history (#693) 2023-03-25 00:55:40 +00:00
Đỗ Văn Hoài Tuân
644c5c4faa Make Vencord title look consistent with Discord (#685)
closes (#649)
2023-03-25 00:42:18 +00:00
Ven
8d8cedd72c Also add Emote Cloner to Emote picker rightclick menu (#664) 2023-03-24 03:42:38 +01:00
Nuckyz
082ac62eda feat(FakeNitro): Transform fake emojis into real ones (#669) 2023-03-23 10:45:39 +00:00
Nuckyz
7923a790e6 Fix MessagePopoverAPI and any error Fake Nitro client theme bypass might have (#665) 2023-03-23 02:11:28 -03:00
Vendicated
1368c25824 ci: Auto generate plugin json 2023-03-23 04:37:53 +01:00
iwa
d0b3678ad6 fix messagelogger deleted styles (#642)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-22 04:37:04 +01:00
Vendicated
cae8b1a93b Bump to v1.1.2 2023-03-22 04:07:12 +01:00
Nuckyz
a1c1fec8cb Improve Fake Nitro client themes bypass (#654) 2023-03-22 03:01:32 +00:00
Lewis Crichton
55a66dbb39 fix(RoleColorEverywhere): MessageLinkEmbeds DM error (#648) 2023-03-21 23:57:53 -03:00
Nuckyz
a2f0c912f0 Fix Fake Nitro making Discord unusable and ColorSighted not working sometimes (#640) 2023-03-21 09:41:31 +00:00
Nuckyz
e29bbf73aa Fix Nitro Themes with FakeNitro (#639) 2023-03-21 09:03:28 +00:00
Nuckyz
0ba3e9f469 I'm sorry for hurting you Fake Nitro (#637) 2023-03-21 06:41:11 +00:00
Nuckyz
6f200e9218 Fix grammar and SHC patches matching wrong var (#636) 2023-03-21 06:30:09 +00:00
Nuckyz
586b26d2d4 Fixes and ShowHiddenChannels improvements (#634)
~ Fixes #630
~ Fixes #618
2023-03-21 06:07:16 +00:00
Ven
d482d33d6f Fix the infamous MessageClickActions bug 2023-03-21 03:19:02 +01:00
Dossy Shiobara
37c2a8a5de fix: settings input validation and error handling (#609)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-21 02:16:01 +00:00
Dossy Shiobara
265547213c docs: clarify empty patches array behavior (#610)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-21 02:14:27 +00:00
afn
87e46f5a5a chore(friendInvites): improve descriptions formatting (#628) 2023-03-21 03:13:11 +01:00
Nuckyz
e36f4e5b0a Fixes and make guild tooltip show users inside hidden voice channels (#613)
* Fix #509

* Fix #597

* Fix #594
2023-03-19 22:03:33 -03:00
Xinto
4aff11421f Replace update notices with notifications (#558) 2023-03-19 09:21:26 +00:00
Nuckyz
ea642d9e90 Fix #598 (#612) 2023-03-19 08:44:11 +00:00
fawn
17c3496542 feat(typingIndicator): Option to not show indicator for blocked users (#513) 2023-03-19 05:13:17 -03:00
Nuckyz
0fb79b763d Improvements, changes and fixes (#611) 2023-03-19 04:53:00 -03:00
whqwert
5873bde6a6 fix(apiMessagePopover): fix match (#608) 2023-03-18 22:38:08 +01:00
Nuckyz
0b79387800 feat(PlatformIndicators): Colored mobile indicator option (#536)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-18 04:58:49 +01:00
Lewis Crichton
6b493bc7d9 feat(plugin): F8Break (#581)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-18 04:54:19 +01:00
LordElias
de53bc7991 messageLogger: fix edited timestamp styling & add i18n (#607) 2023-03-18 04:37:55 +01:00
Lewis Crichton
4c5a56a8a5 fix(RoleColorEverywhere): Chat mentions (#605) 2023-03-15 22:27:46 -03:00
Vendicated
ed873ef9de Bump to v1.1.1 2023-03-15 18:01:04 +01:00
LordElias
d8a553feb0 improve MessageLogger deleted image hover animation (#603) 2023-03-14 18:48:36 +01:00
Ven
4717612090 :shipit: 2023-03-12 16:37:41 +01:00
Vendicated
5d1283bd85 Add Web/Desktop specific plugin capabilities; misc fixes 2023-03-11 14:18:32 +01:00
Ven
3b945b87b8 Delete src/plugins/reviewDB directory
Api owner refusing to properly moderate hate speech and related illegal / ToS infringing content
2023-03-11 12:26:54 +01:00
Vendicated
19c762f9c1 DevCompanion: Fix Deps 2023-03-11 00:28:27 +01:00
Vendicated
990adf7527 Fix casing in filename 2023-03-11 00:27:02 +01:00
Vendicated
983414d024 Add DevCompanion plugin (https://github.com/Vencord/Companion) 2023-03-11 00:25:49 +01:00
Vendicated
d5c05d857f Add DevOnly plugin capability 2023-03-11 00:25:32 +01:00
Nuckyz
bff6788546 feat(plugins): SilentMessageToggle (#586)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-09 01:19:28 +01:00
Nuckyz
253183a16a Fix Emote Cloner and improve ReverseImageSearch (#489) 2023-03-08 04:01:24 -03:00
Nuckyz
0fb3901a18 Fix Context Menu API (#583) 2023-03-08 06:01:15 +00:00
Nuckyz
1b199ec5d8 feat: Context Menu API (#496) 2023-03-08 01:59:50 -03:00
Nuckyz
40395d562a Improvements for patches and misc stuff (#582) 2023-03-08 00:11:59 -03:00
Nuckyz
7322c3af04 Fix Crash Loops and prevent metrics (#580) 2023-03-06 22:54:01 +01:00
Nuckyz
36c27f1111 VCDoubleClick: Fix applying to non voice channels (#572) 2023-03-06 02:39:53 +01:00
Nuckyz
95db6c32a3 Fix Ignore Activities button on platforms different than Windows (#528)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-06 00:12:52 +01:00
Nuckyz
bed5e98bb0 Misc fixes and improvements (#555)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-05 22:49:59 +01:00
Nico
a5392e5c53 fix(silentTyping): fix chatbar icon patch (#570) 2023-03-05 22:30:37 +01:00
Sammy
abbd298b31 Fix(InvisibleChat) Fix chatbar icon patch (closes #560) (#566)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-05 22:05:46 +01:00
Nuckyz
e219aaa062 Notifications: Permanent option and close button (#563)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-04 18:49:15 +01:00
Vendicated
cab72e1be6 Strongly type useSettings (supersedes #559) 2023-03-04 18:41:32 +01:00
Berlin
92372bde1d Update 1_INSTALLING.md (#562) 2023-03-03 23:55:21 +00:00
megumin
6747276a87 Add admin warnings to INSTALLING.md (#561) 2023-03-03 23:07:48 +00:00
Vendicated
03915b7533 Bump to v1.1.0 2023-03-02 21:19:33 +01:00
Vendicated
5e2ec368ad patches: Make $self more robust 2023-03-02 21:17:15 +01:00
Vendicated
ab8c93fbac Rewrite MessageLinkEmbeds part 2 2023-03-02 21:05:09 +01:00
Vendicated
d6a3edefd9 Rewrite MessageLinkEmbeds to improve Code Quality 2023-03-02 21:01:31 +01:00
Vendicated
727297ec4e Fix messageLinkEmbeds 2023-03-02 18:49:24 +01:00
megumin
eccc4b0be1 feat(plugins): add FixInbox plugin (#552) 2023-03-02 04:55:30 +00:00
Vendicated
8465140bc4 Bump to v1.0.9 2023-03-01 21:40:31 +01:00
Lewis Crichton
e6ccb751a0 Fix for latest Discord Update (#550)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-03-01 21:35:08 +01:00
Marvin Witt
dfc7a15083 chore: extend description of NoDevtoolsWarning plugin (#545)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-01 18:32:58 +01:00
Vendicated
37003edae9 fix(Notifications): Correctly close errored notifications 2023-03-01 05:45:17 +01:00
Nuckyz
faa90eccd3 feat: Crash Handler (#531)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-01 05:26:13 +01:00
Cloudburst
c91b0df607 GMPolyfill: add header prop (#543) 2023-02-28 23:13:49 +01:00
Ven
f56d99e133 Update README.md 2023-02-28 22:38:02 +01:00
Ven
c690662802 Improve README 2023-02-28 22:37:09 +01:00
Vendicated
4918d699d5 Windows: Add Option to use native titlebar ~ Closes #537 2023-02-28 22:17:39 +01:00
Justice Almanzar
5ec517875e typings for defaultless settings (#512)
* typings for defaultless settings

* fix other silly typings

* type guard utils

---------

Co-authored-by: Ven <vendicated@riseup.net>
2023-02-28 06:12:35 +01:00
Vendicated
cf56ad985b oop oop oop 2023-02-28 02:43:58 +01:00
Vendicated
c09d1558f7 Add SupportHelper plugin 2023-02-28 02:40:45 +01:00
Vendicated
eb190b660e Bump to v1.0.8 2023-02-28 01:50:17 +01:00
Lewis Crichton
d6f9068695 feat: SearchableSelect (#518)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-28 00:48:58 +01:00
Nico
cb507babaa fix: vcDoubleClick and revealAllSpoilers patch (#517)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-28 00:41:14 +01:00
Vendicated
235d114193 Improve ConsoleShortcuts plugin 2023-02-28 00:38:28 +01:00
Vendicated
9aba70dcb1 Fix MenuItemDeobfuscator 2023-02-28 00:17:39 +01:00
Vendicated
0b61d29c31 Fix TypingTweaks 2023-02-28 00:17:28 +01:00
megumin
335a13a38a fix tooltip component check (#541) 2023-02-27 21:19:01 +00:00
Vendicated
128ee41252 ErrorBoundary: Do not use any Discord components to be more robust 2023-02-25 19:10:01 +01:00
Vendicated
ccca41a168 Bump to v1.0.7 2023-02-24 06:08:45 +01:00
Vendicated
af4c7d8a90 Fix Cards (they look ugly now, wtf Discord) 2023-02-24 05:48:37 +01:00
nick
77c691651e ReviewDB: Show edit instead of create review where applicable (#466)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-18 03:35:51 +01:00
Nuckyz
e14ec96e21 feat(FakeNitro): Bypass client themes and fixes (#504)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-18 03:32:02 +01:00
Vendicated
ff1f337699 Fix QuickCSS on electron 20+ 2023-02-17 15:37:38 +01:00
Nuckyz
3ca87848e5 TypingIndicator: Fix a dumb (#503)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-17 01:31:55 +01:00
Vendicated
9420735bc7 Version 1.0.6 2023-02-16 23:40:38 +01:00
Vendicated
6807820f6c Badges should use ErrorBoundaries 2023-02-16 22:46:51 +01:00
Vendicated
3cad0d60b4 Silly Discord changed a bunch of css vars 2023-02-16 22:40:19 +01:00
Vendicated
fbbc198b1b Fix PlatformIndicator 2023-02-16 22:31:13 +01:00
Nuckyz
224ae979f2 feat(plugins): Typing Indicator (#502) 2023-02-16 03:57:57 +01:00
Lewis Crichton
27fc20118b feat(plugin): RoleColorEverywhere (#482)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-16 02:50:42 +01:00
Nuckyz
60ccd8cc25 Various plugin fixes (#492)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-16 02:00:09 +01:00
Lewis Crichton
5c1519156b feat(plugin): ColorSighted (#501) 2023-02-16 01:46:14 +01:00
Vendicated
58270ef925 bump to v1.0.5 2023-02-14 19:22:01 +01:00
Vendicated
68055977d2 NotificationAPI: Correctly request browser permissions 2023-02-14 19:20:10 +01:00
Sammy
2b0c25b45c Feat(InvisibleChat): Add Autodecryption (#490)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-12 22:10:03 +01:00
Vendicated
c154965d70 TypingTweaks: Fix crash after changing language 2023-02-12 21:07:05 +01:00
Ven
614234ad20 MessageLinkEmbeds: Prevent infinite cycles (#488) 2023-02-12 19:43:57 +01:00
Nuckyz
2489bc6831 Fix WhoReacted (#487)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-12 18:58:44 +01:00
fawn
d95be1acba refactor: update plugins to use $self (#478)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-10 22:41:49 +01:00
Ven
1d995e58f5 Notification API (#467)
Co-authored-by: Ven <vendicated@riseup.net>
Co-authored-by: afn <hey@afn.lol>
Co-authored-by: afn <afnzmn@gmail.com>
2023-02-10 22:33:34 +01:00
Justice Almanzar
6114bc6b16 make proxies enumerable (#476)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-09 21:21:14 +01:00
Vendicated
ae98401bd3 Fix lag when alt tabbing to Discord 2023-02-09 19:36:30 +01:00
Nuckyz
992a77e76c ShowHiddenChannels: Stage and voice channels support (#469)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-08 21:54:11 +01:00
Nuckyz
291f38115c New webpack filter: byDisplayName (#474) 2023-02-08 21:48:26 +01:00
cryptofyre
8a52189378 feat(plugin): richerCider (#471)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-08 21:48:12 +01:00
Vendicated
70278f64a9 Fix broken patches 2023-02-01 18:00:25 +01:00
Vendicated
7b1d03699d ci(reporter): Ignore 404/429 errors 2023-02-01 14:13:55 +01:00
Nico
8b40760187 fix(showHiddenChannels): revert lock icon to correct path (#465) 2023-02-01 13:59:58 +01:00
whqwert
de0990434e feat(plugin): RevealAllSpoilers (#381)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-01 13:38:02 +01:00
Nuckyz
369d179bbf ShowHiddenChannels: New screen for showing hidden channels (#460)
Co-authored-by: Ven <vendicated@riseup.net>
2023-02-01 12:11:05 +01:00
Nick
8f4e8d0a9b TypingTweaks: fix crash on non en-US locales (#463) 2023-01-31 06:35:52 +01:00
Vendicated
62f7e4d45c Add stylelint 2023-01-30 05:04:06 +01:00
Vendicated
fce7d6b681 Make webpack types importable from @webpack/types 2023-01-30 04:53:28 +01:00
Vendicated
69715070b9 browser ext: change applications to browser_specific_settings 2023-01-29 00:22:11 +01:00
Vendicated
d9fb7f45b5 ci: fix extension publishing 2023-01-29 00:19:39 +01:00
Vendicated
e32388e3ac ci: fix version check 2023-01-29 00:12:27 +01:00
Vendicated
823fa2d0c3 Bump to v1.0.4 2023-01-29 00:10:17 +01:00
Vendicated
3cdffe444e chore: Fix inconsistent file name casing 2023-01-29 00:09:17 +01:00
Nick
429ab9d363 feat(plugin): TypingTweaks (#432)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-29 00:06:33 +01:00
Kaydax
072ad3d7e6 feat(settings): Add the ability to make the window transparent (#457) 2023-01-28 23:54:38 +01:00
Sofia
6e22a96d9e feat(ShowHiddenChannels): fix channel switch keybinds jumping to hiddens (#459)
Co-authored-by: Nuckyz
2023-01-28 01:40:10 +01:00
Ven
bc4c7473e8 ci: fix typo 2023-01-26 22:51:30 +01:00
Ven
399305fd8a Automatic extension publishing (#453) 2023-01-26 22:38:02 +01:00
Vendicated
0c030a3a27 VcNarrator: Show all voices, better defaults 2023-01-25 21:08:45 +01:00
Vendicated
49aacccc19 shc: Make topic not inline 2023-01-25 21:06:22 +01:00
Vendicated
6ab4b48b47 chore: bump deps 2023-01-25 21:05:35 +01:00
Vendicated
103cd14361 Fix Themes Tab 2023-01-25 17:49:19 +01:00
Vendicated
41226f0358 Fix ShowHiddenChannels 2023-01-25 04:08:37 +01:00
Ven
5d3148cf50 New plugin: VcNarrator (#402)
Co-authored-by: Nico <nico@d3sox.me>
2023-01-25 03:42:01 +01:00
Nuckyz
d628924b59 ShowHiddenChannels: More improvements (#454)
- Remove buttons like the invite button when hovering hidden channels (as they do not work correctly)
- Make hideUnreads false work with HiddenIconWithMutedStyle
- migrate to definePluginSettings
- Change hardcoded constants to webpack gathering
- Clean up some patches
- Other minor things
- Make all patches use lookbehind for cleaner replacements (and better performance too lmao)
- Handle trying to connect to hidden channels
2023-01-25 03:35:34 +01:00
Ven
f19504f828 split up webpack commons into categories & type everything (#455) 2023-01-25 03:25:29 +01:00
Vendicated
a38ac956df chore: Remove legacy workarounds 2023-01-24 13:50:02 +01:00
Vendicated
34276301c3 Fix Settings UI (Discord removed default margins 2023-01-24 13:35:57 +01:00
Ven
b2ecb02335 Make Windows Ctrl+Q feature optional; add opt-in auto update (#451) 2023-01-24 01:42:57 +01:00
Vendicated
25d32ce292 Settings: Fix plugin switch state not updating (fixes #209) 2023-01-23 22:43:25 +01:00
Dominik
cb4c50842f [SpotifyControls] Add option to show Controls on hover (#352)
Co-authored-by: Dominik <domi@bambus.me>
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-23 22:25:00 +01:00
megumin
83757b19be fix: emojis with duplicate names failing to clone (#449) 2023-01-23 21:11:28 +00:00
Nuckyz
75050e74ca ShowHiddenChannels: better ui, alternative display mode (#446)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-23 22:04:50 +01:00
Vendicated
8a43e9b25f dev: Better errors when using Node < v18 2023-01-23 04:02:09 +01:00
hunter
84cfe531af Ignore dotfiles in plugin dirs (#447) 2023-01-23 00:27:55 +01:00
Vendicated
68e80c4d4c Fix small QuickCss bug 2023-01-22 04:29:58 +01:00
Vendicated
b4f98e5066 Fix Settings ContextMenu Shortcuts & Settings on canary 2023-01-22 04:26:33 +01:00
Nuckyz
9602f527d8 Future proof Volume Booster to work with volume settings syncing (#439) 2023-01-21 14:41:10 +01:00
Nuckyz
64180362fd ViewIcons: Fix finding ImageModal and props passing to MaskedLink (#442)
* Fix finding ImageModal and props passing to MaskedLink

* gonna stick this here
2023-01-21 14:37:36 +01:00
Nuckyz
6e44b8c47e Fix Message Accessories API (#441) 2023-01-21 01:47:24 +01:00
Vendicated
2641adb29b canary bad 2023-01-19 19:39:33 +01:00
Vendicated
ef5b3e1818 add issue title templates 2023-01-19 19:35:53 +01:00
Vendicated
7fe3a2c805 Add issue templates 2023-01-19 19:31:14 +01:00
Nuckyz
c4d2b4a8cd Fix message logger patch (#431) 2023-01-19 00:36:31 +01:00
The Captain
08a2030bbc feat(Plugin): customRPC (#406)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-18 23:47:40 +01:00
Nuckyz
5fe0600d6c Fix Message Popover API (#425) 2023-01-17 03:33:33 +01:00
Ven
ebdcbcaf0c Update README.md 2023-01-16 05:19:04 +01:00
Ven
1d287357ca Reimplement Discord's Switch to fix performance (#413) 2023-01-15 21:26:02 +00:00
megumin
e49151ff33 remove power user instructions from readme (#417)
normal users will stop asking dumb questions in support!! pog
2023-01-15 04:59:20 +00:00
Vendicated
7478e880a8 ShowHiddenChannels: Use Lock as ChannelIcon 2023-01-14 23:01:19 +01:00
Nico
be7fa0cb3f fix(showHiddenChannels): remove obsolete icons patch (#416)
resolves https://github.com/Vendicated/Vencord/issues/415
2023-01-14 20:32:33 +01:00
Sofia
9338b92b1a feat(SilentTyping): add toggle command and icon (#398)
Co-authored-by: Ven <vendicated@riseup.net>
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
2023-01-14 18:47:12 +01:00
Ven
efb0ef8b9c Dev inject: add Mac support (#414) 2023-01-14 18:44:02 +01:00
Vendicated
fd766bc98f Dev: Hot reload core css 2023-01-14 02:15:17 +01:00
Swishilicous
0e5b8b07c9 make plugin cards prettier (#389)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-14 00:25:24 +01:00
Jeroen Claassens
7582feb603 feat(messageActions): make features toggleable (#373)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-13 23:59:31 +01:00
Dominik
6329499b1d git updater: Fix macOS (#391)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-13 23:24:51 +01:00
Nico
32cdb63885 fix(vcDoubleClick): fix functionality (#410)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-13 23:18:12 +01:00
Justice Almanzar
ea748dfb60 feat: Typesafe Settings Definitions (#403)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-13 23:15:45 +01:00
Ven
6c5fcc4119 Use GUI installer for pnpm inject/uninject (#407)
* Use GUI installer for pnpm inject/uninject

* Run Installer in DevMode
2023-01-13 17:52:28 +01:00
Vendicated
26f2b51eb9 Fix Frameless 2023-01-13 01:50:58 +01:00
Ven
075b0e0970 Update patcher.ts 2023-01-13 01:34:06 +01:00
Nick
10fd51071e feat: Add option to disable the window frame (#400)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-12 23:48:37 +01:00
Ven
e70abc57b6 Update Windows Update patcher (#404) 2023-01-12 23:15:38 +01:00
Vendicated
a8678db78c Fix React DevTools 2023-01-12 04:44:00 +01:00
Nick
bedb7b212b feat(Plugin): Add AlwaysTrust (#401)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-12 02:55:02 +01:00
Nuckyz
b39cbcd934 fix(IgnoreActivities): Fix for upcoming change (#399) 2023-01-12 02:50:31 +01:00
Vendicated
19c9a13273 Fix InvisibleChat button getting hidden by themes 2023-01-11 01:50:00 +01:00
Vendicated
c525672777 Fix BetterRoleDot crash 2023-01-11 01:24:55 +01:00
Vendicated
a772aa62f5 Fix PetPet & CorruptMp4s 2023-01-09 23:19:00 +01:00
ZerXDE "Till O
23a461c36d oneko: Disable pointer events to not block below buttons (#395)
Updated version of oneko which disables pointer Events.

Co-authored-by: Ven <vendicated@riseup.net>
2023-01-09 16:53:33 +01:00
Vendicated
da2d317555 Unhardcode PluginSettings styles 2023-01-09 16:23:40 +01:00
Vendicated
95df164e44 PluginSettings: Try to improve performance 2023-01-09 15:57:02 +01:00
Vendicated
ae9fe7fcfd Fix Ctrl+Q shortcut working when Discord minimised 2023-01-08 19:13:57 +01:00
Nuckyz
f0240ec345 chore(plugins): Fix IgnoreActivities & clean up other plugins (#384) 2023-01-08 02:15:22 +01:00
Vendicated
15aa2299c3 Add Ctrl+Q Exit shortcut on Windows 2023-01-07 22:53:41 +01:00
Sammy
06aa72c636 feat(Plugin): InvisibleChat (#349)
Co-authored-by: Ven <vendicated@riseup.net>
2023-01-07 22:52:55 +01:00
Dominik
1713450540 [PlatformIndicators] Fix Chat Badges in DMs (#367) 2023-01-07 21:31:45 +01:00
ActuallyTheSun
eecc555dac Fix Badges & MessageLinkEmbeds (#383) 2023-01-07 17:17:18 +01:00
Vendicated
5a3fbbfb30 unscuff profiles 2023-01-07 03:49:43 +01:00
Vendicated
cc51f6e2d2 Fix blurNSFW on canary 2023-01-07 03:28:40 +01:00
Vendicated
8113ed3c8c Fix canary 2023-01-07 03:19:37 +01:00
ActuallyTheSun
b8ed72286b fix(ViewIcons): module not found (#382) 2023-01-05 16:03:44 +01:00
Ven
9c5a149fb1 Move Installer to Vencord org 2023-01-04 20:56:14 +01:00
Ven
cf2bf2b43a oop 2023-01-04 01:17:08 +01:00
Elliott Tallis
e6f759eecd Add ImgOps to ReverseImageSearch (#379) 2023-01-03 19:42:06 +01:00
Vendicated
933216fcd5 QoL(PluginSettings): auto focus search bar 2023-01-03 02:37:22 +01:00
Vendicated
bcbbc79365 Happy new year 2023-01-03 02:32:08 +01:00
A user
374531d10e Extract inline styles to css (#370) 2023-01-03 02:30:54 +01:00
Ven
2e5d27b6b6 feat: Proper CSS api & css bundle (#269)
Co-authored-by: Vap0r1ze <superdash993@gmail.com>
2022-12-25 20:47:35 +01:00
Dominik
2172cae779 [PlatformIndicators] Add own Status (#350)
Co-authored-by: Dominik <domi@bambus.me>
Co-authored-by: HypedDomi <HypedDomi@users.noreply.github.com>
Co-authored-by: Ven <vendicated@riseup.net>
2022-12-23 04:16:17 +01:00
Ven
e740f55450 Fix Vencord 2022-12-23 03:46:39 +01:00
Nickyux
aff1b68d6b Add a "NEW" Badge for New Plugins (V2)! (#234)
Co-authored-by: Ven <vendicated@riseup.net>
Co-authored-by: Justice Almanzar <superdash993@gmail.com>
Co-authored-by: ArjixWasTaken <53124886+ArjixWasTaken@users.noreply.github.com>
2022-12-23 03:17:19 +01:00
Nuckyz
074542f0b3 feat(plugins): NoScreensharePreview plugin (#358) 2022-12-23 03:00:59 +01:00
Vendicated
b0c41d556a Improve treeshaking 2022-12-22 18:05:04 +01:00
Elliott Tallis
af0d34b155 pointy is a "contributor" (#359) 2022-12-22 17:42:54 +01:00
Ven
6dd705f951 Update build.yml 2022-12-22 17:25:57 +01:00
Ven
259f0284f0 Update build.yml 2022-12-21 22:43:54 +01:00
Ven
cb9eb1f772 i hate ci i hate ci 2022-12-21 21:12:28 +01:00
Ven
42b4eebca1 Update build.yml 2022-12-21 21:05:52 +01:00
Ven
a9ee0c7e50 Delete obsolete FUNDING.yml 2022-12-21 20:59:06 +01:00
Elliott Tallis
73b7f11d7a Also push builds to https://github.com/Vencord/builds (#344)
Co-authored-by: Ven <vendicated@riseup.net>
2022-12-21 20:58:07 +01:00
ActuallyTheSun
d806be1346 feat(PlatformIndicators): add indicator to messages (#343) 2022-12-21 20:16:32 +01:00
V3L0C1T13S
1f73cfa91a EmoteCloner: Use CDN_HOST variable to support unofficial backends (#356) 2022-12-21 16:12:05 +01:00
Nuckyz
7e6077367a feat(plugins): DisableDMCallIdle (#355) 2022-12-20 23:54:47 +01:00
Vendicated
103c499310 Monaco Popup: Add metadata, store window instance 2022-12-20 18:04:33 +01:00
Vendicated
9dcafbf468 Fix Notices
Have I ever mentioned how terrible Discord's Notices code is?
2022-12-20 18:03:58 +01:00
Nuckyz
1742bb6020 Fix StartupTimings (#353) 2022-12-20 16:18:15 +01:00
Vendicated
0743c1215e Add canary test 2022-12-20 02:59:16 +01:00
Vendicated
94ad8e8f61 Add useEffect/useState/useMemo to webpack commons 2022-12-20 00:34:26 +01:00
Justice Almanzar
989bd36eeb refactor: identifier escapes + "self" group (#339)
Co-authored-by: Ven <vendicated@riseup.net>
2022-12-19 22:59:54 +00:00
Nuckyz
4974c53f9c Improve PronounDB patch (#348) 2022-12-18 05:13:34 +01:00
Manti
47de9fab2e Make some changes to reviewdb ui and add badges to it (#245) 2022-12-17 23:30:29 +01:00
Nico
3efc79224f [ShowHiddenChannels] Fix last message date (#342) 2022-12-16 15:51:23 +01:00
Nuckyz
456164253d fix(MessageLogger): Fix module not being found (#338) 2022-12-16 14:16:47 +01:00
Vendicated
c257f86576 Fix experiments 2022-12-15 17:53:12 +01:00
Pedro
f6122a00ca feat(NoReplyMention): exempt list support (#337) 2022-12-15 14:05:44 +00:00
Ven
f1bdfdd6b9 Update nsfwGateBypass.ts 2022-12-14 23:50:00 +01:00
ActuallyTheSun
c8f2141114 feat(plugin): add MessageLinkEmbeds (#264)
Co-authored-by: Ven <vendicated@riseup.net>
2022-12-14 23:44:58 +01:00
megumin
fea8c60a40 hotfix injector for ptb/canary/dev (#330) 2022-12-14 23:35:02 +01:00
A user
a67db11dc2 Improve Settings UI & View Raw Modal (#332)
very cool
2022-12-14 00:44:57 +01:00
Box_
9a088b7a31 MoreKaomoji: Add more kaomoji (#299) 2022-12-09 22:54:46 +01:00
Justice Almanzar
ebb8da0f23 fix(FakeNitro): more reliable patches (#304) 2022-12-09 04:32:16 +01:00
Commandtechno
f2e0542614 New Plugin: NSFWGateBypass (#300) 2022-12-09 00:35:09 +01:00
megumin
ee24439795 feat(plugin): sort friend requests by date received (#280) 2022-12-08 23:53:12 +01:00
David Ralph
022bf17140 fix inconsistent margins & capitalisation (#281) 2022-12-08 23:51:18 +01:00
Justice Almanzar
2de461985d fix(ShikiCodeblocks): spoilers (#298)
* fix(ShikiCodeblocks): spoilers

* fix a settings bug i thikn
2022-12-08 15:54:19 +01:00
Justice Almanzar
2d08dd8a9c Shiki settings preview (#297) 2022-12-07 15:33:40 +01:00
Commandtechno
49b45d8262 google changed their shit (#294) 2022-12-05 23:14:48 +00:00
Cloudburst
8a5a5c7d1e UserScript: add csp bypassing fetch (#284) 2022-12-04 13:58:29 +01:00
Nuckyz
53d0a55561 refactor(IgnoreActivities): Use React Components and support Embedded Activities (#282) 2022-12-04 02:16:47 +01:00
Commandtechno
25ef5d60b4 add me to contributors (#287) 2022-12-03 22:42:18 +00:00
Commandtechno
c74241fde6 add commas in member count (#286)
l
2022-12-03 23:11:08 +01:00
Vendicated
4d8145f12c Fix arrpc 2022-12-03 14:58:00 +01:00
Ven
d4f70218ba ci: Do not release extension-v2.zip 2022-12-03 13:42:46 +01:00
Ven
6b4b4772bb Update README.md 2022-12-03 13:41:31 +01:00
Justice Almanzar
54010aab94 fix: hljs fallback (#283) 2022-12-03 11:32:14 +01:00
A user
62b2acebe6 Add support for Flatpak for Git updating (#274)
Co-authored-by: Ven <vendicated@riseup.net>
2022-12-02 16:55:53 +01:00
Justice Almanzar
41dddc9eee feat(plugin): ShikiCodeblocks (#267)
Co-authored-by: ArjixWasTaken <53124886+ArjixWasTaken@users.noreply.github.com>
Co-authored-by: Ven <vendicated@riseup.net>
2022-12-02 16:43:37 +01:00
12944qwerty
4760af7f0e add ViewRaw plugin & MiniPopover API (#275)
Co-authored-by: Vendicated <vendicated@riseup.net>
2022-12-02 16:38:52 +01:00
Vendicated
06d32ae414 browser: remove firefox extension id 2022-12-02 14:24:23 +01:00
Vendicated
2564ab73f5 ci: unlisted firefox builds for now 2022-12-02 14:21:44 +01:00
Vendicated
5e97cc0fc3 QuickCss: Hide MenuBar; explicitly enable contextIsolation
Closes #260
2022-12-02 14:11:20 +01:00
Vendicated
b9e9d9bd64 Add --vanilla flag, strip csp on mainFrame only 2022-12-02 14:10:40 +01:00
Vendicated
daf3a1dcac Try to make firefox publish work 2022-12-01 19:43:57 +01:00
Ven
f1fb79d2c5 Fix workflow 2022-12-01 19:22:49 +01:00
Roman / Linnea Gräf
0ff6d3dd41 Add Firefox extension build (#277) 2022-12-01 19:16:09 +01:00
Vendicated
734054ff68 feat(Settings): Allow moving Vencord section to different places 2022-12-01 03:38:17 +01:00
Vendicated
f94cbfb2f4 Add basic themes tab 2022-12-01 03:01:44 +01:00
Sofia
fc09460d82 feat(plugin): add ServerListIndicators (#272) 2022-11-29 00:25:07 +01:00
Vendicated
e884738f42 MemberCount: Fix misleading count, add tooltip 2022-11-28 23:01:09 +01:00
megumin
c583bad6bf the shiggy wiggy (#270) 2022-11-28 18:59:42 +00:00
Vendicated
36b787812e Add MemberCount plugin 2022-11-28 19:29:46 +01:00
Ven
836ae72076 Delete report.md 2022-11-28 16:00:54 +01:00
Vendicated
d0a40bc0ed chore: update deps 2022-11-28 15:59:15 +01:00
Vendicated
3b4879f9d9 perf(settings): Cache proxies 2022-11-28 15:44:53 +01:00
Vendicated
a0a1a4d139 enforce path aliases with eslint 2022-11-28 13:59:53 +01:00
Ven
bad96b7887 Path aliases, better lazyWebpack (#268) 2022-11-28 13:37:55 +01:00
Vendicated
7a4402f142 BlurNSFW: Support videos 2022-11-28 01:08:58 +01:00
Vendicated
3e9672c6b8 oop 2022-11-28 00:58:26 +01:00
Vendicated
a9fee6248e BlurNSFW: Add amount setting 2022-11-28 00:55:50 +01:00
Vendicated
f0ee16f173 [skip ci] update genPluginList 2022-11-28 00:45:41 +01:00
Vendicated
3db3c63b42 BlurNsfw plugin 2022-11-28 00:42:42 +01:00
megumin
4fc41c8c0b fix: add predicate to updater menu item (#266)
* fix: add predicate to updater menu item

* dont include Updater in web builds

* i can spell
2022-11-27 16:07:31 +01:00
Hana
47c181beec [skip ci] Add support for DVM installation path. (#265) 2022-11-27 14:32:54 +01:00
megumin
c4fc01c7ff [skip ci] feat(ci): test web builds (#262) 2022-11-25 23:51:36 +01:00
Ven
5a94201578 Megu blowing up main :blobcatcozyscared: 🚎 2022-11-25 23:41:02 +01:00
megumin
6b55dee9fb feat(settings): new settings design (#261) 2022-11-25 22:38:55 +00:00
Vendicated
a85ec594a7 [skip ci] docs docs docs 2022-11-25 19:25:35 +01:00
Vendicated
c2c6c9fccb CallTimer: Fix lag 2022-11-25 18:28:15 +01:00
Vendicated
b60f6cb18d WhoReacted: Make more reliable & don't spam api 2022-11-25 18:07:29 +01:00
Vendicated
bb398970ef HideAttachments: Fix embeds
Closes #259
2022-11-25 18:06:31 +01:00
Vendicated
50a96e8047 CallTimer: Fix typo 2022-11-25 16:16:07 +01:00
Vendicated
c5b5b754e2 CallTimer 2022-11-25 15:59:47 +01:00
KraXen72
0f644dff73 loadingQuotes quote fix (#255) 2022-11-24 14:26:38 +01:00
Snare-Hawk
6210d3a597 Make ReviewDB Look More Native (#256) 2022-11-24 14:26:18 +01:00
Nico
e7573382fe fix(betterNotes): add restart needed for hide notes patch (#258) 2022-11-24 14:02:11 +01:00
Vendicated
f4d7a1f4fb New Plugin: BetterNotesBox 2022-11-24 02:02:15 +01:00
Vendicated
5dd0a3a746 New Plugin: HideAttachments 2022-11-24 01:00:13 +01:00
Ven
c9fac8ffff fix tags 2022-11-23 20:04:25 +01:00
KraXen72
f93607fc66 add new quotes to loadingQuotes (#254)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-23 20:00:19 +01:00
Luna
63ffb5bebc feat(messageTags): Add message quick reply plugin (#241) 2022-11-23 19:56:20 +01:00
jd
2788d264d4 feat(plugin): Urban Dictionary (#222) 2022-11-23 14:30:59 +01:00
Nuckyz
91f1d68e29 feat(plugins): Keep Current Channel plugin (#248) 2022-11-23 02:51:45 +01:00
Nuckyz
7e4f4f1794 feat(plugins): Volume Booster plugin (#249) 2022-11-22 23:22:54 +01:00
megumin
9f7ec0aa8d settings: better button text for plugin settings modal (#251) 2022-11-22 22:05:46 +00:00
Vendicated
0239bb0aac Commands: Show plugin name instead of 'Built-In' 2022-11-22 22:42:22 +01:00
Vendicated
ec20556d5c PlatformIndicators: Fix icon colours 2022-11-22 17:06:24 +01:00
Ven
11191b5943 Update 1_INSTALLING.md 2022-11-21 22:26:12 +01:00
CanadaHonk
1f72a0fc27 fix(arRPC): fix error on null activity (#244) 2022-11-21 20:40:40 +01:00
megumin
31ec1ec1b4 better platformindicators settings (#243) 2022-11-21 20:12:46 +01:00
Nickyux
0f7c80fd4d Fix no gap (#242) 2022-11-21 19:54:48 +01:00
Ven
b5bc88c7d4 Settings export/import (#235) 2022-11-21 19:25:40 +01:00
Kareem Olim
b42b8d755f Platform indicators: ignore unnecessary element (#240) 2022-11-21 19:25:21 +01:00
megumin
bfe1fd9912 fix: add keys to plugins grid (#237) 2022-11-21 18:45:22 +01:00
Cloudburst
c45d89697a make userscript autoincrement version :trollface: (#233) 2022-11-21 16:32:56 +01:00
Vendicated
0a92bd6521 PlatformIndicators: Fix server list 2022-11-21 15:59:19 +01:00
Kareem Olim
33c33eb0fd feat(plugin): PlatformIndicators (#227)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-21 15:44:30 +01:00
obscurity
dcf1148bb4 feat(plugin): TimeBarAllActivities (#228) 2022-11-21 11:53:28 +01:00
obscurity
58e28b4281 feat(fakeNitro): add an option to change emote sizes (#225)
closes https://github.com/Vendicated/Vencord/issues/204
2022-11-21 03:43:16 +01:00
Sofia
bb14d4989d feat(plugin): NoUnblockToJump (#229) 2022-11-21 03:40:15 +01:00
CanadaHonk
9bcdc8451f feat(arRPC): update for server 2.2 (#230) 2022-11-21 00:57:30 +01:00
Nuckyz
46b14cb2e0 feat(plugins):WhoReacted keep reaction count (#231) 2022-11-21 00:56:17 +01:00
CanadaHonk
9240865f65 feat(arRPC): update for server 2.0 (#224) 2022-11-20 16:21:42 +01:00
CanadaHonk
e85d763f22 feat(plugin): WebRichPresence (arRPC) (#223) 2022-11-20 14:31:00 +01:00
Vendicated
82911386db oop 2022-11-19 22:17:55 +01:00
Vendicated
e63ed9cac4 onekocord 2022-11-19 22:13:16 +01:00
Sofia
ba45ecda56 feat(plugin): Last.fm rich presence (#220)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-19 18:40:52 +01:00
megumin
7ff2d2ba8a fix startup timings page (#219) 2022-11-19 15:52:17 +00:00
Kareem Olim
a5154d6283 feat(plugin): Quick mention button (#218)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-19 16:11:11 +01:00
Kareem Olim
5ce2dc1bb4 feat(plugin): Read all notifications button (#217)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-19 14:54:48 +01:00
Vendicated
8f2c247f27 Fix commands showing up multiple times Part 2 2022-11-18 23:31:53 +01:00
Vendicated
43f41d20fa Fix commands showing up multiple times 2022-11-18 23:29:34 +01:00
Ven
50c356e397 fix brain fart 2022-11-18 13:39:43 +01:00
Berlin
503a2ec517 Add option to ignore incoming blocked messages (#179) 2022-11-18 05:12:45 +01:00
Vendicated
83b3b1f16b fix settings debug info on web 2022-11-17 13:49:51 +01:00
Vendicated
2628bdce42 WebContextMenus: Port copy/open link items to Discord Web 2022-11-17 01:30:23 +01:00
Vendicated
8b0911b86a Updater: Ignore non release commits 2022-11-17 00:45:00 +01:00
Ven
47d127a895 Update FUNDING.yml 2022-11-17 00:41:20 +01:00
Vendicated
410613726b Donor Badges && Add donate info to settings 2022-11-17 00:21:20 +01:00
Ven
8b3f290e3c Create FUNDING.yml 2022-11-16 22:40:26 +01:00
Vendicated
a788813383 VencordWeb: Migrate to manifest v3 2022-11-16 16:23:52 +01:00
Vendicated
e1de6f88fe Unexplode Modals on canary 2022-11-16 14:52:05 +01:00
Vendicated
ae86848cf6 Fix ReviewDB 2022-11-16 01:02:23 +01:00
Manti
84ec839b04 Add ReviewDB Plugin (#187)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-16 00:40:46 +01:00
Vendicated
b30508aef8 better handling for settings ui errors 2022-11-15 17:29:31 +01:00
Nico
eabbf7d9bd fix(fakeNitro): add missing predicate for sticker bypass (#215) 2022-11-15 09:34:53 +01:00
Vendicated
be088f9072 Don't unnecessarily create functions many times 2022-11-15 09:30:33 +01:00
Vendicated
2ca98a87d2 Fix Settings UI on canary 2022-11-15 09:28:06 +01:00
Vendicated
b49ac6b541 ClickableRoleDot -> BetterRoleDot; now allows using both role colour styles at once 2022-11-14 21:42:02 +01:00
Ven
82e444e196 Less confusing plugin names (bulk plugin rename) (#214)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2022-11-14 18:05:41 +01:00
Ven
a96f8a89f3 MessageLogger: fixes + ignoreSelf & ignoreBots option (#213) 2022-11-14 16:22:50 +01:00
afn
4642b54260 feat(plugin): FriendInvites (#208)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-13 23:34:10 +01:00
Ven
15b257a7b0 Friendlier Readme 2022-11-13 23:24:25 +01:00
rushii
0dbec8d0cd feat: message logger plugin (#49)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-13 23:13:32 +01:00
Vendicated
e5b23ff556 EmoteYoink: Don't depend on ReverseImageSearch whoopsie 2022-11-13 04:12:37 +01:00
Vendicated
9110d1f9bd Emote Clone plugin 2022-11-13 03:46:46 +01:00
Vendicated
81edc14070 fix PronounDB crash with new profile in dms, force start dependencies 2022-11-12 17:20:19 +01:00
Vendicated
b48c8d8a4a NitroSpoof: Fix inbuilt sticker logic; cleanup 2022-11-12 16:25:28 +01:00
Vendicated
8380328465 InteractionKeybinds: Scroll to message if offscreen
Closes https://github.com/Vendicated/Vencord/issues/200
2022-11-12 00:29:36 +01:00
megumin
30ca4f1cf9 feat: Badge API (#206) 2022-11-11 23:50:09 +01:00
Nuckyz
62e0787cf2 fix(plugins): Fix IgnoreActivities (#205) 2022-11-11 19:02:03 +01:00
Vendicated
cc7c14ec88 Reporting: do not error for patches with all:true 2022-11-11 19:01:01 +01:00
Vendicated
a86452e774 fix react hook error 2022-11-11 18:58:30 +01:00
Vendicated
dddb28192c even more plugin fixes 2022-11-11 18:49:47 +01:00
Vendicated
2133823bd3 more plugin fixes 2022-11-11 16:43:40 +01:00
Vendicated
1176896a1b fix(plugins): PronounDB, ViewIcons, WebhookTags, NoBlockedMessages, BetterGifAltText, MessageAccessories 2022-11-11 16:14:09 +01:00
Ven
f3aba3edb0 ci: Add webhook secret to reporter env 2022-11-11 13:30:51 +01:00
Vendicated
409e54a9d8 ci(reporter): Post results to discord webhook 2022-11-11 13:27:44 +01:00
Vendicated
31fb19b8c9 ci: Hopefully fix reporting 2022-11-11 13:06:04 +01:00
Vendicated
a26f636c9b ci: Automated plugin test with puppeteer 2022-11-11 12:37:37 +01:00
Vendicated
8ba9c96f20 Fix most plugins 2022-11-11 00:11:44 +01:00
Jānis
57f3feba68 spotifyControls: make album of local tracks unclickable (#203) 2022-11-10 19:33:00 +01:00
megumin
010523eeac feat(plugins): add vc effect event to moyai plugin (#199) 2022-11-10 14:04:06 +01:00
Nico
15f12073cf spotifyControls: make title/artists of local tracks unclickable (#201)
Co-authored-by: Vendicated <vendicated@riseup.net>
2022-11-10 14:02:34 +01:00
Vendicated
58636a9a82 CorruptMp4s: Depend on CommandsAPI ~ PronounDB: Add pronoundb link 2022-11-09 23:01:59 +01:00
Vendicated
0bc894d065 CorruptMp4s: Better default 2022-11-09 21:17:21 +01:00
Vendicated
6f38c4b7fe new plugin(CorruptMp4s): Mp4s with infinite/negative duration 2022-11-09 21:15:52 +01:00
Vendicated
c1d2f0078f StickerSpoof: Fix not correctly cleaning previous frame 2022-11-09 20:29:35 +01:00
Vendicated
3c8084ec36 Add VSCode debug config 2022-11-09 19:26:46 +01:00
Nico
3b65384b94 fix(spotifyControls): add album/cover null checks (local files) (#198)
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-09 17:36:20 +01:00
Vendicated
e0450531ef StickerSpoof: fix small resolutions; AnonymiseFiles: fix extension logic 2022-11-09 17:30:48 +01:00
Ven
b032e9b6e3 oop 2022-11-09 16:08:11 +01:00
Vendicated
1e6b967d24 Fix moyai and fart 2022-11-09 12:47:16 +01:00
Vendicated
460f329e4f fix double click actions using outdated content 2022-11-08 18:09:11 +01:00
Nuckyz
3a3a52c493 fix(NitroBypass): Fix using stickers bypass with Nitro Classic (#196) 2022-11-08 17:51:09 +01:00
afn
4e57ae66f1 feat(SpotifyControls): prettier design (#194)
Co-authored-by: afn <hey@afn.lol>
Co-authored-by: KraXen72 <DPELECH1@GMAil.com>
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-08 17:31:36 +01:00
Vendicated
f7d9be9140 lint: Disallow utils index imports
This keeps leading to issues due to circular imports.
Import from specific files instead, index just reexports
2022-11-07 23:34:14 +01:00
Vendicated
955573d31b me when i dont depend on MenuItemDeobfuscatorApi 2022-11-07 22:36:06 +01:00
Ven
6a8564089b SpotifyControls plugin (#190) 2022-11-07 22:28:29 +01:00
Nico
7d5ade21fc feat(nitroBypass): add sticker bypass (#184)
Co-authored-by: Vendicated <vendicated@riseup.net>
2022-11-07 22:23:34 +01:00
Ven
d69dfd6205 ahem 2022-11-07 21:39:49 +01:00
Ven
177d353f50 Rename DevBuild releases to include git hash 2022-11-07 21:38:14 +01:00
Vendicated
a13c0df1cd build: Add metadata header to all bundles 2022-11-07 21:29:31 +01:00
Vendicated
0af4579204 Add tracer, fix MessageActions slow startup 2022-11-07 21:05:33 +01:00
Vendicated
851d07f31a fix(ReverseImageSearch): Don't apply to non image files 2022-11-07 18:52:34 +01:00
Vendicated
963a7332b4 Migrate proxied components to and fix LazyComponent 2022-11-06 18:37:01 +01:00
Vendicated
440baf6028 Improve proxyLazy 2022-11-06 18:00:59 +01:00
megumin
9663e229a6 feat(plugins): add Startup Timings (#189) 2022-11-05 11:09:05 +01:00
megumin
0cb24cad7e feat: make text selectable in PatchHelper (#188)
* feat: make text selectable in PatchHelper

* real div moment
2022-11-05 10:02:29 +01:00
Vendicated
65620f4976 Webpack: Do not emit errors if devtools open 2022-11-03 20:36:17 +01:00
Vendicated
cb7469afad Simplemarkdown pleeeeease 2022-11-03 19:15:51 +01:00
Vendicated
2c3dee4120 qol improvements 2022-11-03 19:12:50 +01:00
Vendicated
c20dc269d2 Modify CSP instead of deleting it 2022-11-02 22:15:55 +01:00
Vendicated
a7795533df Remove clipboardImageFix - Discord fixed the bug woooooo 2022-11-02 20:54:39 +01:00
Vendicated
5e1b42120c Fix plugins on new update 2022-11-02 20:13:55 +01:00
Vendicated
676f5c7e30 ViewIcons: size 2048 -> 512, to fit on screen 2022-11-02 17:30:15 +01:00
Vendicated
13c73699e9 Fix Webpack modules that are not arrow funcs, Part II 2022-11-01 15:06:15 +01:00
Vendicated
64aed87de4 Fix Webpack modules that are not arrow funcs 2022-11-01 14:28:25 +01:00
Nickyux
1944f3957f fix forceOwnerCrown Plugin Spamming Errors in Console (#180)
Co-authored-by: Nico <nico@d3sox.me>
Co-authored-by: Ven <vendicated@riseup.net>
2022-11-01 02:19:07 +01:00
Ven
04d6f341ee PatchHelper, a tool to help you write patches (#182) 2022-11-01 01:49:41 +01:00
rushii
0c25278c59 fix renamed app.asar detection on Windows (#185) 2022-11-01 01:47:07 +01:00
Vendicated
0fda900ccc Fix: settings.appearance may be undefined 2022-10-31 17:17:54 +01:00
Ven
8adf7ca155 Webpack Warnings & Errors (#178)
* dev: Useful strict Warnings & Errors

* Always log error

* Ignore pending patches with all or whose predicate = false

* Error -> Warn
2022-10-30 20:45:18 +01:00
Snek
b905743077 removed channel type (#170)
Co-authored-by: Ven <vendicated@riseup.net>
2022-10-30 19:17:46 +01:00
Nico
a43a41f61f vcDoubleClick: don't require dbl click on active vc, fix stage channels (#172) 2022-10-30 18:47:12 +01:00
Ven
3af9a14a0e Patcher: More useful errors with code diffs (#177)
* Patcher: More useful errors with code diffs

* Nicer log formatting

* PluginCards: ellipsises
2022-10-30 02:58:11 +01:00
Vendicated
739b1e47d4 New plugin: LoadingQuotes 2022-10-29 22:53:23 +02:00
Ven
d72542405a Implement Subcommands; fix errors due to Settings <-> Plugins circular imports (#174) 2022-10-29 20:45:31 +02:00
Vendicated
95aa2d9d8d ClipboardImageFix is not actually required 2022-10-29 20:36:43 +02:00
Ven
93859883c1 build: inject createElement alias (#176) 2022-10-29 20:27:48 +02:00
Cynthia Foxwell
37105ac416 feat(plugin): ClipboardImageFix (#173) 2022-10-29 20:25:40 +02:00
Vendicated
f6e0efe20a Reverse image search plugin 2022-10-29 15:25:34 +02:00
Vendicated
1764206e19 Add MenuItemDeobfuscator 2022-10-29 15:25:10 +02:00
Nuckyz
6b0caaae37 fix(ShowHiddenChannels): Fix New unreads box for hidden channels #168 2022-10-27 20:26:54 +02:00
Snek
c76e9f5e3d better patch & visual bug fix (#167) 2022-10-27 18:37:54 +02:00
Ven
ce73a5f172 use gh cli to update release (#166) 2022-10-27 10:55:56 +02:00
Nuckyz
9548978d80 fix(IgnoreActivities): id -> exePath (#164)
Co-authored-by: Ven <vendicated@riseup.net>
2022-10-27 10:27:52 +02:00
megumin
13882b5732 feat: custom components in settings (#165) 2022-10-26 23:42:26 +02:00
Jakup
49e72bab32 moarKaomojis plugin (#137) 2022-10-26 15:38:41 +02:00
Nuckyz
bbd3633038 fix(Ignore Activities): Fixes games manually added not being able to be ignored (#162)
Co-authored-by: Ven <vendicated@riseup.net>
2022-10-26 15:31:55 +02:00
Vendicated
c65f757bc4 Fix betterUploadButton 2022-10-26 15:10:11 +02:00
Vendicated
b87f0bf3f9 Settings: Cache default value 2022-10-26 14:28:27 +02:00
Vendicated
670b7d7d01 Make tsc happy 2022-10-26 13:54:23 +02:00
Vendicated
f492d26379 Make jsFactory shorter -> bundle size -10% 2022-10-26 13:49:28 +02:00
Ven
56b00f715a ci: Only rebuild on actual code changes 2022-10-26 13:03:53 +02:00
Vendicated
fe5a78ddc9 Update README 2022-10-25 21:40:23 +02:00
megumin
5e7c155f6e feat(settings): add beforeSave check (#161) 2022-10-25 18:49:50 +01:00
Nico
e06ba68c40 fix(vcDoubleClick): add required parentheses (#160) 2022-10-25 18:15:21 +02:00
Nico
d6fe937a70 fix(vcDoubleClick): exclude text channel mentions (#159) 2022-10-25 18:09:21 +02:00
Nico
2f46b934c9 feat: add new plugin ForceOwnerCrown (#157) 2022-10-25 17:32:05 +02:00
Vendicated
2af324c302 Improve genPluginList 2022-10-25 12:29:55 +02:00
Vendicated
4bddcee40b Add autogenerated plugin list, closes #151 2022-10-25 12:20:29 +02:00
Nico
559edbfffe Fix vcDoubleClick, add support for stage channels (#158) 2022-10-25 10:53:06 +02:00
Nuckyz
6c38362401 Ignore Activities: Fix button not working (#156)
explode
2022-10-25 00:18:30 +02:00
Nuckyz
00402c69d6 feat(plugin): Ignore Activities (#142) 2022-10-25 00:05:40 +02:00
Nico
30dd4b9e01 [ShowHiddenChannels] Add last message info, fix collapsing (#146)
Co-authored-by: Snek <107999380+sneksnake@users.noreply.github.com>
2022-10-24 23:22:39 +02:00
452 changed files with 38014 additions and 4756 deletions

View File

@ -2,40 +2,36 @@
"root": true,
"parser": "@typescript-eslint/parser",
"ignorePatterns": ["dist", "browser"],
"plugins": ["header", "simple-import-sort", "unused-imports"],
"plugins": [
"@typescript-eslint",
"simple-header",
"simple-import-sort",
"unused-imports",
"path-alias"
],
"settings": {
"import/resolver": {
"alias": {
"map": [
["@webpack", "./src/webpack"],
["@webpack/common", "./src/webpack/common"],
["@utils", "./src/utils"],
["@api", "./src/api"],
["@components", "./src/components"]
]
}
}
},
"rules": {
// 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
// information
"header/header": [
2,
"block",
[
{
"pattern": "!?",
"template": " "
},
" * Vencord, a modification for Discord's desktop app",
{
"pattern": " \\* Copyright \\(c\\) \\d{4}",
"template": " * 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/>.",
""
],
2
"simple-header/header": [
"error",
{
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
"templates": { "author": [".*", "Vendicated and contributors"] }
}
],
"quotes": ["error", "double", { "avoidEscape": true }],
"jsx-quotes": ["error", "prefer-double"],
@ -43,7 +39,7 @@
"indent": ["error", 4, { "SwitchCase": 1 }],
"arrow-parens": ["error", "as-needed"],
"eol-last": ["error", "always"],
"func-call-spacing": ["error", "never"],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
@ -63,9 +59,13 @@
"no-constant-condition": ["error", { "checkLoops": false }],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"consistent-return": ["warn", { "treatUndefinedAsUnspecified": true }],
"dot-notation": "error",
"no-useless-escape": "error",
"no-useless-escape": [
"error",
{
"extra": "i"
}
],
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
@ -88,6 +88,8 @@
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"unused-imports/no-unused-imports": "error"
"unused-imports/no-unused-imports": "error",
"path-alias/no-relative": "error"
}
}

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

66
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Bug/Crash Report
description: Create a bug or crash report for Vencord
labels: [bug]
title: "[Bug] <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: bug-description
attributes:
label: What happens when the bug or crash occurs?
description: Where does this bug or crash occur, when does it occur, etc.
placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ...
validations:
required: true
- type: textarea
id: expected-behaviour
attributes:
label: What is the expected behaviour?
description: Simply detail what the expected behaviour is.
placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ...
validations:
required: true
- type: textarea
id: steps-to-take
attributes:
label: How do you recreate this bug or crash?
description: Give us a list of steps in order to recreate the bug or crash.
placeholder: |
1. Do ...
2. Then ...
3. Do this ..., ... and then ...
4. Observe "the bug" or "the crash"
validations:
required: true
- type: textarea
id: crash-log
attributes:
label: Errors
description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```".
value: |
```
Replace this text with your crash-log.
```
validations:
required: false
- type: checkboxes
id: agreement-check
attributes:
label: Request Agreement
description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable
options:
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Vencord Support Server
url: https://discord.gg/D9uwnFnqmd
about: If you need help regarding Vencord, please join our support server!
- name: Vencord Installer
url: https://github.com/Vencord/Installer
about: You can find the Vencord Installer here

View File

@ -0,0 +1,32 @@
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

View File

@ -1,8 +1,15 @@
name: Build latest
name: Build DevBuild
on:
push:
branches:
- main
paths:
- .github/workflows/build.yml
- src/**
- browser/**
- scripts/build/**
- package.json
- pnpm-lock.yaml
env:
FORCE_COLOR: true
@ -15,42 +22,59 @@ jobs:
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18
uses: actions/setup-node@v2
- name: Use Node.js 19
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 19
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build web
run: pnpm buildWeb
run: pnpm buildWeb --standalone
- name: Build
run: pnpm build --standalone
- name: Get some values needed for the release
id: vars
shell: bash
- name: Generate plugin list
run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
- name: Clean up obsolete files
run: |
echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
- uses: dev-drprasad/delete-tag-and-release@085c6969f18bad0de1b9f3fe6692a3cd01f64fe5 # v0.2.0
with:
delete_release: true
tag_name: devbuild
- name: Get some values needed for the release
id: release_values
run: |
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Upload DevBuild as release
if: github.repository == 'Vendicated/Vencord'
run: |
gh release upload devbuild --clobber dist/*
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ env.release_tag }}
- name: Create the release
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v1
- name: Upload DevBuild to builds repo
if: github.repository == 'Vendicated/Vencord'
run: |
git config --global user.name "$USERNAME"
git config --global user.email actions@github.com
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload
cd upload
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
rm -rf *
cp -r ../dist/* .
git add -A
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: devbuild
name: Dev Build ${{ steps.vars.outputs.sha_short }}
draft: false
prerelease: false
files: |
dist/*
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
GH_REPO: Vencord/builds
USERNAME: GitHub-Actions

22
.github/workflows/codeberg-mirror.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Sync to Codeberg
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
on:
push:
workflow_dispatch:
schedule:
- cron: "0 */6 * * *"
jobs:
codeberg:
if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
with:
target_repo_url: "git@codeberg.org:Ven/cord.git"
ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }}

61
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Release Browser Extension
on:
push:
tags:
- v*
jobs:
Publish:
if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: check that tag matches package.json version
run: |
pkg_version="v$(jq -r .version < package.json)"
if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then
echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2
exit 1
fi
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- name: Use Node.js 19
uses: actions/setup-node@v3
with:
node-version: 19
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build web
run: pnpm buildWeb --standalone
- name: Publish extension
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
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
# 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:
# Chrome
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
# Firefox
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}

View File

@ -0,0 +1,58 @@
name: Test Patches
on:
workflow_dispatch:
schedule:
# Every day at midnight
- cron: 0 0 * * *
jobs:
TestPlugins:
if: github.repository == 'Vendicated/Vencord'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- name: Use Node.js 19
uses: actions/setup-node@v3
with:
node-version: 19
cache: "pnpm"
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
pnpm add puppeteer
sudo apt-get install -y chromium-browser
- name: Build web
run: pnpm buildWeb --standalone
- name: Create Report
timeout-minutes: 10
run: |
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser)
esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
- name: Create Report (Canary)
timeout-minutes: 10
if: success() || failure() # even run if previous one failed
run: |
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser)
export USE_CANARY=true
esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}

View File

@ -15,7 +15,7 @@ jobs:
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
@ -23,5 +23,11 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint & Test if it compiles
- name: Lint & Test if desktop version compiles
run: pnpm test
- name: Test if web version compiles
run: pnpm buildWeb
- name: Test if plugin structure is valid
run: pnpm generatePluginJson

4
.gitignore vendored
View File

@ -5,6 +5,7 @@ node_modules
vencord_installer
.idea
.DS_Store
yarn.lock
package-lock.json
@ -18,3 +19,6 @@ lerna-debug.log*
*.tsbuildinfo
src/userplugins
ExtensionCache/
settings/

1
.npmrc Normal file
View File

@ -0,0 +1 @@
strict-peer-dependencies=false

6
.stylelintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4
}
}

View File

@ -1,3 +1,11 @@
{
"recommendations": [ "EditorConfig.EditorConfig" ]
"recommendations": [
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"EditorConfig.EditorConfig",
"ExodiusStudios.comment-anchors",
"formulahendry.auto-rename-tag",
"GregorBiswanger.json2ts",
"stylelint.vscode-stylelint"
]
}

37
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,37 @@
{
// this allows you to debug Vencord from VSCode.
// How to use:
// You need to run Discord via the command line to pass some flags to it.
// If you want to debug the main (node.js) process (preload.ts, ipcMain/*, patcher.ts),
// add the --inspect flag
// To debug the renderer (99% of Vencord), add the --remote-debugging-port=9223 flag
//
// Now launch the desired configuration in VSCode and start Discord with the flags.
// For example, to debug both process, run Electron: All then launch Discord with
// discord --remote-debugging-port=9223 --inspect
"version": "0.2.0",
"configurations": [
{
"name": "Electron: Main",
"type": "node",
"request": "attach",
"port": 9229,
"timeout": 30000
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"timeout": 30000,
"webRoot": "${workspaceFolder}/src"
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": ["Electron: Main", "Electron: Renderer"]
}
]
}

View File

@ -12,5 +12,12 @@
"javascript.format.semicolons": "insert",
"typescript.format.semicolons": "insert",
"typescript.preferences.quoteStyle": "double",
"javascript.preferences.quoteStyle": "double"
"javascript.preferences.quoteStyle": "double",
"gitlens.remotes": [
{
"domain": "codeberg.org",
"type": "Gitea"
}
]
}

25
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,25 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"command": "pnpm build",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Watch",
"type": "shell",
"command": "pnpm watch",
"problemMatcher": [],
"group": {
"kind": "build"
}
}
]
}

20
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,20 @@
# Code of Conduct
Our community is welcoming to everyone, regardless of their characteristics.
As such, we expect you to treat everyone with respect and contribute to an open and welcoming community.
DO
- have empathy and be nice to others
- be respectful of differing opinions, even if you disagree
- give and accept constructive criticism
DON'T
- use offensive or derogatory language
- troll or spam
- personally attack or harass others
Repetitive violations of these guidelines might get your access to the repository restricted.
If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from publicly challenging them and instead contact a Moderator on our Discord server or send an email to vendicated+conduct@riseup.net!

View File

@ -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();`,
`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
- 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"

View File

@ -1,49 +1,79 @@
# Vencord
A Discord client mod that does things differently
[![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Ven/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Ven/cord)
The cutest Discord client mod
![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334)
## Features
- Works on Discord's latest update that breaks all other mods
- Browser Support (experimental): Run Vencord in your Browser instead of the desktop app
- Custom Css and Themes: Manually edit `%appdata%/Vencord/settings/quickCss.css` / `~/.config/Vencord/settings/quickCss.css` with your favourite editor and the client will automatically apply your changes. To import BetterDiscord themes, just add `@import url(theUrl)` on the top of this file. (Make sure the url is a github raw URL or similar and only contains plain text, and NOT a nice looking website)
- Many Useful™ plugins - [List](https://github.com/Vendicated/Vencord/tree/main/src/plugins)
- Experiments
- Proper context isolation -> Works in newer Electron versions (Confirmed working on versions 13-22)
- Inline patches: Patch Discord's code with regex replacements! See [the experiments plugin](src/plugins/experiments.ts) for an example. While being more complex, this is more powerful than monkey patching since you can patch only small parts of functions instead of fully replacing them, access non exported/local variables and even replace constants (like in the aforementioned experiments patch!)
- Super easy to install (Download Installer, open, click install button, done)
- 100+ plugins built in: [See a list](https://vencord.dev/plugins)
- Some highlights: SpotifyControls, MessageLogger, Experiments, GameActivityToggle, Translate, NoTrack, QuickReply, Free Emotes/Stickers, PermissionsViewer, CustomCommands, ShowHiddenChannels, PronounDB
- Fairly lightweight despite the many inbuilt plugins
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!)
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry
- Maintained very actively, broken plugins are usually fixed within 12 hours
- Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional)
## Installing / Uninstalling
Read [Megu's Installation Guide!](docs/1_INSTALLING.md)
Click the below button to install Vencord to the Discord Desktop app
[![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
Run the same commands as in the regular install method. Now run
[![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)
```sh
pnpm buildWeb
```
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
You will find the built extension at dist/extension.zip. Now just install this extension in your Browser
<details>
<summary>Alternative Downloads</summary>
## Installing Plugins
## Vencord Desktop
Vencord comes with a bunch of plugins out of the box!
However, if you want to install your own ones, create a `userplugins` folder in the `src` directory and create or clone your plugins in there.
Don't forget to rebuild!
> **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
Want to learn how to create your own plugin, and maybe PR it into Vencord? See the [Contributing](#contributing) section below!
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
## Contributing
[![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)
See [CONTRIBUTING.md](CONTRIBUTING.md) and [Megu's Plugin Guide!](docs/2_PLUGINS.md)
</details>
[contribute]: CONTRIBUTING.md
## Join our Support/Community Server
[contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute]
https://discord.gg/D9uwnFnqmd
## Join
## Star History
[join]: https://discord.gg/D9uwnFnqmd
<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>
[join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join]
## Disclaimer
Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity.
Mention of it does not imply any affiliation with or endorsement by Discord Inc.
<details>
<summary>Using Vencord violates Discord's terms of service</summary>
Client modifications are against Discords Terms of Service.
However, Discord is pretty indifferent about them and there are no known cases of users getting banned for using client mods! So you should generally be fine as long as you dont use any plugins that implement abusive behaviour. But no worries, all inbuilt plugins are safe to use!
Regardless, if your account is very important to you and it getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Vencord), just to be safe
Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it
</details>

75
browser/GMPolyfill.js Normal file
View File

@ -0,0 +1,75 @@
/*
* 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/>.
*/
function parseHeaders(headers) {
if (!headers)
return {};
const result = {};
const headersArr = headers.trim().split("\n");
for (var i = 0; i < headersArr.length; i++) {
var row = headersArr[i];
var index = row.indexOf(":")
, key = row.slice(0, index).trim().toLowerCase()
, value = row.slice(index + 1).trim();
if (result[key] === undefined) {
result[key] = value;
} else if (Array.isArray(result[key])) {
result[key].push(value);
} else {
result[key] = [result[key], value];
}
}
return result;
}
function blobTo(to, blob) {
if (to === "arrayBuffer" && blob.arrayBuffer) return blob.arrayBuffer();
return new Promise((resolve, reject) => {
var fileReader = new FileReader();
fileReader.onload = event => resolve(event.target.result);
if (to === "arrayBuffer") fileReader.readAsArrayBuffer(blob);
else if (to === "text") fileReader.readAsText(blob, "utf-8");
else reject("unknown to");
});
}
function GM_fetch(url, opt) {
return new Promise((resolve, reject) => {
// https://www.tampermonkey.net/documentation.php?ext=dhdg#GM_xmlhttpRequest
const options = opt || {};
options.url = url;
options.data = options.body;
options.responseType = "blob";
options.onload = resp => {
var blob = resp.response;
resp.blob = () => Promise.resolve(blob);
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
resp.text = () => blobTo("text", blob);
resp.json = async () => JSON.parse(await blobTo("text", blob));
resp.headers = new Headers(parseHeaders(resp.responseHeaders));
resp.ok = resp.status >= 200 && resp.status < 300;
resolve(resp);
};
options.ontimeout = () => reject("fetch timeout");
options.onerror = () => reject("fetch error");
options.onabort = () => reject("fetch abort");
GM_xmlhttpRequest(options);
});
}
export const fetch = GM_fetch;

View File

@ -16,51 +16,86 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import IpcEvents from "../src/utils/IpcEvents";
/// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" />
import monacoHtml from "~fileContent/../src/components/monacoWin.html";
import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils";
import { getTheme, Theme } from "../src/utils/discord";
import { getThemeInfo } from "../src/main/themes";
// Discord deletes this so need to store in variable
const { localStorage } = window;
// listeners for ipc.on
const listeners = {} as Record<string, Set<Function>>;
const cssListeners = new Set<(css: string) => void>();
const NOOP = () => { };
const NOOP_ASYNC = async () => { };
const handlers = {
[IpcEvents.GET_REPO]: () => "https://github.com/Vendicated/Vencord", // shrug
[IpcEvents.GET_SETTINGS_DIR]: () => "LocalStorage",
const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css));
[IpcEvents.GET_QUICK_CSS]: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
[IpcEvents.SET_QUICK_CSS]: (css: string) => {
DataStore.set("VencordQuickCss", css);
listeners[IpcEvents.QUICK_CSS_UPDATE]?.forEach(l => l(null, css));
},
[IpcEvents.GET_SETTINGS]: () => localStorage.getItem("VencordSettings") || "{}",
[IpcEvents.SET_SETTINGS]: (s: string) => localStorage.setItem("VencordSettings", s),
[IpcEvents.GET_UPDATES]: () => ({ ok: true, value: [] }),
[IpcEvents.OPEN_EXTERNAL]: (url: string) => open(url, "_blank"),
};
function onEvent(event: string, ...args: any[]) {
const handler = handlers[event];
if (!handler) throw new Error(`Event ${event} not implemented.`);
return handler(...args);
}
const themeStore = DataStore.createStore("VencordThemes", "VencordThemeData");
// probably should make this less cursed at some point
window.VencordNative = {
getVersions: () => ({}),
ipc: {
send: (event: string, ...args: any[]) => void onEvent(event, ...args),
sendSync: onEvent,
on(event: string, listener: () => {}) {
(listeners[event] ??= new Set()).add(listener);
},
off(event: string, listener: () => {}) {
return listeners[event]?.delete(listener);
},
invoke: (event: string, ...args: any[]) => Promise.resolve(onEvent(event, ...args))
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)
},
native: {
getVersions: () => ({}),
openExternal: async (url) => void open(url, "_blank")
},
updater: {
getRepo: async () => ({ ok: true, value: "https://github.com/Vendicated/Vencord" }),
getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }),
},
quickCss: {
get: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
set: async (css: string) => {
await DataStore.set("VencordQuickCss", css);
cssListeners.forEach(l => l(css));
},
addChangeListener(cb) {
cssListeners.add(cb);
},
addThemeChangeListener: NOOP,
openFile: NOOP_ASYNC,
async openEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
const win = open("about:blank", "VencordQuickCss", features);
if (!win) {
alert("Failed to open QuickCSS popup. Make sure to allow popups!");
return;
}
win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () =>
getTheme() === Theme.Light
? "vs-light"
: "vs-dark";
win.document.write(monacoHtml);
},
},
settings: {
get: () => localStorage.getItem("VencordSettings") || "{}",
set: async (s: string) => localStorage.setItem("VencordSettings", s),
getSettingsDir: async () => "LocalStorage"
},
pluginHelpers: {} as any,
};

View File

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

View File

@ -2,7 +2,18 @@ if (typeof browser === "undefined") {
var browser = chrome;
}
var script = document.createElement("script");
const script = document.createElement("script");
script.src = browser.runtime.getURL("dist/Vencord.js");
// documentElement because we load before body/head are ready
document.documentElement.appendChild(script);
const style = document.createElement("link");
style.type = "text/css";
style.rel = "stylesheet";
style.href = browser.runtime.getURL("dist/Vencord.css");
document.documentElement.append(script);
document.addEventListener(
"DOMContentLoaded",
() => document.documentElement.append(style),
{ once: true }
);

BIN
browser/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,32 +1,45 @@
{
"manifest_version": 2,
"manifest_version": 3,
"minimum_chrome_version": "91",
"name": "Vencord Web",
"description": "Yeee",
"version": "1.0.0",
"description": "The cutest Discord mod now in your browser",
"author": "Vendicated",
"homepage_url": "https://github.com/Vendicated/Vencord",
"background": {
"scripts": [
"background.js"
]
"icons": {
"128": "icon.png"
},
"host_permissions": [
"*://*.discord.com/*",
"https://raw.githubusercontent.com/*"
],
"permissions": ["declarativeNetRequest"],
"content_scripts": [
{
"run_at": "document_start",
"matches": [
"*://*.discord.com/*"
],
"js": [
"content.js"
]
"matches": ["*://*.discord.com/*"],
"js": ["content.js"],
"all_frames": true
}
],
"permissions": [
"*://*.discord.com/*",
"webRequest",
"webRequestBlocking"
],
"web_accessible_resources": [
"dist/Vencord.js"
]
{
"resources": ["dist/Vencord.js", "dist/Vencord.css"],
"matches": ["*://*.discord.com/*"]
}
],
"declarative_net_request": {
"rule_resources": [
{
"id": "modifyResponseHeaders",
"enabled": true,
"path": "modifyResponseHeaders.json"
}
]
}
}

41
browser/manifestv2.json Normal file
View File

@ -0,0 +1,41 @@
{
"manifest_version": 2,
"minimum_chrome_version": "91",
"name": "Vencord Web",
"description": "The cutest Discord mod now in your browser",
"author": "Vendicated",
"homepage_url": "https://github.com/Vendicated/Vencord",
"icons": {
"128": "icon.png"
},
"permissions": [
"webRequest",
"webRequestBlocking",
"*://*.discord.com/*",
"https://raw.githubusercontent.com/*"
],
"content_scripts": [
{
"run_at": "document_start",
"matches": ["*://*.discord.com/*"],
"js": ["content.js"],
"all_frames": true
}
],
"background": {
"scripts": ["background.js"]
},
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
"browser_specific_settings": {
"gecko": {
"id": "vencord-firefox@vendicated.dev",
"strict_min_version": "91.0"
}
}
}

View File

@ -0,0 +1,38 @@
[
{
"id": 1,
"action": {
"type": "modifyHeaders",
"responseHeaders": [
{
"header": "content-security-policy",
"operation": "remove"
},
{
"header": "content-security-policy-report-only",
"operation": "remove"
}
]
},
"condition": {
"resourceTypes": ["main_frame", "sub_frame"]
}
},
{
"id": 2,
"action": {
"type": "modifyHeaders",
"responseHeaders": [
{
"header": "content-type",
"operation": "set",
"value": "text/css"
}
]
},
"condition": {
"resourceTypes": ["stylesheet"],
"urlFilter": "https://raw.githubusercontent.com/*"
}
}
]

View File

@ -7,7 +7,7 @@
// @supportURL https://github.com/Vendicated/Vencord
// @license GPL-3.0
// @match *://*.discord.com/*
// @grant none
// @grant GM_xmlhttpRequest
// @run-at document-start
// @compatible chrome Chrome + Tampermonkey or Violentmonkey
// @compatible firefox Firefox Tampermonkey

View File

@ -1,3 +0,0 @@
// FIXME: Delete this soon, for now it is needed so people can update
import("./scripts/build/build.mjs");

View File

@ -1,3 +1,7 @@
> [!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.
> 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
Welcome to Megu's Installation Guide! In this file, you will learn about how to download, install, and uninstall Vencord!
@ -10,12 +14,6 @@ Welcome to Megu's Installation Guide! In this file, you will learn about how to
- [Installing Vencord](#installing-vencord)
- [Updating Vencord](#updating-vencord)
- [Uninstalling Vencord](#uninstalling-vencord)
- [Manually Installing Vencord](#manually-installing-vencord)
- [On Windows](#on-windows)
- [On Linux](#on-linux)
- [On MacOS](#on-macos)
- [Manual Patching](#manual-patching)
- [Manually Uninstalling Vencord](#manually-uninstalling-vencord)
## Dependencies
@ -24,16 +22,16 @@ Welcome to Megu's Installation Guide! In this file, you will learn about how to
## Installing Vencord
> :exclamation: If this doesn't work, see [Manually Installing Vencord](#manually-installing-vencord)
Install `pnpm`:
> :exclamation: this may need to be run as admin depending on your system, and you may need to close and reopen your terminal.
> :exclamation: This next command may need to be run as admin/root depending on your system, and you may need to close and reopen your terminal for pnpm to be in your PATH.
```shell
npm i -g pnpm
```
> :exclamation: **IMPORTANT** Make sure you aren't using an admin/root terminal from here onwards. It **will** mess up your Discord/Vencord instance and you **will** most likely have to reinstall.
Clone Vencord:
```shell
@ -65,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.
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:
@ -98,103 +96,4 @@ Simply run:
pnpm uninject
```
The above command may ask you to also run:
```shell
pnpm install --frozen-lockfile
pnpm uninject
```
## Manually Installing Vencord
- [Windows](#on-windows)
- [Linux](#on-linux)
- [MacOS](#on-macos)
### On Windows
Press Win+R and enter: `%LocalAppData%` and hit enter. In this page, find the page (Discord, DiscordPTB, DiscordCanary, etc) that you want to patch.
Now follow the instructions at [Manual Patching](#manual-patching)
### On Linux
The Discord folder is usually in one of the following paths:
- /usr/share
- /usr/lib64
- /opt
- /home/$USER/.local/share
If you use flatpak, it will usually be in one of the following paths:
- /var/lib/flatpak/app/com.discordapp.Discord/current/active/files
- /home/$USER/.local/share/flatpak/app/com.discordapp.Discord/current/active/files
You will need to give flatpak access to vencord with one of the following commands:
> :exclamation: If not on stable, replace `com.discordapp.Discord` with your branch name, e.g., `com.discordapp.DiscordCanary`
> :exclamation: Replace `/path/to/vencord/` with the path to your vencord folder (NOT the dist folder)
If Discord flatpak install is in /home/:
```shell
flatpak override --user com.discordapp.Discord --filesystem="/path/to/vencord/"
```
If Discord flatpak install not in /home/:
```shell
sudo flatpak override com.discordapp.Discord --filesystem="/path/to/vencord"
```
Now follow the instructions at [Manual Patching](#manual-patching)
### On MacOS
Open finder and go to your Applications folder. Right-Click on the Discord application you want to patch, and view contents.
Go to the `Contents/Resources` folder.
Now follow the instructions at [Manual Patching](#manual-patching)
### Manual Patching
> :exclamation: If using Flatpak on linux, go to the folder that contains the `app.asar` file, and skip to where we create the `app` folder below.
> :exclamation: On Linux/MacOS, there's a chance there won't be an `app-<number>` folder, but there probably is a `resources` folder, so keep reading :)
Inside there, look for the `app-<number>` folders. If you have multiple, use the highest number. If that doesn't work, do it for the rest of the `app-<number>` folders.
Inside there, go to the `resources` folder. There should be a file called `app.asar`. If there isn't, look at a different `app-<number>` folder instead.
Make a new folder in `resources` called `app`. In here, we will make two files:
`package.json` and `index.js`
In `index.js`:
> :exclamation: Replace the path in the first line with the path to `patcher.js` in your vencord dist folder.
> On Windows, you can get this by shift-rightclicking the patcher.js file and selecting "copy as path"
```js
require("C:/Users/<your user>/path/to/vencord/dist/patcher.js");
require("../app.asar");
```
And in `package.json`:
```json
{ "name": "discord", "main": "index.js" }
```
Finally, fully close & reopen your Discord client and check to see that `Vencord` appears in settings!
### Manually Uninstalling Vencord
> :exclamation: Do not delete `app.asar` - Only delete the `app` folder we created.
Use the instructions above to find the `app` folder, and delete it. Then Close & Reopen Discord.
If you need more help, ask in the support channel in our [Discord Server](https://discord.gg/D9uwnFnqmd).

View File

@ -15,7 +15,7 @@ You don't need to run `pnpm build` every time you make a change. Instead, use `p
3. In `index.ts`, copy-paste the following template code:
```ts
import definePlugin from "../../utils/types";
import definePlugin from "@utils/types";
export default definePlugin({
name: "Epic Plugin",

View File

@ -1,9 +1,8 @@
{
"name": "vencord",
"private": "true",
"version": "1.0.0",
"description": "A Discord client mod that does things differently",
"keywords": [],
"version": "1.4.7",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
"url": "https://github.com/Vendicated/Vencord/issues"
@ -12,7 +11,7 @@
"type": "git",
"url": "git+https://github.com/Vendicated/Vencord.git"
},
"license": "GPL-3.0",
"license": "GPL-3.0-or-later",
"author": "Vendicated",
"directories": {
"doc": "docs"
@ -20,32 +19,81 @@
"scripts": {
"build": "node scripts/build/build.mjs",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
"inject": "node scripts/patcher/install.js",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"generatePluginJson": "tsx scripts/generatePluginList.ts",
"inject": "node scripts/runInstaller.mjs",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix",
"test": "pnpm lint && pnpm build && pnpm testTsc",
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit",
"uninject": "node scripts/patcher/uninstall.js",
"uninject": "node scripts/runInstaller.mjs",
"watch": "node scripts/build/build.mjs --watch"
},
"dependencies": {
"console-menu": "^0.1.0",
"fflate": "^0.7.4"
"@sapphi-red/web-noise-suppressor": "0.3.3",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",
"eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4",
"nanoid": "^4.0.2",
"virtual-merge": "^1.0.1"
},
"devDependencies": {
"@types/node": "^18.7.13",
"@types/react": "^18.0.17",
"@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194",
"@types/node": "^18.16.3",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/yazl": "^2.4.2",
"@typescript-eslint/parser": "^5.39.0",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"diff": "^5.1.0",
"discord-types": "^1.3.26",
"esbuild": "^0.15.5",
"eslint": "^8.24.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-simple-import-sort": "^8.0.0",
"esbuild": "^0.15.18",
"eslint": "^8.46.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"highlight.js": "10.6.0",
"moment": "^2.29.4",
"puppeteer-core": "^19.11.1",
"standalone-electron-types": "^1.0.0",
"type-fest": "^3.1.0",
"typescript": "^4.8.4"
"stylelint": "^15.6.0",
"stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.7",
"type-fest": "^3.9.0",
"typescript": "^5.0.4"
},
"packageManager": "pnpm@7.13.4"
"packageManager": "pnpm@8.1.1",
"pnpm": {
"patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
"eslint@8.46.0": "patches/eslint@8.46.0.patch"
},
"peerDependencyRules": {
"ignoreMissing": [
"eslint-plugin-import",
"eslint"
]
},
"allowedDeprecatedVersions": {
"source-map-resolve": "*",
"resolve-url": "*",
"source-map-url": "*",
"urix": "*"
}
},
"webExt": {
"artifactsDir": "./dist",
"build": {
"overwriteDest": true
},
"sourceDir": "./dist/extension-v2-unpacked"
},
"engines": {
"node": ">=18",
"pnpm": ">=8"
}
}

View File

@ -0,0 +1,13 @@
diff --git a/lib/rules/no-relative.js b/lib/rules/no-relative.js
index 71594c83f1f4f733ffcc6047d7f7084348335dbe..d8623d87c89499c442171db3272cba07c9efabbe 100644
--- a/lib/rules/no-relative.js
+++ b/lib/rules/no-relative.js
@@ -41,7 +41,7 @@ module.exports = {
ImportDeclaration(node) {
const importPath = node.source.value;
- if (!/^(\.?\.\/)/.test(importPath)) {
+ if (!/^(\.\.\/)/.test(importPath)) {
return;
}

View File

@ -0,0 +1,49 @@
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index 0e0f6f09f2c35f3276173c08f832cde9f2cf56a0..7dc22851715f3574d935f513c1b5e35552985711 100644
--- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js
@@ -65,13 +65,31 @@ module.exports = {
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
},
- schema: []
+ schema: [{
+ type: "object",
+ properties: {
+ extra: {
+ type: "string",
+ default: ""
+ },
+ extraCharClass: {
+ type: "string",
+ default: ""
+ },
+ },
+ additionalProperties: false
+ }]
},
create(context) {
+ const options = context.options[0] || {};
+ const { extra, extraCharClass } = options;
const sourceCode = context.sourceCode;
const parser = new RegExpParser();
+ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra));
+ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass));
+
/**
* Reports a node
* @param {ASTNode} node The node to report
@@ -200,9 +218,9 @@ module.exports = {
let allowedEscapes;
if (characterClassStack.length) {
- allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
+ allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : CHARCLASS_ESCAPES;
} else {
- allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
+ allowedEscapes = NON_CHARCLASS_ESCAPES;
}
if (allowedEscapes.has(escapedChar)) {
return;

2879
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -19,22 +19,20 @@
import esbuild from "esbuild";
import { commonOpts, gitHash, globPlugins, isStandalone } from "./common.mjs";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
const defines = {
IS_STANDALONE: isStandalone
IS_STANDALONE: isStandalone,
IS_DEV: JSON.stringify(watch),
IS_UPDATER_DISABLED: updaterDisabled,
VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP,
};
if (defines.IS_STANDALONE === "false")
// If this is a local build (not standalone), optimise
// for the specific platform we're on
defines["process.platform"] = JSON.stringify(process.platform);
const header = `
// Vencord ${gitHash}
// Standalone: ${defines.IS_STANDALONE}
// Platform: ${defines["process.platform"] || "Universal"}
`.trim();
/**
* @type {esbuild.BuildOptions}
*/
@ -43,29 +41,26 @@ const nodeCommonOpts = {
format: "cjs",
platform: "node",
target: ["esnext"],
minify: true,
bundle: true,
external: ["electron", ...commonOpts.external],
define: defines,
banner: {
js: header
}
};
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
const sourcemap = watch ? "inline" : "external";
await Promise.all([
// Discord Desktop main & renderer & preload
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/preload.ts"],
outfile: "dist/preload.js",
footer: { js: "//# sourceURL=VencordPreload\n//# sourceMappingURL=vencord://preload.js.map" },
sourcemap: "external",
}),
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/patcher.ts"],
entryPoints: ["src/main/index.ts"],
outfile: "dist/patcher.js",
footer: { js: "//# sourceURL=VencordPatcher\n//# sourceMappingURL=vencord://patcher.js.map" },
sourcemap: "external",
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false
}
}),
esbuild.build({
...commonOpts,
@ -73,16 +68,76 @@ await Promise.all([
outfile: "dist/renderer.js",
format: "iife",
target: ["esnext"],
footer: { js: "//# sourceURL=VencordRenderer\n//# sourceMappingURL=vencord://renderer.js.map" },
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
globalName: "Vencord",
sourcemap: "external",
sourcemap,
plugins: [
globPlugins,
globPlugins("discordDesktop"),
...commonOpts.plugins
],
define: {
IS_WEB: "false",
IS_STANDALONE: isStandalone
...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: true,
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 & preload
esbuild.build({
...nodeCommonOpts,
entryPoints: ["src/main/index.ts"],
outfile: "dist/vencordDesktopMain.js",
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
sourcemap,
define: {
...defines,
IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true
}
}),
esbuild.build({
...commonOpts,
entryPoints: ["src/Vencord.ts"],
outfile: "dist/vencordDesktopRenderer.js",
format: "iife",
target: ["esnext"],
footer: { js: "//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
globalName: "Vencord",
sourcemap,
plugins: [
globPlugins("vencordDesktop"),
...commonOpts.plugins
],
define: {
...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: false,
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 => {

113
scripts/build/buildWeb.mjs Executable file → Normal file
View File

@ -20,13 +20,11 @@
import esbuild from "esbuild";
import { zip } from "fflate";
import { readFileSync, writeFileSync } from "fs";
import { readFile } from "fs/promises";
import { readFileSync } from "fs";
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
import { commonOpts, fileIncludePlugin, gitHashPlugin, gitRemotePlugin, globPlugins } from "./common.mjs";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@ -36,17 +34,21 @@ const commonOptions = {
entryPoints: ["browser/Vencord.ts"],
globalName: "Vencord",
format: "iife",
external: ["plugins", "git-hash"],
external: ["plugins", "git-hash", "/assets/*"],
plugins: [
globPlugins,
gitHashPlugin,
gitRemotePlugin,
fileIncludePlugin
globPlugins("web"),
...commonOpts.plugins,
],
target: ["esnext"],
define: {
IS_WEB: "true",
IS_STANDALONE: "true"
IS_STANDALONE: "true",
IS_DEV: JSON.stringify(watch),
IS_DISCORD_DESKTOP: "false",
IS_VESKTOP: "false",
IS_UPDATER_DISABLED: "true",
VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP,
}
};
@ -59,32 +61,89 @@ await Promise.all(
}),
esbuild.build({
...commonOptions,
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
define: {
"window": "unsafeWindow",
...(commonOptions?.define)
},
outfile: "dist/Vencord.user.js",
banner: {
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", PackageJSON.version)
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`)
},
footer: {
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
js: "Object.defineProperty(window,'Vencord',{get:()=>Vencord});"
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
},
})
]
);
zip({
dist: {
"Vencord.js": readFileSync("dist/browser.js")
},
...Object.fromEntries(await Promise.all(["background.js", "content.js", "manifest.json"].map(async f => [
f,
await readFile(join("browser", f))
]))),
}, {}, (err, data) => {
if (err) {
console.error(err);
process.exitCode = 1;
/**
* @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>}
*/
async function buildPluginZip(target, files, shouldZip) {
const entries = {
"dist/Vencord.js": await readFile("dist/browser.js"),
"dist/Vencord.css": await readFile("dist/browser.css"),
...Object.fromEntries(await Promise.all(files.map(async f => {
let content = await readFile(join("browser", f));
if (f.startsWith("manifest")) {
const json = JSON.parse(content.toString("utf-8"));
json.version = VERSION;
content = new TextEncoder().encode(JSON.stringify(json));
}
return [
f.startsWith("manifest") ? "manifest.json" : f,
content
];
}))),
};
if (shouldZip) {
return new Promise((resolve, reject) => {
zip(entries, {}, (err, data) => {
if (err) {
reject(err);
} else {
const out = join("dist", target);
writeFile(out, data).then(() => {
console.info("Extension written to " + out);
resolve();
}).catch(reject);
}
});
});
} else {
writeFileSync("dist/extension.zip", data);
console.info("Extension written to dist/extension.zip");
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);
}
}
const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => {
const cssRuntime = `
;document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(
Object.assign(document.createElement("style"), {
textContent: \`${content.replaceAll("`", "\\`")}\`,
id: "vencord-css-core"
})
), { once: true });
`;
return appendFile("dist/Vencord.user.js", cssRuntime);
});
await Promise.all([
appendCssRuntime,
buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true),
buildPluginZip("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false),
buildPluginZip("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"], false),
]);

View File

@ -16,19 +16,40 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js";
import { exec, execSync } from "child_process";
import esbuild from "esbuild";
import { existsSync } from "fs";
import { existsSync, readFileSync } from "fs";
import { readdir, readFile } from "fs/promises";
import { join } from "path";
import { join, relative } from "path";
import { promisify } from "util";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
import { getPluginTarget } from "../utils.mjs";
export const VERSION = PackageJSON.version;
// 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 isStandalone = JSON.stringify(process.argv.includes("--standalone"));
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 = {
js: `
// Vencord ${gitHash}
// Standalone: ${isStandalone}
// Platform: ${isStandalone === "false" ? process.platform : "Universal"}
// Updater disabled: ${updaterDisabled}
`.trim()
};
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
/**
* @type {esbuild.Plugin}
* @type {import("esbuild").Plugin}
*/
export const makeAllPackagesExternalPlugin = {
name: "make-all-packages-external",
@ -39,9 +60,9 @@ export const makeAllPackagesExternalPlugin = {
};
/**
* @type {esbuild.Plugin}
* @type {(kind: "web" | "discordDesktop" | "vencordDesktop") => import("esbuild").Plugin}
*/
export const globPlugins = {
export const globPlugins = kind => ({
name: "glob-plugins",
setup: build => {
const filter = /^~plugins$/;
@ -53,7 +74,7 @@ export const globPlugins = {
});
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
const pluginDirs = ["plugins", "userplugins"];
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
let code = "";
let plugins = "\n";
let i = 0;
@ -61,11 +82,20 @@ export const globPlugins = {
if (!existsSync(`./src/${dir}`)) continue;
const files = await readdir(`./src/${dir}`);
for (const file of files) {
if (file === "index.ts") {
continue;
if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue;
const target = getPluginTarget(file);
if (target) {
if (target === "dev" && !watch) continue;
if (target === "web" && kind === "discordDesktop") continue;
if (target === "desktop" && kind === "web") continue;
if (target === "discordDesktop" && kind !== "discordDesktop") continue;
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
}
const mod = `p${i}`;
code += `import ${mod} from "./${dir}/${file.replace(/.tsx?$/, "")}";\n`;
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`;
plugins += `[${mod}.name]:${mod},\n`;
i++;
}
@ -77,11 +107,10 @@ export const globPlugins = {
};
});
}
};
});
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
/**
* @type {esbuild.Plugin}
* @type {import("esbuild").Plugin}
*/
export const gitHashPlugin = {
name: "git-hash-plugin",
@ -97,7 +126,7 @@ export const gitHashPlugin = {
};
/**
* @type {esbuild.Plugin}
* @type {import("esbuild").Plugin}
*/
export const gitRemotePlugin = {
name: "git-remote-plugin",
@ -107,11 +136,14 @@ export const gitRemotePlugin = {
namespace: "git-remote", path: args.path
}));
build.onLoad({ filter, namespace: "git-remote" }, async () => {
const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" });
const remote = res.stdout.trim()
.replace("https://github.com/", "")
.replace("git@github.com:", "")
.replace(/.git$/, "");
let remote = process.env.VENCORD_REMOTE;
if (!remote) {
const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" });
remote = res.stdout.trim()
.replace("https://github.com/", "")
.replace("git@github.com:", "")
.replace(/.git$/, "");
}
return { contents: `export default "${remote}"` };
});
@ -119,7 +151,7 @@ export const gitRemotePlugin = {
};
/**
* @type {esbuild.Plugin}
* @type {import("esbuild").Plugin}
*/
export const fileIncludePlugin = {
name: "file-include-plugin",
@ -141,8 +173,33 @@ export const fileIncludePlugin = {
}
};
const styleModule = readFileSync("./scripts/build/module/style.js", "utf-8");
/**
* @type {esbuild.BuildOptions}
* @type {import("esbuild").Plugin}
*/
export const stylePlugin = {
name: "style-plugin",
setup: ({ onResolve, onLoad }) => {
onResolve({ filter: /\.css\?managed$/, namespace: "file" }, ({ path, resolveDir }) => ({
path: relative(process.cwd(), join(resolveDir, path.replace("?managed", ""))),
namespace: "managed-style",
}));
onLoad({ filter: /\.css$/, namespace: "managed-style" }, async ({ path }) => {
const css = await readFile(path, "utf-8");
const name = relative(process.cwd(), path).replaceAll("\\", "/");
return {
loader: "js",
contents: styleModule
.replaceAll("STYLE_SOURCE", JSON.stringify(css))
.replaceAll("STYLE_NAME", JSON.stringify(name))
};
});
}
};
/**
* @type {import("esbuild").BuildOptions}
*/
export const commonOpts = {
logLevel: "info",
@ -151,6 +208,12 @@ export const commonOpts = {
minify: !watch,
sourcemap: watch ? "inline" : "",
legalComments: "linked",
plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin],
external: ["~plugins", "~git-hash", "~git-remote"]
banner,
plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
inject: ["./scripts/build/inject/react.mjs"],
jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment",
// Work around https://github.com/evanw/esbuild/issues/2460
tsconfig: "./scripts/build/tsconfig.esbuild.json"
};

View File

@ -0,0 +1,21 @@
/*
* 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/>.
*/
export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
export let VencordCreateElement =
(...args) => (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);

View File

@ -16,6 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export { default as PluginSettings } from "./PluginSettings";
export { default as Settings } from "./Settings";
export { default as Updater } from "./Updater";
(window.VencordStyles ??= new Map()).set(STYLE_NAME, {
name: STYLE_NAME,
source: STYLE_SOURCE,
classNames: {},
dom: null,
});
export default STYLE_NAME;

View File

@ -0,0 +1,7 @@
// Work around https://github.com/evanw/esbuild/issues/2460
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}

View File

@ -0,0 +1,20 @@
/*
* 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/>.
*/
if (Number(process.versions.node.split(".")[0]) < 18)
throw `Your node version (${process.version}) is too old, please update to v18 or higher https://nodejs.org/en/download/`;

View File

@ -0,0 +1,222 @@
/*
* 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 { Dirent, readdirSync, readFileSync, writeFileSync } from "fs";
import { access, readFile } from "fs/promises";
import { join } from "path";
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 {
name: string;
id: string;
}
interface PluginData {
name: string;
description: string;
tags: string[];
authors: Dev[];
dependencies: string[];
hasPatches: boolean;
hasCommands: boolean;
required: boolean;
enabledByDefault: boolean;
target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
}
const devs = {} as Record<string, Dev>;
function getName(node: NamedDeclaration) {
return node.name && isIdentifier(node.name) ? node.name.text : undefined;
}
function hasName(node: NamedDeclaration, name: string) {
return getName(node) === name;
}
function getObjectProp(node: ObjectLiteralExpression, name: string) {
const prop = node.properties.find(p => hasName(p, name));
if (prop && isPropertyAssignment(prop)) return prop.initializer;
return prop;
}
function parseDevs() {
const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest);
for (const child of file.getChildAt(0).getChildren()) {
if (!isVariableStatement(child)) continue;
const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "Devs"));
if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue;
const value = devsDeclaration.initializer.arguments[0];
if (!isSatisfiesExpression(value) || !isObjectLiteralExpression(value.expression)) throw new Error("Failed to parse devs: not an object literal");
for (const prop of value.expression.properties) {
const name = (prop.name as Identifier).text;
const value = isPropertyAssignment(prop) ? prop.initializer : prop;
if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse devs: ${name} is not an object literal`);
devs[name] = {
name: (getObjectProp(value, "name") as StringLiteral).text,
id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1)
};
}
return;
}
throw new Error("Could not find Devs constant");
}
async function parseFile(fileName: string) {
const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest);
const fail = (reason: string) => {
return new Error(`Invalid plugin ${fileName}, because ${reason}`);
};
for (const node of file.getChildAt(0).getChildren()) {
if (!isExportAssignment(node) || !isCallExpression(node.expression)) continue;
const call = node.expression;
if (!isIdentifier(call.expression) || call.expression.text !== "definePlugin") continue;
const pluginObj = node.expression.arguments[0];
if (!isObjectLiteralExpression(pluginObj)) throw fail("no object literal passed to definePlugin");
const data = {
hasPatches: false,
hasCommands: false,
enabledByDefault: false,
required: false,
tags: [] as string[]
} as PluginData;
for (const prop of pluginObj.properties) {
const key = getName(prop);
const value = isPropertyAssignment(prop) ? prop.initializer : prop;
switch (key) {
case "name":
case "description":
if (!isStringLiteral(value)) throw fail(`${key} is not a string literal`);
data[key] = value.text;
break;
case "patches":
data.hasPatches = true;
break;
case "commands":
data.hasCommands = true;
break;
case "authors":
if (!isArrayLiteralExpression(value)) throw fail("authors is not an array literal");
data.authors = value.elements.map(e => {
if (!isPropertyAccessExpression(e)) throw fail("authors array contains non-property access expressions");
const d = devs[getName(e)!];
if (!d) throw fail(`couldn't look up author ${getName(e)}`);
return d;
});
break;
case "tags":
if (!isArrayLiteralExpression(value)) throw fail("tags is not an array literal");
data.tags = value.elements.map(e => {
if (!isStringLiteral(e)) throw fail("tags array contains non-string literals");
return e.text;
});
break;
case "dependencies":
if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal");
const { elements } = value;
if (elements.some(e => !isStringLiteral(e))) throw fail("dependencies array contains non-string elements");
data.dependencies = (elements as NodeArray<StringLiteral>).map(e => e.text);
break;
case "required":
case "enabledByDefault":
data[key] = value.kind === SyntaxKind.TrueKeyword;
break;
}
}
if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing");
const target = getPluginTarget(fileName);
if (target) {
if (!["web", "discordDesktop", "vencordDesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`);
data.target = target as any;
}
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");
}
async function getEntryPoint(dir: string, dirent: Dirent) {
const base = join(dir, dirent.name);
if (!dirent.isDirectory()) return base;
for (const name of ["index.ts", "index.tsx"]) {
const full = join(base, name);
try {
await access(full);
return full;
} catch { }
}
throw new Error(`${dirent.name}: Couldn't find entry point`);
}
function isPluginFile({ name }: { name: string; }) {
if (name === "index.ts") return false;
return !name.startsWith("_") && !name.startsWith(".");
}
(async () => {
parseDevs();
const plugins = [] as PluginData[];
const readmes = {} as Record<string, string>;
await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir =>
readdirSync(dir, { withFileTypes: true })
.filter(isPluginFile)
.map(async dirent => {
const [data, readme] = await parseFile(await getEntryPoint(dir, dirent));
plugins.push(data);
if (readme) readmes[data.name] = readme;
})
));
const data = JSON.stringify(plugins);
if (process.argv.length > 3) {
writeFileSync(process.argv[2], data);
writeFileSync(process.argv[3], JSON.stringify(readmes));
} else {
console.log(data);
}
})();

295
scripts/generateReport.ts Normal file
View File

@ -0,0 +1,295 @@
/*
* 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/>.
*/
// eslint-disable-next-line spaced-comment
/// <reference types="../src/globals" />
// eslint-disable-next-line spaced-comment
/// <reference types="../src/modules" />
import { readFileSync } from "fs";
import pup, { JSHandle } from "puppeteer-core";
for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
if (!process.env[variable]) {
console.error(`Missing environment variable ${variable}`);
process.exit(1);
}
}
const CANARY = process.env.USE_CANARY === "true";
const browser = await pup.launch({
headless: true,
executablePath: process.env.CHROMIUM_BIN
});
const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
function maybeGetError(handle: JSHandle) {
return (handle as JSHandle<Error>)?.getProperty("message")
.then(m => m.jsonValue());
}
const report = {
badPatches: [] as {
plugin: string;
type: string;
id: string;
match: string;
error?: string;
}[],
badStarts: [] as {
plugin: string;
error: string;
}[],
otherErrors: [] as string[]
};
function toCodeBlock(s: string) {
s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```";
}
async function printReport() {
console.log("# Vencord Report" + (CANARY ? " (Canary)" : ""));
console.log();
console.log("## Bad Patches");
report.badPatches.forEach(p => {
console.log(`- ${p.plugin} (${p.type})`);
console.log(` - ID: \`${p.id}\``);
console.log(` - Match: ${toCodeBlock(p.match)}`);
if (p.error) console.log(` - Error: ${toCodeBlock(p.error)}`);
});
console.log();
console.log("## Bad Starts");
report.badStarts.forEach(p => {
console.log(`- ${p.plugin}`);
console.log(` - Error: ${toCodeBlock(p.error)}`);
});
console.log("## Discord Errors");
report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`);
});
if (process.env.DISCORD_WEBHOOK) {
// this code was written almost entirely by Copilot xD
await fetch(process.env.DISCORD_WEBHOOK, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
description: "Here's the latest Vencord Report!",
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
avatar_url: "https://cdn.discordapp.com/icons/1015060230222131221/f0204a918c6c9c9a43195997e97d8adf.webp",
embeds: [
{
title: "Bad Patches",
description: report.badPatches.map(p => {
const lines = [
`**__${p.plugin} (${p.type}):__**`,
`ID: \`${p.id}\``,
`Match: ${toCodeBlock(p.match)}`
];
if (p.error) lines.push(`Error: ${toCodeBlock(p.error)}`);
return lines.join("\n");
}).join("\n\n") || "None",
color: report.badPatches.length ? 0xff0000 : 0x00ff00
},
{
title: "Bad Starts",
description: report.badStarts.map(p => {
const lines = [
`**__${p.plugin}:__**`,
toCodeBlock(p.error)
];
return lines.join("\n");
}
).join("\n\n") || "None",
color: report.badStarts.length ? 0xff0000 : 0x00ff00
},
{
title: "Discord Errors",
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None",
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
}
]
})
}).then(res => {
if (!res.ok) console.error(`Webhook failed with status ${res.status}`);
else console.error("Posted to Discord Webhook successfully");
});
}
}
page.on("console", async e => {
const level = e.type();
const args = e.args();
const firstArg = (await args[0]?.jsonValue());
if (firstArg === "PUPPETEER_TEST_DONE_SIGNAL") {
await browser.close();
await printReport();
process.exit();
}
const isVencord = (await args[0]?.jsonValue()) === "[Vencord]";
const isDebug = (await args[0]?.jsonValue()) === "[PUP_DEBUG]";
if (isVencord) {
// make ci fail
process.exitCode = 1;
const jsonArgs = await Promise.all(args.map(a => a.jsonValue()));
const [, tag, message] = jsonArgs;
const cause = await maybeGetError(args[3]);
switch (tag) {
case "WebpackInterceptor:":
const [, plugin, type, id, regex] = (message as string).match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
report.badPatches.push({
plugin,
type,
id,
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
error: cause
});
break;
case "PluginManager:":
const [, name] = (message as string).match(/Failed to start (.+)/)!;
report.badStarts.push({
plugin: name,
error: cause
});
break;
}
} else if (isDebug) {
console.error(e.text());
} else if (level === "error") {
const text = await Promise.all(
e.args().map(async a => {
try {
return await maybeGetError(a) || await a.jsonValue();
} catch (e) {
return a.toString();
}
})
).then(a => a.join(" ").trim());
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of")) {
console.error("Got unexpected error", text);
report.otherErrors.push(text);
}
}
});
page.on("error", e => console.error("[Error]", e));
page.on("pageerror", e => console.error("[Page Error]", e));
await page.setBypassCSP(true);
function runTime(token: string) {
console.error("[PUP_DEBUG]", "Starting test...");
try {
// spoof languages to not be suspicious
Object.defineProperty(navigator, "languages", {
get: function () {
return ["en-US", "en"];
},
});
// Monkey patch Logger to not log with custom css
// @ts-ignore
Vencord.Util.Logger.prototype._log = function (level, levelColor, args) {
if (level === "warn" || level === "error")
console[level]("[Vencord]", this.name + ":", ...args);
};
// force enable all plugins and patches
Vencord.Plugins.patches.length = 0;
Object.values(Vencord.Plugins.plugins).forEach(p => {
// Needs native server to run
if (p.name === "WebRichPresence (arRPC)") return;
Vencord.Settings.plugins[p.name].enabled = true;
p.patches?.forEach(patch => {
patch.plugin = p.name;
delete patch.predicate;
if (!Array.isArray(patch.replacement))
patch.replacement = [patch.replacement];
Vencord.Plugins.patches.push(patch);
});
});
Vencord.Webpack.waitFor(
"loginToken",
m => {
console.error("[PUP_DEBUG]", "Logging in with token...");
m.loginToken(token);
}
);
// force load all chunks
Vencord.Webpack.onceReady.then(() => setTimeout(async () => {
console.error("[PUP_DEBUG]", "Webpack is ready!");
const { wreq } = Vencord.Webpack;
console.error("[PUP_DEBUG]", "Loading all chunks...");
const ids = Function("return" + wreq.u.toString().match(/\{.+\}/s)![0])();
for (const id in ids) {
const isWasm = await fetch(wreq.p + wreq.u(id))
.then(r => r.text())
.then(t => t.includes(".module.wasm") || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
if (!isWasm)
await wreq.e(id as any);
await new Promise(r => setTimeout(r, 150));
}
console.error("[PUP_DEBUG]", "Finished loading chunks!");
for (const patch of Vencord.Plugins.patches) {
if (!patch.all) {
new Vencord.Util.Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
}
}
setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000);
}, 1000));
} catch (e) {
console.error("[PUP_DEBUG]", "A fatal error occured");
console.error("[PUP_DEBUG]", e);
process.exit(1);
}
}
await page.evaluateOnNewDocument(`
${readFileSync("./dist/browser.js", "utf-8")}
;(${runTime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
`);
await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login");

3
scripts/header-new.txt Normal file
View 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
View 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/>.
*/

View File

@ -1,341 +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/>.
*/
const path = require("path");
const readline = require("readline");
const fs = require("fs");
const menu = require("console-menu");
const BRANCH_NAMES = [
"Discord",
"DiscordPTB",
"DiscordCanary",
"DiscordDevelopment",
"discord",
"discordptb",
"discordcanary",
"discorddevelopment",
"discord-ptb",
"discord-canary",
"discord-development",
// Flatpak
"com.discordapp.Discord",
"com.discordapp.DiscordPTB",
"com.discordapp.DiscordCanary",
"com.discordapp.DiscordDevelopment",
];
const MACOS_DISCORD_DIRS = [
"Discord.app",
"Discord PTB.app",
"Discord Canary.app",
"Discord Development.app",
];
if (process.platform === "linux" && process.env.SUDO_USER) {
process.env.HOME = fs
.readFileSync("/etc/passwd", "utf-8")
.match(new RegExp(`^${process.env.SUDO_USER}.+$`, "m"))[0]
.split(":")[5];
}
const LINUX_DISCORD_DIRS = [
"/usr/share",
"/usr/lib64",
"/opt",
`${process.env.HOME}/.local/share`,
"/var/lib/flatpak/app",
`${process.env.HOME}/.local/share/flatpak/app`,
];
const FLATPAK_NAME_MAPPING = {
DiscordCanary: "discord-canary",
DiscordPTB: "discord-ptb",
DiscordDevelopment: "discord-development",
Discord: "discord",
};
const ENTRYPOINT = path
.join(process.cwd(), "dist", "patcher.js")
.replace(/\\/g, "/");
function question(question) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
return new Promise(resolve => {
rl.question(question, answer => {
rl.close();
resolve(answer);
});
});
}
async function getMenuItem(installations) {
const menuItems = installations.map(info => ({
title: info.patched ? "[MODIFIED] " + info.location : info.location,
info,
}));
const result = await menu(
[
...menuItems,
{ title: "Specify custom path", info: "custom" },
{ title: "Exit without patching", exit: true }
],
{
header: "Select a Discord installation to patch:",
border: true,
helpMessage:
"Use the up/down arrow keys to select an option. " +
"Press ENTER to confirm.",
}
);
if (!result || !result.info || result.exit) {
console.log("No installation selected.");
process.exit(0);
}
if (result.info === "custom") {
const customPath = await question("Please enter the path: ");
if (!customPath || !fs.existsSync(customPath)) {
console.log("No such Path or not specifed.");
process.exit();
}
const resourceDir = path.join(customPath, "resources");
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
console.log("Unsupported Install. resources/app.asar not found");
process.exit();
}
const appDir = path.join(resourceDir, "app");
result.info = {
branch: "unknown",
patched: fs.existsSync(appDir),
location: customPath,
versions: [{
path: appDir,
name: null
}],
arch: process.platform === "linux" ? "linux" : "win32",
isFlatpak: false,
};
}
if (result.info.patched) {
const answer = await question(
"This installation has already been modified. Overwrite? [Y/n]: "
);
if (!["y", "yes", "yeah", ""].includes(answer.toLowerCase())) {
console.log("Not patching.");
process.exit(0);
}
}
return result.info;
}
function getWindowsDirs() {
const dirs = [];
for (const dir of fs.readdirSync(process.env.LOCALAPPDATA)) {
if (!BRANCH_NAMES.includes(dir)) continue;
const location = path.join(process.env.LOCALAPPDATA, dir);
if (!fs.statSync(location).isDirectory()) continue;
const appDirs = fs
.readdirSync(location, { withFileTypes: true })
.filter(file => file.isDirectory())
.filter(file => file.name.startsWith("app-"))
.map(file => path.join(location, file.name));
const versions = [];
let patched = false;
for (const fqAppDir of appDirs) {
const resourceDir = path.join(fqAppDir, "resources");
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
continue;
}
const appDir = path.join(resourceDir, "app");
if (fs.existsSync(appDir)) {
patched = true;
}
versions.push({
path: appDir,
name: /app-([0-9.]+)/.exec(fqAppDir)[1],
});
}
if (appDirs.length) {
dirs.push({
branch: dir,
patched,
location,
versions,
arch: "win32",
flatpak: false,
});
}
}
return dirs;
}
function getDarwinDirs() {
const dirs = [];
for (const dir of fs.readdirSync("/Applications")) {
if (!MACOS_DISCORD_DIRS.includes(dir)) continue;
const location = path.join("/Applications", dir, "Contents");
if (!fs.existsSync(location)) continue;
if (!fs.statSync(location).isDirectory()) continue;
const appDirs = fs
.readdirSync(location, { withFileTypes: true })
.filter(file => file.isDirectory())
.filter(file => file.name.startsWith("Resources"))
.map(file => path.join(location, file.name));
const versions = [];
let patched = false;
for (const resourceDir of appDirs) {
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
continue;
}
const appDir = path.join(resourceDir, "app");
if (fs.existsSync(appDir)) {
patched = true;
}
versions.push({
path: appDir,
name: null, // MacOS installs have no version number
});
}
if (appDirs.length) {
dirs.push({
branch: dir,
patched,
location,
versions,
arch: "win32",
});
}
}
return dirs;
}
function getLinuxDirs() {
const dirs = [];
for (const dir of LINUX_DISCORD_DIRS) {
if (!fs.existsSync(dir)) continue;
for (const branch of fs.readdirSync(dir)) {
if (!BRANCH_NAMES.includes(branch)) continue;
const location = path.join(dir, branch);
if (!fs.statSync(location).isDirectory()) continue;
const isFlatpak = location.includes("/flatpak/");
let appDirs = [];
if (isFlatpak) {
const fqDir = path.join(location, "current", "active", "files");
if (!/com\.discordapp\.(\w+)\//.test(fqDir)) continue;
const branchName = /com\.discordapp\.(\w+)\//.exec(fqDir)[1];
if (!Object.keys(FLATPAK_NAME_MAPPING).includes(branchName)) {
continue;
}
const appDir = path.join(
fqDir,
FLATPAK_NAME_MAPPING[branchName]
);
if (!fs.existsSync(appDir)) continue;
if (!fs.statSync(appDir).isDirectory()) continue;
const resourceDir = path.join(appDir, "resources");
appDirs.push(resourceDir);
} else {
appDirs = fs
.readdirSync(location, { withFileTypes: true })
.filter(file => file.isDirectory())
.filter(
file =>
file.name.startsWith("app-") ||
file.name === "resources"
)
.map(file => path.join(location, file.name));
}
const versions = [];
let patched = false;
for (const resourceDir of appDirs) {
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
continue;
}
const appDir = path.join(resourceDir, "app");
if (fs.existsSync(appDir)) {
patched = true;
}
const version = /app-([0-9.]+)/.exec(resourceDir);
versions.push({
path: appDir,
name: version && version.length > 1 ? version[1] : null,
});
}
if (appDirs.length) {
dirs.push({
branch,
patched,
location,
versions,
arch: "linux",
isFlatpak,
});
}
}
}
return dirs;
}
module.exports = {
BRANCH_NAMES,
MACOS_DISCORD_DIRS,
LINUX_DISCORD_DIRS,
FLATPAK_NAME_MAPPING,
ENTRYPOINT,
question,
getMenuItem,
getWindowsDirs,
getDarwinDirs,
getLinuxDirs,
};

View File

@ -1,130 +0,0 @@
#!/usr/bin/node
/*
* 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/>.
*/
const path = require("path");
const fs = require("fs");
const { execSync } = require("child_process");
console.log("\nVencord Installer\n");
if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) {
console.log("You need to install dependencies first. Run:", "pnpm install --frozen-lockfile");
process.exit(1);
}
if (!fs.existsSync(path.join(process.cwd(), "dist", "patcher.js"))) {
console.log("You need to build the project first. Run:", "pnpm build");
process.exit(1);
}
const {
getMenuItem,
getWindowsDirs,
getDarwinDirs,
getLinuxDirs,
ENTRYPOINT,
} = require("./common");
switch (process.platform) {
case "win32":
install(getWindowsDirs());
break;
case "darwin":
install(getDarwinDirs());
break;
case "linux":
install(getLinuxDirs());
break;
default:
console.log("Unknown OS");
break;
}
async function install(installations) {
const selected = await getMenuItem(installations);
// Attempt to give flatpak perms
if (selected.isFlatpak) {
try {
const { branch } = selected;
const cwd = process.cwd();
const globalCmd = `flatpak override ${branch} --filesystem=${cwd}`;
const userCmd = `flatpak override --user ${branch} --filesystem=${cwd}`;
const cmd = selected.location.startsWith("/home")
? userCmd
: globalCmd;
execSync(cmd);
console.log("Successfully gave write perms to Discord Flatpak.");
} catch (e) {
console.log("Failed to give write perms to Discord Flatpak.");
console.log(
"Try running this script as an administrator:",
"sudo pnpm inject"
);
process.exit(1);
}
}
for (const version of selected.versions) {
const dir = version.path;
// Check if we have write perms to the install directory...
try {
fs.accessSync(selected.location, fs.constants.W_OK);
} catch (e) {
console.error("No write access to", selected.location);
console.error(
"Try running this script as an administrator:",
"sudo pnpm inject"
);
process.exit(1);
}
if (fs.existsSync(dir) && fs.lstatSync(dir).isDirectory()) {
fs.rmSync(dir, { recursive: true });
}
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(
path.join(dir, "index.js"),
`require("${ENTRYPOINT}");`
);
fs.writeFileSync(
path.join(dir, "package.json"),
JSON.stringify({
name: "discord",
main: "index.js",
})
);
const requiredFiles = ["index.js", "package.json"];
if (requiredFiles.every(f => fs.existsSync(path.join(dir, f)))) {
console.log(
"Successfully patched",
version.name
? `${selected.branch} ${version.name}`
: selected.branch
);
} else {
console.log("Failed to patch", dir);
console.log("Files in directory:", fs.readdirSync(dir));
}
}
}

View File

@ -1,78 +0,0 @@
#!/usr/bin/node
/*
* 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/>.
*/
const path = require("path");
const fs = require("fs");
console.log("\nVencord Uninstaller\n");
if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) {
console.log("You need to install dependencies first. Run:", "pnpm install --frozen-lockfile");
process.exit(1);
}
const {
getMenuItem,
getWindowsDirs,
getDarwinDirs,
getLinuxDirs,
} = require("./common");
switch (process.platform) {
case "win32":
uninstall(getWindowsDirs());
break;
case "darwin":
uninstall(getDarwinDirs());
break;
case "linux":
uninstall(getLinuxDirs());
break;
default:
console.log("Unknown OS");
break;
}
async function uninstall(installations) {
const selected = await getMenuItem(installations);
for (const version of selected.versions) {
const dir = version.path;
// Check if we have write perms to the install directory...
try {
fs.accessSync(selected.location, fs.constants.W_OK);
} catch (e) {
console.error("No write access to", selected.location);
console.error(
"Try running this script as an administrator:",
"sudo pnpm uninject"
);
process.exit(1);
}
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true });
}
console.log(
"Successfully unpatched",
version.name
? `${selected.branch} ${version.name}`
: selected.branch
);
}
}

128
scripts/runInstaller.mjs Normal file
View File

@ -0,0 +1,128 @@
/*
* 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 "./checkNodeVersion.js";
import { execFileSync, execSync } from "child_process";
import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { Readable } from "stream";
import { finished } from "stream/promises";
import { fileURLToPath } from "url";
const BASE_URL = "https://github.com/Vencord/Installer/releases/latest/download/";
const INSTALLER_PATH_DARWIN = "VencordInstaller.app/Contents/MacOS/VencordInstaller";
const BASE_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
const FILE_DIR = join(BASE_DIR, "dist", "Installer");
const ETAG_FILE = join(FILE_DIR, "etag.txt");
function getFilename() {
switch (process.platform) {
case "win32":
return "VencordInstaller.exe";
case "darwin":
return "VencordInstaller.MacOS.zip";
case "linux":
return "VencordInstaller-" + (process.env.WAYLAND_DISPLAY ? "wayland" : "x11");
default:
throw new Error("Unsupported platform: " + process.platform);
}
}
async function ensureBinary() {
const filename = getFilename();
console.log("Downloading " + filename);
mkdirSync(FILE_DIR, { recursive: true });
const downloadName = join(FILE_DIR, filename);
const outputFile = process.platform === "darwin"
? join(FILE_DIR, "VencordInstaller")
: downloadName;
const etag = existsSync(outputFile) && existsSync(ETAG_FILE)
? readFileSync(ETAG_FILE, "utf-8")
: null;
const res = await fetch(BASE_URL + filename, {
headers: {
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)",
"If-None-Match": etag
}
});
if (res.status === 304) {
console.log("Up to date, not redownloading!");
return outputFile;
}
if (!res.ok)
throw new Error(`Failed to download installer: ${res.status} ${res.statusText}`);
writeFileSync(ETAG_FILE, res.headers.get("etag"));
if (process.platform === "darwin") {
console.log("Unzipping...");
const zip = new Uint8Array(await res.arrayBuffer());
const ff = await import("fflate");
const bytes = ff.unzipSync(zip, {
filter: f => f.name === INSTALLER_PATH_DARWIN
})[INSTALLER_PATH_DARWIN];
writeFileSync(outputFile, bytes, { mode: 0o755 });
console.log("Overriding security policy for installer binary (this is required to run it)");
console.log("xattr might error, that's okay");
const logAndRun = cmd => {
console.log("Running", cmd);
try {
execSync(cmd);
} catch { }
};
logAndRun(`sudo spctl --add '${outputFile}' --label "Vencord Installer"`);
logAndRun(`sudo xattr -d com.apple.quarantine '${outputFile}'`);
} else {
// WHY DOES NODE FETCH RETURN A WEB STREAM OH MY GOD
const body = Readable.fromWeb(res.body);
await finished(body.pipe(createWriteStream(outputFile, {
mode: 0o755,
autoClose: true
})));
}
console.log("Finished downloading!");
return outputFile;
}
const installerBin = await ensureBinary();
console.log("Now running Installer...");
execFileSync(installerBin, {
stdio: "inherit",
env: {
...process.env,
VENCORD_USER_DATA_DIR: BASE_DIR,
VENCORD_DEV_INSTALL: "1"
}
});

30
scripts/utils.mjs Normal file
View 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);
}

View File

@ -22,45 +22,121 @@ export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss";
export * as Updater from "./utils/updater";
export * as Webpack from "./webpack";
export { PlainSettings, Settings };
import { popNotice, showNotice } from "./api/Notices";
import { PlainSettings,Settings } from "./api/settings";
import { startAllPlugins } from "./plugins";
export { PlainSettings,Settings };
import "./webpack/patchWebpack";
import "./utils/quickCss";
import "./webpack/patchWebpack";
import { checkForUpdates, UpdateLogger } from "./utils/updater";
import { get as dsGet } from "./api/DataStore";
import { showNotification } from "./api/Notifications";
import { PlainSettings, Settings } from "./api/Settings";
import { patches, PMLogger, startAllPlugins } from "./plugins";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
import { Router } from "./webpack/common";
import { SettingsRouter } from "./webpack/common";
export let Components: any;
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;
}
async function init() {
await onceReady;
startAllPlugins();
Components = await import("./components");
if (!IS_WEB) {
try {
const isOutdated = await checkForUpdates();
if (isOutdated && Settings.notifyAboutUpdates)
setTimeout(() => {
showNotice(
"A Vencord update is available!",
"View Update",
() => {
popNotice();
Router.open("VencordUpdater");
}
);
}, 10000);
} catch (err) {
UpdateLogger.error("Failed to check for updates", err);
if (
Settings.cloud.settingsSync && // if it's enabled
Settings.cloud.authenticated // if cloud integrations are enabled
) {
if (localStorage.Vencord_settingsDirty) {
await putCloudSettings();
delete localStorage.Vencord_settingsDirty;
} else if (await getCloudSettings(false)) { // if we synchronized something (false means no sync)
// we show a notification here instead of allowing getCloudSettings() to show one to declutter the amount of
// potential notifications that might occur. getCloudSettings() will always send a notification regardless if
// there was an error to notify the user, but besides that we only want to show one notification instead of all
// of the possible ones it has (such as when your settings are newer).
showNotification({
title: "Cloud Settings",
body: "Your settings have been updated! Click here to restart to fully apply changes!",
color: "var(--green-360)",
onClick: relaunch
});
}
}
}
async function init() {
await onceReady;
startAllPlugins();
syncSettings();
if (!IS_WEB) {
try {
const isOutdated = await checkForUpdates();
if (!isOutdated) return;
if (Settings.autoUpdate) {
await update();
if (Settings.autoUpdateNotification)
setTimeout(() => showNotification({
title: "Vencord has been updated!",
body: "Click here to restart",
permanent: true,
noPersist: true,
onClick: relaunch
}), 10_000);
return;
}
if (Settings.notifyAboutUpdates)
setTimeout(() => showNotification({
title: "A Vencord update is available!",
body: "Click here to view the update",
permanent: true,
noPersist: true,
onClick() {
SettingsRouter.open("VencordUpdater");
}
}), 10_000);
} catch (err) {
UpdateLogger.error("Failed to check for updates", err);
}
}
if (IS_DEV) {
const pendingPatches = patches.filter(p => !p.all && p.predicate?.() !== false);
if (pendingPatches.length)
PMLogger.warn(
"Webpack has finished initialising, but some patches haven't been applied yet.",
"This might be expected since some Modules are lazy loaded, but please verify",
"that all plugins are working as intended.",
"You are seeing this warning because this is a Development build of Vencord.",
"\nThe following patches have not been applied:",
"\n\n" + pendingPatches.map(p => `${p.plugin}: ${p.find}`).join("\n")
);
}
}
init();
if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) {
document.addEventListener("DOMContentLoaded", () => {
document.head.append(Object.assign(document.createElement("style"), {
id: "vencord-native-titlebar-style",
textContent: "[class*=titleBar-]{display: none!important}"
}));
}, { once: true });
}

View File

@ -1,50 +1,71 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022
*
* 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/>.
*/
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcRenderer, ipcRenderer } from "electron";
import { IpcEvents } from "@utils/IpcEvents";
import { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron";
import type { UserThemeHeader } from "main/themes";
import IPC_EVENTS from "./utils/IpcEvents";
function assertEventAllowed(event: string) {
if (!(event in IPC_EVENTS)) throw new Error(`Event ${event} not allowed.`);
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.invoke(event, ...args) as Promise<T>;
}
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.sendSync(event, ...args) as T;
}
export default {
getVersions: () => process.versions,
ipc: {
send(event: string, ...args: any[]) {
assertEventAllowed(event);
ipcRenderer.send(event, ...args);
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)
},
updater: {
getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES),
update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE),
rebuild: () => invoke<IpcRes<boolean>>(IpcEvents.BUILD),
getRepo: () => invoke<IpcRes<string>>(IpcEvents.GET_REPO),
},
settings: {
get: () => sendSync<string>(IpcEvents.GET_SETTINGS),
set: (settings: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings),
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
},
quickCss: {
get: () => invoke<string>(IpcEvents.GET_QUICK_CSS),
set: (css: string) => invoke<void>(IpcEvents.SET_QUICK_CSS, css),
addChangeListener(cb: (newCss: string) => void) {
ipcRenderer.on(IpcEvents.QUICK_CSS_UPDATE, (_, css) => cb(css));
},
sendSync<T = any>(event: string, ...args: any[]): T {
assertEventAllowed(event);
return ipcRenderer.sendSync(event, ...args);
addThemeChangeListener(cb: () => void) {
ipcRenderer.on(IpcEvents.THEME_UPDATE, () => cb());
},
on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) {
assertEventAllowed(event);
ipcRenderer.on(event, listener);
openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS),
openEditor: () => invoke<void>(IpcEvents.OPEN_MONACO_EDITOR),
},
native: {
getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
},
pluginHelpers: {
OpenInApp: {
resolveRedirect: (url: string) => invoke<string>(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, url),
},
off(event: string, listener: Parameters<IpcRenderer["off"]>[1]) {
assertEventAllowed(event);
ipcRenderer.off(event, listener);
},
invoke<T = any>(event: string, ...args: any[]): Promise<T> {
assertEventAllowed(event);
return ipcRenderer.invoke(event, ...args);
VoiceMessages: {
readRecording: (path: string) => invoke<Uint8Array | null>(IpcEvents.VOICE_MESSAGES_READ_RECORDING, path),
}
}
};

110
src/api/Badges.ts Normal file
View File

@ -0,0 +1,110 @@
/*
* 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 ErrorBoundary from "@components/ErrorBoundary";
import { User } from "discord-types/general";
import { ComponentType, HTMLProps } from "react";
import Plugins from "~plugins";
export const enum BadgePosition {
START,
END
}
export interface ProfileBadge {
/** The tooltip to show on hover. Required for image badges */
description?: string;
/** Custom component for the badge (tooltip not included) */
component?: ComponentType<ProfileBadge & BadgeUserArgs>;
/** The custom image to use */
image?: string;
link?: string;
/** Action to perform when you click the badge */
onClick?(): void;
/** Should the user display this badge? */
shouldShow?(userInfo: BadgeUserArgs): boolean;
/** Optional props (e.g. style) for the badge, ignored for component badges */
props?: HTMLProps<HTMLImageElement>;
/** Insert at start or end? */
position?: BadgePosition;
/** The badge name to display, Discord uses this. Required for component badges */
key?: string;
}
const Badges = new Set<ProfileBadge>();
/**
* Register a new badge with the Badges API
* @param badge The badge to register
*/
export function addBadge(badge: ProfileBadge) {
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
Badges.add(badge);
}
/**
* Unregister a badge from the Badges API
* @param badge The badge to remove
*/
export function removeBadge(badge: ProfileBadge) {
return Badges.delete(badge);
}
/**
* Inject badges into the profile badges array.
* You probably don't need to use this.
*/
export function _getBadges(args: BadgeUserArgs) {
const badges = [] as ProfileBadge[];
for (const badge of Badges) {
if (!badge.shouldShow || badge.shouldShow(args)) {
badge.position === BadgePosition.START
? badges.unshift({ ...badge, ...args })
: badges.push({ ...badge, ...args });
}
}
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id);
if (donorBadges) badges.unshift(...donorBadges);
return badges;
}
export interface BadgeUserArgs {
user: User;
profile: Profile;
premiumSince: Date;
premiumGuildSince?: Date;
}
interface ConnectedAccount {
type: string;
id: string;
name: string;
verified: boolean;
}
interface Profile {
connectedAccounts: ConnectedAccount[];
premiumType: number;
premiumSince: string;
premiumGuildSince?: any;
lastFetched: number;
profileFetchFailed: boolean;
application?: any;
}

View File

@ -16,18 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { mergeDefaults } from "@utils/misc";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general";
import type { PartialDeep } from "type-fest";
import { lazyWebpack, mergeDefaults } from "../../utils/misc";
import { filters, waitFor } from "../../webpack";
import { Argument } from "./types";
const createBotMessage = lazyWebpack(filters.byCode('username:"Clyde"'));
const MessageSender = lazyWebpack(filters.byProps(["receiveMessage"]));
let SnowflakeUtils: any;
waitFor("fromTimestamp", m => SnowflakeUtils = m);
const createBotMessage = findByCodeLazy('username:"Clyde"');
const MessageSender = findByPropsLazy("receiveMessage");
export function generateId() {
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;

View File

@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { makeCodeblock } from "../../utils/misc";
import { generateId, sendBotMessage } from "./commandHelpers";
import { ApplicationCommandInputType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";
import { makeCodeblock } from "@utils/text";
import { sendBotMessage } from "./commandHelpers";
import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";
export * from "./commandHelpers";
export * from "./types";
@ -79,7 +80,12 @@ export const _handleCommand = function (cmd: Command, args: Argument[], ctx: Com
}
} as never;
function modifyOpt(opt: Option | Command) {
/**
* Prepare a Command Option for Discord by filling missing fields
* @param opt
*/
export function prepareOption<O extends Option | Command>(opt: O): O {
opt.displayName ||= opt.name;
opt.displayDescription ||= opt.description;
opt.options?.forEach((opt, i, opts) => {
@ -88,11 +94,37 @@ function modifyOpt(opt: Option | Command) {
else if (opt === ReqPlaceholder) opts[i] = RequiredMessageOption;
opt.choices?.forEach(x => x.displayName ||= x.name);
modifyOpt(opts[i]);
prepareOption(opts[i]);
});
return opt;
}
// Yes, Discord registers individual commands for each subcommand
// TODO: This probably doesn't support nested subcommands. If that is ever needed,
// investigate
function registerSubCommands(cmd: Command, plugin: string) {
cmd.options?.forEach(o => {
if (o.type !== ApplicationCommandOptionType.SUB_COMMAND)
throw new Error("When specifying sub-command options, all options must be sub-commands.");
const subCmd = {
...cmd,
...o,
type: ApplicationCommandType.CHAT_INPUT,
name: `${cmd.name} ${o.name}`,
id: `${o.name}-${cmd.id}`,
displayName: `${cmd.name} ${o.name}`,
subCommandPath: [{
name: o.name,
type: o.type,
displayName: o.name
}],
rootCommand: cmd
};
registerCommand(subCmd as any, plugin);
});
}
export function registerCommand(command: Command, plugin: string) {
export function registerCommand<C extends Command>(command: C, plugin: string) {
if (!BUILT_IN) {
console.warn(
"[CommandsAPI]",
@ -106,13 +138,19 @@ export function registerCommand(command: Command, plugin: string) {
throw new Error(`Command '${command.name}' already exists.`);
command.isVencordCommand = true;
command.id ??= generateId();
command.id ??= `-${BUILT_IN.length + 1}`;
command.applicationId ??= "-1"; // BUILT_IN;
command.type ??= ApplicationCommandType.CHAT_INPUT;
command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;
command.plugin ||= plugin;
modifyOpt(command);
prepareOption(command);
if (command.options?.[0]?.type === ApplicationCommandOptionType.SUB_COMMAND) {
registerSubCommands(command, plugin);
return;
}
commands[command.name] = command;
BUILT_IN.push(command);
}

View File

@ -24,7 +24,7 @@ export interface CommandContext {
guild?: Guild;
}
export enum ApplicationCommandOptionType {
export const enum ApplicationCommandOptionType {
SUB_COMMAND = 1,
SUB_COMMAND_GROUP = 2,
STRING = 3,
@ -38,7 +38,7 @@ export enum ApplicationCommandOptionType {
ATTACHMENT = 11,
}
export enum ApplicationCommandInputType {
export const enum ApplicationCommandInputType {
BUILT_IN = 0,
BUILT_IN_TEXT = 1,
BUILT_IN_INTEGRATION = 2,
@ -64,7 +64,7 @@ export interface ChoicesOption {
displayName?: string;
}
export enum ApplicationCommandType {
export const enum ApplicationCommandType {
CHAT_INPUT = 1,
USER = 2,
MESSAGE = 3,
@ -81,6 +81,7 @@ export interface Argument {
name: string;
value: string;
focused: undefined;
options: Argument[];
}
export interface Command {

158
src/api/ContextMenu.ts Normal file
View File

@ -0,0 +1,158 @@
/*
* 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 { Logger } from "@utils/Logger";
import type { ReactElement } from "react";
type ContextMenuPatchCallbackReturn = (() => void) | void;
/**
* @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
/**
* @param navId The navId of the context menu being patched
* @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
const ContextMenuLogger = new Logger("ContextMenu");
export const navPatches = new Map<string, Set<NavContextMenuPatchCallback>>();
export const globalPatches = new Set<GlobalContextMenuPatchCallback>();
/**
* Add a context menu patch
* @param navId The navId(s) for the context menu(s) to patch
* @param patch The patch to be applied
*/
export function addContextMenuPatch(navId: string | Array<string>, patch: NavContextMenuPatchCallback) {
if (!Array.isArray(navId)) navId = [navId];
for (const id of navId) {
let contextMenuPatches = navPatches.get(id);
if (!contextMenuPatches) {
contextMenuPatches = new Set();
navPatches.set(id, contextMenuPatches);
}
contextMenuPatches.add(patch);
}
}
/**
* Add a global context menu patch that fires the patch for all context menus
* @param patch The patch to be applied
*/
export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback) {
globalPatches.add(patch);
}
/**
* Remove a context menu patch
* @param navId The navId(s) for the context menu(s) to remove the patch
* @param patch The patch to be removed
* @returns Wheter the patch was sucessfully removed from the context menu(s)
*/
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 results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
return (Array.isArray(navId) ? results : results[0]) as T extends string ? boolean : Array<boolean>;
}
/**
* Remove a global context menu patch
* @param patch The patch to be removed
* @returns Wheter the patch was sucessfully removed
*/
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
return globalPatches.delete(patch);
}
/**
* A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
* @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children
*/
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>, _itemsArray?: Array<ReactElement | null>): Array<ReactElement | null> | null {
for (const child of children) {
if (child == null) continue;
if (
(Array.isArray(id) && id.some(id => child.props?.id === id))
|| child.props?.id === id
) return _itemsArray ?? null;
let nextChildren = child.props?.children;
if (nextChildren) {
if (!Array.isArray(nextChildren)) {
nextChildren = [nextChildren];
child.props.children = nextChildren;
}
const found = findGroupChildrenByChildId(id, nextChildren, nextChildren);
if (found !== null) return found;
}
}
return null;
}
interface ContextMenuProps {
contextMenuApiArguments?: Array<any>;
navId: string;
children: Array<ReactElement | null>;
"aria-label": string;
onSelect: (() => void) | undefined;
onClose: (callback: (...args: Array<any>) => any) => void;
}
const patchedMenus = new WeakSet();
export function _patchContextMenu(props: ContextMenuProps) {
props.contextMenuApiArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId);
if (!Array.isArray(props.children)) props.children = [props.children];
if (contextMenuPatches) {
for (const patch of contextMenuPatches) {
try {
const callback = patch(props.children, ...props.contextMenuApiArguments);
if (!patchedMenus.has(props)) callback?.();
} catch (err) {
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
}
}
}
for (const patch of globalPatches) {
try {
const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments);
if (!patchedMenus.has(props)) callback?.();
} catch (err) {
ContextMenuLogger.error("Global patch errored,", err);
}
}
patchedMenus.add(props);
}

View File

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

View File

@ -0,0 +1,65 @@
/*
* 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 { Channel, User } from "discord-types/general/index.js";
interface DecoratorProps {
activities: any[];
canUseAvatarDecorations: boolean;
channel: Channel;
/**
* Only for DM members
*/
channelName?: string;
/**
* Only for server members
*/
currentUser?: User;
guildId?: string;
isMobile: boolean;
isOwner?: boolean;
isTyping: boolean;
selected: boolean;
status: string;
user: User;
[key: string]: any;
}
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
type OnlyIn = "guilds" | "dms";
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
decorators.set(identifier, { decorator, onlyIn });
}
export function removeDecorator(identifier: string) {
decorators.delete(identifier);
}
export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] {
const isInGuild = !!(props.guildId);
return [...decorators.values()].map(decoratorObj => {
const { decorator, onlyIn } = decoratorObj;
// this can most likely be done cleaner
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
return decorator(props);
}
return null;
});
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element;
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
export type Accessory = {
callback: AccessoryCallback;
position?: number;
@ -44,6 +44,15 @@ export function _modifyAccessories(
props: Record<string, any>
) {
for (const accessory of accessories.values()) {
let accessories = accessory.callback(props);
if (accessories == null)
continue;
if (!Array.isArray(accessories))
accessories = [accessories];
else if (accessories.length === 0)
continue;
elements.splice(
accessory.position != null
? accessory.position < 0
@ -51,7 +60,7 @@ export function _modifyAccessories(
: accessory.position
: elements.length,
0,
accessory.callback(props)
...accessories.filter(e => e != null) as JSX.Element[]
);
}

View File

@ -0,0 +1,63 @@
/*
* 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 { Channel, Message } from "discord-types/general/index.js";
interface DecorationProps {
author: {
/**
* Will be username if the user has no nickname
*/
nick: string;
iconRoleId: string;
guildMemberAvatar: string;
colorRoleName: string;
colorString: string;
};
channel: Channel;
compact: boolean;
decorations: {
/**
* Element for the [BOT] tag if there is one
*/
0: JSX.Element | null;
/**
* Other decorations (including ones added with this api)
*/
1: JSX.Element[];
};
message: Message;
[key: string]: any;
}
export type Decoration = (props: DecorationProps) => JSX.Element | null;
export const decorations = new Map<string, Decoration>();
export function addDecoration(identifier: string, decoration: Decoration) {
decorations.set(identifier, decoration);
}
export function removeDecoration(identifier: string) {
decorations.delete(identifier);
}
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
return [...decorations.values()].map(decoration => {
return decoration(props);
});
}

View File

@ -16,46 +16,91 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import type { Channel,Message } from "discord-types/general";
import Logger from "../utils/logger";
import { Logger } from "@utils/Logger";
import { MessageStore } from "@webpack/common";
import { CustomEmoji } from "@webpack/types";
import type { Channel, Message } from "discord-types/general";
import type { Promisable } from "type-fest";
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
export interface Emoji {
require_colons: boolean,
originalName: string,
animated: boolean;
guildId: string,
name: string,
url: string,
id: string,
}
export interface MessageObject {
content: string,
validNonShortcutEmojis: Emoji[];
validNonShortcutEmojis: CustomEmoji[];
invalidEmojis: any[];
tts: boolean;
}
export type SendListener = (channelId: string, messageObj: MessageObject, extra: any) => void;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => void;
export interface Upload {
classification: string;
currentSize: number;
description: string | null;
filename: string;
id: string;
isImage: boolean;
isVideo: boolean;
item: {
file: File;
platform: number;
};
loaded: number;
mimeType: string;
preCompressionSize: number;
responseUrl: string;
sensitive: boolean;
showLargeMessageDialog: boolean;
spoiler: boolean;
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED";
uniqueId: string;
uploadedFilename: string;
}
export interface MessageReplyOptions {
messageReference: Message["messageReference"];
allowedMentions?: {
parse: Array<string>;
repliedUser: boolean;
};
}
export interface MessageExtra {
stickers?: string[];
uploads?: Upload[];
replyOptions: MessageReplyOptions;
content: string;
channel: Channel;
type?: any;
openWarningPopout: (props: any) => any;
}
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void>;
const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>();
export function _handlePreSend(channelId: string, messageObj: MessageObject, extra: any) {
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
extra.replyOptions = replyOptions;
for (const listener of sendListeners) {
try {
listener(channelId, messageObj, extra);
} catch (e) { MessageEventsLogger.error(`MessageSendHandler: Listener encoutered an unknown error. (${e})`); }
const result = await listener(channelId, messageObj, extra);
if (result && result.cancel === true) {
return true;
}
} catch (e) {
MessageEventsLogger.error("MessageSendHandler: Listener encountered an unknown error\n", e);
}
}
return false;
}
export function _handlePreEdit(channeld: string, messageId: string, messageObj: MessageObject) {
export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
for (const listener of editListeners) {
try {
listener(channeld, messageId, messageObj);
} catch (e) { MessageEventsLogger.error(`MessageEditHandler: Listener encoutered an unknown error. (${e})`); }
await listener(channelId, messageId, messageObj);
} catch (e) {
MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e);
}
}
}
@ -87,10 +132,14 @@ type ClickListener = (message: Message, channel: Channel, event: MouseEvent) =>
const listeners = new Set<ClickListener>();
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
// message object may be outdated, so (try to) fetch latest one
message = MessageStore.getMessage(channel.id, message.id) ?? message;
for (const listener of listeners) {
try {
listener(message, channel, event);
} catch (e) { MessageEventsLogger.error(`MessageClickHandler: Listener encoutered an unknown error. (${e})`); }
} catch (e) {
MessageEventsLogger.error("MessageClickHandler: Listener encountered an unknown error\n", e);
}
}
}

69
src/api/MessagePopover.ts Normal file
View File

@ -0,0 +1,69 @@
/*
* 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 { Logger } from "@utils/Logger";
import { Channel, Message } from "discord-types/general";
import type { MouseEventHandler } from "react";
const logger = new Logger("MessagePopover");
export interface ButtonItem {
key?: string,
label: string,
icon: React.ComponentType<any>,
message: Message,
channel: Channel,
onClick?: MouseEventHandler<HTMLButtonElement>,
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
}
export type getButtonItem = (message: Message) => ButtonItem | null;
export const buttons = new Map<string, getButtonItem>();
export function addButton(
identifier: string,
item: getButtonItem,
) {
buttons.set(identifier, item);
}
export function removeButton(identifier: string) {
buttons.delete(identifier);
}
export function _buildPopoverElements(
msg: Message,
makeButton: (item: ButtonItem) => React.ComponentType
) {
const items = [] as React.ComponentType[];
for (const [identifier, getItem] of buttons.entries()) {
try {
const item = getItem(msg);
if (item) {
item.key ??= identifier;
items.push(makeButton(item));
}
} catch (err) {
logger.error(`[${identifier}]`, err);
}
}
return items;
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { waitFor } from "../webpack";
import { waitFor } from "@webpack";
let NoticesModule: any;
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);

View File

@ -0,0 +1,123 @@
/*
* 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 "./styles.css";
import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common";
import { NotificationData } from "./Notifications";
export default ErrorBoundary.wrap(function NotificationComponent({
title,
body,
richBody,
color,
icon,
onClick,
onClose,
image,
permanent,
className,
dismissOnClick
}: NotificationData & { className?: string; }) {
const { timeout, position } = useSettings(["notifications.timeout", "notifications.position"]).notifications;
const hasFocus = useStateFromStores([WindowStore], () => WindowStore.isFocused());
const [isHover, setIsHover] = useState(false);
const [elapsed, setElapsed] = useState(0);
const start = useMemo(() => Date.now(), [timeout, isHover, hasFocus]);
useEffect(() => {
if (isHover || !hasFocus || timeout === 0 || permanent) return void setElapsed(0);
const intervalId = setInterval(() => {
const elapsed = Date.now() - start;
if (elapsed >= timeout)
onClose!();
else
setElapsed(elapsed);
}, 10);
return () => clearInterval(intervalId);
}, [timeout, isHover, hasFocus]);
const timeoutProgress = elapsed / timeout;
return (
<button
className={classes("vc-notification-root", className)}
style={position === "bottom-right" ? { bottom: "1rem" } : { top: "3rem" }}
onClick={() => {
onClick?.();
if (dismissOnClick !== false)
onClose!();
}}
onContextMenu={e => {
e.preventDefault();
e.stopPropagation();
onClose!();
}}
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<div className="vc-notification">
{icon && <img className="vc-notification-icon" src={icon} alt="" />}
<div className="vc-notification-content">
<div className="vc-notification-header">
<h2 className="vc-notification-title">{title}</h2>
<button
className="vc-notification-close-btn"
onClick={e => {
e.preventDefault();
e.stopPropagation();
onClose!();
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
role="img"
aria-labelledby="vc-notification-dismiss-title"
>
<title id="vc-notification-dismiss-title">Dismiss Notification</title>
<path fill="currentColor" d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
</svg>
</button>
</div>
<div>
{richBody ?? <p className="vc-notification-p">{body}</p>}
</div>
</div>
</div>
{image && <img className="vc-notification-img" src={image} alt="" />}
{timeout !== 0 && !permanent && (
<div
className="vc-notification-progressbar"
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-experiment)" }}
/>
)}
</button>
);
}, {
onError: ({ props }) => props.onClose!()
});

View File

@ -0,0 +1,110 @@
/*
* 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 { Settings } from "@api/Settings";
import { Queue } from "@utils/Queue";
import { ReactDOM } from "@webpack/common";
import type { ReactNode } from "react";
import type { Root } from "react-dom/client";
import NotificationComponent from "./NotificationComponent";
import { persistNotification } from "./notificationLog";
const NotificationQueue = new Queue();
let reactRoot: Root;
let id = 42;
function getRoot() {
if (!reactRoot) {
const container = document.createElement("div");
container.id = "vc-notification-container";
document.body.append(container);
reactRoot = ReactDOM.createRoot(container);
}
return reactRoot;
}
export interface NotificationData {
title: string;
body: string;
/**
* Same as body but can be a custom component.
* Will be used over body if present.
* Not supported on desktop notifications, those will fall back to body */
richBody?: ReactNode;
/** Small icon. This is for things like profile pictures and should be square */
icon?: string;
/** Large image. Optimally, this should be around 16x9 but it doesn't matter much. Desktop Notifications might not support this */
image?: string;
onClick?(): void;
onClose?(): void;
color?: string;
/** Whether this notification should not have a timeout */
permanent?: boolean;
/** Whether this notification should not be persisted in the Notification Log */
noPersist?: boolean;
/** Whether this notification should be dismissed when clicked (defaults to true) */
dismissOnClick?: boolean;
}
function _showNotification(notification: NotificationData, id: number) {
const root = getRoot();
return new Promise<void>(resolve => {
root.render(
<NotificationComponent key={id} {...notification} onClose={() => {
notification.onClose?.();
root.render(null);
resolve();
}} />,
);
});
}
function shouldBeNative() {
if (typeof Notification === "undefined") return false;
const { useNative } = Settings.notifications;
if (useNative === "always") return true;
if (useNative === "not-focused") return !document.hasFocus();
return false;
}
export async function requestPermission() {
return (
Notification.permission === "granted" ||
(Notification.permission !== "denied" && (await Notification.requestPermission()) === "granted")
);
}
export async function showNotification(data: NotificationData) {
persistNotification(data);
if (shouldBeNative() && await requestPermission()) {
const { title, body, icon, image, onClick = null, onClose = null } = data;
const n = new Notification(title, {
body,
icon,
image
});
n.onclick = onClick;
n.onclose = onClose;
} else {
NotificationQueue.push(() => _showNotification(data, id++));
}
}

View File

@ -0,0 +1,19 @@
/*
* 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/>.
*/
export * from "./Notifications";

View File

@ -0,0 +1,203 @@
/*
* 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 * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
import { nanoid } from "nanoid";
import type { DispatchWithoutAction } from "react";
import NotificationComponent from "./NotificationComponent";
import type { NotificationData } from "./Notifications";
interface PersistentNotificationData extends Pick<NotificationData, "title" | "body" | "image" | "icon" | "color"> {
timestamp: number;
id: string;
}
const KEY = "notification-log";
const getLog = async () => {
const log = await DataStore.get(KEY) as PersistentNotificationData[] | undefined;
return log ?? [];
};
const cl = classNameFactory("vc-notification-log-");
const signals = new Set<DispatchWithoutAction>();
export async function persistNotification(notification: NotificationData) {
if (notification.noPersist) return;
const limit = Settings.notifications.logLimit;
if (limit === 0) return;
await DataStore.update(KEY, (old: PersistentNotificationData[] | undefined) => {
const log = old ?? [];
// Omit stuff we don't need
const {
onClick, onClose, richBody, permanent, noPersist, dismissOnClick,
...pureNotification
} = notification;
log.unshift({
...pureNotification,
timestamp: Date.now(),
id: nanoid()
});
if (log.length > limit && limit !== 200)
log.length = limit;
return log;
});
signals.forEach(x => x());
}
export async function deleteNotification(timestamp: number) {
const log = await getLog();
const index = log.findIndex(x => x.timestamp === timestamp);
if (index === -1) return;
log.splice(index, 1);
await DataStore.set(KEY, log);
signals.forEach(x => x());
}
export function useLogs() {
const [signal, setSignal] = useReducer(x => x + 1, 0);
useEffect(() => {
signals.add(setSignal);
return () => void signals.delete(setSignal);
}, []);
const [log, _, pending] = useAwaiter(getLog, {
fallbackValue: [],
deps: [signal]
});
return [log, pending] as const;
}
function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
const [removing, setRemoving] = useState(false);
const ref = React.useRef<HTMLDivElement>(null);
useEffect(() => {
const div = ref.current!;
const setHeight = () => {
if (div.clientHeight === 0) return requestAnimationFrame(setHeight);
div.style.height = `${div.clientHeight}px`;
};
setHeight();
}, []);
return (
<div className={cl("wrapper", { removing })} ref={ref}>
<NotificationComponent
{...data}
permanent={true}
dismissOnClick={false}
onClose={() => {
if (removing) return;
setRemoving(true);
setTimeout(() => deleteNotification(data.timestamp), 200);
}}
richBody={
<div className={cl("body")}>
{data.body}
<Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} />
</div>
}
/>
</div>
);
}
export function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) {
if (!log.length && !pending)
return (
<div className={cl("container")}>
<div className={cl("empty")} />
<Forms.FormText style={{ textAlign: "center" }}>
No notifications yet
</Forms.FormText>
</div>
);
return (
<div className={cl("container")}>
{log.map(n => <NotificationEntry data={n} key={n.id} />)}
</div>
);
}
function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) {
const [log, pending] = useLogs();
return (
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Log</Text>
<ModalCloseButton onClick={close} />
</ModalHeader>
<ModalContent>
<NotificationLog log={log} pending={pending} />
</ModalContent>
<ModalFooter>
<Button
disabled={log.length === 0}
onClick={() => {
Alerts.show({
title: "Are you sure?",
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
async onConfirm() {
await DataStore.set(KEY, []);
signals.forEach(x => x());
},
confirmText: "Do it!",
confirmColor: "vc-notification-log-danger-btn",
cancelText: "Nevermind"
});
}}
>
Clear Notification Log
</Button>
</ModalFooter>
</ModalRoot>
);
}
export function openNotificationLogModal() {
const key = openModal(modalProps => (
<LogModal
modalProps={modalProps}
close={() => closeModal(key)}
/>
));
}

View File

@ -0,0 +1,122 @@
.vc-notification-root {
/* clear default button styles */
all: unset;
display: flex;
flex-direction: column;
color: var(--text-normal);
background-color: var(--background-secondary-alt);
border-radius: 6px;
overflow: hidden;
cursor: pointer;
width: 100%;
}
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
position: absolute;
z-index: 2147483647;
right: 1rem;
width: 25vw;
min-height: 10vh;
}
.vc-notification {
display: flex;
flex-direction: row;
padding: 1.25rem;
gap: 1.25rem;
}
.vc-notification-content {
width: 100%;
}
.vc-notification-header {
display: flex;
justify-content: space-between;
}
.vc-notification-title {
color: var(--header-primary);
font-size: 1rem;
font-weight: 600;
line-height: 1.25rem;
text-transform: uppercase;
}
.vc-notification-close-btn {
all: unset;
cursor: pointer;
color: var(--interactive-normal);
opacity: 0.5;
transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out;
}
.vc-notification-close-btn:hover {
color: var(--interactive-hover);
opacity: 1;
}
.vc-notification-icon {
height: 4rem;
width: 4rem;
border-radius: 6px;
}
.vc-notification-progressbar {
height: 0.25rem;
border-radius: 5px;
margin-top: auto;
}
.vc-notification-p {
margin: 0.5rem 0 0;
line-height: 140%;
}
.vc-notification-img {
width: 100%;
}
.vc-notification-log-empty {
height: 218px;
background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat;
margin-bottom: 40px;
}
.vc-notification-log-container {
display: flex;
flex-direction: column;
padding: 1em;
overflow: hidden;
}
.vc-notification-log-wrapper {
transition: 200ms ease;
transition-property: height, opacity;
}
.vc-notification-log-wrapper:not(:last-child) {
margin-bottom: 1em;
}
.vc-notification-log-removing {
height: 0 !important;
opacity: 0;
margin-bottom: 1em;
}
.vc-notification-log-body {
display: flex;
flex-direction: column;
}
.vc-notification-log-timestamp {
margin-left: auto;
font-size: 0.8em;
font-weight: lighter;
}
.vc-notification-log-danger-btn {
color: var(--white-500);
background-color: var(--button-danger-background);
}

55
src/api/ServerList.ts Normal file
View File

@ -0,0 +1,55 @@
/*
* 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 { Logger } from "@utils/Logger";
const logger = new Logger("ServerListAPI");
export const enum ServerListRenderPosition {
Above,
In,
}
const renderFunctionsAbove = new Set<Function>();
const renderFunctionsIn = new Set<Function>();
function getRenderFunctions(position: ServerListRenderPosition) {
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
}
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
getRenderFunctions(position).add(renderFunction);
}
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
getRenderFunctions(position).delete(renderFunction);
}
export const renderAll = (position: ServerListRenderPosition) => {
const ret: Array<JSX.Element> = [];
for (const renderFunction of getRenderFunctions(position)) {
try {
ret.unshift(renderFunction());
} catch (e) {
logger.error("Failed to render server list element:", e);
}
}
return ret;
};

View File

@ -16,57 +16,118 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { debounce } from "@utils/debounce";
import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/misc";
import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React } from "@webpack/common";
import plugins from "~plugins";
import IpcEvents from "../utils/IpcEvents";
import { mergeDefaults } from "../utils/misc";
import { OptionType } from "../utils/types";
import { React } from "../webpack/common";
const logger = new Logger("Settings");
export interface Settings {
notifyAboutUpdates: boolean;
autoUpdate: boolean;
autoUpdateNotification: boolean,
useQuickCss: boolean;
enableReactDevtools: boolean;
themeLinks: string[];
enabledThemes: string[];
frameless: boolean;
transparent: boolean;
winCtrlQ: boolean;
macosTranslucency: boolean;
disableMinSize: boolean;
winNativeTitleBar: boolean;
plugins: {
[plugin: string]: {
enabled: boolean;
[setting: string]: any;
};
};
notifications: {
timeout: number;
position: "top-right" | "bottom-right";
useNative: "always" | "never" | "not-focused";
logLimit: number;
};
cloud: {
authenticated: boolean;
url: string;
settingsSync: boolean;
settingsSyncVersion: number;
};
}
const DefaultSettings: Settings = {
notifyAboutUpdates: true,
autoUpdate: false,
autoUpdateNotification: true,
useQuickCss: true,
themeLinks: [],
enabledThemes: [],
enableReactDevtools: false,
plugins: {}
frameless: false,
transparent: false,
winCtrlQ: false,
macosTranslucency: false,
disableMinSize: false,
winNativeTitleBar: false,
plugins: {},
notifications: {
timeout: 5000,
position: "bottom-right",
useNative: "not-focused",
logLimit: 50
},
cloud: {
authenticated: false,
url: "https://api.vencord.dev/",
settingsSync: false,
settingsSyncVersion: 0
}
};
for (const plugin in plugins) {
DefaultSettings.plugins[plugin] = {
enabled: plugins[plugin].required ?? false
};
}
try {
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
var settings = JSON.parse(VencordNative.settings.get()) as Settings;
mergeDefaults(settings, DefaultSettings);
} catch (err) {
console.error("Corrupt settings file. ", err);
var settings = mergeDefaults({} as Settings, DefaultSettings);
logger.error("An error occurred while loading the settings. Corrupt settings file?\n", err);
}
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; };
const saveSettingsOnFrequentAction = debounce(async () => {
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
await putCloudSettings();
delete localStorage.Vencord_settingsDirty;
}
}, 60_000);
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _paths?: Array<string>; };
const subscriptions = new Set<SubscriptionCallback>();
const proxyCache = {} as Record<string, any>;
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
function makeProxy(settings: Settings, root = settings, path = ""): Settings {
return new Proxy(settings, {
function makeProxy(settings: any, root = settings, path = ""): Settings {
return proxyCache[path] ??= new Proxy(settings, {
get(target, p: string) {
const v = target[p];
// using "in" is important in the following cases to properly handle falsy or nullish values
if (!(p in target)) {
// Return empty for plugins with no settings
if (path === "plugins" && p in plugins)
return target[p] = makeProxy({
enabled: plugins[p].required ?? plugins[p].enabledByDefault ?? false
}, root, `plugins.${p}`);
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
// the default value.
if (path.startsWith("plugins.")) {
@ -76,9 +137,13 @@ function makeProxy(settings: Settings, root = settings, path = ""): Settings {
if (!setting) return v;
if ("default" in setting)
// normal setting with a default value
return setting.default;
if (setting.type === OptionType.SELECT)
return setting.options.find(o => o.default)?.value;
return (target[p] = setting.default);
if (setting.type === OptionType.SELECT) {
const def = setting.options.find(o => o.default);
if (def)
target[p] = def.value;
return def?.value;
}
}
}
return v;
@ -99,13 +164,17 @@ function makeProxy(settings: Settings, root = settings, path = ""): Settings {
target[p] = v;
// Call any listeners that are listening to a setting of this path
const setPath = `${path}${path && "."}${p}`;
delete proxyCache[setPath];
for (const subscription of subscriptions) {
if (!subscription._path || subscription._path === setPath) {
if (!subscription._paths || subscription._paths.includes(setPath)) {
subscription(v, setPath);
}
}
// And don't forget to persist the settings!
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4));
PlainSettings.cloud.settingsSyncVersion = Date.now();
localStorage.Vencord_settingsDirty = true;
saveSettingsOnFrequentAction();
VencordNative.settings.set(JSON.stringify(root, null, 4));
return true;
}
});
@ -131,14 +200,20 @@ export const Settings = makeProxy(settings);
* Settings hook for React components. Returns a smart settings
* object that automagically triggers a rerender if any properties
* are altered
* @param paths An optional list of paths to whitelist for rerenders
* @returns Settings
*/
export function useSettings() {
// TODO: Representing paths as essentially "string[].join('.')" wont allow dots in paths, change to "paths?: string[][]" later
export function useSettings(paths?: UseSettings<Settings>[]) {
const [, forceUpdate] = React.useReducer(() => ({}), {});
const onUpdate: SubscriptionCallback = paths
? (value, path) => paths.includes(path as UseSettings<Settings>) && forceUpdate()
: forceUpdate;
React.useEffect(() => {
subscriptions.add(forceUpdate);
return () => void subscriptions.delete(forceUpdate);
subscriptions.add(onUpdate);
return () => void subscriptions.delete(onUpdate);
}, []);
return Settings;
@ -162,6 +237,58 @@ 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 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) {
(onUpdate as SubscriptionCallback)._path = path;
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
subscriptions.add(onUpdate);
}
export function migratePluginSettings(name: string, ...oldNames: string[]) {
const { plugins } = settings;
if (name in plugins) return;
for (const oldName of oldNames) {
if (oldName in plugins) {
logger.info(`Migrating settings from old name ${oldName} to ${name}`);
plugins[name] = plugins[oldName];
delete plugins[oldName];
VencordNative.settings.set(JSON.stringify(settings, null, 4));
break;
}
}
}
export function definePluginSettings<
Def extends SettingsDefinition,
Checks extends SettingsChecks<Def>,
PrivateSettings extends object = {}
>(def: Def, checks?: Checks) {
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
get store() {
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
return Settings.plugins[definedSettings.pluginName] as any;
},
use: settings => useSettings(
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
).plugins[definedSettings.pluginName] as any,
def,
checks: checks ?? {} as any,
pluginName: "",
withPrivateSettings<T extends object>() {
return this as DefinedSettings<Def, Checks, T>;
}
};
return definedSettings;
}
type UseSettings<T extends object> = ResolveUseSettings<T>[keyof T];
type ResolveUseSettings<T extends object> = {
[Key in keyof T]:
Key extends string
? T[Key] extends Record<string, unknown>
// @ts-ignore "Type instantiation is excessively deep and possibly infinite"
? UseSettings<T[Key]> extends string ? `${Key}.${UseSettings<T[Key]>}` : never
: Key
: never;
};

69
src/api/SettingsStore.ts Normal file
View File

@ -0,0 +1,69 @@
/*
* 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));
}

162
src/api/Styles.ts Normal file
View File

@ -0,0 +1,162 @@
/*
* 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 type { MapValue } from "type-fest/source/entry";
export type Style = MapValue<typeof VencordStyles>;
export const styleMap = window.VencordStyles ??= new Map();
export function requireStyle(name: string) {
const style = styleMap.get(name);
if (!style) throw new Error(`Style "${name}" does not exist`);
return style;
}
/**
* A style's name can be obtained from importing a stylesheet with `?managed` at the end of the import
* @param name The name of the style
* @returns `false` if the style was already enabled, `true` otherwise
* @example
* import pluginStyle from "./plugin.css?managed";
*
* // Inside some plugin method like "start()" or "[option].onChange()"
* enableStyle(pluginStyle);
*/
export function enableStyle(name: string) {
const style = requireStyle(name);
if (style.dom?.isConnected)
return false;
if (!style.dom) {
style.dom = document.createElement("style");
style.dom.dataset.vencordName = style.name;
}
compileStyle(style);
document.head.appendChild(style.dom);
return true;
}
/**
* @param name The name of the style
* @returns `false` if the style was already disabled, `true` otherwise
* @see {@link enableStyle} for info on getting the name of an imported style
*/
export function disableStyle(name: string) {
const style = requireStyle(name);
if (!style.dom?.isConnected)
return false;
style.dom.remove();
style.dom = null;
return true;
}
/**
* @param name The name of the style
* @returns `true` in most cases, may return `false` in some edge cases
* @see {@link enableStyle} for info on getting the name of an imported style
*/
export const toggleStyle = (name: string) => isStyleEnabled(name) ? disableStyle(name) : enableStyle(name);
/**
* @param name The name of the style
* @returns Whether the style is enabled
* @see {@link enableStyle} for info on getting the name of an imported style
*/
export const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnected ?? false;
/**
* Sets the variables of a style
* ```ts
* // -- plugin.ts --
* import pluginStyle from "./plugin.css?managed";
* import { setStyleVars } from "@api/Styles";
* import { findByPropsLazy } from "@webpack";
* const classNames = findByPropsLazy("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... }
*
* // Inside some plugin method like "start()"
* setStyleClassNames(pluginStyle, classNames);
* enableStyle(pluginStyle);
* ```
* ```scss
* // -- plugin.css --
* .plugin-root [--thin]::-webkit-scrollbar { ... }
* ```
* ```scss
* // -- final stylesheet --
* .plugin-root .thin-31rlnD.scrollerBase-_bVAAt::-webkit-scrollbar { ... }
* ```
* @param name The name of the style
* @param classNames An object where the keys are the variable names and the values are the variable values
* @param recompile Whether to recompile the style after setting the variables, defaults to `true`
* @see {@link enableStyle} for info on getting the name of an imported style
*/
export const setStyleClassNames = (name: string, classNames: Record<string, string>, recompile = true) => {
const style = requireStyle(name);
style.classNames = classNames;
if (recompile && isStyleEnabled(style.name))
compileStyle(style);
};
/**
* Updates the stylesheet after doing the following to the sourcecode:
* - Interpolate style classnames
* @param style **_Must_ be a style with a DOM element**
* @see {@link setStyleClassNames} for more info on style classnames
*/
export const compileStyle = (style: Style) => {
if (!style.dom) throw new Error("Style has no DOM element");
style.dom.textContent = style.source
.replace(/\[--(\w+)\]/g, (match, name) => {
const className = style.classNames[name];
return className ? classNameToSelector(className) : match;
});
};
/**
* @param name The classname
* @param prefix A prefix to add each class, defaults to `""`
* @return A css selector for the classname
* @example
* classNameToSelector("foo bar") // => ".foo.bar"
*/
export const classNameToSelector = (name: string, prefix = "") => name.split(" ").map(n => `.${prefix}${n}`).join("");
type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
/**
* @param prefix The prefix to add to each class, defaults to `""`
* @returns A classname generator function
* @example
* const cl = classNameFactory("plugin-");
*
* cl("base", ["item", "editable"], { selected: null, disabled: true })
* // => "plugin-base plugin-item plugin-editable plugin-disabled"
*/
export const classNameFactory = (prefix: string = "") => (...args: ClassNameFactoryArg[]) => {
const classNames = new Set<string>();
for (const arg of args) {
if (arg && typeof arg === "string") classNames.add(arg);
else if (Array.isArray(arg)) arg.forEach(name => 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(" ");
};

View File

@ -16,11 +16,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as $Badges from "./Badges";
import * as $Commands from "./Commands";
import * as $ContextMenu from "./ContextMenu";
import * as $DataStore from "./DataStore";
import * as $MemberListDecorators from "./MemberListDecorators";
import * as $MessageAccessories from "./MessageAccessories";
import * as $MessageDecorations from "./MessageDecorations";
import * as $MessageEventsAPI from "./MessageEvents";
import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList";
import * as $Settings from "./Settings";
import * as $SettingsStore from "./SettingsStore";
import * as $Styles from "./Styles";
/**
* An API allowing you to listen to Message Clicks or run your own logic
@ -28,16 +38,16 @@ import * as $Notices from "./Notices";
*
* If your plugin uses this, you must add MessageEventsAPI to its dependencies
*/
const MessageEvents = $MessageEventsAPI;
export const MessageEvents = $MessageEventsAPI;
/**
* An API allowing you to create custom notices
* (snackbars on the top, like the Update prompt)
*/
const Notices = $Notices;
export const Notices = $Notices;
/**
* An API allowing you to register custom commands
*/
const Commands = $Commands;
export const Commands = $Commands;
/**
* A wrapper around IndexedDB. This can store arbitrarily
* large data and supports a lot of datatypes (Blob, Map, ...).
@ -52,10 +62,50 @@ const Commands = $Commands;
* This is actually just idb-keyval, so if you're familiar with that, you're golden!
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types}
*/
const DataStore = $DataStore;
export const DataStore = $DataStore;
/**
* An API allowing you to add custom components as message accessories
*/
const MessageAccessories = $MessageAccessories;
export const MessageAccessories = $MessageAccessories;
/**
* An API allowing you to add custom buttons in the message popover
*/
export const MessagePopover = $MessagePopover;
/**
* An API allowing you to add badges to user profiles
*/
export const Badges = $Badges;
/**
* An API allowing you to add custom elements to the server list
*/
export const ServerList = $ServerList;
/**
* An API allowing you to add components as message accessories
*/
export const MessageDecorations = $MessageDecorations;
/**
* An API allowing you to add components to member list users, in both DM's and servers
*/
export const MemberListDecorators = $MemberListDecorators;
/**
* An API allowing you to persist data
*/
export const Settings = $Settings;
/**
* An API allowing you to read, manipulate and automatically update components based on Discord settings
*/
export const SettingsStore = $SettingsStore;
/**
* An API allowing you to dynamically load styles
* a
*/
export const Styles = $Styles;
/**
* An API allowing you to display notifications
*/
export const Notifications = $Notifications;
export { Commands,DataStore, MessageAccessories, MessageEvents, Notices };
/**
* An api allowing you to patch and add/remove items to/from context menus
*/
export const ContextMenu = $ContextMenu;

29
src/components/Badge.tsx Normal file
View File

@ -0,0 +1,29 @@
/*
* 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/>.
*/
export function Badge({ text, color }): JSX.Element {
return (
<div className="vc-plugins-badge" style={{
backgroundColor: color,
justifySelf: "flex-end",
marginLeft: "auto"
}}>
{text}
</div>
);
}

View File

@ -0,0 +1,68 @@
/*
* 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 { React, TextInput } from "@webpack/common";
// TODO: Refactor settings to use this as well
interface TextInputProps {
/**
* WARNING: Changing this between renders will have no effect!
*/
value: string;
/**
* This will only be called if the new value passed validate()
*/
onChange(newValue: string): void;
/**
* Optionally validate the user input
* Return true if the input is valid
* Otherwise, return a string containing the reason for this input being invalid
*/
validate(v: string): true | string;
}
/**
* A very simple wrapper around Discord's TextInput that validates input and shows
* the user an error message and only calls your onChange when the input is valid
*/
export function CheckedTextInput({ value: initialValue, onChange, validate }: TextInputProps) {
const [value, setValue] = React.useState(initialValue);
const [error, setError] = React.useState<string>();
function handleChange(v: string) {
setValue(v);
const res = validate(v);
if (res === true) {
setError(void 0);
onChange(v);
} else {
setError(res);
}
}
return (
<>
<TextInput
type="text"
value={value}
onChange={handleChange}
error={error}
/>
</>
);
}

View File

@ -0,0 +1,21 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { 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>
);
}

View File

@ -0,0 +1,35 @@
/*
* 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 { Button } from "@webpack/common";
import { Heart } from "./Heart";
export default function DonateButton(props: any) {
return (
<Button
{...props}
look={Button.Looks.LINK}
color={Button.Colors.TRANSPARENT}
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
>
<Heart />
Donate
</Button>
);
}

View File

@ -16,14 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Logger from "../utils/logger";
import { Margins, React } from "../webpack/common";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { LazyComponent } from "@utils/react";
import { React } from "@webpack/common";
import { ErrorCard } from "./ErrorCard";
interface Props {
interface Props<T = any> {
/** Render nothing if an error occurs */
noop?: boolean;
/** Fallback component to render if an error occurs */
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
onError?(error: Error, errorInfo: React.ErrorInfo): void;
/** called when an error occurs. The props property is only available if using .wrap */
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
/** Custom error message */
message?: string;
/** The props passed to the wrapped component. Only used by wrap */
wrappedProps?: T;
}
const color = "#e78284";
@ -32,68 +43,75 @@ const logger = new Logger("React ErrorBoundary", color);
const NO_ERROR = {};
export default class ErrorBoundary extends React.Component<React.PropsWithChildren<Props>> {
static wrap<T = any>(Component: React.ComponentType<T>): (props: T) => React.ReactElement {
return props => (
<ErrorBoundary>
<Component {...props as any/* I hate react typings ??? */} />
</ErrorBoundary>
);
}
// We might want to import this in a place where React isn't ready yet.
// Thus, wrap in a LazyComponent
const ErrorBoundary = LazyComponent(() => {
return class ErrorBoundary extends React.PureComponent<React.PropsWithChildren<Props>> {
state = {
error: NO_ERROR as any,
stack: "",
message: ""
};
state = {
error: NO_ERROR as any,
stack: "",
message: ""
};
static getDerivedStateFromError(error: any) {
let stack = error?.stack ?? "";
let message = error?.message || String(error);
static getDerivedStateFromError(error: any) {
let stack = error?.stack ?? "";
let message = error?.message || String(error);
if (error instanceof Error && stack) {
const eolIdx = stack.indexOf("\n");
if (eolIdx !== -1) {
message = stack.slice(0, eolIdx);
stack = stack.slice(eolIdx + 1).replace(/https:\/\/\S+\/assets\//g, "");
if (error instanceof Error && stack) {
const eolIdx = stack.indexOf("\n");
if (eolIdx !== -1) {
message = stack.slice(0, eolIdx);
stack = stack.slice(eolIdx + 1).replace(/https:\/\/\S+\/assets\//g, "");
}
}
return { error, stack, message };
}
return { error, stack, message };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
logger.error("A component threw an Error\n", error);
logger.error("Component Stack", errorInfo.componentStack);
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.props.onError?.(error, errorInfo);
logger.error("A component threw an Error\n", error);
logger.error("Component Stack", errorInfo.componentStack);
}
render() {
if (this.state.error === NO_ERROR) return this.props.children;
render() {
if (this.state.error === NO_ERROR) return this.props.children;
if (this.props.noop) return null;
if (this.props.fallback)
return <this.props.fallback
children={this.props.children}
{...this.state}
/>;
if (this.props.fallback)
return <this.props.fallback
children={this.props.children}
{...this.state}
/>;
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
return (
<ErrorCard style={{
overflow: "hidden",
}}>
<h1>Oh no!</h1>
<p>{msg}</p>
<code>
{this.state.message}
{!!this.state.stack && (
<pre className={Margins.marginTop8}>
{this.state.stack}
</pre>
)}
</code>
</ErrorCard>
);
}
}
return (
<ErrorCard style={{ overflow: "hidden" }}>
<h1>Oh no!</h1>
<p>{msg}</p>
<code>
{this.state.message}
{!!this.state.stack && (
<pre className={Margins.top8}>
{this.state.stack}
</pre>
)}
</code>
</ErrorCard>
);
}
};
}) as
React.ComponentType<React.PropsWithChildren<Props>> & {
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.FunctionComponent<T>;
};
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
<ErrorBoundary {...errorBoundaryProps} wrappedProps={props}>
<Component {...props} />
</ErrorBoundary>
);
export default ErrorBoundary;

View File

@ -0,0 +1,7 @@
.vc-error-card {
padding: 2em;
background-color: #e7828430;
border: 1px solid #e78284;
border-radius: 5px;
color: var(--text-normal, white);
}

View File

@ -16,24 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Card } from "../webpack/common";
import "./ErrorCard.css";
interface Props {
style?: React.CSSProperties;
className?: string;
}
export function ErrorCard(props: React.PropsWithChildren<Props>) {
import { classes } from "@utils/misc";
import type { HTMLProps } from "react";
export function ErrorCard(props: React.PropsWithChildren<HTMLProps<HTMLDivElement>>) {
return (
<Card className={props.className} style={
{
padding: "2em",
backgroundColor: "#e7828430",
borderColor: "#e78284",
color: "var(--text-normal)",
...props.style
}
}>
<div {...props} className={classes(props.className, "vc-error-card")}>
{props.children}
</Card>
</div>
);
}

View File

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

View File

@ -0,0 +1,108 @@
/*
* 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 { classNameFactory } from "@api/Styles";
import { Text, Tooltip, useState } from "@webpack/common";
export const cl = classNameFactory("vc-expandableheader-");
import "./ExpandableHeader.css";
export interface ExpandableHeaderProps {
onMoreClick?: () => void;
moreTooltipText?: string;
onDropDownClick?: (state: boolean) => void;
defaultState?: boolean;
headerText: string;
children: React.ReactNode;
buttons?: React.ReactNode[];
}
export default function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) {
const [showContent, setShowContent] = useState(defaultState);
return (
<>
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "8px"
}}>
<Text
tag="h2"
variant="eyebrow"
style={{
color: "var(--header-primary)",
display: "inline"
}}
>
{headerText}
</Text>
<div className={cl("center-flex")}>
{
buttons ?? null
}
{
onMoreClick && // only show more button if callback is provided
<Tooltip text={moreTooltipText}>
{tooltipProps => (
<button
{...tooltipProps}
className={cl("btn")}
onClick={onMoreClick}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
>
<path fill="var(--text-normal)" d="M7 12.001C7 10.8964 6.10457 10.001 5 10.001C3.89543 10.001 3 10.8964 3 12.001C3 13.1055 3.89543 14.001 5 14.001C6.10457 14.001 7 13.1055 7 12.001ZM14 12.001C14 10.8964 13.1046 10.001 12 10.001C10.8954 10.001 10 10.8964 10 12.001C10 13.1055 10.8954 14.001 12 14.001C13.1046 14.001 14 13.1055 14 12.001ZM19 10.001C20.1046 10.001 21 10.8964 21 12.001C21 13.1055 20.1046 14.001 19 14.001C17.8954 14.001 17 13.1055 17 12.001C17 10.8964 17.8954 10.001 19 10.001Z" />
</svg>
</button>
)}
</Tooltip>
}
<Tooltip text={showContent ? "Hide " + headerText : "Show " + headerText}>
{tooltipProps => (
<button
{...tooltipProps}
className={cl("btn")}
onClick={() => {
setShowContent(v => !v);
onDropDownClick?.(showContent);
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
transform={showContent ? "scale(1 -1)" : "scale(1 1)"}
>
<path fill="var(--text-normal)" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
</svg>
</button>
)}
</Tooltip>
</div>
</div>
{showContent && children}
</>
);
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import type { React } from "../webpack/common";
import type { React } from "@webpack/common";
export function Flex(props: React.PropsWithChildren<{
flexDirection?: React.CSSProperties["flexDirection"];
@ -24,9 +24,11 @@ export function Flex(props: React.PropsWithChildren<{
className?: string;
} & React.HTMLProps<HTMLDivElement>>) {
props.style ??= {};
props.style.flexDirection ||= props.flexDirection;
props.style.gap ??= "1em";
props.style.display = "flex";
// TODO(ven): Remove me, what was I thinking??
props.style.gap ??= "1em";
props.style.flexDirection ||= props.flexDirection;
delete props.flexDirection;
return (
<div {...props}>
{props.children}

35
src/components/Heart.tsx Normal file
View File

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

205
src/components/Icons.tsx Normal file
View File

@ -0,0 +1,205 @@
/*
* 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 "./iconStyles.css";
import { classes } from "@utils/misc";
import { i18n } from "@webpack/common";
import type { PropsWithChildren, SVGProps } from "react";
interface BaseIconProps extends IconProps {
viewBox: string;
}
interface IconProps extends SVGProps<SVGSVGElement> {
className?: string;
height?: number;
width?: number;
}
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
return (
<svg
className={classes(className, "vc-icon")}
role="img"
width={width}
height={height}
viewBox={viewBox}
{...svgProps}
>
{children}
</svg>
);
}
/**
* Discord's link icon, as seen in the Message context menu "Copy Message Link" option
*/
export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
return (
<Icon
height={height}
width={width}
className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24"
>
<g fill="none" fill-rule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} />
</g>
</Icon>
);
}
/**
* Discord's copy icon, as seen in the user popout right of the username when clicking
* your own username in the bottom left user panel
*/
export function CopyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-copy-icon")}
viewBox="0 0 24 24"
>
<g fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" />
</g>
</Icon>
);
}
/**
* Discord's open external icon, as seen in the user profile connections
*/
export function OpenExternalIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-open-external-icon")}
viewBox="0 0 24 24"
>
<polygon
fill="currentColor"
fill-rule="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"
/>
</Icon>
);
}
export function ImageIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-image-icon")}
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
</Icon>
);
}
export function InfoIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-info-icon")}
viewBox="0 0 12 12"
>
<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" />
</Icon>
);
}
export function OwnerCrownIcon(props: IconProps) {
return (
<Icon
aria-label={i18n.Messages.GUILD_OWNER}
{...props}
className={classes(props.className, "vc-owner-crown-icon")}
role="img"
viewBox="0 0 16 16"
>
<path
fill="currentColor"
fill-rule="evenodd"
clip-rule="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"
/>
</Icon>
);
}
/**
* Discord's screenshare icon, as seen in the connection panel
*/
export function ScreenshareIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-screenshare-icon")}
viewBox="0 0 24 24"
>
<path
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"
/>
</Icon>
);
}
export function ImageVisible(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-image-visible")}
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
</Icon>
);
}
export function ImageInvisible(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-image-invisible")}
viewBox="0 0 24 24"
>
<path fill="currentColor" d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
</Icon>
);
}
export function Microphone(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-microphone")}
viewBox="0 0 24 24"
>
<path fill-rule="evenodd" clip-rule="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 fill-rule="evenodd" clip-rule="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 >
);
}

View File

@ -16,21 +16,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { React } from "../webpack/common";
import { React } from "@webpack/common";
interface Props {
href: string;
interface Props extends React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> {
disabled?: boolean;
style?: React.CSSProperties;
}
export function Link(props: React.PropsWithChildren<Props>) {
if (props.disabled) {
props.style ??= {};
props.style.pointerEvents = "none";
props["aria-disabled"] = true;
}
return (
<a href={props.href} target="_blank" style={props.style}>
<a role="link" target="_blank" {...props}>
{props.children}
</a>
);

View File

@ -1,42 +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 monacoHtml from "~fileContent/monacoWin.html";
import { IpcEvents } from "../utils";
import { debounce } from "../utils/debounce";
import { Queue } from "../utils/Queue";
import { find } from "../webpack/webpack";
const queue = new Queue();
const setCss = debounce((css: string) => {
queue.add(() => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, css));
});
export async function launchMonacoEditor() {
const win = open("about:blank", void 0, "popup,width=1000,height=1000")!;
win.setCss = setCss;
win.getCurrentCss = () => VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
win.getTheme = () => find(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings"))
.getCurrentValue().appearance.theme === 1
? "vs-dark"
: "vs-light";
win.document.write(monacoHtml);
}

View File

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

View File

@ -16,29 +16,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Forms } from "@components";
import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { OptionType, Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack";
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
import { User } from "discord-types/general";
import { Constructor } from "type-fest";
import { generateId } from "../../api/Commands";
import { useSettings } from "../../api/settings";
import { lazyWebpack, proxyLazy } from "../../utils";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "../../utils/modal";
import { OptionType, Plugin } from "../../utils/types";
import { filters } from "../../webpack";
import { Button, FluxDispatcher, React, Text, Tooltip, UserStore, UserUtils } from "../../webpack/common";
import ErrorBoundary from "../ErrorBoundary";
import { Flex } from "../Flex";
import {
ISettingElementProps,
SettingBooleanComponent,
SettingInputComponent,
SettingCustomComponent,
SettingNumericComponent,
SettingSelectComponent,
SettingSliderComponent
SettingSliderComponent,
SettingTextComponent
} from "./components";
import { openContributorModal } from "./ContributorModal";
const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"]));
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
interface PluginModalProps extends ModalProps {
@ -46,11 +51,12 @@ interface PluginModalProps extends ModalProps {
onRestartNeeded(): void;
}
/** To stop discord making unwanted requests... */
function makeDummyUser(user: { name: string, id: BigInt; }) {
function makeDummyUser(user: { username: string; id?: string; avatar?: string; }) {
const newUser = new UserRecord({
username: user.name,
id: generateId(),
username: user.username,
id: user.id ?? generateId(),
avatar: user.avatar,
/** To stop discord making unwanted requests... */
bot: true,
});
FluxDispatcher.dispatch({
@ -60,6 +66,16 @@ function makeDummyUser(user: { name: string, id: BigInt; }) {
return newUser;
}
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
[OptionType.STRING]: SettingTextComponent,
[OptionType.NUMBER]: SettingNumericComponent,
[OptionType.BIGINT]: SettingNumericComponent,
[OptionType.BOOLEAN]: SettingBooleanComponent,
[OptionType.SELECT]: SettingSelectComponent,
[OptionType.SLIDER]: SettingSliderComponent,
[OptionType.COMPONENT]: SettingCustomComponent
};
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
const [authors, setAuthors] = React.useState<Partial<User>[]>([]);
@ -68,23 +84,39 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const [tempSettings, setTempSettings] = React.useState<Record<string, any>>({});
const [errors, setErrors] = React.useState<Record<string, boolean>>({});
const [saveError, setSaveError] = React.useState<string | null>(null);
const canSubmit = () => Object.values(errors).every(e => !e);
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
React.useEffect(() => {
(async () => {
for (const user of plugin.authors.slice(0, 6)) {
const author = user.id ? await UserUtils.fetchUser(`${user.id}`).catch(() => null) : makeDummyUser(user);
setAuthors(a => [...a, author || makeDummyUser(user)]);
const author = user.id
? await UserUtils.fetchUser(`${user.id}`)
.catch(() => makeDummyUser({ username: user.name }))
: makeDummyUser({ username: user.name });
setAuthors(a => [...a, author]);
}
})();
}, []);
function saveAndClose() {
async function saveAndClose() {
if (!plugin.options) {
onClose();
return;
}
if (plugin.beforeSave) {
const result = await Promise.resolve(plugin.beforeSave(tempSettings));
if (result !== true) {
setSaveError(result);
return;
}
}
let restartNeeded = false;
for (const [key, value] of Object.entries(tempSettings)) {
const option = plugin.options[key];
@ -97,46 +129,36 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
}
function renderSettings() {
if (!pluginSettings || !plugin.options) {
if (!hasSettings || !plugin.options) {
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
} else {
const options = Object.entries(plugin.options).map(([key, setting]) => {
if (setting.hidden) return null;
function onChange(newValue: any) {
setTempSettings(s => ({ ...s, [key]: newValue }));
}
function onError(hasError: boolean) {
setErrors(e => ({ ...e, [key]: hasError }));
}
const Component = Components[setting.type];
return (
<Component
id={key}
key={key}
option={setting}
onChange={onChange}
onError={onError}
pluginSettings={pluginSettings}
definedSettings={plugin.settings}
/>
);
});
return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>;
}
const options: JSX.Element[] = [];
for (const [key, setting] of Object.entries(plugin.options)) {
function onChange(newValue) {
setTempSettings(s => ({ ...s, [key]: newValue }));
}
function onError(hasError: boolean) {
setErrors(e => ({ ...e, [key]: hasError }));
}
const props = { onChange, pluginSettings, id: key, onError };
switch (setting.type) {
case OptionType.SELECT: {
options.push(<SettingSelectComponent key={key} option={setting} {...props} />);
break;
}
case OptionType.STRING: {
options.push(<SettingInputComponent key={key} option={setting} {...props} />);
break;
}
case OptionType.NUMBER:
case OptionType.BIGINT: {
options.push(<SettingNumericComponent key={key} option={setting} {...props} />);
break;
}
case OptionType.BOOLEAN: {
options.push(<SettingBooleanComponent key={key} option={setting} {...props} />);
break;
}
case OptionType.SLIDER: {
options.push(<SettingSliderComponent key={key} option={setting} {...props} />);
break;
}
}
}
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
}
function renderMoreUsers(_label: string, count: number) {
@ -160,15 +182,17 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
}
return (
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-md/bold">{plugin.name}</Text>
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
<ModalContent>
<Forms.FormSection>
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
<Forms.FormText>{plugin.description}</Forms.FormText>
<div style={{ marginTop: 8, marginBottom: 8, width: "fit-content" }}>
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
<div style={{ width: "fit-content", marginBottom: 8 }}>
<UserSummaryItem
users={authors}
count={plugin.authors.length}
@ -178,48 +202,65 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
showDefaultAvatarsForNullUsers
showUserPopout
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>
</Forms.FormSection>
{!!plugin.settingsAboutComponent && (
<div style={{ marginBottom: 8 }}>
<div className={classes(Margins.bottom8, "vc-text-selectable")}>
<Forms.FormSection>
<ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
<plugin.settingsAboutComponent />
<plugin.settingsAboutComponent tempSettings={tempSettings} />
</ErrorBoundary>
</Forms.FormSection>
</div>
)}
<Forms.FormSection>
<Forms.FormSection className={Margins.bottom16}>
<Forms.FormTitle tag="h3">Settings</Forms.FormTitle>
{renderSettings()}
</Forms.FormSection>
</ModalContent>
<ModalFooter>
<Flex>
<Button
onClick={onClose}
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
>
Exit Without Saving
</Button>
<Tooltip text="You must fix all errors before saving" shouldShow={!canSubmit()}>
{({ onMouseEnter, onMouseLeave }) => (
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.BRAND}
onClick={saveAndClose}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
disabled={!canSubmit()}
>
Save & Exit
</Button>
)}
</Tooltip>
{hasSettings && <ModalFooter>
<Flex flexDirection="column" style={{ width: "100%" }}>
<Flex style={{ marginLeft: "auto" }}>
<Button
onClick={onClose}
size={Button.Sizes.SMALL}
color={Button.Colors.WHITE}
look={Button.Looks.LINK}
>
Cancel
</Button>
<Tooltip text="You must fix all errors before saving" shouldShow={!canSubmit()}>
{({ onMouseEnter, onMouseLeave }) => (
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.BRAND}
onClick={saveAndClose}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
disabled={!canSubmit()}
>
Save & Close
</Button>
)}
</Tooltip>
</Flex>
{saveError && <Text variant="text-md/semibold" style={{ color: "var(--text-danger)" }}>Error while saving: {saveError}</Text>}
</Flex>
</ModalFooter>
</ModalFooter>}
</ModalRoot>
);
}

View File

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

View File

@ -16,14 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Guild } from "discord-types/general";
import { PluginOptionComponent } from "@utils/types";
import { ChannelStore, GuildStore,SelectedChannelStore } from "../webpack/common";
import { ISettingElementProps } from ".";
export function getCurrentChannel() {
return ChannelStore.getChannel(SelectedChannelStore.getChannelId());
}
export function getCurrentGuild(): Guild | undefined {
return GuildStore.getGuild(getCurrentChannel()?.guild_id);
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
return option.component({ setValue: onChange, setError: onError, option });
}

View File

@ -16,15 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Forms } from "@components";
import { OptionType, PluginOptionNumber } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common";
import { OptionType, PluginOptionNumber } from "../../../utils/types";
import { React, TextInput } from "../../../webpack/common";
import { ISettingElementProps } from ".";
const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER);
export function SettingNumericComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionNumber>) {
export function SettingNumericComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionNumber>) {
function serialize(value: any) {
if (option.type === OptionType.BIGINT) return BigInt(value);
return Number(value);
@ -38,10 +37,13 @@ export function SettingNumericComponent({ option, pluginSettings, id, onChange,
}, [error]);
function handleChange(newValue) {
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
setError(null);
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
setState(`${Number.MAX_SAFE_INTEGER}`);
onChange(serialize(newValue));
} else {
@ -59,7 +61,7 @@ export function SettingNumericComponent({ option, pluginSettings, id, onChange,
value={state}
onChange={handleChange}
placeholder={option.placeholder ?? "Enter a number"}
disabled={option.disabled?.() ?? false}
disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps}
/>
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}

View File

@ -16,14 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { FormSection, FormText, FormTitle } from "@components/Forms";
import Select from "@components/Select";
import { PluginOptionSelect } from "@utils/types";
import { Forms, React, Select } from "@webpack/common";
import { PluginOptionSelect } from "../../../utils/types";
import { React } from "../../../webpack/common";
import { ISettingElementProps } from ".";
export function SettingSelectComponent({ option, pluginSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) {
export function SettingSelectComponent({ option, pluginSettings, definedSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) {
const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value;
const [state, setState] = React.useState<any>(def ?? null);
@ -34,20 +32,21 @@ export function SettingSelectComponent({ option, pluginSettings, onChange, onErr
}, [error]);
function handleChange(newValue) {
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
setError(null);
setState(newValue);
onChange(newValue);
}
}
return (
<FormSection>
<FormTitle>{option.description}</FormTitle>
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Select
isDisabled={option.disabled?.() ?? false}
isDisabled={option.disabled?.call(definedSettings) ?? false}
options={option.options}
placeholder={option.placeholder ?? "Select an option"}
maxVisibleItems={5}
@ -57,7 +56,7 @@ export function SettingSelectComponent({ option, pluginSettings, onChange, onErr
serialize={v => String(v)}
{...option.componentProps}
/>
{error && <FormText style={{ color: "var(--text-danger)" }}>{error}</FormText>}
</FormSection>
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</Forms.FormSection>
);
}

View File

@ -16,10 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Forms } from "@components";
import { PluginOptionSlider } from "@utils/types";
import { Forms, React, Slider } from "@webpack/common";
import { PluginOptionSlider } from "../../../utils/types";
import { React, Slider } from "../../../webpack/common";
import { ISettingElementProps } from ".";
export function makeRange(start: number, end: number, step = 1) {
@ -30,7 +29,7 @@ export function makeRange(start: number, end: number, step = 1) {
return ranges;
}
export function SettingSliderComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionSlider>) {
export function SettingSliderComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionSlider>) {
const def = pluginSettings[id] ?? option.default;
const [error, setError] = React.useState<string | null>(null);
@ -40,7 +39,7 @@ export function SettingSliderComponent({ option, pluginSettings, id, onChange, o
}, [error]);
function handleChange(newValue: number): void {
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
@ -53,7 +52,7 @@ export function SettingSliderComponent({ option, pluginSettings, id, onChange, o
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Slider
disabled={option.disabled?.() ?? false}
disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers}
minValue={option.markers[0]}
maxValue={option.markers[option.markers.length - 1]}

View File

@ -16,13 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Forms } from "@components";
import { PluginOptionString } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common";
import { PluginOptionString } from "../../../utils/types";
import { React, TextInput } from "../../../webpack/common";
import { ISettingElementProps } from ".";
export function SettingInputComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) {
export function SettingTextComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) {
const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null);
const [error, setError] = React.useState<string | null>(null);
@ -31,13 +30,13 @@ export function SettingInputComponent({ option, pluginSettings, id, onChange, on
}, [error]);
function handleChange(newValue) {
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
setState(newValue);
onChange(newValue);
}
else setError(null);
setState(newValue);
onChange(newValue);
}
return (
@ -48,7 +47,7 @@ export function SettingInputComponent({ option, pluginSettings, id, onChange, on
value={state}
onChange={handleChange}
placeholder={option.placeholder ?? "Enter a value"}
disabled={option.disabled?.() ?? false}
disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps}
/>
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { PluginOptionBase } from "../../../utils/types";
import { DefinedSettings, PluginOptionBase } from "@utils/types";
export interface ISettingElementProps<T extends PluginOptionBase> {
option: T;
@ -27,10 +27,14 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
};
id: string;
onError(hasError: boolean): void;
definedSettings?: DefinedSettings;
}
export * from "../../Badge";
export * from "./SettingBooleanComponent";
export * from "./SettingCustomComponent";
export * from "./SettingNumericComponent";
export * from "./SettingSelectComponent";
export * from "./SettingSliderComponent";
export * from "./SettingTextComponent";

View File

@ -0,0 +1,57 @@
.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;
}
.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;
}

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