Compare commits
744 commits
main
...
immediate-
Author | SHA1 | Date | |
---|---|---|---|
|
7fcd9c3f06 | ||
|
8bfacc139a | ||
|
a9a8c338a6 | ||
|
262319a97c | ||
|
0379beb0ba | ||
|
13ada86934 | ||
|
fae5f9bfab | ||
|
7d4fc5e8e1 | ||
|
26a71caced | ||
|
b1ad0a0d58 | ||
|
4a9f258e34 | ||
|
301b0e7dfc | ||
|
2f806dde95 | ||
|
30ef53403b | ||
|
ade9a523ac | ||
|
464dddff13 | ||
|
e8965866e6 | ||
|
e3d9b2001f | ||
|
e4bc7f2add | ||
|
edc87ac75e | ||
|
5c7773c7bb | ||
|
3546632bbf | ||
|
9a48454a9f | ||
|
0e2c0b9bfb | ||
|
84c78a4262 | ||
|
cfbe4affc0 | ||
|
e8f017b15e | ||
|
22a81ab142 | ||
|
93d450a600 | ||
|
50bb6d867b | ||
|
6cc2f53501 | ||
|
f9776ce1ae | ||
|
18c5ca216c | ||
|
3f0d915c75 | ||
|
a8f77a12df | ||
|
44785d3498 | ||
|
473109b1d9 | ||
|
f398a1c871 | ||
|
9871e16e28 | ||
|
b22d4622ed | ||
|
f05d462a73 | ||
|
c183018fb1 | ||
|
f9c11a1149 | ||
|
45a876a0d5 | ||
|
4d88a13f5e | ||
|
eeb4d26812 | ||
|
2429c283aa | ||
|
1f1e3e8456 | ||
|
64e1f294e6 | ||
|
e801c71fdf | ||
|
4677ac779a | ||
|
ed32b7964d | ||
|
76f8b0e38e | ||
|
e24bdc2b6f | ||
|
4bcefb744d | ||
|
4d03a695aa | ||
|
ca3768ca5d | ||
|
6bdd0ba4a9 | ||
|
633a84581d | ||
|
4a590d3d0a | ||
|
1905aa0ae8 | ||
|
026e716710 | ||
|
9e6f24cc92 | ||
|
75a8098590 | ||
|
1f20074d4e | ||
|
fb394c6e1a | ||
|
465dd15e2c | ||
|
f676613173 | ||
|
be6209ee44 | ||
|
15a3694533 | ||
|
9fd16825e2 | ||
|
129b770ffa | ||
|
42353373db | ||
|
5f7c44f478 | ||
|
2df13a2e17 | ||
|
dc116fc3b1 | ||
|
cd3534f6fc | ||
|
01bd41413d | ||
|
654a271eae | ||
|
efd48e0364 | ||
|
09447d562e | ||
|
e57c99399d | ||
|
a3612dd4ee | ||
|
e9328cadea | ||
|
8fa19656ae | ||
|
d58d8aea71 | ||
|
5462bb1438 | ||
|
9dadde6884 | ||
|
81a1822e70 | ||
|
974953ba01 | ||
|
27fd143318 | ||
|
3bc71c688a | ||
|
d869b2ddbb | ||
|
fb3ae98419 | ||
|
70df451e64 | ||
|
2fb616ebf1 | ||
|
bd25e0cd75 | ||
|
62d4add6c1 | ||
|
3f5491d379 | ||
|
e35d0f680f | ||
|
187ece1905 | ||
|
2b99f4ece3 | ||
|
8bf98be472 | ||
|
0506505449 | ||
|
3edc36d5e0 | ||
|
6ffa969955 | ||
|
6213fca9ed | ||
|
56477d4e96 | ||
|
104f193a76 | ||
|
293b90a6d2 | ||
|
8159937827 | ||
|
aa35602827 | ||
|
42c4845261 | ||
|
710f627016 | ||
|
4389b78410 | ||
|
fde823fabf | ||
|
cce8f14d72 | ||
|
c8356570a3 | ||
|
021baa54d3 | ||
|
ddd5d9e0c3 | ||
|
920fb20d8e | ||
|
438b3e0598 | ||
|
763b24aa92 | ||
|
65013f0149 | ||
|
24795ba9f8 | ||
|
aa29e479b9 | ||
|
dd25044afd | ||
|
8c1979481e | ||
|
2a1684d902 | ||
|
b377cad1e6 | ||
|
4e09e347e2 | ||
|
ea6fccb54e | ||
|
c784bc5dbd | ||
|
edba931afe | ||
|
208882e8ec | ||
|
e0cb7242d6 | ||
|
b3ab32180c | ||
|
2506ba1b2e | ||
|
0d0c63f596 | ||
|
8abb68c553 | ||
|
f6147c6c13 | ||
|
b5fdb43cbf | ||
|
30057985c4 | ||
|
a95037d80a | ||
|
99e21ad284 | ||
|
d90bcdb063 | ||
|
771d51bcc3 | ||
|
c7e49672f5 | ||
|
3331ed2155 | ||
|
bc34d72b57 | ||
|
b333deb731 | ||
|
614cbc1146 | ||
|
a985286c88 | ||
|
1297408171 | ||
|
cc536bdbf4 | ||
|
ea859f59b0 | ||
|
a04ccd0d11 | ||
|
c1b4eb5b04 | ||
|
3f059e17be | ||
|
48442aee1d | ||
|
9ca13d1250 | ||
|
2c3be4533e | ||
|
7bf2152353 | ||
|
a6f4622837 | ||
|
2f55c7b126 | ||
|
14d1a2d1ea | ||
|
332f3e532b | ||
|
28a7fbb309 | ||
|
4caf86b60e | ||
|
5a85d6e78e | ||
|
20cfa932a4 | ||
|
9e998db456 | ||
|
3d4a7638a4 | ||
|
ab8da19e23 | ||
|
b8335108e4 | ||
|
403276d307 | ||
|
748a456cfb | ||
|
0cd0c813f2 | ||
|
f647ff0ee9 | ||
|
c2d708a621 | ||
|
35343bd790 | ||
|
cc5d4a7b3a | ||
|
ae655111de | ||
|
b768c61ef8 | ||
|
d95367b31d | ||
|
1e4ad4db4c | ||
|
d8ba959d1e | ||
|
0742cecd41 | ||
|
ed4a797ad3 | ||
|
505246954b | ||
|
eecc21db91 | ||
|
962eaa9df7 | ||
|
0190ada1a1 | ||
|
b5a1b3a124 | ||
|
1f3b8cdfd8 | ||
|
1f2c26cdba | ||
|
3b6bef1cda | ||
|
858b0e3dd7 | ||
|
718ebd56ac | ||
|
1511260666 | ||
|
4ff7c22505 | ||
|
057ccfde11 | ||
|
edf4e75ae2 | ||
|
62d9d1310d | ||
|
e592a50d7c | ||
|
c785053b86 | ||
|
e9081d2a52 | ||
|
98c67e1165 | ||
|
6c7d4259e1 | ||
|
d8e3381fb3 | ||
|
bdb9c75c74 | ||
|
a67b7847b1 | ||
|
bab081d4a9 | ||
|
0e5099416c | ||
|
20efa661b0 | ||
|
94ba593be9 | ||
|
c9295d3a85 | ||
|
d55770797a | ||
|
b4b67a98da | ||
|
453c0b62f4 | ||
|
a3fa74735f | ||
|
a3cab94a68 | ||
|
c4e7233604 | ||
|
889eb311f9 | ||
|
03120d5a98 | ||
|
9446efadd5 | ||
|
9c21c23364 | ||
|
ba6d3c7e60 | ||
|
cb1fff8d86 | ||
|
c400db41c3 | ||
|
de4743828a | ||
|
700a442ee4 | ||
|
dd3904badd | ||
|
0d360fb7e7 | ||
|
2429231965 | ||
|
570137040c | ||
|
ad3bb91bc1 | ||
|
161be30b2b | ||
|
0a4bcd5ccd | ||
|
3a5859b815 | ||
|
1ea5f56549 | ||
|
a7b02f2f1f | ||
|
32306cc6c3 | ||
|
8cfb289dc7 | ||
|
220062acdb | ||
|
e5d5bc2d34 | ||
|
17cc81f15d | ||
|
eba42489e9 | ||
|
51dee70ced | ||
|
11561e50f9 | ||
|
2095cc8d03 | ||
|
6e61ef297c | ||
|
58e810dfbe | ||
|
b2497373f0 | ||
|
18841f4551 | ||
|
da6ceb8479 | ||
|
6b811dce55 | ||
|
8982a53b47 | ||
|
3cf702a2ba | ||
|
cd79150a84 | ||
|
343e2802c3 | ||
|
dc8b579488 | ||
|
066001c57a | ||
|
24a0f62801 | ||
|
ef512e6ea1 | ||
|
c6b2eeb86d | ||
|
e457e58b49 | ||
|
cac0398276 | ||
|
e93562b95b | ||
|
f6dbcb7709 | ||
|
a82a0636cf | ||
|
2fc814124f | ||
|
a43d5d595a | ||
|
ceaaf9ab8a | ||
|
a01ee40591 | ||
|
26c2674652 | ||
|
9595f75a9a | ||
|
2189ce407b | ||
|
7b4f97fa08 | ||
|
f208472841 | ||
|
4e4a9f806f | ||
|
afe6d9f7b3 | ||
|
2dac66d7c6 | ||
|
83b09a8073 | ||
|
13d47d91bf | ||
|
004f764c2b | ||
|
a815122e3c | ||
|
b88e7faa72 | ||
|
bb22355a57 | ||
|
591f433135 | ||
|
f726376ab8 | ||
|
74bd89b721 | ||
|
aa4c6e5496 | ||
|
e8d696c18c | ||
|
d473d52047 | ||
|
36c8058920 | ||
|
8d31f3cf62 | ||
|
5021d2e344 | ||
|
92653eaf96 | ||
|
2f6e39c0bd | ||
|
5e52ecf42e | ||
|
b2ae33344b | ||
|
b24e8cb9e6 | ||
|
73b1c7b811 | ||
|
210fada19a | ||
|
002a1cfc16 | ||
|
e9e25306eb | ||
|
5931a72de2 | ||
|
3d0ce5449d | ||
|
31c39bc761 | ||
|
1d2b8a43c0 | ||
|
6d587e6530 | ||
|
853585c0bc | ||
|
6826fe003c | ||
|
c3bbc92fa2 | ||
|
ed35c496dc | ||
|
dc8ac98a6e | ||
|
337850c33a | ||
|
08f1b6a3c9 | ||
|
c8ad68c80b | ||
|
7b056364da | ||
|
7f3cad2528 | ||
|
5d290ecd35 | ||
|
b702b31744 | ||
|
3dbe621059 | ||
|
55195676b8 | ||
|
76cb49c381 | ||
|
95c4cc09af | ||
|
a5b30e762a | ||
|
5bbf467be0 | ||
|
46c771f351 | ||
|
94ea4f8e5c | ||
|
fc8a0e4904 | ||
|
9eb77cb7fd | ||
|
e1bbd67a9a | ||
|
625b248a95 | ||
|
94c365b1c3 | ||
|
f70e9290ce | ||
|
57b011aea7 | ||
|
c8097f0f1a | ||
|
5999af76c6 | ||
|
05717d6bc3 | ||
|
53dd86fa6e | ||
|
363f2e84fb | ||
|
066ce07512 | ||
|
f5b1b65db8 | ||
|
6e6ee4db68 | ||
|
ac1c417e1c | ||
|
4e14232b5a | ||
|
eddfca06ab | ||
|
61e1eada01 | ||
|
85322f9e90 | ||
|
d329d74fec | ||
|
ac6a21374f | ||
|
b385be51d6 | ||
|
d8524b087c | ||
|
c491122937 | ||
|
13337950dc | ||
|
281ab9194b | ||
|
a1881bff3d | ||
|
c50208b019 | ||
|
a8974f01eb | ||
|
b79a0dadcb | ||
|
c4c92ed366 | ||
|
78fd37a4c6 | ||
|
9de6e67cf1 | ||
|
33d5753ca7 | ||
|
8d3feef9a1 | ||
|
8580332c82 | ||
|
4d8c56689c | ||
|
5cbb816c1c | ||
|
b6594051d9 | ||
|
50415a949d | ||
|
4b03454bce | ||
|
f1b649e996 | ||
|
db5397f41a | ||
|
c34f40e30d | ||
|
dd4d80872e | ||
|
eb381767cd | ||
|
0aff3c29bc | ||
|
e7fdb3881a | ||
|
5977ff8f6b | ||
|
dd13b96728 | ||
|
01fdeb7292 | ||
|
6a3831c437 | ||
|
c485864ee8 | ||
|
2ab7d3209e | ||
|
654b2e8398 | ||
|
3f573c9af3 | ||
|
cbd9a9312c | ||
|
2656b60347 | ||
|
fbb7ee50dd | ||
|
4187932aa7 | ||
|
a19fb71c70 | ||
|
e21a6be4b6 | ||
|
b53c78c2ba | ||
|
db63fffe18 | ||
|
41ab8da5ad | ||
|
f68effc7e2 | ||
|
f21bfb9d78 | ||
|
f4cc95da5a | ||
|
3a32490937 | ||
|
545d69aa6d | ||
|
aa5ca42676 | ||
|
5197170b3d | ||
|
47eec049fc | ||
|
022a0497c3 | ||
|
e2f936538e | ||
|
b323fbb486 | ||
|
0c4e265530 | ||
|
111a4f2aa8 | ||
|
46710bf8cb | ||
|
1f938211a8 | ||
|
8d362fc394 | ||
|
79849fd37d | ||
|
a838d587ae | ||
|
3a25da5f14 | ||
|
a1e72e78d7 | ||
|
48fb6ecd1a | ||
|
3c62e393a3 | ||
|
034ef554fb | ||
|
2da9bf8f0e | ||
|
eb5fb0df29 | ||
|
479a4069b2 | ||
|
e22575805b | ||
|
c0984db612 | ||
|
9a9c1b0487 | ||
|
072619e539 | ||
|
9084459933 | ||
|
36f9eda3a8 | ||
|
99160638df | ||
|
2574d5311d | ||
|
7816727e26 | ||
|
892de53603 | ||
|
8de1ca86a9 | ||
|
01ba87cd76 | ||
|
83222631ab | ||
|
b74581908e | ||
|
896b8970b6 | ||
|
c8602ef52b | ||
|
ac1423f90c | ||
|
d0e4ec0ad5 | ||
|
7509f203cc | ||
|
17b575070f | ||
|
4a872236e1 | ||
|
d5440349c9 | ||
|
0f9b5f2d09 | ||
|
4db6c4b88a | ||
|
6e1094a5d1 | ||
|
15df3485d1 | ||
|
ea86df7174 | ||
|
66c466c9f0 | ||
|
96d5e1b89b | ||
|
4b4caa798a | ||
|
73503945fb | ||
|
43b679d676 | ||
|
011b375d02 | ||
|
9f1f2b15a4 | ||
|
a1309ea897 | ||
|
a4073703fd | ||
|
84b9e3fec1 | ||
|
6bbd91fafc | ||
|
d78f1c8000 | ||
|
64262001e8 | ||
|
6b648f3d38 | ||
|
1eeadbcd97 | ||
|
f016d277b7 | ||
|
9c2545ab16 | ||
|
b36d3b4385 | ||
|
acbc932542 | ||
|
3590d5abff | ||
|
c9c09b95a3 | ||
|
9128eb5760 | ||
|
513d9f4209 | ||
|
06393211b1 | ||
|
ac3130d37e | ||
|
3e2952890f | ||
|
a0751a0f84 | ||
|
fa45beb8ca | ||
|
539b70cd52 | ||
|
e96458fafa | ||
|
a1542bcec5 | ||
|
e00da485e8 | ||
|
513eea8d14 | ||
|
490e9131b5 | ||
|
9ceb0ae7cb | ||
|
6d59a943da | ||
|
00d71fbeab | ||
|
4459d11a97 | ||
|
1eab0146dd | ||
|
f345484e5b | ||
|
65adaf335c | ||
|
437e4eae44 | ||
|
f7d12d209c | ||
|
8a5678bec5 | ||
|
1f99162b5a | ||
|
8c5f2c897e | ||
|
9ada9bc1a9 | ||
|
8fd22f6deb | ||
|
c1e78b4397 | ||
|
32a2c90761 | ||
|
dae3841f10 | ||
|
4d27643d39 | ||
|
c2047e5f3b | ||
|
18142ecccb | ||
|
440cb1f29c | ||
|
64b1ab1112 | ||
|
3da88100df | ||
|
3e3201ad0d | ||
|
a2bda05211 | ||
|
1ed956114f | ||
|
2e27a513ac | ||
|
a8fa685cfa | ||
|
b2036762b5 | ||
|
067a16c9dc | ||
|
265cf12d39 | ||
|
b580d6dcfb | ||
|
affd527bc1 | ||
|
7d928e53aa | ||
|
b10afebc98 | ||
|
1a8a901e0a | ||
|
d936604f35 | ||
|
4dcc265be0 | ||
|
3e6e399544 | ||
|
9af63b362d | ||
|
090a3bc0a8 | ||
|
3331723496 | ||
|
999d6c812f | ||
|
c9c1e0c6c5 | ||
|
1e55ae5327 | ||
|
5b96881177 | ||
|
daa1a35fc3 | ||
|
ef895e787a | ||
|
2f16cdf77b | ||
|
e990795783 | ||
|
8dde496757 | ||
|
35f950c6e3 | ||
|
5fbabb0b70 | ||
|
ff9cc2e9e5 | ||
|
3793acd9bb | ||
|
ebae259f5d | ||
|
3296ee1c4b | ||
|
2f87363b89 | ||
|
bacf021a28 | ||
|
1138e22f58 | ||
|
d6b5bc58ce | ||
|
bd2264c125 | ||
|
86f69e84c1 | ||
|
488f133a90 | ||
|
77d8e70059 | ||
|
f64b228cb9 | ||
|
90f37471e5 | ||
|
b192c89146 | ||
|
ae10d3667c | ||
|
f727d101fc | ||
|
9bbec66ea5 | ||
|
0146789514 | ||
|
b38ab066d9 | ||
|
37514e0720 | ||
|
aa9be97f83 | ||
|
d8c6559967 | ||
|
72b014fe67 | ||
|
8eb4eab2b0 | ||
|
07361ff6fe | ||
|
64212512ca | ||
|
5e762ddd04 | ||
|
74f36a093b | ||
|
c01a8c2f78 | ||
|
90c26432f5 | ||
|
6f25005057 | ||
|
23c8818ed2 | ||
|
16d05c8935 | ||
|
c5541d297d | ||
|
a120b35f4f | ||
|
9740d28530 | ||
|
8e9434cdd5 | ||
|
f5be78d101 | ||
|
bd95cc449f | ||
|
bdd4b0f143 | ||
|
a60c8374ed | ||
|
1013c34840 | ||
|
7b7b87316d | ||
|
6567ff7435 | ||
|
22a75c4589 | ||
|
67b5ecca51 | ||
|
cdbd9ad101 | ||
|
1e96638219 | ||
|
7371abbaec | ||
|
cfb493c593 | ||
|
ac61a0377f | ||
|
bb3f629672 | ||
|
3ab68f9296 | ||
|
01a4ac9c13 | ||
|
66e1db1fdb | ||
|
688449ed62 | ||
|
5ca4e58fad | ||
|
deb0f7b9c6 | ||
|
40a1f48267 | ||
|
bc367b1d2a | ||
|
f6a7cdc430 | ||
|
d5bcbf54f9 | ||
|
4b2734610f | ||
|
c4645f79c6 | ||
|
cf9bbfc78f | ||
|
6cc40eb095 | ||
|
95acb147d1 | ||
|
21fea87551 | ||
|
2a2124c5a0 | ||
|
1a1e5c2e1d | ||
|
c9d9d53fb1 | ||
|
dc81d1c14c | ||
|
b938bdf138 | ||
|
e74aabb988 | ||
|
ab24ce08e3 | ||
|
c2435bb9dd | ||
|
d678e68899 | ||
|
dbb6f4165b | ||
|
9a1a7b374f | ||
|
ec373069b3 | ||
|
329bc68bf5 | ||
|
2a77941dff | ||
|
d36c361b47 | ||
|
b17963169f | ||
|
118361723c | ||
|
97e2e2c1c8 | ||
|
dbee2d9ecc | ||
|
1283494b46 | ||
|
e72c732d58 | ||
|
d40dfc345f | ||
|
70f60193b0 | ||
|
9e60d4979d | ||
|
3562c8ae11 | ||
|
093de43d54 | ||
|
86ad3bd4fc | ||
|
8045d65e99 | ||
|
63a1fe2c70 | ||
|
55760bc42b | ||
|
983013446d | ||
|
569dace244 | ||
|
8e5a2f6e59 | ||
|
41a1732df2 | ||
|
487af1c789 | ||
|
5168f326a3 | ||
|
4feaa40de7 | ||
|
7f5aaada88 | ||
|
725103a77c | ||
|
a878ea0b39 | ||
|
0cc89996c6 | ||
|
c72099fc39 | ||
|
84c223d471 | ||
|
82f9e36e5a | ||
|
7c8c2ff5b8 | ||
|
dc73b89b29 | ||
|
2596bf0990 | ||
|
7169dfe61a | ||
|
5ba5eca1a1 | ||
|
4209bfd3aa | ||
|
c007fc7478 | ||
|
903fa2cf11 | ||
|
7d91dea822 | ||
|
4fc4f1cb75 | ||
|
45d2cf49e0 | ||
|
b798f31669 | ||
|
7e1524362c | ||
|
e04dd85957 | ||
|
258e1efdce | ||
|
f6e484034c | ||
|
5676f45781 | ||
|
e11d651e33 | ||
|
09a6a9bf85 | ||
|
1b64419ef1 | ||
|
5edff4a3c5 | ||
|
b043d8abb7 | ||
|
e15868d499 | ||
|
a892a507be | ||
|
33a5f30ae4 | ||
|
38b6c7fa6c | ||
|
bd88625d25 | ||
|
5fca61f581 | ||
|
cc6cef02c1 | ||
|
546c5105b2 | ||
|
f65749e2d4 | ||
|
fb8f00137c | ||
|
20b071aa2d | ||
|
b7c1b495af | ||
|
4d80c53dd0 | ||
|
035e2add0b | ||
|
f70cc5205c | ||
|
fa948e8639 | ||
|
4cc6789b5b | ||
|
4753efa33c | ||
|
283a4b23ba | ||
|
3b7f1be9a2 | ||
|
c2e12e2827 | ||
|
8b6620ca2f | ||
|
f1e2d5572a | ||
|
49fcfd74df | ||
|
511ec1b129 | ||
|
94843a77ac | ||
|
8873391496 | ||
|
b77b654df1 | ||
|
c65e1f88d2 | ||
|
9cd20e3cea | ||
|
f04bd8a7ad | ||
|
fa82939e62 | ||
|
a1fefcf27a | ||
|
65b6440024 | ||
|
6e0064c5fc | ||
|
b047f9ce28 | ||
|
d97596a16a | ||
|
5b6d0f42fd | ||
|
4a89fadedd | ||
|
886071af4d | ||
|
12c9c324a1 | ||
|
e311bd9f76 | ||
|
707c23b188 | ||
|
49e78793c5 | ||
|
8d31330d3e | ||
|
17397dbe08 | ||
|
b98544ed1f | ||
|
043675f3f8 | ||
|
3b1cb02663 | ||
|
bc68bc0312 | ||
|
b3819228ed | ||
|
c23c65733b | ||
|
cfefdbc7a7 | ||
|
70103e8111 | ||
|
240705652c | ||
|
d42c9547ab | ||
|
ac85b3508b | ||
|
9ece4dddd3 | ||
|
36f5d9acc6 | ||
|
d4956f3ec7 | ||
|
8279c8d0ba | ||
|
74e3ba42e0 | ||
|
784ae0a02d | ||
|
901d803ba9 | ||
|
bdb693302a | ||
|
f8a7d1d634 | ||
|
770078f236 | ||
|
f1dd39885b | ||
|
a29115ba78 | ||
|
cd5e0aeec1 | ||
|
75fa0ff708 |
384 changed files with 9506 additions and 10718 deletions
42
.github/ISSUE_TEMPLATE/blank.yml
vendored
42
.github/ISSUE_TEMPLATE/blank.yml
vendored
|
@ -2,24 +2,30 @@ name: Blank Issue
|
|||
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||

|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# READ THIS BEFORE OPENING AN ISSUE
|
||||
|
||||
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||
|
||||
DO NOT USE THIS FORM, unless
|
||||
- you are a vencord contributor
|
||||
- you were given explicit permission to use this form by a moderator in our support server
|
||||
|
||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
options:
|
||||
- label: I have read the requirements for opening an issue above
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
options:
|
||||
- label: I have read the requirements for opening an issue above
|
||||
required: true
|
||||
|
|
127
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
127
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -4,63 +4,78 @@ labels: [bug]
|
|||
title: "[Bug] <title>"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||

|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# READ THIS BEFORE OPENING AN ISSUE
|
||||
|
||||
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||
|
||||
DO NOT USE THIS FORM, unless
|
||||
- you are a vencord contributor
|
||||
- you were given explicit permission to use this form by a moderator in our support server
|
||||
|
||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||
|
||||
- type: input
|
||||
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:
|
||||
- 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
|
||||
|
||||
- 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:
|
||||
- label: I have read the requirements for opening an issue above
|
||||
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
|
||||
- label: I am a Vencord Developer
|
||||
required: true
|
||||
|
|
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
|||
|
||||
- name: Clean up obsolete files
|
||||
run: |
|
||||
rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||
rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||
|
||||
- name: Get some values needed for the release
|
||||
id: release_values
|
||||
|
|
70
.github/workflows/reportBrokenPlugins.yml
vendored
70
.github/workflows/reportBrokenPlugins.yml
vendored
|
@ -1,22 +1,9 @@
|
|||
name: Test Patches
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
discord_branch:
|
||||
type: choice
|
||||
description: "Discord Branch to test patches on"
|
||||
options:
|
||||
- both
|
||||
- stable
|
||||
- canary
|
||||
default: both
|
||||
webhook_url:
|
||||
type: string
|
||||
description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot."
|
||||
required: false
|
||||
# schedule:
|
||||
# # Every day at midnight
|
||||
# - cron: 0 0 * * *
|
||||
schedule:
|
||||
# Every day at midnight
|
||||
- cron: 0 0 * * *
|
||||
|
||||
jobs:
|
||||
TestPlugins:
|
||||
|
@ -53,43 +40,28 @@ jobs:
|
|||
- name: Build Vencord Reporter Version
|
||||
run: pnpm buildReporter
|
||||
|
||||
- name: Run Reporter
|
||||
- name: Create Report
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
|
||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||
|
||||
stable_output_file=$(mktemp)
|
||||
canary_output_file=$(mktemp)
|
||||
|
||||
pids=""
|
||||
|
||||
branch="${{ inputs.discord_branch }}"
|
||||
if [[ "${{ github.event_name }}" = "schedule" ]]; then
|
||||
branch="both"
|
||||
fi
|
||||
|
||||
if [[ "$branch" = "both" || "$branch" = "stable" ]]; then
|
||||
node dist/report.mjs > "$stable_output_file" &
|
||||
pids+=" $!"
|
||||
fi
|
||||
|
||||
if [[ "$branch" = "both" || "$branch" = "canary" ]]; then
|
||||
USE_CANARY=true node dist/report.mjs > "$canary_output_file" &
|
||||
pids+=" $!"
|
||||
fi
|
||||
|
||||
exit_code=0
|
||||
for pid in $pids; do
|
||||
if ! wait "$pid"; then
|
||||
exit_code=1
|
||||
fi
|
||||
done
|
||||
|
||||
cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY
|
||||
exit $exit_code
|
||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }}
|
||||
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
|
||||
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=${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
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 }}
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -8,7 +8,6 @@ vencord_installer
|
|||
.DS_Store
|
||||
|
||||
yarn.lock
|
||||
bun.lock
|
||||
package-lock.json
|
||||
|
||||
*.log
|
||||
|
@ -19,5 +18,7 @@ lerna-debug.log*
|
|||
.pnpm-debug.log*
|
||||
*.tsbuildinfo
|
||||
|
||||
src/userplugins
|
||||
|
||||
ExtensionCache/
|
||||
settings/
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "src/userplugins/vc-message-logger-enhanced"]
|
||||
path = src/userplugins/vc-message-logger-enhanced
|
||||
url = https://github.com/Syncxv/vc-message-logger-enhanced.git
|
|
@ -31,7 +31,6 @@ Before starting your plugin:
|
|||
- No FakeDeafen or FakeMute
|
||||
- No StereoMic
|
||||
- No plugins that simply hide or redesign ui elements. This can be done with CSS
|
||||
- No plugins that interact with specific Discord bots (official Discord apps like Youtube WatchTogether are okay)
|
||||
- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc)
|
||||
- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones
|
||||
- No plugins that require the user to enter their own API key
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["dist/*", "vendor/*"],
|
||||
"resources": ["dist/*", "third-party/*"],
|
||||
"matches": ["*://*.discord.com/*"]
|
||||
}
|
||||
],
|
||||
|
|
|
@ -15,7 +15,7 @@ declare global {
|
|||
const getTheme: () => string;
|
||||
}
|
||||
|
||||
const BASE = "/vendor/monaco/vs";
|
||||
const BASE = "/dist/monaco/vs";
|
||||
|
||||
self.MonacoEnvironment = {
|
||||
getWorkerUrl(_moduleId: unknown, label: string) {
|
||||
|
|
|
@ -24,12 +24,12 @@
|
|||
|
||||
<script>
|
||||
const script = document.createElement("script");
|
||||
script.src = new URL("/vendor/monaco/index.js", baseUrl);
|
||||
script.src = new URL("/dist/monaco/index.js", baseUrl);
|
||||
|
||||
const style = document.createElement("link");
|
||||
style.type = "text/css";
|
||||
style.rel = "stylesheet";
|
||||
style.href = new URL("/vendor/monaco/index.css", baseUrl);
|
||||
style.href = new URL("/dist/monaco/index.css", baseUrl);
|
||||
|
||||
document.body.append(style, script);
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// @author Vendicated (https://github.com/Vendicated)
|
||||
// @namespace https://github.com/Vendicated/Vencord
|
||||
// @supportURL https://github.com/Vendicated/Vencord
|
||||
// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png
|
||||
// @license GPL-3.0
|
||||
// @match *://*.discord.com/*
|
||||
// @grant GM_xmlhttpRequest
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import pathAlias from "eslint-plugin-path-alias";
|
||||
import react from "eslint-plugin-react";
|
||||
import header from "eslint-plugin-simple-header";
|
||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
|
@ -14,22 +15,6 @@ import tseslint from "typescript-eslint";
|
|||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||
settings: {
|
||||
react: {
|
||||
version: "18"
|
||||
}
|
||||
},
|
||||
...react.configs.flat.recommended,
|
||||
rules: {
|
||||
...react.configs.flat.recommended.rules,
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||
plugins: {
|
||||
|
@ -38,7 +23,7 @@ export default tseslint.config(
|
|||
"@typescript-eslint": tseslint.plugin,
|
||||
"simple-import-sort": simpleImportSort,
|
||||
"unused-imports": unusedImports,
|
||||
"path-alias": pathAlias
|
||||
"path-alias": pathAlias,
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
|
@ -134,7 +119,7 @@ export default tseslint.config(
|
|||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": ["error", { destructuring: "all" }],
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
|
||||
// Plugin Rules
|
||||
|
|
82
package.json
82
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.11.5",
|
||||
"version": "1.9.8",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -24,65 +24,63 @@
|
|||
"dev": "pnpm watch",
|
||||
"watchWeb": "pnpm buildWeb --watch",
|
||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false",
|
||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||
"inject": "node scripts/runInstaller.mjs",
|
||||
"uninject": "node scripts/runInstaller.mjs",
|
||||
"lint": "eslint",
|
||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"test": "pnpm buildStandalone && pnpm testTsc && pnpm lint && pnpm lint-styles && pnpm generatePluginJson",
|
||||
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||
"testTsc": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intrnl/xxhash64": "^0.1.2",
|
||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||
"@vap/core": "0.0.12",
|
||||
"@vap/shiki": "0.10.5",
|
||||
"fflate": "^0.8.2",
|
||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"nanoid": "^5.0.9",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "^4.0.0",
|
||||
"@types/chrome": "^0.0.304",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@stylistic/eslint-plugin": "^2.6.1",
|
||||
"@types/chrome": "^0.0.269",
|
||||
"@types/diff": "^5.2.1",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^22.0.3",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/yazl": "^2.4.5",
|
||||
"diff": "^7.0.0",
|
||||
"diff": "^5.2.0",
|
||||
"discord-types": "^1.3.26",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.20.1",
|
||||
"esbuild": "^0.15.18",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-path-alias": "2.1.0",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-simple-header": "^1.2.1",
|
||||
"eslint-plugin-simple-header": "^1.1.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"highlight.js": "11.11.1",
|
||||
"eslint-plugin-unused-imports": "^4.0.1",
|
||||
"highlight.js": "10.7.3",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"moment": "^2.22.2",
|
||||
"puppeteer-core": "^24.2.1",
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"stylelint": "^16.12.0",
|
||||
"stylelint-config-standard": "^37.0.0",
|
||||
"ts-patch": "^3.3.0",
|
||||
"ts-pattern": "^5.6.0",
|
||||
"tsx": "^4.19.2",
|
||||
"type-fest": "^4.31.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.19.0",
|
||||
"typescript-transform-paths": "^3.5.3",
|
||||
"moment": "^2.30.1",
|
||||
"puppeteer-core": "^22.15.0",
|
||||
"standalone-electron-types": "^1.0.0",
|
||||
"stylelint": "^16.8.1",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"ts-patch": "^3.2.1",
|
||||
"tsx": "^4.16.5",
|
||||
"type-fest": "^4.23.0",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"typescript-transform-paths": "^3.4.7",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"packageManager": "pnpm@9.1.0",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint@9.20.1": "patches/eslint@9.20.1.patch",
|
||||
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
|
||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
|
@ -95,14 +93,18 @@
|
|||
"source-map-resolve": "*",
|
||||
"resolve-url": "*",
|
||||
"source-map-url": "*",
|
||||
"urix": "*",
|
||||
"q": "*"
|
||||
"urix": "*"
|
||||
}
|
||||
},
|
||||
"webExt": {
|
||||
"artifactsDir": "./dist",
|
||||
"build": {
|
||||
"overwriteDest": true
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
"sourceDir": "./dist/firefox-unpacked"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=18",
|
||||
"pnpm": ">=9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@vencord/types",
|
||||
"private": false,
|
||||
"version": "1.11.5",
|
||||
"version": "0.1.3",
|
||||
"description": "",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
|
@ -13,16 +13,16 @@
|
|||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"fs-extra": "^11.3.0",
|
||||
"tsx": "^4.19.2"
|
||||
"fs-extra": "^11.2.0",
|
||||
"tsx": "^3.12.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "4.17.15",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/react": "18.3.1",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"discord-types": "^1.3.26",
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"type-fest": "^4.35.0"
|
||||
"standalone-electron-types": "^1.0.0",
|
||||
"type-fest": "^3.5.3"
|
||||
}
|
||||
}
|
||||
|
|
4025
pnpm-lock.yaml
4025
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -17,41 +17,38 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import esbuild from "esbuild";
|
||||
import { readdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs";
|
||||
|
||||
const defines = stringifyValues({
|
||||
const defines = {
|
||||
IS_STANDALONE,
|
||||
IS_DEV,
|
||||
IS_REPORTER,
|
||||
IS_UPDATER_DISABLED,
|
||||
IS_WEB: false,
|
||||
IS_EXTENSION: false,
|
||||
VERSION,
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
BUILD_TIMESTAMP
|
||||
});
|
||||
};
|
||||
|
||||
if (defines.IS_STANDALONE === "false") {
|
||||
if (defines.IS_STANDALONE === false)
|
||||
// If this is a local build (not standalone), optimize
|
||||
// for the specific platform we're on
|
||||
defines["process.platform"] = JSON.stringify(process.platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
* @type {esbuild.BuildOptions}
|
||||
*/
|
||||
const nodeCommonOpts = {
|
||||
...commonOpts,
|
||||
define: defines,
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
target: ["esnext"],
|
||||
// @ts-ignore this is never undefined
|
||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
||||
define: defines
|
||||
};
|
||||
|
||||
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||
|
@ -105,27 +102,25 @@ const globNativesPlugin = {
|
|||
}
|
||||
};
|
||||
|
||||
/** @type {import("esbuild").BuildOptions[]} */
|
||||
const buildConfigs = ([
|
||||
await Promise.all([
|
||||
// Discord Desktop main & renderer & preload
|
||||
{
|
||||
esbuild.build({
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/main/index.ts"],
|
||||
outfile: "dist/patcher.js",
|
||||
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||
sourcemap,
|
||||
plugins: [
|
||||
// @ts-ignore this is never undefined
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "true",
|
||||
IS_VESKTOP: "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
},
|
||||
plugins: [
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
]
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOpts,
|
||||
entryPoints: ["src/Vencord.ts"],
|
||||
outfile: "dist/renderer.js",
|
||||
|
@ -140,11 +135,11 @@ const buildConfigs = ([
|
|||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "true",
|
||||
IS_VESKTOP: "false"
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
}
|
||||
},
|
||||
{
|
||||
}),
|
||||
esbuild.build({
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/preload.ts"],
|
||||
outfile: "dist/preload.js",
|
||||
|
@ -152,29 +147,29 @@ const buildConfigs = ([
|
|||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "true",
|
||||
IS_VESKTOP: "false"
|
||||
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
|
||||
},
|
||||
plugins: [
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
]
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOpts,
|
||||
entryPoints: ["src/Vencord.ts"],
|
||||
outfile: "dist/vencordDesktopRenderer.js",
|
||||
|
@ -189,11 +184,11 @@ const buildConfigs = ([
|
|||
],
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "true"
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
}
|
||||
},
|
||||
{
|
||||
}),
|
||||
esbuild.build({
|
||||
...nodeCommonOpts,
|
||||
entryPoints: ["src/preload.ts"],
|
||||
outfile: "dist/vencordDesktopPreload.js",
|
||||
|
@ -201,10 +196,14 @@ const buildConfigs = ([
|
|||
sourcemap,
|
||||
define: {
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VESKTOP: "true"
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
await buildOrWatchAll(buildConfigs);
|
||||
}),
|
||||
]).catch(err => {
|
||||
console.error("Build failed");
|
||||
console.error(err.message);
|
||||
// make ci fail
|
||||
if (!commonOpts.watch)
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
|
|
@ -17,30 +17,29 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import esbuild from "esbuild";
|
||||
import { readFileSync } from "fs";
|
||||
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import Zip from "zip-local";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
|
||||
|
||||
/**
|
||||
* @type {import("esbuild").BuildOptions}
|
||||
* @type {esbuild.BuildOptions}
|
||||
*/
|
||||
const commonOptions = {
|
||||
...commonOpts,
|
||||
entryPoints: ["browser/Vencord.ts"],
|
||||
format: "iife",
|
||||
globalName: "Vencord",
|
||||
format: "iife",
|
||||
external: ["~plugins", "~git-hash", "/assets/*"],
|
||||
target: ["esnext"],
|
||||
plugins: [
|
||||
globPlugins("web"),
|
||||
...commonRendererPlugins
|
||||
],
|
||||
define: stringifyValues({
|
||||
target: ["esnext"],
|
||||
define: {
|
||||
IS_WEB: true,
|
||||
IS_EXTENSION: false,
|
||||
IS_STANDALONE: true,
|
||||
|
@ -49,9 +48,9 @@ const commonOptions = {
|
|||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: false,
|
||||
IS_UPDATER_DISABLED: true,
|
||||
VERSION,
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
BUILD_TIMESTAMP
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const MonacoWorkerEntryPoints = [
|
||||
|
@ -59,59 +58,70 @@ const MonacoWorkerEntryPoints = [
|
|||
"vs/editor/editor.worker.js"
|
||||
];
|
||||
|
||||
/** @type {import("esbuild").BuildOptions[]} */
|
||||
const buildConfigs = [
|
||||
{
|
||||
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outbase: "node_modules/monaco-editor/esm/",
|
||||
outdir: "dist/vendor/monaco"
|
||||
},
|
||||
{
|
||||
entryPoints: ["browser/monaco.ts"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outfile: "dist/vendor/monaco/index.js",
|
||||
loader: {
|
||||
".ttf": "file"
|
||||
}
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
outfile: "dist/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
outfile: "dist/extension.js",
|
||||
define: {
|
||||
...commonOptions.define,
|
||||
IS_EXTENSION: "true"
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
},
|
||||
{
|
||||
...commonOptions,
|
||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||
define: {
|
||||
...commonOptions.define,
|
||||
window: "unsafeWindow",
|
||||
},
|
||||
outfile: "dist/Vencord.user.js",
|
||||
banner: {
|
||||
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(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||
}
|
||||
}
|
||||
const RnNoiseFiles = [
|
||||
"dist/rnnoise.wasm",
|
||||
"dist/rnnoise_simd.wasm",
|
||||
"dist/rnnoise/workletProcessor.js",
|
||||
"LICENSE"
|
||||
];
|
||||
|
||||
await buildOrWatchAll(buildConfigs);
|
||||
await Promise.all(
|
||||
[
|
||||
esbuild.build({
|
||||
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outbase: "node_modules/monaco-editor/esm/",
|
||||
outdir: "dist/monaco"
|
||||
}),
|
||||
esbuild.build({
|
||||
entryPoints: ["browser/monaco.ts"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: "iife",
|
||||
outfile: "dist/monaco/index.js",
|
||||
loader: {
|
||||
".ttf": "file"
|
||||
}
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/extension.js",
|
||||
define: {
|
||||
...commonOptions?.define,
|
||||
IS_EXTENSION: true,
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" }
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||
define: {
|
||||
...(commonOptions?.define),
|
||||
window: "unsafeWindow",
|
||||
},
|
||||
outfile: "dist/Vencord.user.js",
|
||||
banner: {
|
||||
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(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||
}
|
||||
})
|
||||
]
|
||||
).catch(err => {
|
||||
console.error("Build failed");
|
||||
console.error(err.message);
|
||||
if (!commonOpts.watch)
|
||||
process.exit(1);
|
||||
});;
|
||||
|
||||
/**
|
||||
* @type {(dir: string) => Promise<string[]>}
|
||||
|
@ -145,13 +155,16 @@ async function buildExtension(target, files) {
|
|||
const entries = {
|
||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||
...await loadDir("dist/vendor/monaco", "dist/"),
|
||||
...await loadDir("dist/monaco"),
|
||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
||||
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
||||
))),
|
||||
...Object.fromEntries(await Promise.all(files.map(async f => {
|
||||
let content = await readFile(join("browser", f));
|
||||
if (f.startsWith("manifest")) {
|
||||
const json = JSON.parse(content.toString("utf-8"));
|
||||
json.version = VERSION;
|
||||
content = Buffer.from(new TextEncoder().encode(JSON.stringify(json)));
|
||||
content = new TextEncoder().encode(JSON.stringify(json));
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -197,6 +210,7 @@ if (!process.argv.includes("--skip-extension")) {
|
|||
|
||||
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
|
||||
} else {
|
||||
await appendCssRuntime;
|
||||
}
|
||||
|
|
|
@ -16,13 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import "../suppressExperimentalWarnings.js";
|
||||
import "../checkNodeVersion.js";
|
||||
|
||||
import { exec, execSync } from "child_process";
|
||||
import esbuild, { build, context } from "esbuild";
|
||||
import esbuild from "esbuild";
|
||||
import { constants as FsConstants, readFileSync } from "fs";
|
||||
import { access, readdir, readFile } from "fs/promises";
|
||||
import { minify as minifyHtml } from "html-minifier-terser";
|
||||
|
@ -33,7 +31,7 @@ import { getPluginTarget } from "../utils.mjs";
|
|||
import { builtinModules } from "module";
|
||||
|
||||
/** @type {import("../../package.json")} */
|
||||
const PackageJSON = JSON.parse(readFileSync("package.json", "utf-8"));
|
||||
const PackageJSON = JSON.parse(readFileSync("package.json"));
|
||||
|
||||
export const VERSION = PackageJSON.version;
|
||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||
|
@ -56,34 +54,6 @@ export const banner = {
|
|||
`.trim()
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON.stringify all values in an object
|
||||
* @type {(obj: Record<string, any>) => Record<string, string>}
|
||||
*/
|
||||
export function stringifyValues(obj) {
|
||||
for (const key in obj) {
|
||||
obj[key] = JSON.stringify(obj[key]);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("esbuild").BuildOptions[]} buildConfigs
|
||||
*/
|
||||
export async function buildOrWatchAll(buildConfigs) {
|
||||
if (watch) {
|
||||
await Promise.all(buildConfigs.map(cfg =>
|
||||
context(cfg).then(ctx => ctx.watch())
|
||||
));
|
||||
} else {
|
||||
await Promise.all(buildConfigs.map(cfg => build(cfg)))
|
||||
.catch(error => {
|
||||
console.error(error.message);
|
||||
process.exit(1); // exit immediately to skip the rest of the builds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
||||
/**
|
||||
* @param {string} base
|
||||
|
@ -341,16 +311,18 @@ export const banImportPlugin = (filter, message) => ({
|
|||
export const commonOpts = {
|
||||
logLevel: "info",
|
||||
bundle: true,
|
||||
watch,
|
||||
minify: !watch && !IS_REPORTER,
|
||||
sourcemap: watch ? "inline" : "external",
|
||||
sourcemap: watch ? "inline" : "",
|
||||
legalComments: "linked",
|
||||
banner,
|
||||
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
|
||||
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
|
||||
inject: ["./scripts/build/inject/react.mjs"],
|
||||
jsx: "transform",
|
||||
jsxFactory: "VencordCreateElement",
|
||||
jsxFragment: "VencordFragment"
|
||||
jsxFragment: "VencordFragment",
|
||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
||||
};
|
||||
|
||||
const escapedBuiltinModules = builtinModules
|
||||
|
@ -362,7 +334,5 @@ export const commonRendererPlugins = [
|
|||
banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"),
|
||||
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
||||
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
||||
banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"),
|
||||
// @ts-ignore this is never undefined
|
||||
...commonOpts.plugins
|
||||
];
|
||||
|
|
7
scripts/build/tsconfig.esbuild.json
Normal file
7
scripts/build/tsconfig.esbuild.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
|
@ -16,32 +16,28 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="../src/globals" />
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="../src/modules" />
|
||||
|
||||
import { createHmac } from "crypto";
|
||||
import { readFileSync } from "fs";
|
||||
import pup, { JSHandle } from "puppeteer-core";
|
||||
|
||||
const logStderr = (...data: any[]) => console.error(`${CANARY ? "CANARY" : "STABLE"} ---`, ...data);
|
||||
|
||||
for (const variable of ["CHROMIUM_BIN"]) {
|
||||
if (!process.env[variable]) {
|
||||
logStderr(`Missing environment variable ${variable}`);
|
||||
console.error(`Missing environment variable ${variable}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const CANARY = process.env.USE_CANARY === "true";
|
||||
let metaData = {
|
||||
buildNumber: "Unknown Build Number",
|
||||
buildHash: "Unknown Build Hash"
|
||||
};
|
||||
|
||||
const browser = await pup.launch({
|
||||
headless: true,
|
||||
executablePath: process.env.CHROMIUM_BIN,
|
||||
args: ["--no-sandbox"]
|
||||
executablePath: process.env.CHROMIUM_BIN
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
@ -54,17 +50,14 @@ async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
|
|||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
interface PatchInfo {
|
||||
plugin: string;
|
||||
type: string;
|
||||
id: string;
|
||||
match: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
const report = {
|
||||
badPatches: [] as PatchInfo[],
|
||||
slowPatches: [] as PatchInfo[],
|
||||
badPatches: [] as {
|
||||
plugin: string;
|
||||
type: string;
|
||||
id: string;
|
||||
match: string;
|
||||
error?: string;
|
||||
}[],
|
||||
badStarts: [] as {
|
||||
plugin: string;
|
||||
error: string;
|
||||
|
@ -134,88 +127,56 @@ async function printReport() {
|
|||
|
||||
console.log();
|
||||
|
||||
if (process.env.WEBHOOK_URL) {
|
||||
const patchesToEmbed = (title: string, patches: PatchInfo[], color: number) => ({
|
||||
title,
|
||||
color,
|
||||
description: patches.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin} (${p.type}):__**`,
|
||||
`ID: \`${p.id}\``,
|
||||
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
||||
];
|
||||
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
||||
|
||||
return lines.join("\n");
|
||||
}).join("\n\n"),
|
||||
});
|
||||
|
||||
const embeds = [
|
||||
{
|
||||
author: {
|
||||
name: `Discord ${CANARY ? "Canary" : "Stable"} (${metaData.buildNumber})`,
|
||||
url: `https://nelly.tools/builds/app/${metaData.buildHash}`,
|
||||
icon_url: CANARY ? "https://cdn.discordapp.com/emojis/1252721945699549327.png?size=128" : "https://cdn.discordapp.com/emojis/1252721943463985272.png?size=128"
|
||||
},
|
||||
color: CANARY ? 0xfbb642 : 0x5865f2
|
||||
},
|
||||
report.badPatches.length > 0 && patchesToEmbed("Bad Patches", report.badPatches, 0xff0000),
|
||||
report.slowPatches.length > 0 && patchesToEmbed("Slow Patches", report.slowPatches, 0xf0b232),
|
||||
report.badWebpackFinds.length > 0 && {
|
||||
title: "Bad Webpack Finds",
|
||||
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
||||
color: 0xff0000
|
||||
},
|
||||
report.badStarts.length > 0 && {
|
||||
title: "Bad Starts",
|
||||
description: report.badStarts.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin}:__**`,
|
||||
toCodeBlock(p.error, 0, true)
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
).join("\n\n") || "None",
|
||||
color: 0xff0000
|
||||
},
|
||||
report.otherErrors.length > 0 && {
|
||||
title: "Discord Errors",
|
||||
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
|
||||
color: 0xff0000
|
||||
}
|
||||
].filter(Boolean);
|
||||
|
||||
if (embeds.length === 1) {
|
||||
embeds.push({
|
||||
title: "No issues found",
|
||||
description: "Seems like everything is working fine (for now) <:shipit:1330992641466433556>",
|
||||
color: 0x00ff00
|
||||
});
|
||||
}
|
||||
|
||||
const body = JSON.stringify({
|
||||
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
||||
embeds
|
||||
});
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
|
||||
// functions similar to https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
||||
// used by venbot to ensure webhook invocations are genuine (since we will pass the webhook url as a workflow input which is publicly visible)
|
||||
// generate a secret with something like `openssl rand -hex 128`
|
||||
if (process.env.WEBHOOK_SECRET) {
|
||||
headers["X-Signature"] = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(body).digest("hex");
|
||||
}
|
||||
|
||||
await fetch(process.env.WEBHOOK_URL, {
|
||||
if (process.env.DISCORD_WEBHOOK) {
|
||||
await fetch(process.env.DISCORD_WEBHOOK, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
description: "Here's the latest Vencord Report!",
|
||||
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
||||
embeds: [
|
||||
{
|
||||
title: "Bad Patches",
|
||||
description: report.badPatches.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin} (${p.type}):__**`,
|
||||
`ID: \`${p.id}\``,
|
||||
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
||||
];
|
||||
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
||||
return lines.join("\n");
|
||||
}).join("\n\n") || "None",
|
||||
color: report.badPatches.length ? 0xff0000 : 0x00ff00
|
||||
},
|
||||
{
|
||||
title: "Bad Webpack Finds",
|
||||
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
||||
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
|
||||
},
|
||||
{
|
||||
title: "Bad Starts",
|
||||
description: report.badStarts.map(p => {
|
||||
const lines = [
|
||||
`**__${p.plugin}:__**`,
|
||||
toCodeBlock(p.error, 0, true)
|
||||
];
|
||||
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"), 0, true) : "None",
|
||||
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
|
||||
}
|
||||
]
|
||||
})
|
||||
}).then(res => {
|
||||
if (!res.ok) logStderr(`Webhook failed with status ${res.status}`);
|
||||
else logStderr("Posted to Webhook successfully");
|
||||
if (!res.ok) console.error(`Webhook failed with status ${res.status}`);
|
||||
else console.error("Posted to Discord Webhook successfully");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -224,13 +185,10 @@ page.on("console", async e => {
|
|||
const level = e.type();
|
||||
const rawArgs = e.args();
|
||||
|
||||
async function getText(skipFirst = true) {
|
||||
let args = e.args();
|
||||
if (skipFirst) args = args.slice(1);
|
||||
|
||||
async function getText() {
|
||||
try {
|
||||
return await Promise.all(
|
||||
args.map(async a => {
|
||||
e.args().map(async a => {
|
||||
return await maybeGetError(a) || await a.jsonValue();
|
||||
})
|
||||
).then(a => a.join(" ").trim());
|
||||
|
@ -243,12 +201,6 @@ page.on("console", async e => {
|
|||
|
||||
const isVencord = firstArg === "[Vencord]";
|
||||
const isDebug = firstArg === "[PUP_DEBUG]";
|
||||
const isReporterMeta = firstArg === "[REPORTER_META]";
|
||||
|
||||
if (isReporterMeta) {
|
||||
metaData = await rawArgs[1].jsonValue() as any;
|
||||
return;
|
||||
}
|
||||
|
||||
outer:
|
||||
if (isVencord) {
|
||||
|
@ -262,21 +214,18 @@ page.on("console", async e => {
|
|||
|
||||
switch (tag) {
|
||||
case "WebpackInterceptor:":
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/);
|
||||
const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/);
|
||||
const match = patchFailMatch ?? patchSlowMatch;
|
||||
if (!match) break;
|
||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module|took [\d.]+?ms) \(Module id is (.+?)\): (.+)/)!;
|
||||
if (!patchFailMatch) break;
|
||||
|
||||
logStderr(await getText());
|
||||
console.error(await getText());
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, plugin, type, id, regex] = match;
|
||||
const list = patchFailMatch ? report.badPatches : report.slowPatches;
|
||||
list.push({
|
||||
const [, plugin, type, id, regex] = patchFailMatch;
|
||||
report.badPatches.push({
|
||||
plugin,
|
||||
type,
|
||||
id,
|
||||
match: regex,
|
||||
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
|
||||
error: await maybeGetError(e.args()[3])
|
||||
});
|
||||
|
||||
|
@ -285,7 +234,7 @@ page.on("console", async e => {
|
|||
const failedToStartMatch = message.match(/Failed to start (.+)/);
|
||||
if (!failedToStartMatch) break;
|
||||
|
||||
logStderr(await getText());
|
||||
console.error(await getText());
|
||||
process.exitCode = 1;
|
||||
|
||||
const [, name] = failedToStartMatch;
|
||||
|
@ -296,7 +245,7 @@ page.on("console", async e => {
|
|||
|
||||
break;
|
||||
case "LazyChunkLoader:":
|
||||
logStderr(await getText());
|
||||
console.error(await getText());
|
||||
|
||||
switch (message) {
|
||||
case "A fatal error occurred:":
|
||||
|
@ -305,7 +254,7 @@ page.on("console", async e => {
|
|||
|
||||
break;
|
||||
case "Reporter:":
|
||||
logStderr(await getText());
|
||||
console.error(await getText());
|
||||
|
||||
switch (message) {
|
||||
case "A fatal error occurred:":
|
||||
|
@ -323,27 +272,27 @@ page.on("console", async e => {
|
|||
}
|
||||
|
||||
if (isDebug) {
|
||||
logStderr(await getText());
|
||||
console.error(await getText());
|
||||
} else if (level === "error") {
|
||||
const text = await getText(false);
|
||||
const text = await getText();
|
||||
|
||||
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
|
||||
if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
|
||||
report.ignoredErrors.push(text);
|
||||
} else {
|
||||
logStderr("[Unexpected Error]", text);
|
||||
console.error("[Unexpected Error]", text);
|
||||
report.otherErrors.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
page.on("error", e => logStderr("[Error]", e.message));
|
||||
page.on("error", e => console.error("[Error]", e.message));
|
||||
page.on("pageerror", e => {
|
||||
if (e.message.includes("Sentry successfully disabled")) return;
|
||||
|
||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
|
||||
logStderr("[Page Error]", e.message);
|
||||
console.error("[Page Error]", e.message);
|
||||
report.otherErrors.push(e.message);
|
||||
} else {
|
||||
report.ignoredErrors.push(e.message);
|
||||
|
|
20
setup.bat
20
setup.bat
|
@ -1,20 +0,0 @@
|
|||
@echo off
|
||||
|
||||
:: Check if 'upstream' remote exists
|
||||
git remote | findstr upstream >nul
|
||||
if errorlevel 1 (
|
||||
:: Add upstream remote
|
||||
git remote add upstream https://github.com/Vendicated/Vencord.git
|
||||
echo Added upstream remote
|
||||
)
|
||||
|
||||
:: Disable push to upstream by setting push URL to 'no_push'
|
||||
git remote set-url --push upstream no_push
|
||||
echo Disabled push to upstream remote
|
||||
|
||||
:: Add alias for sync: fetch, merge, and push to origin
|
||||
git config alias.sync "!git fetch upstream && git merge upstream/main && git push origin main"
|
||||
echo Configured sync alias
|
||||
|
||||
echo Setup completed!
|
||||
|
18
setup.sh
18
setup.sh
|
@ -1,18 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Add upstream remote if it doesn't exist
|
||||
if ! git remote | grep -q 'upstream'; then
|
||||
git remote add upstream https://github.com/Vendicated/Vencord.git
|
||||
echo "Added upstream remote"
|
||||
fi
|
||||
|
||||
# Disable push to upstream by removing its push URL
|
||||
git remote set-url --push upstream no_push
|
||||
echo "Disabled push to upstream remote"
|
||||
|
||||
# Add alias for sync: fetch, merge, and push to origin
|
||||
git config alias.sync '!git fetch upstream && git merge upstream/main && git push origin main'
|
||||
echo "Configured sync alias"
|
||||
|
||||
echo "Setup completed!"
|
||||
|
|
@ -27,7 +27,6 @@ export * as WebpackPatcher from "./webpack/patchWebpack";
|
|||
export { PlainSettings, Settings };
|
||||
|
||||
import "./utils/quickCss";
|
||||
import "./webpack/patchWebpack";
|
||||
|
||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||
import { StartAt } from "@utils/types";
|
||||
|
@ -40,7 +39,7 @@ 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 { onceDiscordLoaded } from "./webpack";
|
||||
import { SettingsRouter } from "./webpack/common";
|
||||
|
||||
if (IS_REPORTER) {
|
||||
|
@ -87,7 +86,7 @@ async function syncSettings() {
|
|||
}
|
||||
|
||||
async function init() {
|
||||
await onceReady;
|
||||
await onceDiscordLoaded;
|
||||
startAllPlugins(StartAt.WebpackReady);
|
||||
|
||||
syncSettings();
|
||||
|
@ -126,7 +125,7 @@ async function init() {
|
|||
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.",
|
||||
"Webpack has finished initializing, 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.",
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Settings } from "@api/Settings";
|
||||
import { PluginIpcMappings } from "@main/ipcPlugins";
|
||||
import type { UserThemeHeader } from "@main/themes";
|
||||
import { IpcEvents } from "@shared/IpcEvents";
|
||||
import { IpcRes } from "@utils/types";
|
||||
import type { Settings } from "api/Settings";
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
|
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
|||
* Register a new badge with the Badges API
|
||||
* @param badge The badge to register
|
||||
*/
|
||||
export function addProfileBadge(badge: ProfileBadge) {
|
||||
export function addBadge(badge: ProfileBadge) {
|
||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||
Badges.add(badge);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export function addProfileBadge(badge: ProfileBadge) {
|
|||
* Unregister a badge from the Badges API
|
||||
* @param badge The badge to remove
|
||||
*/
|
||||
export function removeProfileBadge(badge: ProfileBadge) {
|
||||
export function removeBadge(badge: ProfileBadge) {
|
||||
return Badges.delete(badge);
|
||||
}
|
||||
|
||||
|
@ -100,3 +100,20 @@ export interface BadgeUserArgs {
|
|||
userId: string;
|
||||
guildId: string;
|
||||
}
|
||||
|
||||
interface ConnectedAccount {
|
||||
type: string;
|
||||
id: string;
|
||||
name: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
interface Profile {
|
||||
connectedAccounts: ConnectedAccount[];
|
||||
premiumType: number;
|
||||
premiumSince: string;
|
||||
premiumGuildSince?: any;
|
||||
lastFetched: number;
|
||||
profileFetchFailed: boolean;
|
||||
application?: any;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,12 @@ import "./ChatButton.css";
|
|||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { waitFor } from "@webpack";
|
||||
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
import { findByProps } from "@webpack";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
||||
|
||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||
const ChannelTextAreaClasses = findByProps<Record<"button" | "buttonContainer", string>>("buttonContainer", "channelTextArea");
|
||||
|
||||
export interface ChatBarProps {
|
||||
channel: Channel;
|
||||
|
@ -74,9 +73,9 @@ export interface ChatBarProps {
|
|||
};
|
||||
}
|
||||
|
||||
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||
|
||||
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||
const buttonFactories = new Map<string, ChatBarButton>();
|
||||
const logger = new Logger("ChatButtons");
|
||||
|
||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||
|
@ -91,7 +90,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
|||
}
|
||||
}
|
||||
|
||||
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||
|
||||
export interface ChatBarButtonProps {
|
||||
|
@ -99,8 +98,7 @@ export interface ChatBarButtonProps {
|
|||
tooltip: string;
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||
onAuxClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu" | "onAuxClick">;
|
||||
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
|
||||
}
|
||||
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||
return (
|
||||
|
@ -110,13 +108,12 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
|||
<Button
|
||||
aria-label={props.tooltip}
|
||||
size=""
|
||||
look={Button.Looks.BLANK}
|
||||
look={ButtonLooks.BLANK}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||
onClick={props.onClick}
|
||||
onContextMenu={props.onContextMenu}
|
||||
onAuxClick={props.onAuxClick}
|
||||
{...props.buttonProps}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
*/
|
||||
|
||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { findByCode } from "@webpack";
|
||||
import { MessageActions, SnowflakeUtils } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
|
||||
import { Argument } from "./types";
|
||||
|
||||
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||
const createBotMessage = findByCode('username:"Clyde"');
|
||||
|
||||
export function generateId() {
|
||||
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
||||
|
@ -54,5 +54,5 @@ export function sendBotMessage(channelId: string, message: PartialDeep<Message>)
|
|||
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
|
||||
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
||||
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
|
||||
return (args.find(a => a.name === name)?.value ?? fallbackValue) as any;
|
||||
return (args.find(a => a.name === name)?.value || fallbackValue) as any;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { makeCodeblock } from "@utils/text";
|
||||
|
||||
import { sendBotMessage } from "./commandHelpers";
|
||||
|
@ -47,10 +46,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder;
|
|||
export const _init = function (cmds: Command[]) {
|
||||
try {
|
||||
BUILT_IN = cmds;
|
||||
OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
|
||||
RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0];
|
||||
OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0];
|
||||
RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0];
|
||||
} catch (e) {
|
||||
new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds);
|
||||
console.error("Failed to load CommandsApi");
|
||||
}
|
||||
return cmds;
|
||||
} as never;
|
||||
|
@ -110,7 +109,6 @@ function registerSubCommands(cmd: Command, plugin: string) {
|
|||
const subCmd = {
|
||||
...cmd,
|
||||
...o,
|
||||
options: o.options !== undefined ? o.options : undefined,
|
||||
type: ApplicationCommandType.CHAT_INPUT,
|
||||
name: `${cmd.name} ${o.name}`,
|
||||
id: `${o.name}-${cmd.id}`,
|
||||
|
@ -140,8 +138,6 @@ export function registerCommand<C extends Command>(command: C, plugin: string) {
|
|||
throw new Error(`Command '${command.name}' already exists.`);
|
||||
|
||||
command.isVencordCommand = true;
|
||||
command.untranslatedName ??= command.name;
|
||||
command.untranslatedDescription ??= command.description;
|
||||
command.id ??= `-${BUILT_IN.length + 1}`;
|
||||
command.applicationId ??= "-1"; // BUILT_IN;
|
||||
command.type ??= ApplicationCommandType.CHAT_INPUT;
|
||||
|
|
|
@ -93,10 +93,8 @@ export interface Command {
|
|||
isVencordCommand?: boolean;
|
||||
|
||||
name: string;
|
||||
untranslatedName?: string;
|
||||
displayName?: string;
|
||||
description: string;
|
||||
untranslatedDescription?: string;
|
||||
displayDescription?: string;
|
||||
|
||||
options?: Option[];
|
||||
|
|
|
@ -24,13 +24,13 @@ import type { ReactElement } from "react";
|
|||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
*/
|
||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
||||
/**
|
||||
* @param navId The navId of the context menu being patched
|
||||
* @param children The rendered context menu elements
|
||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||
*/
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
||||
|
||||
const ContextMenuLogger = new Logger("ContextMenu");
|
||||
|
||||
|
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
|
|||
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||
*/
|
||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||
const navIds: string[] = Array.isArray(navId) ? navId : [navId];
|
||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
||||
|
||||
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
||||
|
||||
|
@ -90,20 +90,19 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
|||
* 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
|
||||
* @param matchSubstring Whether to check if the id is a substring of the child id
|
||||
*/
|
||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
|
||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null {
|
||||
for (const child of children) {
|
||||
if (child == null) continue;
|
||||
|
||||
if (Array.isArray(child)) {
|
||||
const found = findGroupChildrenByChildId(id, child, matchSubstring);
|
||||
const found = findGroupChildrenByChildId(id, child);
|
||||
if (found !== null) return found;
|
||||
}
|
||||
|
||||
if (
|
||||
(Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|
||||
|| (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
|
||||
(Array.isArray(id) && id.some(id => child.props?.id === id))
|
||||
|| child.props?.id === id
|
||||
) return children;
|
||||
|
||||
let nextChildren = child.props?.children;
|
||||
|
@ -113,7 +112,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
|||
child.props.children = nextChildren;
|
||||
}
|
||||
|
||||
const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
|
||||
const found = findGroupChildrenByChildId(id, nextChildren);
|
||||
if (found !== null) return found;
|
||||
}
|
||||
}
|
||||
|
@ -122,9 +121,9 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
|||
}
|
||||
|
||||
interface ContextMenuProps {
|
||||
contextMenuAPIArguments?: Array<any>;
|
||||
contextMenuApiArguments?: Array<any>;
|
||||
navId: string;
|
||||
children: Array<ReactElement<any> | null>;
|
||||
children: Array<ReactElement | null>;
|
||||
"aria-label": string;
|
||||
onSelect: (() => void) | undefined;
|
||||
onClose: (callback: (...args: Array<any>) => any) => void;
|
||||
|
@ -136,7 +135,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
|||
children: cloneMenuChildren(props.children),
|
||||
};
|
||||
|
||||
props.contextMenuAPIArguments ??= [];
|
||||
props.contextMenuApiArguments ??= [];
|
||||
const contextMenuPatches = navPatches.get(props.navId);
|
||||
|
||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||
|
@ -144,7 +143,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
|||
if (contextMenuPatches) {
|
||||
for (const patch of contextMenuPatches) {
|
||||
try {
|
||||
patch(props.children, ...props.contextMenuAPIArguments);
|
||||
patch(props.children, ...props.contextMenuApiArguments);
|
||||
} catch (err) {
|
||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||
}
|
||||
|
@ -153,7 +152,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
|||
|
||||
for (const patch of globalPatches) {
|
||||
try {
|
||||
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
||||
} catch (err) {
|
||||
ContextMenuLogger.error("Global patch errored,", err);
|
||||
}
|
||||
|
@ -162,7 +161,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
|||
return props;
|
||||
}
|
||||
|
||||
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
|
||||
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(cloneMenuChildren);
|
||||
}
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Channel, User } from "discord-types/general/index.js";
|
||||
import { JSX } from "react";
|
||||
|
||||
interface DecoratorProps {
|
||||
activities: any[];
|
||||
|
@ -40,39 +38,27 @@ interface DecoratorProps {
|
|||
user: User;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
||||
type OnlyIn = "guilds" | "dms";
|
||||
|
||||
export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
||||
|
||||
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||
decoratorsFactories.set(identifier, { render, onlyIn });
|
||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
||||
decorators.set(identifier, { decorator, onlyIn });
|
||||
}
|
||||
|
||||
export function removeMemberListDecorator(identifier: string) {
|
||||
decoratorsFactories.delete(identifier);
|
||||
export function removeDecorator(identifier: string) {
|
||||
decorators.delete(identifier);
|
||||
}
|
||||
|
||||
export function __getDecorators(props: DecoratorProps): JSX.Element {
|
||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||
const isInGuild = !!(props.guildId);
|
||||
|
||||
const decorators = Array.from(
|
||||
decoratorsFactories.entries(),
|
||||
([key, { render: Decorator, onlyIn }]) => {
|
||||
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||
<Decorator {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
return Array.from(decorators.values(), decoratorObj => {
|
||||
const { decorator, onlyIn } = decoratorObj;
|
||||
// this can most likely be done cleaner
|
||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
||||
return decorator(props);
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="vc-member-list-decorators-wrapper">
|
||||
{decorators}
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
});
|
||||
}
|
|
@ -16,29 +16,26 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { JSX, ReactNode } from "react";
|
||||
|
||||
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||
export type MessageAccessory = {
|
||||
render: MessageAccessoryFactory;
|
||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
||||
export type Accessory = {
|
||||
callback: AccessoryCallback;
|
||||
position?: number;
|
||||
};
|
||||
|
||||
export const accessories = new Map<string, MessageAccessory>();
|
||||
export const accessories = new Map<String, Accessory>();
|
||||
|
||||
export function addMessageAccessory(
|
||||
export function addAccessory(
|
||||
identifier: string,
|
||||
render: MessageAccessoryFactory,
|
||||
callback: AccessoryCallback,
|
||||
position?: number
|
||||
) {
|
||||
accessories.set(identifier, {
|
||||
render,
|
||||
callback,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
export function removeMessageAccessory(identifier: string) {
|
||||
export function removeAccessory(identifier: string) {
|
||||
accessories.delete(identifier);
|
||||
}
|
||||
|
||||
|
@ -46,12 +43,15 @@ export function _modifyAccessories(
|
|||
elements: JSX.Element[],
|
||||
props: Record<string, any>
|
||||
) {
|
||||
for (const [key, accessory] of accessories.entries()) {
|
||||
const res = (
|
||||
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||
<accessory.render {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
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
|
||||
|
@ -60,7 +60,7 @@ export function _modifyAccessories(
|
|||
: accessory.position
|
||||
: elements.length,
|
||||
0,
|
||||
res
|
||||
...accessories.filter(e => e != null) as JSX.Element[]
|
||||
);
|
||||
}
|
||||
|
|
@ -16,11 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Channel, Message } from "discord-types/general/index.js";
|
||||
import { JSX } from "react";
|
||||
|
||||
export interface MessageDecorationProps {
|
||||
interface DecorationProps {
|
||||
author: {
|
||||
/**
|
||||
* Will be username if the user has no nickname
|
||||
|
@ -46,31 +44,20 @@ export interface MessageDecorationProps {
|
|||
message: Message;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
||||
|
||||
export const decorationsFactories = new Map<string, MessageDecorationFactory>();
|
||||
export const decorations = new Map<string, Decoration>();
|
||||
|
||||
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||
decorationsFactories.set(identifier, decoration);
|
||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
||||
decorations.set(identifier, decoration);
|
||||
}
|
||||
|
||||
export function removeMessageDecoration(identifier: string) {
|
||||
decorationsFactories.delete(identifier);
|
||||
export function removeDecoration(identifier: string) {
|
||||
decorations.delete(identifier);
|
||||
}
|
||||
|
||||
export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element {
|
||||
const decorations = Array.from(
|
||||
decorationsFactories.entries(),
|
||||
([key, Decoration]) => (
|
||||
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||
<Decoration {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="vc-message-decorations-wrapper">
|
||||
{decorations}
|
||||
</div>
|
||||
);
|
||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
||||
return [...decorations.values()].map(decoration => {
|
||||
return decoration(props);
|
||||
});
|
||||
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
|||
openWarningPopout: (props: any) => any;
|
||||
}
|
||||
|
||||
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||
|
||||
const sendListeners = new Set<MessageSendListener>();
|
||||
const editListeners = new Set<MessageEditListener>();
|
||||
const sendListeners = new Set<SendListener>();
|
||||
const editListeners = new Set<EditListener>();
|
||||
|
||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||
extra.replyOptions = replyOptions;
|
||||
|
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
|
|||
/**
|
||||
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||
*/
|
||||
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||
export function addPreSendListener(listener: SendListener) {
|
||||
sendListeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
/**
|
||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||
*/
|
||||
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||
export function addPreEditListener(listener: EditListener) {
|
||||
editListeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||
export function removePreSendListener(listener: SendListener) {
|
||||
return sendListeners.delete(listener);
|
||||
}
|
||||
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||
export function removePreEditListener(listener: EditListener) {
|
||||
return editListeners.delete(listener);
|
||||
}
|
||||
|
||||
|
||||
// Message clicks
|
||||
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||
|
||||
const listeners = new Set<MessageClickListener>();
|
||||
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
|
||||
|
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
|||
}
|
||||
}
|
||||
|
||||
export function addMessageClickListener(listener: MessageClickListener) {
|
||||
export function addClickListener(listener: ClickListener) {
|
||||
listeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||
export function removeClickListener(listener: ClickListener) {
|
||||
return listeners.delete(listener);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
|
|||
|
||||
const logger = new Logger("MessagePopover");
|
||||
|
||||
export interface MessagePopoverButtonItem {
|
||||
export interface ButtonItem {
|
||||
key?: string,
|
||||
label: string,
|
||||
icon: ComponentType<any>,
|
||||
|
@ -33,23 +33,23 @@ export interface MessagePopoverButtonItem {
|
|||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
||||
|
||||
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||
export const buttons = new Map<string, getButtonItem>();
|
||||
|
||||
export function addMessagePopoverButton(
|
||||
export function addButton(
|
||||
identifier: string,
|
||||
item: MessagePopoverButtonFactory,
|
||||
item: getButtonItem,
|
||||
) {
|
||||
buttons.set(identifier, item);
|
||||
}
|
||||
|
||||
export function removeMessagePopoverButton(identifier: string) {
|
||||
export function removeButton(identifier: string) {
|
||||
buttons.delete(identifier);
|
||||
}
|
||||
|
||||
export function _buildPopoverElements(
|
||||
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||
Component: React.ComponentType<ButtonItem>,
|
||||
message: Message
|
||||
) {
|
||||
const items: React.ReactNode[] = [];
|
||||
|
|
|
@ -16,23 +16,22 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { waitFor } from "@webpack";
|
||||
import { find } from "@webpack";
|
||||
|
||||
let NoticesModule: any;
|
||||
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
||||
const Notices = find(m => m.show && m.dismiss && !m.suppressAll);
|
||||
|
||||
export const noticesQueue = [] as any[];
|
||||
export let currentNotice: any = null;
|
||||
|
||||
export function popNotice() {
|
||||
NoticesModule.dismiss();
|
||||
Notices.dismiss();
|
||||
}
|
||||
|
||||
export function nextNotice() {
|
||||
currentNotice = noticesQueue.shift();
|
||||
|
||||
if (currentNotice) {
|
||||
NoticesModule.show(...currentNotice, "VencordNotice");
|
||||
Notices.show(...currentNotice, "VencordNotice");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,36 +16,40 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { ComponentType } from "react";
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
||||
const logger = new Logger("ServerListAPI");
|
||||
|
||||
export const enum ServerListRenderPosition {
|
||||
Above,
|
||||
In,
|
||||
}
|
||||
|
||||
const componentsAbove = new Set<ComponentType>();
|
||||
const componentsBelow = new Set<ComponentType>();
|
||||
const renderFunctionsAbove = new Set<Function>();
|
||||
const renderFunctionsIn = new Set<Function>();
|
||||
|
||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
||||
}
|
||||
|
||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
||||
getRenderFunctions(position).add(renderFunction);
|
||||
}
|
||||
|
||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
||||
getRenderFunctions(position).delete(renderFunction);
|
||||
}
|
||||
|
||||
export const renderAll = (position: ServerListRenderPosition) => {
|
||||
return Array.from(
|
||||
getRenderFunctions(position),
|
||||
(Component, i) => (
|
||||
<ErrorBoundary noop key={i}>
|
||||
<Component />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
);
|
||||
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;
|
||||
};
|
|
@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||
import { putCloudSettings } from "@utils/settingsSync";
|
||||
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
||||
import { React, useEffect } from "@webpack/common";
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
import plugins from "~plugins";
|
||||
|
||||
|
@ -32,10 +32,10 @@ export interface Settings {
|
|||
autoUpdate: boolean;
|
||||
autoUpdateNotification: boolean,
|
||||
useQuickCss: boolean;
|
||||
themeLinks: string[];
|
||||
eagerPatches: boolean;
|
||||
enabledThemes: string[];
|
||||
enableReactDevtools: boolean;
|
||||
themeLinks: string[];
|
||||
frameless: boolean;
|
||||
transparent: boolean;
|
||||
winCtrlQ: boolean;
|
||||
|
@ -118,7 +118,6 @@ const saveSettingsOnFrequentAction = debounce(async () => {
|
|||
}
|
||||
}, 60_000);
|
||||
|
||||
|
||||
export const SettingsStore = new SettingsStoreClass(settings, {
|
||||
readOnly: true,
|
||||
getDefaultValue({
|
||||
|
@ -194,7 +193,7 @@ export const Settings = SettingsStore.store;
|
|||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if (paths) {
|
||||
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||
|
@ -202,7 +201,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
|
|||
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||
}
|
||||
}, [paths]);
|
||||
}, []);
|
||||
|
||||
return SettingsStore.store;
|
||||
}
|
||||
|
@ -222,17 +221,6 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
|||
}
|
||||
}
|
||||
|
||||
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
|
||||
const settings = SettingsStore.plain.plugins[pluginName];
|
||||
if (!settings) return;
|
||||
|
||||
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
|
||||
|
||||
settings[newSetting] = settings[oldSetting];
|
||||
delete settings[oldSetting];
|
||||
SettingsStore.markAsChanged();
|
||||
}
|
||||
|
||||
export function definePluginSettings<
|
||||
Def extends SettingsDefinition,
|
||||
Checks extends SettingsChecks<Def>,
|
||||
|
|
|
@ -89,8 +89,8 @@ export const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnec
|
|||
* // -- 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", ... }
|
||||
* import { findByProps } from "@webpack";
|
||||
* const classNames = findByProps("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... }
|
||||
*
|
||||
* // Inside some plugin method like "start()"
|
||||
* setStyleClassNames(pluginStyle, classNames);
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||
import { findByFactoryCode } from "@webpack";
|
||||
|
||||
interface UserSettingDefinition<T> {
|
||||
/**
|
||||
|
@ -43,12 +42,7 @@ interface UserSettingDefinition<T> {
|
|||
userSettingsAPIName: string;
|
||||
}
|
||||
|
||||
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
|
||||
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
||||
if (modId == null) return new Logger("UserSettingsAPI ").error("Didn't find settings module.");
|
||||
|
||||
return wreq(modId as any);
|
||||
});
|
||||
export const UserSettings = findByFactoryCode<Record<PropertyKey, UserSettingDefinition<any>>>('"textAndImages","renderSpoilers"');
|
||||
|
||||
/**
|
||||
* Get the setting with the given setting group and name.
|
||||
|
@ -56,7 +50,7 @@ export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | und
|
|||
* @param group The setting group
|
||||
* @param name The name of the setting
|
||||
*/
|
||||
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
|
||||
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> {
|
||||
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency.");
|
||||
|
||||
for (const key in UserSettings) {
|
||||
|
@ -66,10 +60,12 @@ export function getUserSetting<T = any>(group: string, name: string): UserSettin
|
|||
return userSetting;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`UserSettingsAPI: Setting ${group}.${name} not found.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getUserSettingDefinition}, lazy.
|
||||
* Lazy version of {@link getUserSetting}
|
||||
*
|
||||
* Get the setting with the given setting group and name.
|
||||
*
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function Badge({ text, color }) {
|
||||
export function Badge({ text, color }): JSX.Element {
|
||||
return (
|
||||
<div className="vc-plugins-badge" style={{
|
||||
backgroundColor: color,
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { findByProps } from "@webpack";
|
||||
import { Parser } from "@webpack/common";
|
||||
|
||||
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
|
||||
const CodeContainerClasses = findByProps("markup", "codeContainer");
|
||||
|
||||
/**
|
||||
* Renders code in a Discord codeblock
|
||||
|
|
|
@ -17,22 +17,16 @@
|
|||
*/
|
||||
|
||||
import { Button } from "@webpack/common";
|
||||
import { ButtonProps } from "@webpack/types";
|
||||
|
||||
import { Heart } from "./Heart";
|
||||
|
||||
export default function DonateButton({
|
||||
look = Button.Looks.LINK,
|
||||
color = Button.Colors.TRANSPARENT,
|
||||
...props
|
||||
}: Partial<ButtonProps>) {
|
||||
export default function DonateButton(props: any) {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
look={look}
|
||||
color={color}
|
||||
look={Button.Looks.LINK}
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
||||
innerClassName="vc-donate-button"
|
||||
>
|
||||
<Heart />
|
||||
Donate
|
||||
|
|
|
@ -27,7 +27,7 @@ interface Props<T = any> {
|
|||
/** Render nothing if an error occurs */
|
||||
noop?: boolean;
|
||||
/** Fallback component to render if an error occurs */
|
||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
|
||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
||||
/** called when an error occurs. The props property is only available if using .wrap */
|
||||
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
||||
/** Custom error message */
|
||||
|
@ -70,7 +70,8 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||
logger.error("A component threw an Error\n", error);
|
||||
logger.error("Component Stack", errorInfo.componentStack);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -79,14 +80,10 @@ const ErrorBoundary = LazyComponent(() => {
|
|||
if (this.props.noop) return null;
|
||||
|
||||
if (this.props.fallback)
|
||||
return (
|
||||
<this.props.fallback
|
||||
wrappedProps={this.props.wrappedProps}
|
||||
{...this.state}
|
||||
>
|
||||
{this.props.children}
|
||||
</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.";
|
||||
|
||||
|
|
11
src/components/ExpandableHeader.css
Normal file
11
src/components/ExpandableHeader.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.vc-expandableheader-center-flex {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.vc-expandableheader-btn {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
121
src/components/ExpandableHeader.tsx
Normal file
121
src/components/ExpandableHeader.tsx
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./ExpandableHeader.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Text, Tooltip, useState } from "@webpack/common";
|
||||
|
||||
const cl = classNameFactory("vc-expandableheader-");
|
||||
|
||||
export interface ExpandableHeaderProps {
|
||||
onMoreClick?: () => void;
|
||||
moreTooltipText?: string;
|
||||
onDropDownClick?: (state: boolean) => void;
|
||||
defaultState?: boolean;
|
||||
headerText: string;
|
||||
children: React.ReactNode;
|
||||
buttons?: React.ReactNode[];
|
||||
forceOpen?: boolean;
|
||||
}
|
||||
|
||||
export function ExpandableHeader({
|
||||
children,
|
||||
onMoreClick,
|
||||
buttons,
|
||||
moreTooltipText,
|
||||
onDropDownClick,
|
||||
headerText,
|
||||
defaultState = false,
|
||||
forceOpen = false,
|
||||
}: ExpandableHeaderProps) {
|
||||
const [showContent, setShowContent] = useState(defaultState || forceOpen);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "8px"
|
||||
}}>
|
||||
<Text
|
||||
tag="h2"
|
||||
variant="eyebrow"
|
||||
style={{
|
||||
color: "var(--header-primary)",
|
||||
display: "inline"
|
||||
}}
|
||||
>
|
||||
{headerText}
|
||||
</Text>
|
||||
|
||||
<div className={cl("center-flex")}>
|
||||
{
|
||||
buttons ?? null
|
||||
}
|
||||
|
||||
{
|
||||
onMoreClick && // only show more button if callback is provided
|
||||
<Tooltip text={moreTooltipText}>
|
||||
{tooltipProps => (
|
||||
<button
|
||||
{...tooltipProps}
|
||||
className={cl("btn")}
|
||||
onClick={onMoreClick}>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="var(--text-normal)" d="M7 12.001C7 10.8964 6.10457 10.001 5 10.001C3.89543 10.001 3 10.8964 3 12.001C3 13.1055 3.89543 14.001 5 14.001C6.10457 14.001 7 13.1055 7 12.001ZM14 12.001C14 10.8964 13.1046 10.001 12 10.001C10.8954 10.001 10 10.8964 10 12.001C10 13.1055 10.8954 14.001 12 14.001C13.1046 14.001 14 13.1055 14 12.001ZM19 10.001C20.1046 10.001 21 10.8964 21 12.001C21 13.1055 20.1046 14.001 19 14.001C17.8954 14.001 17 13.1055 17 12.001C17 10.8964 17.8954 10.001 19 10.001Z" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
|
||||
<Tooltip text={showContent ? "Hide " + headerText : "Show " + headerText}>
|
||||
{tooltipProps => (
|
||||
<button
|
||||
{...tooltipProps}
|
||||
className={cl("btn")}
|
||||
onClick={() => {
|
||||
setShowContent(v => !v);
|
||||
onDropDownClick?.(showContent);
|
||||
}}
|
||||
disabled={forceOpen}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
transform={showContent ? "scale(1 -1)" : "scale(1 1)"}
|
||||
>
|
||||
<path fill="var(--text-normal)" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{showContent && children}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { CSSProperties, JSX } from "react";
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
interface Props {
|
||||
columns: number;
|
||||
|
|
|
@ -16,22 +16,18 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { classes } from "@utils/misc";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export function Heart(props: SVGProps<SVGSVGElement>) {
|
||||
export function Heart() {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 16 16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
{...props}
|
||||
className={classes("vc-heart-icon", props.className)}
|
||||
style={{ marginRight: "0.5em", transform: "translateY(2px)" }}
|
||||
>
|
||||
<path
|
||||
fill="#db61a2"
|
||||
fillRule="evenodd"
|
||||
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>
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
|
||||
import "./iconStyles.css";
|
||||
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import type { JSX, PropsWithChildren } from "react";
|
||||
import { i18n } from "@webpack/common";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
interface BaseIconProps extends IconProps {
|
||||
viewBox: string;
|
||||
|
@ -55,7 +56,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
|||
className={classes(className, "vc-link-icon")}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<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>
|
||||
|
@ -64,7 +65,8 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks
|
||||
* 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 (
|
||||
|
@ -74,9 +76,8 @@ export function CopyIcon(props: IconProps) {
|
|||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="currentColor">
|
||||
<path d="M3 16a1 1 0 0 1-1-1v-5a8 8 0 0 1 8-8h5a1 1 0 0 1 1 1v.5a.5.5 0 0 1-.5.5H10a6 6 0 0 0-6 6v5.5a.5.5 0 0 1-.5.5H3Z" />
|
||||
<path d="M6 18a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-4h-3a5 5 0 0 1-5-5V6h-4a4 4 0 0 0-4 4v8Z" />
|
||||
<path d="M21.73 12a3 3 0 0 0-.6-.88l-4.25-4.24a3 3 0 0 0-.88-.61V9a3 3 0 0 0 3 3h2.73Z" />
|
||||
<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>
|
||||
);
|
||||
|
@ -122,8 +123,8 @@ export function InfoIcon(props: IconProps) {
|
|||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd"
|
||||
transform="translate(2 2)"
|
||||
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
|
@ -132,7 +133,7 @@ export function InfoIcon(props: IconProps) {
|
|||
export function OwnerCrownIcon(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
aria-label={getIntlMessage("GUILD_OWNER")}
|
||||
aria-label={i18n.Messages.GUILD_OWNER}
|
||||
{...props}
|
||||
className={classes(props.className, "vc-owner-crown-icon")}
|
||||
role="img"
|
||||
|
@ -211,10 +212,9 @@ export function CogWheel(props: IconProps) {
|
|||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
|
||||
clipRule="evenodd"
|
||||
fill="currentColor"
|
||||
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
|
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
|
|||
viewBox="0 0 18 18"
|
||||
>
|
||||
<polygon
|
||||
fillRule="nonzero"
|
||||
fill-rule="nonzero"
|
||||
fill="currentColor"
|
||||
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
||||
/>
|
||||
|
@ -407,30 +407,23 @@ export function PencilIcon(props: IconProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function GithubIcon(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
viewBox="-3 -3 30 30"
|
||||
>
|
||||
<path
|
||||
fill={props.fill || "currentColor"}
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.745.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.998.108-.775.42-1.305.763-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.398 3-.403 1.02.005 2.043.137 3 .403 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.873.118 3.176.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.625-5.475 5.92.43.37.823 1.102.823 2.222v3.293c0 .32.218.694.825.577C20.565 21.797 24 17.298 24 12c0-6.63-5.37-12-12-12z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||
|
||||
export function GithubIcon(props: ImageProps) {
|
||||
const src = getTheme() === Theme.Light
|
||||
? GithubIconLight
|
||||
: GithubIconDark;
|
||||
|
||||
return <img {...props} src={src} />;
|
||||
}
|
||||
|
||||
export function WebsiteIcon(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill={props.fill || "currentColor"}
|
||||
d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zM4 12c0-.899.156-1.762.431-2.569L6 11l2 2v2l2 2 1 1v1.931C7.061 19.436 4 16.072 4 12zm14.33 4.873C17.677 16.347 16.687 16 16 16v-1a2 2 0 0 0-2-2h-4v-3a2 2 0 0 0 2-2V7h1a2 2 0 0 0 2-2v-.411C17.928 5.778 20 8.65 20 12a7.947 7.947 0 0 1-1.67 4.873z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
export function WebsiteIcon(props: ImageProps) {
|
||||
const src = getTheme() === Theme.Light
|
||||
? WebsiteIconLight
|
||||
: WebsiteIconDark;
|
||||
|
||||
return <img {...props} src={src} />;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
|
|||
useEffect(() => {
|
||||
if (!profile && !user.bot && user.id)
|
||||
fetchUserProfile(user.id);
|
||||
}, [user.id, user.bot, profile]);
|
||||
}, [user.id]);
|
||||
|
||||
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||
|
|
|
@ -6,19 +6,16 @@
|
|||
|
||||
import "./LinkIconButton.css";
|
||||
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||
|
||||
import { GithubIcon, WebsiteIcon } from "..";
|
||||
|
||||
export function GithubLinkIcon() {
|
||||
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
|
||||
return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
|
||||
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||
}
|
||||
|
||||
export function WebsiteLinkIcon() {
|
||||
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
|
||||
return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
|
||||
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -24,20 +24,18 @@ import { classNameFactory } from "@api/Styles";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { gitRemote } from "@shared/vencordUserAgent";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||
import { find, findByProps, findComponentByCode } from "@webpack";
|
||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserUtils } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { Constructor } from "type-fest";
|
||||
|
||||
import { PluginMeta } from "~plugins";
|
||||
|
||||
import {
|
||||
ISettingCustomElementProps,
|
||||
ISettingElementProps,
|
||||
SettingBooleanComponent,
|
||||
SettingCustomComponent,
|
||||
|
@ -51,9 +49,9 @@ import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
|||
|
||||
const cl = classNameFactory("vc-plugin-modal-");
|
||||
|
||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
|
||||
const UserSummaryItem = findComponentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||
const AvatarStyles = findByProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||
const UserRecord = find<Constructor<Partial<User>>>(m => m?.prototype?.getAvatarURL && m?.prototype?.hasHadPremium);
|
||||
|
||||
interface PluginModalProps extends ModalProps {
|
||||
plugin: Plugin;
|
||||
|
@ -75,15 +73,14 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
|||
return newUser;
|
||||
}
|
||||
|
||||
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
|
||||
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,
|
||||
[OptionType.CUSTOM]: () => null,
|
||||
[OptionType.COMPONENT]: SettingCustomComponent
|
||||
};
|
||||
|
||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||
|
@ -111,7 +108,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
setAuthors(a => [...a, author]);
|
||||
}
|
||||
})();
|
||||
}, [plugin.authors]);
|
||||
}, []);
|
||||
|
||||
async function saveAndClose() {
|
||||
if (!plugin.options) {
|
||||
|
@ -131,8 +128,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
for (const [key, value] of Object.entries(tempSettings)) {
|
||||
const option = plugin.options[key];
|
||||
pluginSettings[key] = value;
|
||||
|
||||
if (option.type === OptionType.CUSTOM) continue;
|
||||
option?.onChange?.(value);
|
||||
if (option?.restartNeeded) restartNeeded = true;
|
||||
}
|
||||
if (restartNeeded) onRestartNeeded();
|
||||
|
@ -144,7 +140,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||
} else {
|
||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||
if (setting.hidden) return null;
|
||||
|
||||
function onChange(newValue: any) {
|
||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
import { PluginOptionComponent } from "@utils/types";
|
||||
|
||||
import { ISettingCustomElementProps } from ".";
|
||||
import { ISettingElementProps } from ".";
|
||||
|
||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
|
||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
|
||||
return option.component({ setValue: onChange, setError: onError, option });
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Margins } from "@utils/margins";
|
||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||
import { OptionType, PluginOptionNumber } from "@utils/types";
|
||||
import { Forms, React, TextInput } from "@webpack/common";
|
||||
|
||||
|
@ -56,8 +54,7 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
|
|||
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||
<TextInput
|
||||
type="number"
|
||||
pattern="-?[0-9]+"
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Margins } from "@utils/margins";
|
||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||
import { PluginOptionSelect } from "@utils/types";
|
||||
import { Forms, React, Select } from "@webpack/common";
|
||||
|
||||
|
@ -46,8 +44,7 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
|
|||
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
|
||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||
<Select
|
||||
isDisabled={option.disabled?.call(definedSettings) ?? false}
|
||||
options={option.options}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Margins } from "@utils/margins";
|
||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||
import { PluginOptionSlider } from "@utils/types";
|
||||
import { Forms, React, Slider } from "@webpack/common";
|
||||
|
||||
|
@ -52,8 +50,7 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings
|
|||
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||
<Slider
|
||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||
markers={option.markers}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Margins } from "@utils/margins";
|
||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||
import { PluginOptionString } from "@utils/types";
|
||||
import { Forms, React, TextInput } from "@webpack/common";
|
||||
|
||||
|
@ -43,8 +41,7 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
|
|||
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||
<TextInput
|
||||
type="text"
|
||||
value={state}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||
|
||||
interface ISettingElementPropsBase<T> {
|
||||
export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||
option: T;
|
||||
onChange(newValue: any): void;
|
||||
pluginSettings: {
|
||||
|
@ -30,9 +30,6 @@ interface ISettingElementPropsBase<T> {
|
|||
definedSettings?: DefinedSettings;
|
||||
}
|
||||
|
||||
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
|
||||
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
|
||||
|
||||
export * from "../../Badge";
|
||||
export * from "./SettingBooleanComponent";
|
||||
export * from "./SettingCustomComponent";
|
||||
|
|
|
@ -33,20 +33,19 @@ import { Margins } from "@utils/margins";
|
|||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { Plugin } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { findByProps } from "@webpack";
|
||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||
|
||||
// Avoid circular dependency
|
||||
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
||||
const PluginManager = proxyLazy(() => require("../../plugins")) as typeof import("../../plugins");
|
||||
|
||||
const cl = classNameFactory("vc-plugins-");
|
||||
const logger = new Logger("PluginSettings", "#a6d189");
|
||||
|
||||
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||
const InputStyles = findByProps("inputWrapper", "inputDefault", "error");
|
||||
const ButtonClasses = findByProps("button", "disabled", "enabled");
|
||||
|
||||
|
||||
function showErrorToast(message: string) {
|
||||
|
@ -69,7 +68,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
|
|||
<Forms.FormText className={cl("dep-text")}>
|
||||
Restart now to apply new plugins and their settings
|
||||
</Forms.FormText>
|
||||
<Button onClick={() => location.reload()} className={cl("restart-button")}>
|
||||
<Button onClick={() => location.reload()}>
|
||||
Restart
|
||||
</Button>
|
||||
</>
|
||||
|
@ -94,14 +93,14 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
|||
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
||||
const settings = Settings.plugins[plugin.name];
|
||||
|
||||
const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);
|
||||
const isEnabled = () => settings.enabled ?? false;
|
||||
|
||||
function toggleEnabled() {
|
||||
const wasEnabled = isEnabled();
|
||||
|
||||
// If we're enabling a plugin, make sure all deps are enabled recursively.
|
||||
if (!wasEnabled) {
|
||||
const { restartNeeded, failures } = startDependenciesRecursive(plugin);
|
||||
const { restartNeeded, failures } = PluginManager.startDependenciesRecursive(plugin);
|
||||
if (failures.length) {
|
||||
logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`);
|
||||
showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null);
|
||||
|
@ -127,7 +126,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
|||
return;
|
||||
}
|
||||
|
||||
const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
|
||||
const result = wasEnabled ? PluginManager.stopPlugin(plugin) : PluginManager.startPlugin(plugin);
|
||||
|
||||
if (!result) {
|
||||
settings.enabled = false;
|
||||
|
@ -158,8 +157,8 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
|||
className={classes(ButtonClasses.button, cl("info-button"))}
|
||||
>
|
||||
{plugin.options && !isObjectEmpty(plugin.options)
|
||||
? <CogWheel className={cl("info-icon")} />
|
||||
: <InfoIcon className={cl("info-icon")} />}
|
||||
? <CogWheel />
|
||||
: <InfoIcon />}
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
@ -293,10 +292,10 @@ export default function PluginSettings() {
|
|||
|
||||
if (!pluginFilter(p)) continue;
|
||||
|
||||
const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||
|
||||
if (isRequired) {
|
||||
const tooltipText = p.required || !depMap[p.name]
|
||||
const tooltipText = p.required
|
||||
? "This plugin is required for Vencord to function."
|
||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||
|
||||
|
@ -388,7 +387,7 @@ function makeDependencyList(deps: string[]) {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -63,7 +63,10 @@
|
|||
height: 8em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.vc-plugins-info-card div {
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.vc-plugins-restart-card {
|
||||
|
@ -73,11 +76,11 @@
|
|||
color: var(--info-warning-text);
|
||||
}
|
||||
|
||||
.vc-plugins-restart-button {
|
||||
.vc-plugins-restart-card button {
|
||||
margin-top: 0.5em;
|
||||
background: var(--info-warning-foreground) !important;
|
||||
}
|
||||
|
||||
.vc-plugins-info-icon:not(:hover, :focus) {
|
||||
.vc-plugins-info-button svg:not(:hover, :focus) {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import "./Switch.css";
|
||||
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { findByProps } from "@webpack";
|
||||
|
||||
interface SwitchProps {
|
||||
checked: boolean;
|
||||
|
@ -29,7 +29,7 @@ interface SwitchProps {
|
|||
|
||||
const SWITCH_ON = "var(--green-360)";
|
||||
const SWITCH_OFF = "var(--primary-400)";
|
||||
const SwitchClasses = findByPropsLazy("slider", "input", "container");
|
||||
const SwitchClasses = findByProps("slider", "input", "container");
|
||||
|
||||
export function Switch({ checked, onChange, disabled }: SwitchProps) {
|
||||
return (
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Margins } from "@utils/margins";
|
|||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
import { makeCodeblock } from "@utils/text";
|
||||
import { Patch, ReplaceFn } from "@utils/types";
|
||||
import { search } from "@webpack";
|
||||
import { searchFactories } from "@webpack";
|
||||
import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
||||
|
||||
import { SettingsTab, wrapTab } from "./shared";
|
||||
|
@ -33,7 +33,7 @@ if (IS_DEV) {
|
|||
}
|
||||
|
||||
const findCandidates = debounce(function ({ find, setModule, setError }) {
|
||||
const candidates = search(find);
|
||||
const candidates = searchFactories(find);
|
||||
const keys = Object.keys(candidates);
|
||||
const len = keys.length;
|
||||
if (len === 0)
|
||||
|
@ -56,7 +56,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||
const [compileResult, setCompileResult] = React.useState<[boolean, string]>();
|
||||
|
||||
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
||||
const src: string = fact.toString().replaceAll("\n", "");
|
||||
const src = String(fact).replaceAll("\n", "");
|
||||
|
||||
try {
|
||||
new RegExp(match);
|
||||
|
@ -65,7 +65,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||
}
|
||||
const canonicalMatch = canonicalizeMatch(new RegExp(match));
|
||||
try {
|
||||
const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]');
|
||||
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
|
||||
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
||||
setReplacementError(void 0);
|
||||
} catch (e) {
|
||||
|
@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||
}
|
||||
|
||||
function renderDiff() {
|
||||
return diff?.map((p, idx) => {
|
||||
return diff?.map(p => {
|
||||
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
||||
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
|
|||
}
|
||||
|
||||
try {
|
||||
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
|
||||
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
|
||||
|
||||
if (!parsed.find) throw new Error("No 'find' field");
|
||||
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
||||
|
@ -382,7 +382,6 @@ function PatchHelper() {
|
|||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||
<CodeBlock lang="js" content={code} />
|
||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||
<Button className={Margins.top8} onClick={() => Clipboard.copy("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
||||
</>
|
||||
)}
|
||||
</SettingsTab>
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./specialCard.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Card, Clickable, Forms, React } from "@webpack/common";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
const cl = classNameFactory("vc-special-");
|
||||
|
||||
interface StyledCardProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
description: string;
|
||||
cardImage?: string;
|
||||
backgroundImage?: string;
|
||||
backgroundColor?: string;
|
||||
buttonTitle?: string;
|
||||
buttonOnClick?: () => void;
|
||||
}
|
||||
|
||||
export function SpecialCard({ title, subtitle, description, cardImage, backgroundImage, backgroundColor, buttonTitle, buttonOnClick: onClick, children }: PropsWithChildren<StyledCardProps>) {
|
||||
const cardStyle: React.CSSProperties = {
|
||||
backgroundColor: backgroundColor || "#9c85ef",
|
||||
backgroundImage: `url(${backgroundImage || ""})`,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={cl("card", "card-special")} style={cardStyle}>
|
||||
<div className={cl("card-flex")}>
|
||||
<div className={cl("card-flex-main")}>
|
||||
<Forms.FormTitle className={cl("title")} tag="h5">{title}</Forms.FormTitle>
|
||||
<Forms.FormText className={cl("subtitle")}>{subtitle}</Forms.FormText>
|
||||
<Forms.FormText className={cl("text")}>{description}</Forms.FormText>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
{cardImage && (
|
||||
<div className={cl("image-container")}>
|
||||
<img
|
||||
role="presentation"
|
||||
src={cardImage}
|
||||
alt=""
|
||||
className={cl("image")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{buttonTitle && (
|
||||
<>
|
||||
<Forms.FormDivider className={cl("seperator")} />
|
||||
<Clickable onClick={onClick} className={cl("hyperlink")}>
|
||||
<Forms.FormText className={cl("hyperlink-text")}>
|
||||
{buttonTitle}
|
||||
</Forms.FormText>
|
||||
</Clickable>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
||||
|
@ -27,9 +27,9 @@ import { openInviteModal } from "@utils/discord";
|
|||
import { Margins } from "@utils/margins";
|
||||
import { showItemInFolder } from "@utils/native";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { findLazy } from "@webpack";
|
||||
import { findComponentByFields } from "@webpack";
|
||||
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||
import type { Ref, SyntheticEvent } from "react";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
|
@ -37,14 +37,14 @@ import { AddonCard } from "./AddonCard";
|
|||
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||
import { SettingsTab, wrapTab } from "./shared";
|
||||
|
||||
type FileInput = ComponentType<{
|
||||
type FileInputProps = {
|
||||
ref: Ref<HTMLInputElement>;
|
||||
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
|
||||
multiple?: boolean;
|
||||
filters?: { name?: string; extensions: string[]; }[];
|
||||
}>;
|
||||
};
|
||||
|
||||
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
||||
const FileInput = findComponentByFields<FileInputProps>("activateUploadDialogue", "setRef");
|
||||
|
||||
const cl = classNameFactory("vc-settings-theme-");
|
||||
|
||||
|
@ -77,16 +77,8 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
|||
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
||||
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
||||
<div>
|
||||
{themeLinks.map(rawLink => {
|
||||
const { label, link } = (() => {
|
||||
const match = /^@(light|dark) (.*)/.exec(rawLink);
|
||||
if (!match) return { label: rawLink, link: rawLink };
|
||||
|
||||
const [, mode, link] = match;
|
||||
return { label: `[${mode} mode only] ${link}`, link };
|
||||
})();
|
||||
|
||||
return <Card style={{
|
||||
{themeLinks.map(link => (
|
||||
<Card style={{
|
||||
padding: ".5em",
|
||||
marginBottom: ".5em",
|
||||
marginTop: ".5em"
|
||||
|
@ -94,11 +86,11 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
|||
<Forms.FormTitle tag="h5" style={{
|
||||
overflowWrap: "break-word"
|
||||
}}>
|
||||
{label}
|
||||
{link}
|
||||
</Forms.FormTitle>
|
||||
<Validator link={link} />
|
||||
</Card>;
|
||||
})}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -257,7 +249,7 @@ function ThemesTab() {
|
|||
Icon={PaintbrushIcon}
|
||||
/>
|
||||
|
||||
{Settings.plugins.ClientTheme.enabled && (
|
||||
{Vencord.Plugins.isPluginEnabled("ClientTheme") && (
|
||||
<QuickAction
|
||||
text="Edit ClientTheme"
|
||||
action={() => openPluginModal(Plugins.ClientTheme)}
|
||||
|
@ -304,7 +296,6 @@ function ThemesTab() {
|
|||
<Card className="vc-settings-card vc-text-selectable">
|
||||
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
||||
<Forms.FormText>One link per line</Forms.FormText>
|
||||
<Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
|
||||
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
|
||||
</Card>
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
|||
title: "Oops!",
|
||||
body: (
|
||||
<ErrorCard>
|
||||
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
|
||||
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
|
||||
</ErrorCard>
|
||||
)
|
||||
});
|
||||
|
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
|
|||
return (
|
||||
<Card style={{ padding: "0 0.5em" }}>
|
||||
{updates.map(({ hash, author, message }) => (
|
||||
<div key={hash} style={{
|
||||
<div style={{
|
||||
marginTop: "0.5em",
|
||||
marginBottom: "0.5em"
|
||||
}}>
|
||||
|
|
|
@ -20,38 +20,29 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
|||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import DonateButton from "@components/DonateButton";
|
||||
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||
import { gitRemote } from "@shared/vencordUserAgent";
|
||||
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { identity, isPluginDev } from "@utils/misc";
|
||||
import { identity } from "@utils/misc";
|
||||
import { relaunch, showItemInFolder } from "@utils/native";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
|
||||
import { Button, Card, Forms, React, Select, Switch } from "@webpack/common";
|
||||
|
||||
import BadgeAPI from "../../plugins/_api/badges";
|
||||
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||
import { SettingsTab, wrapTab } from "./shared";
|
||||
import { SpecialCard } from "./SpecialCard";
|
||||
|
||||
const cl = classNameFactory("vc-settings-");
|
||||
|
||||
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
|
||||
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png";
|
||||
|
||||
const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png";
|
||||
const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png";
|
||||
|
||||
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
|
||||
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
|
||||
|
||||
type KeysOfType<Object, Type> = {
|
||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||
}[keyof Object];
|
||||
|
||||
|
||||
function VencordSettings() {
|
||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||
fallbackValue: "Loading..."
|
||||
|
@ -64,8 +55,6 @@ function VencordSettings() {
|
|||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
||||
|
||||
const user = UserStore.getCurrentUser();
|
||||
|
||||
const Switches: Array<false | {
|
||||
key: KeysOfType<typeof settings, boolean>;
|
||||
title: string;
|
||||
|
@ -110,44 +99,7 @@ function VencordSettings() {
|
|||
|
||||
return (
|
||||
<SettingsTab title="Vencord Settings">
|
||||
{isDonor(user?.id)
|
||||
? (
|
||||
<SpecialCard
|
||||
title="Donations"
|
||||
subtitle="Thank you for donating!"
|
||||
description="All Vencord users can see your badge! You can change it at any time by messaging @vending.machine."
|
||||
cardImage={VENNIE_DONATOR_IMAGE}
|
||||
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
||||
backgroundColor="#ED87A9"
|
||||
>
|
||||
<DonateButtonComponent />
|
||||
</SpecialCard>
|
||||
)
|
||||
: (
|
||||
<SpecialCard
|
||||
title="Support the Project"
|
||||
description="Please consider supporting the development of Vencord by donating!"
|
||||
cardImage={donateImage}
|
||||
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
||||
backgroundColor="#c3a3ce"
|
||||
>
|
||||
<DonateButtonComponent />
|
||||
</SpecialCard>
|
||||
)
|
||||
}
|
||||
{isPluginDev(user?.id) && (
|
||||
<SpecialCard
|
||||
title="Contributions"
|
||||
subtitle="Thank you for contributing!"
|
||||
description="Since you've contributed to Vencord you now have a cool new badge!"
|
||||
cardImage={COZY_CONTRIB_IMAGE}
|
||||
backgroundImage={CONTRIB_BACKGROUND_IMAGE}
|
||||
backgroundColor="#EDCC87"
|
||||
buttonTitle="See what you've contributed to"
|
||||
buttonOnClick={() => openContributorModal(user)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DonateCard image={donateImage} />
|
||||
<Forms.FormSection title="Quick Actions">
|
||||
<QuickActionCard>
|
||||
<QuickAction
|
||||
|
@ -287,19 +239,31 @@ function VencordSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
function DonateButtonComponent() {
|
||||
interface DonateCardProps {
|
||||
image: string;
|
||||
}
|
||||
|
||||
function DonateCard({ image }: DonateCardProps) {
|
||||
return (
|
||||
<DonateButton
|
||||
look={Button.Looks.FILLED}
|
||||
color={Button.Colors.WHITE}
|
||||
style={{ marginTop: "1em" }}
|
||||
/>
|
||||
<Card className={cl("card", "donate")}>
|
||||
<div>
|
||||
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle>
|
||||
<Forms.FormText>Please consider supporting the development of Vencord by donating!</Forms.FormText>
|
||||
<DonateButton style={{ transform: "translateX(-1em)" }} />
|
||||
</div>
|
||||
<img
|
||||
role="presentation"
|
||||
src={image}
|
||||
alt=""
|
||||
height={128}
|
||||
style={{
|
||||
imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0,
|
||||
marginLeft: "auto",
|
||||
transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function isDonor(userId: string): boolean {
|
||||
const donorBadges = BadgeAPI.getDonorBadges(userId);
|
||||
return GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges;
|
||||
}
|
||||
|
||||
export default wrapTab(VencordSettings, "Vencord Settings");
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
.vc-settings-quickActions-card {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, max-content));
|
||||
gap: 0.5em;
|
||||
padding: 0.5em;
|
||||
justify-content: center;
|
||||
padding: 0.5em 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
@media (width <=1040px) {
|
||||
.vc-settings-quickActions-card {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.vc-settings-quickActions-pill {
|
||||
all: unset;
|
||||
background: var(--background-secondary);
|
||||
|
@ -19,16 +14,12 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
padding: 8px 9px;
|
||||
border-radius: 8px;
|
||||
transition: 0.1s ease-out;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 12px;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.vc-settings-quickActions-pill:hover {
|
||||
background: var(--background-secondary-alt);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--elevation-high);
|
||||
}
|
||||
|
||||
.vc-settings-quickActions-pill:focus-visible {
|
||||
|
@ -39,4 +30,4 @@
|
|||
.vc-settings-quickActions-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
.vc-donate-button {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.vc-donate-button .vc-heart-icon {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.vc-donate-button:hover .vc-heart-icon {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vc-settings-card {
|
||||
padding: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vc-special-card-special {
|
||||
padding: 1em 1.5em;
|
||||
margin-bottom: 1em;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.vc-special-card-flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.vc-special-card-flex-main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vc-special-title {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.vc-special-subtitle {
|
||||
color: black;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.vc-special-text {
|
||||
color: black;
|
||||
font-size: 1em;
|
||||
margin-top: .75em;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.vc-special-seperator {
|
||||
margin-top: .75em;
|
||||
border-top: 1px solid white;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.vc-special-hyperlink {
|
||||
margin-top: 1em;
|
||||
cursor: pointer;
|
||||
|
||||
.vc-special-hyperlink-text {
|
||||
color: black;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
transition: text-decoration 0.5s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover .vc-special-hyperlink-text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-special-image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 1em;
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.vc-special-image {
|
||||
width: 65%;
|
||||
}
|
|
@ -5,8 +5,3 @@
|
|||
.vc-owner-crown-icon {
|
||||
color: var(--text-warning);
|
||||
}
|
||||
|
||||
.vc-heart-icon {
|
||||
margin-right: 0.5em;
|
||||
translate: 0 2px;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ export * from "./CodeBlock";
|
|||
export * from "./DonateButton";
|
||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||
export * from "./ErrorCard";
|
||||
export * from "./ExpandableHeader";
|
||||
export * from "./Flex";
|
||||
export * from "./Heart";
|
||||
export * from "./Icons";
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import * as Webpack from "@webpack";
|
||||
import { wreq } from "@webpack";
|
||||
import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d";
|
||||
import { AnyModuleFactory, ModuleFactory } from "webpack";
|
||||
|
||||
export async function loadLazyChunks() {
|
||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||
|
@ -16,36 +16,24 @@ export async function loadLazyChunks() {
|
|||
try {
|
||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||
|
||||
const validChunks = new Set<PropertyKey>();
|
||||
const invalidChunks = new Set<PropertyKey>();
|
||||
const deferredRequires = new Set<PropertyKey>();
|
||||
const validChunks = new Set<number>();
|
||||
const invalidChunks = new Set<number>();
|
||||
const deferredRequires = new Set<number>();
|
||||
|
||||
const { promise: chunksSearchingDone, resolve: chunksSearchingResolve } = Promise.withResolvers<void>();
|
||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||
|
||||
// True if resolved, false otherwise
|
||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||
|
||||
/* This regex loads all language packs which makes webpack finds testing extremely slow, so for now, lets use one which doesnt include those
|
||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i(?:\.\i)?\.bind\(\i,"?([^)]+?)"?(?:,[^)]+?)?\)\)/g);
|
||||
*/
|
||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||
|
||||
let foundCssDebuggingLoad = false;
|
||||
|
||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
|
||||
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
|
||||
|
||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||
const validChunkGroups = new Set<[chunkIds: PropertyKey[], entryPoint: PropertyKey]>();
|
||||
|
||||
const shouldForceDefer = false;
|
||||
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
|
||||
|
||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => {
|
||||
const numChunkId = Number(m[1]);
|
||||
return Number.isNaN(numChunkId) ? m[1] : numChunkId;
|
||||
}) : [];
|
||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
|
||||
|
||||
if (chunkIds.length === 0) {
|
||||
return;
|
||||
|
@ -54,21 +42,11 @@ export async function loadLazyChunks() {
|
|||
let invalidChunkGroup = false;
|
||||
|
||||
for (const id of chunkIds) {
|
||||
if (hasCssDebuggingLoad) {
|
||||
if (chunkIds.length > 1) {
|
||||
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
|
||||
}
|
||||
|
||||
invalidChunks.add(id);
|
||||
invalidChunkGroup = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||
|
||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => /importScripts\(|self\.postMessage/.test(t));
|
||||
.then(t => t.includes("importScripts("));
|
||||
|
||||
if (isWorkerAsset) {
|
||||
invalidChunks.add(id);
|
||||
|
@ -80,8 +58,7 @@ export async function loadLazyChunks() {
|
|||
}
|
||||
|
||||
if (!invalidChunkGroup) {
|
||||
const numEntryPoint = Number(entryPoint);
|
||||
validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]);
|
||||
validChunkGroups.add([chunkIds, Number(entryPoint)]);
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -96,11 +73,6 @@ export async function loadLazyChunks() {
|
|||
// Requires the entry points for all valid chunk groups
|
||||
for (const [, entryPoint] of validChunkGroups) {
|
||||
try {
|
||||
if (shouldForceDefer) {
|
||||
deferredRequires.add(entryPoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wreq.m[entryPoint]) wreq(entryPoint);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -139,8 +111,8 @@ export async function loadLazyChunks() {
|
|||
}
|
||||
|
||||
Webpack.factoryListeners.add(factoryListener);
|
||||
for (const moduleId in wreq.m) {
|
||||
factoryListener(wreq.m[moduleId]);
|
||||
for (const factoryId in wreq.m) {
|
||||
factoryListener(wreq.m[factoryId]);
|
||||
}
|
||||
|
||||
await chunksSearchingDone;
|
||||
|
@ -152,15 +124,14 @@ export async function loadLazyChunks() {
|
|||
}
|
||||
|
||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||
const allChunks = [] as PropertyKey[];
|
||||
const allChunks = [] as number[];
|
||||
|
||||
// Matches "id" or id:
|
||||
for (const currentMatch of String(wreq.u).matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
||||
const id = currentMatch[1] ?? currentMatch[2];
|
||||
if (id == null) continue;
|
||||
|
||||
const numId = Number(id);
|
||||
allChunks.push(Number.isNaN(numId) ? id : numId);
|
||||
allChunks.push(Number(id));
|
||||
}
|
||||
|
||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||
|
@ -174,7 +145,7 @@ export async function loadLazyChunks() {
|
|||
await Promise.all(chunksLeft.map(async id => {
|
||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
.then(t => /importScripts\(|self\.postMessage/.test(t));
|
||||
.then(t => t.includes("importScripts("));
|
||||
|
||||
// Loads the chunk. Currently this only happens with the language packs which are loaded differently
|
||||
if (!isWorkerAsset) {
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { SYM_LAZY_COMPONENT_INNER } from "@utils/lazyReact";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { SYM_PROXY_INNER_GET, SYM_PROXY_INNER_VALUE } from "@utils/proxyInner";
|
||||
import * as Webpack from "@webpack";
|
||||
import { getBuildNumber, patchTimings } from "@webpack/patcher";
|
||||
import { addPatch, patches } from "plugins";
|
||||
|
||||
import { addPatch, patches } from "../plugins";
|
||||
import { loadLazyChunks } from "./loadLazyChunks";
|
||||
|
||||
async function runReporter() {
|
||||
|
@ -17,7 +18,8 @@ async function runReporter() {
|
|||
try {
|
||||
ReporterLogger.log("Starting test...");
|
||||
|
||||
const { promise: loadLazyChunksDone, resolve: loadLazyChunksResolve } = Promise.withResolvers<void>();
|
||||
let loadLazyChunksResolve: (value: void) => void;
|
||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||
|
||||
// The main patch for starting the reporter chunk loading
|
||||
addPatch({
|
||||
|
@ -37,78 +39,164 @@ async function runReporter() {
|
|||
|
||||
await loadLazyChunksDone;
|
||||
|
||||
if (IS_REPORTER && IS_WEB && !IS_VESKTOP) {
|
||||
console.log("[REPORTER_META]", {
|
||||
buildNumber: getBuildNumber(),
|
||||
buildHash: window.GLOBAL_ENV.SENTRY_TAGS.buildId
|
||||
});
|
||||
}
|
||||
|
||||
for (const patch of patches) {
|
||||
if (!patch.all) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [plugin, moduleId, match, totalTime] of patchTimings) {
|
||||
if (totalTime > 5) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) {
|
||||
if (totalTime > 3) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${totalTime}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
|
||||
let method = searchType;
|
||||
await Promise.all(Webpack.webpackSearchHistory.map(async ([searchType, args]) => {
|
||||
args = [...args];
|
||||
|
||||
if (searchType === "findComponent") method = "find";
|
||||
if (searchType === "findExportedComponent") method = "findByProps";
|
||||
if (searchType === "waitFor" || searchType === "waitForComponent") {
|
||||
if (typeof args[0] === "string") method = "findByProps";
|
||||
else method = "find";
|
||||
}
|
||||
if (searchType === "waitForStore") method = "findStore";
|
||||
|
||||
let result: any;
|
||||
let result = null as any;
|
||||
try {
|
||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
const [factory] = args;
|
||||
result = factory();
|
||||
} else if (method === "extractAndLoadChunks") {
|
||||
const [code, matcher] = args;
|
||||
switch (searchType) {
|
||||
case "webpackDependantLazy":
|
||||
case "webpackDependantLazyComponent": {
|
||||
const [factory] = args;
|
||||
result = factory();
|
||||
break;
|
||||
}
|
||||
case "extractAndLoadChunks": {
|
||||
const extractAndLoadChunks = args.shift();
|
||||
|
||||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||
if (result === false) result = null;
|
||||
} else if (method === "mapMangledModule") {
|
||||
const [code, mapper, includeBlacklistedExports] = args;
|
||||
result = await extractAndLoadChunks();
|
||||
if (result === false) {
|
||||
result = null;
|
||||
}
|
||||
|
||||
result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports);
|
||||
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
||||
} else {
|
||||
// @ts-ignore
|
||||
result = Webpack[method](...args);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const findResult = args.shift();
|
||||
|
||||
if (findResult != null) {
|
||||
if (findResult.$$vencordCallbackCalled != null && findResult.$$vencordCallbackCalled()) {
|
||||
result = findResult;
|
||||
break;
|
||||
}
|
||||
|
||||
if (findResult[SYM_PROXY_INNER_GET] != null) {
|
||||
result = findResult[SYM_PROXY_INNER_VALUE];
|
||||
break;
|
||||
}
|
||||
|
||||
if (findResult[SYM_LAZY_COMPONENT_INNER] != null) {
|
||||
result = findResult[SYM_LAZY_COMPONENT_INNER]();
|
||||
break;
|
||||
}
|
||||
|
||||
if (searchType === "mapMangledModule") {
|
||||
result = findResult;
|
||||
|
||||
for (const innerMap in result) {
|
||||
if (
|
||||
(result[innerMap][SYM_PROXY_INNER_GET] != null && result[innerMap][SYM_PROXY_INNER_VALUE] == null) ||
|
||||
(result[innerMap][SYM_LAZY_COMPONENT_INNER] != null && result[innerMap][SYM_LAZY_COMPONENT_INNER]() == null)
|
||||
) {
|
||||
throw new Error("Webpack Find Fail");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// This can happen if a `find` was immediately found
|
||||
result = findResult;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||
if (result == null) {
|
||||
throw new Error("Webpack Find Fail");
|
||||
}
|
||||
} catch (e) {
|
||||
let logMessage = searchType;
|
||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
if (args[0].$$vencordProps != null) {
|
||||
logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
|
||||
} else {
|
||||
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||
}
|
||||
} else if (method === "extractAndLoadChunks") {
|
||||
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||
} else if (method === "mapMangledModule") {
|
||||
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
||||
|
||||
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||
let filterName = "";
|
||||
let parsedArgs = args;
|
||||
|
||||
if (args[0].$$vencordProps != null) {
|
||||
if (["find", "findComponent", "waitFor"].includes(searchType)) {
|
||||
filterName = args[0].$$vencordProps[0];
|
||||
parsedArgs = args[0].$$vencordProps.slice(1);
|
||||
} else {
|
||||
parsedArgs = args[0].$$vencordProps;
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyFilter(code: Webpack.CodeFilterWithSingle) {
|
||||
if (Array.isArray(code)) {
|
||||
return `[${code.map(arg => arg instanceof RegExp ? String(arg) : JSON.stringify(arg)).join(", ")}]`;
|
||||
}
|
||||
|
||||
return code instanceof RegExp ? String(code) : JSON.stringify(code);
|
||||
}
|
||||
|
||||
// if parsedArgs is the same as args, it means vencordProps of the filter was not available (like in normal filter functions),
|
||||
// so log the filter function instead
|
||||
if (
|
||||
parsedArgs === args &&
|
||||
["waitFor", "find", "findComponent", "webpackDependantLazy", "webpackDependantLazyComponent"].includes(searchType)
|
||||
) {
|
||||
let filter = String(parsedArgs[0]);
|
||||
if (filter.length > 150) {
|
||||
filter = filter.slice(0, 147) + "...";
|
||||
}
|
||||
|
||||
logMessage += `(${filter})`;
|
||||
} else if (searchType === "extractAndLoadChunks") {
|
||||
const [code, matcher] = parsedArgs;
|
||||
|
||||
let regexStr: string;
|
||||
if (matcher === Webpack.DefaultExtractAndLoadChunksRegex) {
|
||||
regexStr = "DefaultExtractAndLoadChunksRegex";
|
||||
} else {
|
||||
regexStr = String(matcher);
|
||||
}
|
||||
|
||||
logMessage += `(${stringifyFilter(code)}, ${regexStr})`;
|
||||
} else if (searchType === "mapMangledModule") {
|
||||
const [code, mappers] = parsedArgs;
|
||||
|
||||
const parsedFailedMappers = Object.entries<any>(mappers)
|
||||
.filter(([key]) =>
|
||||
result == null ||
|
||||
(result[key]?.[SYM_PROXY_INNER_GET] != null && result[key][SYM_PROXY_INNER_VALUE] == null) ||
|
||||
(result[key]?.[SYM_LAZY_COMPONENT_INNER] != null && result[key][SYM_LAZY_COMPONENT_INNER]() == null)
|
||||
)
|
||||
.map(([key, filter]) => {
|
||||
let parsedFilter: string;
|
||||
|
||||
if (filter.$$vencordProps != null) {
|
||||
const filterName = filter.$$vencordProps[0];
|
||||
parsedFilter = `${filterName}(${filter.$$vencordProps.slice(1).map((arg: any) => arg instanceof RegExp ? String(arg) : JSON.stringify(arg)).join(", ")})`;
|
||||
} else {
|
||||
parsedFilter = String(filter);
|
||||
if (parsedFilter.length > 150) {
|
||||
parsedFilter = parsedFilter.slice(0, 147) + "...";
|
||||
}
|
||||
}
|
||||
|
||||
return [key, parsedFilter];
|
||||
});
|
||||
|
||||
logMessage += `(${stringifyFilter(code)}, {\n${parsedFailedMappers.map(([key, parsedFilter]) => `\t${key}: ${parsedFilter}`).join(",\n")}\n})`;
|
||||
} else {
|
||||
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||
logMessage += `(${filterName.length ? `${filterName}(` : ""}${parsedArgs.map(stringifyFilter).join(", ")})${filterName.length ? ")" : ""}`;
|
||||
}
|
||||
|
||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
ReporterLogger.log("Finished test");
|
||||
} catch (e) {
|
||||
|
@ -116,6 +204,5 @@ async function runReporter() {
|
|||
}
|
||||
}
|
||||
|
||||
// Run after the Vencord object has been created.
|
||||
// We need to add extra properties to it, and it is only created after all of Vencord code has ran
|
||||
// Run after the Vencord object has been created
|
||||
setTimeout(runReporter, 0);
|
||||
|
|
|
@ -71,16 +71,13 @@ export async function installExt(id: string) {
|
|||
// React Devtools v4.25
|
||||
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
|
||||
// Unfortunately, Google does not serve old versions, so this is the only way
|
||||
// This zip file is pinned to long commit hash so it cannot be changed remotely
|
||||
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
|
||||
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
|
||||
|
||||
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`;
|
||||
const buf = await get(url, {
|
||||
headers: {
|
||||
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
|
||||
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)"
|
||||
}
|
||||
});
|
||||
|
||||
await extract(crxToZip(buf), extDir).catch(console.error);
|
||||
}
|
||||
|
||||
|
|
3
src/plugins/_api/badges/fixBadgeOverflow.css
Normal file
3
src/plugins/_api/badges/fixBadgeOverflow.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
[class*="profileBadges"] {
|
||||
flex: none;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/* the profile popout badge container(s) */
|
||||
[class*="biteSize_"] [class*="tags_"] [class*="container_"] {
|
||||
/* Discord has padding set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */
|
||||
padding: 0 1px;
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./fixDiscordBadgePadding.css";
|
||||
import "./fixBadgeOverflow.css";
|
||||
|
||||
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
||||
import DonateButton from "@components/DonateButton";
|
||||
|
@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { isPluginDev } from "@utils/misc";
|
||||
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
@ -79,7 +79,7 @@ export default definePlugin({
|
|||
replace: "...$1.props,$& $1.image??"
|
||||
},
|
||||
{
|
||||
match: /(?<="aria-label":(\i)\.description,.{0,200})children:/,
|
||||
match: /(?<=text:(\i)\.description,.{0,50})children:/,
|
||||
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||
},
|
||||
// conditionally override their onClick with badge.onClick if it exists
|
||||
|
@ -102,9 +102,8 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
userProfileBadge: ContributorBadge,
|
||||
|
||||
async start() {
|
||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
||||
await loadBadges();
|
||||
},
|
||||
|
||||
|
@ -144,8 +143,8 @@ export default definePlugin({
|
|||
closeModal(modalKey);
|
||||
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
||||
}}>
|
||||
<ModalRoot {...props}>
|
||||
<ModalHeader>
|
||||
<Modals.ModalRoot {...props}>
|
||||
<Modals.ModalHeader>
|
||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||
<Forms.FormTitle
|
||||
tag="h2"
|
||||
|
@ -159,8 +158,8 @@ export default definePlugin({
|
|||
Vencord Donor
|
||||
</Forms.FormTitle>
|
||||
</Flex>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
</Modals.ModalHeader>
|
||||
<Modals.ModalContent>
|
||||
<Flex>
|
||||
<img
|
||||
role="presentation"
|
||||
|
@ -183,13 +182,13 @@ export default definePlugin({
|
|||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||
</Forms.FormText>
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
</Modals.ModalContent>
|
||||
<Modals.ModalFooter>
|
||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||
<DonateButton />
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
</Modals.ModalFooter>
|
||||
</Modals.ModalRoot>
|
||||
</ErrorBoundary>
|
||||
));
|
||||
},
|
||||
|
|
|
@ -12,16 +12,11 @@ export default definePlugin({
|
|||
description: "API to add buttons to the chat input",
|
||||
authors: [Devs.Ven],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '"sticker")',
|
||||
replacement: {
|
||||
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
|
||||
replace: (m, not, children) => not
|
||||
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
|
||||
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
|
||||
}
|
||||
patches: [{
|
||||
find: '"sticker")',
|
||||
replacement: {
|
||||
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
||||
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
||||
}
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
|
|
@ -34,22 +34,12 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: "navId:",
|
||||
find: ".Menu,{",
|
||||
all: true,
|
||||
noWarn: true,
|
||||
replacement: [
|
||||
{
|
||||
match: /navId:(?=.+?([,}].*?\)))/g,
|
||||
replace: (m, rest) => {
|
||||
// Check if this navId: match is a destructuring statement, ignore it if it is
|
||||
const destructuringMatch = rest.match(/}=.+/);
|
||||
if (destructuringMatch == null) {
|
||||
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
}
|
||||
]
|
||||
replacement: {
|
||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -19,15 +19,10 @@
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
import managedStyle from "./style.css?managed";
|
||||
|
||||
export default definePlugin({
|
||||
name: "MemberListDecoratorsAPI",
|
||||
description: "API to add decorators to member list (both in servers and DMs)",
|
||||
authors: [Devs.TheSun, Devs.Ven],
|
||||
|
||||
managedStyle,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".lostPermission)",
|
||||
|
@ -36,8 +31,8 @@ export default definePlugin({
|
|||
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
||||
replace: "$&vencordProps=$1,"
|
||||
}, {
|
||||
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
||||
replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
||||
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -45,8 +40,8 @@ export default definePlugin({
|
|||
find: "PrivateChannel.renderAvatar",
|
||||
replacement: {
|
||||
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
||||
replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]"
|
||||
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
.vc-member-list-decorators-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.vc-member-list-decorators-wrapper:not(:empty) {
|
||||
/* Margin to match default Discord decorators */
|
||||
margin-left: 0.25em;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
// duplicate values have multiple branches with different types. Just include all to be safe
|
||||
const nameMap = {
|
||||
radio: "MenuRadioItem",
|
||||
separator: "MenuSeparator",
|
||||
checkbox: "MenuCheckboxItem",
|
||||
groupstart: "MenuGroup",
|
||||
|
||||
control: "MenuControlItem",
|
||||
compositecontrol: "MenuControlItem",
|
||||
|
||||
item: "MenuItem",
|
||||
customitem: "MenuItem",
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "MenuItemDemanglerAPI",
|
||||
description: "Demangles Discord's Menu Item module",
|
||||
authors: [Devs.Ven],
|
||||
required: true,
|
||||
patches: [
|
||||
{
|
||||
find: '"Menu API',
|
||||
replacement: {
|
||||
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
|
||||
replace: (m, mod) => {
|
||||
const nameAssignments = [] as string[];
|
||||
|
||||
// if (t.type === m.MenuItem)
|
||||
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
|
||||
// push({type:"item"})
|
||||
const pushTypeRe = /type:"(\w+)"/g;
|
||||
|
||||
let typeMatch: RegExpExecArray | null;
|
||||
// for each if (t.type === ...)
|
||||
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
|
||||
// extract the current menu item
|
||||
const item = typeMatch[1];
|
||||
// Set the starting index of the second regex to that of the first to start
|
||||
// matching from after the if
|
||||
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
|
||||
// extract the first type: "..."
|
||||
const type = pushTypeRe.exec(m)?.[1];
|
||||
if (type && type in nameMap) {
|
||||
const name = nameMap[type];
|
||||
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
|
||||
}
|
||||
}
|
||||
if (nameAssignments.length < 6) {
|
||||
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
|
||||
}
|
||||
|
||||
// Merge all our redefines with the actual module
|
||||
return `${nameAssignments.join(";")};${m}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
|||
authors: [Devs.Cyn],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
|
||||
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
||||
replacement: {
|
||||
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
||||
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
||||
|
|
|
@ -19,22 +19,17 @@
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
import managedStyle from "./style.css?managed";
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageDecorationsAPI",
|
||||
description: "API to add decorations to messages",
|
||||
authors: [Devs.TheSun],
|
||||
|
||||
managedStyle,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '"Message Username"',
|
||||
replacement: {
|
||||
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
|
||||
replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
||||
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
.vc-message-decorations-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.vc-message-decorations-wrapper:not(:empty) {
|
||||
/* Margin to match default Discord decorators */
|
||||
margin-left: 0.25em;
|
||||
|
||||
/* Align vertically */
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
top: 0.1rem;
|
||||
height: calc(1rem + 4px);
|
||||
max-height: calc(1rem + 4px)
|
||||
}
|
|
@ -25,23 +25,26 @@ export default definePlugin({
|
|||
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::EDIT_TEXTAREA_HELP}",
|
||||
find: ".Messages.EDIT_TEXTAREA_HELP",
|
||||
replacement: {
|
||||
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
||||
replace: (match, args) => "" +
|
||||
`async ${match}` +
|
||||
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
||||
"return Promise.resolve({shouldClear:false,shouldRefocus:true});"
|
||||
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".handleSendMessage,onResize",
|
||||
replacement: {
|
||||
// https://regex101.com/r/hBlXpl/1
|
||||
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
|
||||
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
|
||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
||||
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
||||
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
||||
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
||||
`${rest1}async ${rest2}` +
|
||||
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
||||
"return{shouldClear:false,shouldRefocus:true};"
|
||||
"return{shoudClear:true,shouldRefocus:true};"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -49,7 +52,8 @@ export default definePlugin({
|
|||
replacement: {
|
||||
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||
replace: (m, message, channel, event) =>
|
||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
|
||||
// the message param is shadowed by the event param, so need to alias them
|
||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -23,14 +23,11 @@ export default definePlugin({
|
|||
name: "MessagePopoverAPI",
|
||||
description: "API to add buttons to message popovers.",
|
||||
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||
replacement: {
|
||||
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||
}
|
||||
patches: [{
|
||||
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
||||
replacement: {
|
||||
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.Messages\.MESSAGE_ACTION_REPLY.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
|
||||
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
|
||||
}
|
||||
]
|
||||
}],
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||
replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||
replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -25,16 +25,16 @@ export default definePlugin({
|
|||
description: "Api required for plugins that modify the server list",
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::DISCODO_DISABLED}",
|
||||
find: "Messages.DISCODO_DISABLED",
|
||||
replacement: {
|
||||
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::SERVERS}),children",
|
||||
find: "Messages.SERVERS,children",
|
||||
replacement: {
|
||||
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
|
||||
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
|
||||
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { WebpackRequire } from "@webpack/wreq.d";
|
||||
import { WebpackRequire } from "webpack";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
disableAnalytics: {
|
||||
|
@ -49,7 +49,7 @@ export default definePlugin({
|
|||
},
|
||||
},
|
||||
{
|
||||
find: ".METRICS",
|
||||
find: ".METRICS,",
|
||||
replacement: [
|
||||
{
|
||||
match: /this\._intervalId=/,
|
||||
|
@ -62,13 +62,13 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
find: ".BetterDiscord||null!=",
|
||||
find: ".installedLogHooks)",
|
||||
replacement: {
|
||||
// Make hasClientMods return false
|
||||
match: /(?=let \i=window;)/,
|
||||
replace: "return false;"
|
||||
// if getDebugLogging() returns false, the hooks don't get installed.
|
||||
match: "getDebugLogging(){",
|
||||
replace: "getDebugLogging(){return false;"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
startAt: StartAt.Init,
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Settings } from "@api/Settings";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import BackupAndRestoreTab from "@components/VencordSettings/BackupAndRestoreTab";
|
||||
import CloudTab from "@components/VencordSettings/CloudTab";
|
||||
import PatchHelperTab from "@components/VencordSettings/PatchHelperTab";
|
||||
|
@ -25,20 +25,35 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
|
|||
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
||||
import VencordTab from "@components/VencordSettings/VencordTab";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
import { i18n, React } from "@webpack/common";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
|
||||
type SectionType = "HEADER" | "DIVIDER" | "CUSTOM";
|
||||
type SectionTypes = Record<SectionType, SectionType>;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
settingsLocation: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Where to put the Vencord settings section",
|
||||
options: [
|
||||
{ label: "At the very top", value: "top" },
|
||||
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
|
||||
{ label: "Below the Nitro section", value: "belowNitro" },
|
||||
{ label: "Above Activity Settings", value: "aboveActivity" },
|
||||
{ label: "Below Activity Settings", value: "belowActivity" },
|
||||
{ label: "At the very bottom", value: "bottom" },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "Settings",
|
||||
description: "Adds Settings UI and debug info",
|
||||
authors: [Devs.Ven, Devs.Megu],
|
||||
required: true,
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
|
@ -58,21 +73,20 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
find: ".SEARCH_NO_RESULTS&&0===",
|
||||
find: "Messages.ACTIVITY_SETTINGS",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||
},
|
||||
{
|
||||
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
|
||||
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||
replacement: {
|
||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
||||
replace: "$2.open($1);return;"
|
||||
|
@ -138,33 +152,25 @@ export default definePlugin({
|
|||
].filter(Boolean);
|
||||
},
|
||||
|
||||
isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) {
|
||||
const firstChild = settings?.[0];
|
||||
isRightSpot({ header, settingsChilds }: { header?: string; settingsChilds?: string[]; }) {
|
||||
const firstChild = settingsChilds?.[0];
|
||||
// lowest two elements... sanity backup
|
||||
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
|
||||
|
||||
const { settingsLocation } = Settings.plugins.Settings;
|
||||
const { settingsLocation } = settings.store;
|
||||
|
||||
if (settingsLocation === "bottom") return firstChild === "LOGOUT";
|
||||
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
|
||||
|
||||
if (!header) return;
|
||||
|
||||
try {
|
||||
const names = {
|
||||
top: getIntlMessage("USER_SETTINGS"),
|
||||
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
|
||||
belowNitro: getIntlMessage("APP_SETTINGS"),
|
||||
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
||||
};
|
||||
|
||||
if (!names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS"))
|
||||
return firstChild === "PREMIUM";
|
||||
|
||||
return header === names[settingsLocation];
|
||||
} catch {
|
||||
return firstChild === "PREMIUM";
|
||||
}
|
||||
const names = {
|
||||
top: i18n.Messages.USER_SETTINGS,
|
||||
aboveNitro: i18n.Messages.BILLING_SETTINGS,
|
||||
belowNitro: i18n.Messages.APP_SETTINGS,
|
||||
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
|
||||
};
|
||||
return header === names[settingsLocation];
|
||||
},
|
||||
|
||||
patchedSettings: new WeakSet(),
|
||||
|
@ -191,23 +197,8 @@ export default definePlugin({
|
|||
};
|
||||
},
|
||||
|
||||
options: {
|
||||
settingsLocation: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Where to put the Vencord settings section",
|
||||
options: [
|
||||
{ label: "At the very top", value: "top" },
|
||||
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
|
||||
{ label: "Below the Nitro section", value: "belowNitro" },
|
||||
{ label: "Above Activity Settings", value: "aboveActivity" },
|
||||
{ label: "Below Activity Settings", value: "belowActivity" },
|
||||
{ label: "At the very bottom", value: "bottom" },
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
get electronVersion() {
|
||||
return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
|
||||
return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
|
||||
},
|
||||
|
||||
get chromiumVersion() {
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addAccessory } from "@api/MessageAccessories";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||
import { sendMessage } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
@ -33,13 +34,15 @@ import { makeCodeblock } from "@utils/text";
|
|||
import definePlugin from "@utils/types";
|
||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
import plugins, { PluginMeta } from "~plugins";
|
||||
|
||||
import SettingsPlugin from "./settings";
|
||||
|
||||
const VENCORD_GUILD_ID = "1015060230222131221";
|
||||
const VENBOT_USER_ID = "1017176847865352332";
|
||||
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
||||
const CodeBlockRe = /```js\n(.+?)```/s;
|
||||
|
||||
const AllowedChannelIds = [
|
||||
|
@ -49,14 +52,14 @@ const AllowedChannelIds = [
|
|||
];
|
||||
|
||||
const TrustedRolesIds = [
|
||||
CONTRIB_ROLE_ID, // contributor
|
||||
REGULAR_ROLE_ID, // regular
|
||||
DONOR_ROLE_ID, // donor
|
||||
"1026534353167208489", // contributor
|
||||
"1026504932959977532", // regular
|
||||
"1042507929485586532", // donor
|
||||
];
|
||||
|
||||
const AsyncFunction = async function () { }.constructor;
|
||||
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame");
|
||||
|
||||
async function forceUpdate() {
|
||||
const outdated = await checkForUpdates();
|
||||
|
@ -74,7 +77,7 @@ async function generateDebugInfoMessage() {
|
|||
const client = (() => {
|
||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
|
||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
||||
|
||||
// @ts-expect-error
|
||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||
|
@ -139,15 +142,15 @@ export default definePlugin({
|
|||
required: true,
|
||||
description: "Helps us provide support to you",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
patches: [{
|
||||
find: "#{intl::BEGINNING_DM}",
|
||||
find: ".BEGINNING_DM.format",
|
||||
replacement: {
|
||||
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
|
||||
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
|
||||
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
|
||||
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
|
||||
}
|
||||
}],
|
||||
|
||||
|
@ -232,87 +235,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
renderMessageAccessory(props) {
|
||||
const buttons = [] as JSX.Element[];
|
||||
|
||||
const shouldAddUpdateButton =
|
||||
!IS_UPDATER_DISABLED
|
||||
&& (
|
||||
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
||||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
||||
)
|
||||
&& props.message.content?.includes("update");
|
||||
|
||||
if (shouldAddUpdateButton) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-update"
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (await forceUpdate())
|
||||
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
||||
else
|
||||
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while updating:", e);
|
||||
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Update Now
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
||||
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-dbg"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
||||
>
|
||||
Run /vencord-debug
|
||||
</Button>,
|
||||
<Button
|
||||
key="vc-plg-list"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
||||
>
|
||||
Run /vencord-plugins
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.message.author.id === VENBOT_USER_ID) {
|
||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
||||
if (match) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-run-snippet"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await AsyncFunction(match[1])();
|
||||
showToast("Success!", Toasts.Type.SUCCESS);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while running snippet:", e);
|
||||
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Run Snippet
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buttons.length
|
||||
? <Flex>{buttons}</Flex>
|
||||
: null;
|
||||
},
|
||||
|
||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||
const userId = channel.getRecipientId();
|
||||
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
||||
if (!isPluginDev(userId)) return null;
|
||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||
|
||||
|
@ -325,4 +248,85 @@ export default definePlugin({
|
|||
</Card>
|
||||
);
|
||||
}, { noop: true }),
|
||||
|
||||
start() {
|
||||
addAccessory("vencord-debug", props => {
|
||||
const buttons = [] as JSX.Element[];
|
||||
|
||||
const shouldAddUpdateButton =
|
||||
!IS_UPDATER_DISABLED
|
||||
&& (
|
||||
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
||||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
||||
)
|
||||
&& props.message.content?.includes("update");
|
||||
|
||||
if (shouldAddUpdateButton) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-update"
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (await forceUpdate())
|
||||
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
||||
else
|
||||
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while updating:", e);
|
||||
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Update Now
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
||||
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-dbg"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
||||
>
|
||||
Run /vencord-debug
|
||||
</Button>,
|
||||
<Button
|
||||
key="vc-plg-list"
|
||||
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
||||
>
|
||||
Run /vencord-plugins
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.message.author.id === VENBOT_USER_ID) {
|
||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
||||
if (match) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key="vc-run-snippet"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await AsyncFunction(match[1])();
|
||||
showToast("Success!", Toasts.Type.SUCCESS);
|
||||
} catch (e) {
|
||||
new Logger(this.name).error("Error while running snippet:", e);
|
||||
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Run Snippet
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buttons.length
|
||||
? <Flex>{buttons}</Flex>
|
||||
: null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
# AccountPanelServerProfile
|
||||
|
||||
Right click your account panel in the bottom left to view your profile in the current server
|
||||
|
||||

|
||||
|
||||

|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
interface UserProfileProps {
|
||||
popoutProps: Record<string, any>;
|
||||
currentUser: User;
|
||||
originalRenderPopout: () => React.ReactNode;
|
||||
}
|
||||
|
||||
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
|
||||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||
|
||||
let openAlternatePopout = false;
|
||||
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||
|
||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||
|
||||
return (
|
||||
<Menu.Menu
|
||||
navId="vc-ap-server-profile"
|
||||
onClose={ContextMenuApi.closeContextMenu}
|
||||
>
|
||||
<Menu.MenuItem
|
||||
id="vc-ap-view-alternate-popout"
|
||||
label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"}
|
||||
disabled={getCurrentChannel()?.getGuildId() == null}
|
||||
action={e => {
|
||||
openAlternatePopout = true;
|
||||
accountPanelRef.current?.props.onMouseDown();
|
||||
accountPanelRef.current?.props.onClick(e);
|
||||
}}
|
||||
/>
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vc-ap-prioritize-server-profile"
|
||||
label="Prioritize Server Profile"
|
||||
checked={prioritizeServerProfile}
|
||||
action={() => settings.store.prioritizeServerProfile = !prioritizeServerProfile}
|
||||
/>
|
||||
</Menu.Menu>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
||||
const settings = definePluginSettings({
|
||||
prioritizeServerProfile: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Prioritize Server Profile when left clicking your account panel",
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "AccountPanelServerProfile",
|
||||
description: "Right click your account panel in the bottom left to view your profile in the current server",
|
||||
authors: [Devs.Nuckyz, Devs.relitrix],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
||||
group: true,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\.AVATAR_SIZE\);)/,
|
||||
replace: "$self.useAccountPanelRef();"
|
||||
},
|
||||
{
|
||||
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
|
||||
},
|
||||
{
|
||||
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
||||
replace: "$&onRequestClose:$self.onPopoutClose,"
|
||||
},
|
||||
{
|
||||
match: /(?<=#{intl::SET_STATUS}\),)/,
|
||||
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
get accountPanelRef() {
|
||||
return accountPanelRef;
|
||||
},
|
||||
|
||||
useAccountPanelRef() {
|
||||
useEffect(() => () => {
|
||||
accountPanelRef.current = null;
|
||||
}, []);
|
||||
|
||||
return (accountPanelRef = useRef(null));
|
||||
},
|
||||
|
||||
openAccountPanelContextMenu(event: React.UIEvent) {
|
||||
ContextMenuApi.openContextMenu(event, AccountPanelContextMenu);
|
||||
},
|
||||
|
||||
onPopoutClose() {
|
||||
openAlternatePopout = false;
|
||||
},
|
||||
|
||||
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalRenderPopout }: UserProfileProps) => {
|
||||
if (
|
||||
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
||||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
||||
) {
|
||||
return originalRenderPopout();
|
||||
}
|
||||
|
||||
const currentChannel = getCurrentChannel();
|
||||
if (currentChannel?.getGuildId() == null) {
|
||||
return originalRenderPopout();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.accountProfilePopoutWrapper}>
|
||||
<UserProfile {...popoutProps} userId={currentUser.id} guildId={currentChannel.getGuildId()} channelId={currentChannel.id} />
|
||||
</div>
|
||||
);
|
||||
}, { noop: true })
|
||||
});
|
|
@ -41,10 +41,10 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Status emojis
|
||||
find: "#{intl::GUILD_OWNER}),children:",
|
||||
find: ".Messages.GUILD_OWNER,",
|
||||
replacement: {
|
||||
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
|
||||
replace: (_, rest) => `${rest}!0`
|
||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
||||
replace: "!0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# Always Expand Roles
|
||||
|
||||
Always expands the role list in profile popouts
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { migratePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
migratePluginSettings("AlwaysExpandRoles", "ShowAllRoles");
|
||||
export default definePlugin({
|
||||
name: "AlwaysExpandRoles",
|
||||
description: "Always expands the role list in profile popouts",
|
||||
authors: [Devs.surgedevs],
|
||||
patches: [
|
||||
{
|
||||
find: 'action:"EXPAND_ROLES"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
|
||||
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
|
||||
},
|
||||
{
|
||||
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
|
||||
// which makes the collapse button never show up and calculation never occur
|
||||
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
|
||||
replace: isExpanded => "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
|
@ -51,7 +51,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "bitbucket.org",
|
||||
replacement: {
|
||||
match: /function \i\(\i\){(?=.{0,30}pathname:\i)/,
|
||||
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
|
||||
replace: "$&return null;"
|
||||
},
|
||||
predicate: () => settings.store.file
|
||||
|
|
|
@ -21,12 +21,12 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { findByProps, findComponentByCode } from "@webpack";
|
||||
|
||||
type AnonUpload = Upload & { anonymise?: boolean; };
|
||||
|
||||
const ActionBarIcon = findByCodeLazy(".actionBarIcon)");
|
||||
const UploadDraft = findByPropsLazy("popFirstFile", "update");
|
||||
const ActionBarIcon = findComponentByCode(".actionBarIcon)");
|
||||
const UploadDraft = findByProps("popFirstFile", "update");
|
||||
|
||||
const enum Methods {
|
||||
Random,
|
||||
|
@ -71,7 +71,7 @@ export default definePlugin({
|
|||
description: "Anonymise uploaded file names",
|
||||
patches: [
|
||||
{
|
||||
find: "instantBatchUpload:",
|
||||
find: "instantBatchUpload:function",
|
||||
replacement: {
|
||||
match: /uploadFiles:(\i),/,
|
||||
replace:
|
||||
|
@ -86,9 +86,9 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
|
||||
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
|
||||
replacement: {
|
||||
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
|
||||
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
|
||||
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
|
||||
},
|
||||
},
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue