Compare commits
329 commits
asar-bundl
...
main
Author | SHA1 | Date | |
---|---|---|---|
9205db27bd | |||
|
205e07d2c5 | ||
|
fcf8690d26 | ||
|
e4380632e0 | ||
|
4a447c74ef | ||
30748b9930 | |||
|
848c2299d2 | ||
|
1e426b4253 | ||
|
971e186335 | ||
|
949aad8cc7 | ||
|
70ce6ff2d6 | ||
|
a73074b69f | ||
|
ae98cfb637 | ||
|
6cccb54ffc | ||
|
4f5ebec4bb | ||
|
7b9f0a36ba | ||
|
fc4e95806d | ||
|
1eff1a02bd | ||
|
414539f45e | ||
|
b2d5c00a23 | ||
|
a492f7657b | ||
|
e280ed2683 | ||
362f12fa3d | |||
4029ac976b | |||
|
8fccda4a24 | ||
|
68662c9625 | ||
|
7d45862023 | ||
|
240195f9bf | ||
|
5ad35c36e4 | ||
|
a2213d4feb | ||
|
81dda2ce33 | ||
|
7415367d6c | ||
|
33d4f13a24 | ||
|
cdc756193e | ||
|
f43baddc55 | ||
|
21ded874a3 | ||
|
ea1e96185b | ||
|
ceba9776c4 | ||
|
c4f8221f75 | ||
|
3350922c09 | ||
|
f29662c5b3 | ||
|
cf28c65374 | ||
|
87cb1fd930 | ||
|
aac5242dc8 | ||
|
4036fbab92 | ||
|
79cbfe96c8 | ||
|
7ee70e831a | ||
|
e45b867ff0 | ||
229ef03adf | |||
|
78c2f0d61a | ||
|
72ec5e2023 | ||
|
43501bad07 | ||
|
e000a947a3 | ||
|
5c8ba6e542 | ||
|
317121fc08 | ||
|
30647b6bd9 | ||
|
ed99ae7f23 | ||
|
17f1ef275e | ||
|
ea0182a194 | ||
|
9e9d71d014 | ||
|
9bb983d40c | ||
|
5312514de6 | ||
|
8346dba324 | ||
|
47315b0eba | ||
|
a60af65b6d | ||
|
88e3bc037d | ||
|
19361ef790 | ||
|
c8f4ce9785 | ||
|
a53257634e | ||
|
3243120baa | ||
|
4ab297c9e3 | ||
|
154f371b14 | ||
|
263a96c310 | ||
|
5a77149b26 | ||
|
2707b10021 | ||
|
7be3a40b7c | ||
|
16a1c44947 | ||
|
3af06edb95 | ||
|
34629307dd | ||
|
20ed7dc96b | ||
|
0fd76ab15a | ||
|
0e813e78d0 | ||
|
79e2cb15f1 | ||
|
cca5d7dc09 | ||
|
9ccc74bde3 | ||
|
5d1f6e606a | ||
|
48a9aef2eb | ||
|
fc731bc6c8 | ||
|
0a0bd6a713 | ||
|
40a8cf1a85 | ||
|
5f1c5fa370 | ||
|
00c968473e | ||
|
2dc8c2bf76 | ||
|
464c4a9b61 | ||
|
dcfddcbc21 | ||
|
9d3c91e9df | ||
|
8d65bcf743 | ||
|
ae6f37267c | ||
|
4a5f0838e2 | ||
|
99dc65fe4e | ||
|
3a339636d1 | ||
|
cea0a3c9d9 | ||
|
a3f5dc39a0 | ||
|
df44edd41b | ||
|
cdfc89b819 | ||
|
8711dd9a4b | ||
|
df454ca952 | ||
|
6628624082 | ||
|
dd87f360d7 | ||
|
3f61fe722d | ||
|
d70e0f27dc | ||
|
0ac80ce9d1 | ||
|
fcece61995 | ||
|
02f50b161b | ||
|
1150a50355 | ||
|
11321eb693 | ||
|
60b776669b | ||
|
d8df96d1e3 | ||
|
a9d44e3341 | ||
|
e7a54b0587 | ||
|
23c9e2ce22 | ||
|
5fb63246ca | ||
|
2bfeef88ca | ||
|
7ca4ea3d13 | ||
|
f8dfe217b1 | ||
|
f22d0e14a4 | ||
|
ac1b1d44f5 | ||
|
13993f3f69 | ||
|
61ddbe9bcd | ||
|
a0308e03af | ||
|
cd61f4e744 | ||
|
5cf22113cf | ||
|
ea2772476d | ||
|
99458da3be | ||
|
c4f6f151e6 | ||
|
8558b1a589 | ||
|
76df29fba2 | ||
80866a609b | |||
|
25ceff5ec2 | ||
|
e0d66ff071 | ||
|
66a75747f8 | ||
|
211569f7f5 | ||
|
af1edc88bf | ||
|
7ef536c6c6 | ||
|
69dc4fd594 | ||
|
1fe7912ec1 | ||
|
0cb84cee83 | ||
|
49c9fa1c8e | ||
|
fd1aba7bab | ||
|
3b295e1f6f | ||
|
152d4fdbb3 | ||
|
ce0740b885 | ||
|
64c3dd1c16 | ||
|
5f7a2c59c6 | ||
|
cd3a998c4b | ||
|
2270b88a98 | ||
1d5709763a | |||
|
9d4e859a0a | ||
|
439a4f8eb6 | ||
|
00f82e96bd | ||
|
5216bcca1e | ||
|
e7e298d2e7 | ||
|
d897dab054 | ||
|
88e8fa7e90 | ||
|
f5f59be1b6 | ||
|
534ab3eb5f | ||
|
a6ea03bacc | ||
c3bdc71512 | |||
|
e620431210 | ||
|
5afc24b41a | ||
|
7f8e241b9c | ||
|
553293ceee | ||
|
0af820c874 | ||
|
a11ccde40f | ||
|
58c3032bb2 | ||
|
4e89352758 | ||
|
e818905520 | ||
|
aa1b446c07 | ||
|
2dce060cf9 | ||
|
89bb3ee30a | ||
|
47db61d00e | ||
a592586af7 | |||
0567320fc6 | |||
|
416d85dcf0 | ||
|
013c8d061d | ||
|
b5f626d1ff | ||
|
1e01f85217 | ||
|
91a32e22de | ||
|
43b3c137ce | ||
|
18f7b74210 | ||
|
eab0cf9966 | ||
|
e7956413e2 | ||
|
832e874c35 | ||
|
55146db760 | ||
|
bc59fc41b3 | ||
|
1abfb5f0cf | ||
|
cb2848f186 | ||
|
c3f2e76b9c | ||
|
eb63a54fa6 | ||
|
5881716c57 | ||
|
d7cbe270e5 | ||
|
c29362ca89 | ||
|
fd0fbace2e | ||
|
409f47bf24 | ||
|
b0e2f310bc | ||
|
65069c673c | ||
|
b1db18c319 | ||
|
db5fe2a394 | ||
|
e4318a887a | ||
|
eaf62d8c1c | ||
|
22a5b18bfa | ||
|
1dc2d92493 | ||
|
492b0cff08 | ||
|
2d675b4b2e | ||
|
49b0a38c37 | ||
|
467157539c | ||
|
e8242f22c9 | ||
|
f7587d9b2e | ||
|
755e869db7 | ||
|
a015cf96f6 | ||
|
c7e5295da0 | ||
|
8afd79dd50 | ||
|
65c5897dc3 | ||
|
6cce8a8bc4 | ||
|
1848b16536 | ||
|
c572116b97 | ||
|
e26986f66a | ||
|
f12335a371 | ||
|
640d99dcda | ||
|
bcfef05a8a | ||
|
f17b92c2fd | ||
|
292f7d71d3 | ||
|
9d67421b34 | ||
|
b822542352 | ||
|
f27361f017 | ||
|
a765212cfe | ||
|
c07d78fa96 | ||
|
56459bdcad | ||
|
868b2ea9f0 | ||
|
9e7f8829f2 | ||
|
e5a4db6460 | ||
|
8890c8c6b4 | ||
|
61d3c08f1f | ||
|
e6994e1946 | ||
|
40512d7294 | ||
|
be02baffaa | ||
|
99cd423efb | ||
|
7333f40db6 | ||
|
244cb26c32 | ||
|
df8aec8e3c | ||
|
d10e649b63 | ||
|
7f784befc2 | ||
|
30e4e83158 | ||
|
4c4f2894fb | ||
|
c51d7b8fb4 | ||
|
accfc15125 | ||
|
27e81b20db | ||
|
d0ad4e6c1d | ||
|
0c71d6c3fa | ||
|
74fd85bd3d | ||
|
968e688c10 | ||
|
b595a3e33c | ||
|
e07a4e19e6 | ||
|
273981deb7 | ||
|
81eabc7ee2 | ||
|
d5eaae9d51 | ||
|
00276cad7c | ||
|
ee9fbc4d25 | ||
|
0cea347264 | ||
|
eb0d91fd8e | ||
|
07e629d8d4 | ||
|
e473a57c3d | ||
|
ad3d936dfd | ||
|
db2f5c9292 | ||
|
5bfc608f7d | ||
|
7d8214fc37 | ||
|
cdf72cf4be | ||
|
4b16fbcaa9 | ||
|
7ec842d4b0 | ||
|
6659f2c413 | ||
|
1fb5e8df99 | ||
|
1e8f59f13d | ||
|
f0e6986835 | ||
|
8afcb8e4dd | ||
|
2569c39ddf | ||
|
ed9b28febf | ||
|
2e81b9aeba | ||
|
44c8463496 | ||
|
2bdc2f4e82 | ||
|
66b247b2d3 | ||
|
3d80cb2d55 | ||
|
904022a2f7 | ||
|
9cada9ad13 | ||
|
051bce89f8 | ||
|
eaca14bb5a | ||
|
d388aa4347 | ||
|
4301ed889d | ||
|
1a712e75a6 | ||
|
87e6fa8647 | ||
|
003e4a08d5 | ||
|
4e3c178043 | ||
|
5160f906f4 | ||
|
021d9bf179 | ||
|
d1996034b7 | ||
|
8274b67597 | ||
|
e437498c8f | ||
|
09c6c16cf9 | ||
|
e99eec50bc | ||
|
d919cd6bf1 | ||
|
c185f47f4d | ||
|
83d90f03ee | ||
|
0f8d21a846 | ||
|
2658459a98 | ||
|
f8b01c1a31 | ||
|
2382294e8b | ||
|
d47be6c017 | ||
|
6c12a33aa6 | ||
|
9cc42bf457 | ||
|
1bfdcf2697 | ||
|
3013c669c0 | ||
|
e460b5efb6 | ||
|
902a86c3b2 | ||
|
51ae019cd5 | ||
|
0f5cf37ef9 | ||
|
5c88284ed3 | ||
|
5e9a9fe836 | ||
|
bc801853e2 | ||
|
2044264729 | ||
|
3704c71ae1 |
377 changed files with 9510 additions and 7542 deletions
|
@ -1,98 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"ignorePatterns": ["dist", "browser", "packages/vencord-types"],
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint",
|
|
||||||
"simple-header",
|
|
||||||
"simple-import-sort",
|
|
||||||
"unused-imports",
|
|
||||||
"path-alias"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"import/resolver": {
|
|
||||||
"alias": {
|
|
||||||
"map": [
|
|
||||||
["@webpack", "./src/webpack"],
|
|
||||||
["@webpack/common", "./src/webpack/common"],
|
|
||||||
["@utils", "./src/utils"],
|
|
||||||
["@api", "./src/api"],
|
|
||||||
["@components", "./src/components"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
// Since it's only been a month and Vencord has already been stolen
|
|
||||||
// by random skids who rebranded it to "AlphaCord" and erased all license
|
|
||||||
// information
|
|
||||||
"simple-header/header": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
|
|
||||||
"templates": { "author": [".*", "Vendicated and contributors"] }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
|
||||||
"no-mixed-spaces-and-tabs": "error",
|
|
||||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
|
||||||
"arrow-parens": ["error", "as-needed"],
|
|
||||||
"eol-last": ["error", "always"],
|
|
||||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
|
||||||
"no-multi-spaces": "error",
|
|
||||||
"no-trailing-spaces": "error",
|
|
||||||
"no-whitespace-before-property": "error",
|
|
||||||
"semi": ["error", "always"],
|
|
||||||
"semi-style": ["error", "last"],
|
|
||||||
"space-in-parens": ["error", "never"],
|
|
||||||
"block-spacing": ["error", "always"],
|
|
||||||
"object-curly-spacing": ["error", "always"],
|
|
||||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
|
||||||
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
|
||||||
"yoda": "error",
|
|
||||||
"prefer-destructuring": ["error", {
|
|
||||||
"VariableDeclarator": { "array": false, "object": true },
|
|
||||||
"AssignmentExpression": { "array": false, "object": false }
|
|
||||||
}],
|
|
||||||
"operator-assignment": ["error", "always"],
|
|
||||||
"no-useless-computed-key": "error",
|
|
||||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
|
||||||
"no-invalid-regexp": "error",
|
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
|
||||||
"no-duplicate-imports": "error",
|
|
||||||
"no-extra-semi": "error",
|
|
||||||
"dot-notation": "error",
|
|
||||||
"no-useless-escape": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"extra": "i"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-fallthrough": "error",
|
|
||||||
"for-direction": "error",
|
|
||||||
"no-async-promise-executor": "error",
|
|
||||||
"no-cond-assign": "error",
|
|
||||||
"no-dupe-else-if": "error",
|
|
||||||
"no-duplicate-case": "error",
|
|
||||||
"no-irregular-whitespace": "error",
|
|
||||||
"no-loss-of-precision": "error",
|
|
||||||
"no-misleading-character-class": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-regex-spaces": "error",
|
|
||||||
"no-shadow-restricted-names": "error",
|
|
||||||
"no-unexpected-multiline": "error",
|
|
||||||
"no-unsafe-optional-chaining": "error",
|
|
||||||
"no-useless-backreference": "error",
|
|
||||||
"use-isnan": "error",
|
|
||||||
"prefer-const": "error",
|
|
||||||
"prefer-spread": "error",
|
|
||||||
|
|
||||||
"simple-import-sort/imports": "error",
|
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
|
|
||||||
"unused-imports/no-unused-imports": "error",
|
|
||||||
|
|
||||||
"path-alias/no-relative": "error"
|
|
||||||
}
|
|
||||||
}
|
|
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
|
@ -40,28 +40,9 @@ jobs:
|
||||||
- name: Generate plugin list
|
- name: Generate plugin list
|
||||||
run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
|
run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
|
||||||
|
|
||||||
- name: Collect files to be released
|
- name: Clean up obsolete files
|
||||||
run: |
|
run: |
|
||||||
cd dist
|
rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||||
mkdir release
|
|
||||||
|
|
||||||
cp browser/browser.* release
|
|
||||||
cp Vencord.user.{js,js.LEGAL.txt} release
|
|
||||||
|
|
||||||
# copy the plugin data jsons, the extension zips and the desktop/vesktop asars
|
|
||||||
cp *.{json,zip,asar} release
|
|
||||||
|
|
||||||
# legacy un-asared files
|
|
||||||
# FIXME: remove at some point
|
|
||||||
cp desktop/* release
|
|
||||||
for file in vesktop/*; do
|
|
||||||
filename=$(basename "$file")
|
|
||||||
cp "$file" "release/vencordDesktop${filename^}"
|
|
||||||
done
|
|
||||||
|
|
||||||
find release -size 0 -delete
|
|
||||||
rm release/package.json
|
|
||||||
rm release/*.map
|
|
||||||
|
|
||||||
- name: Get some values needed for the release
|
- name: Get some values needed for the release
|
||||||
id: release_values
|
id: release_values
|
||||||
|
@ -69,14 +50,16 @@ jobs:
|
||||||
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload DevBuild as release
|
- name: Upload DevBuild as release
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
run: |
|
run: |
|
||||||
gh release upload devbuild --clobber dist/release/*
|
gh release upload devbuild --clobber dist/*
|
||||||
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
|
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
RELEASE_TAG: ${{ env.release_tag }}
|
RELEASE_TAG: ${{ env.release_tag }}
|
||||||
|
|
||||||
- name: Upload DevBuild to builds repo
|
- name: Upload DevBuild to builds repo
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "$USERNAME"
|
git config --global user.name "$USERNAME"
|
||||||
git config --global user.email actions@github.com
|
git config --global user.email actions@github.com
|
||||||
|
@ -86,7 +69,7 @@ jobs:
|
||||||
|
|
||||||
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
|
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
|
||||||
rm -rf *
|
rm -rf *
|
||||||
cp -r ../dist/release/* .
|
cp -r ../dist/* .
|
||||||
|
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
|
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
|
||||||
|
|
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 19
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
@ -36,7 +36,7 @@ jobs:
|
||||||
|
|
||||||
- name: Publish extension
|
- name: Publish extension
|
||||||
run: |
|
run: |
|
||||||
cd dist/browser/chromium-unpacked
|
cd dist/chromium-unpacked
|
||||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
||||||
env:
|
env:
|
||||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -18,7 +18,5 @@ lerna-debug.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
src/userplugins
|
|
||||||
|
|
||||||
ExtensionCache/
|
ExtensionCache/
|
||||||
settings/
|
settings/
|
||||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "src/userplugins/vc-message-logger-enhanced"]
|
||||||
|
path = src/userplugins/vc-message-logger-enhanced
|
||||||
|
url = https://github.com/Syncxv/vc-message-logger-enhanced.git
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "stylelint-config-standard",
|
"extends": "stylelint-config-standard",
|
||||||
"rules": {
|
"rules": {
|
||||||
"indentation": 4,
|
|
||||||
"selector-class-pattern": [
|
"selector-class-pattern": [
|
||||||
"^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$",
|
"^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$",
|
||||||
{
|
{
|
||||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -14,8 +14,6 @@
|
||||||
"typescript.preferences.quoteStyle": "double",
|
"typescript.preferences.quoteStyle": "double",
|
||||||
"javascript.preferences.quoteStyle": "double",
|
"javascript.preferences.quoteStyle": "double",
|
||||||
|
|
||||||
"eslint.experimental.useFlatConfig": false,
|
|
||||||
|
|
||||||
"gitlens.remotes": [
|
"gitlens.remotes": [
|
||||||
{
|
{
|
||||||
"domain": "codeberg.org",
|
"domain": "codeberg.org",
|
||||||
|
|
|
@ -31,6 +31,7 @@ Before starting your plugin:
|
||||||
- No FakeDeafen or FakeMute
|
- No FakeDeafen or FakeMute
|
||||||
- No StereoMic
|
- No StereoMic
|
||||||
- No plugins that simply hide or redesign ui elements. This can be done with CSS
|
- 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 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 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
|
- No plugins that require the user to enter their own API key
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// @author Vendicated (https://github.com/Vendicated)
|
// @author Vendicated (https://github.com/Vendicated)
|
||||||
// @namespace https://github.com/Vendicated/Vencord
|
// @namespace https://github.com/Vendicated/Vencord
|
||||||
// @supportURL 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
|
// @license GPL-3.0
|
||||||
// @match *://*.discord.com/*
|
// @match *://*.discord.com/*
|
||||||
// @grant GM_xmlhttpRequest
|
// @grant GM_xmlhttpRequest
|
||||||
|
|
147
eslint.config.mjs
Normal file
147
eslint.config.mjs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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";
|
||||||
|
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: {
|
||||||
|
"simple-header": header,
|
||||||
|
"@stylistic": stylistic,
|
||||||
|
"@typescript-eslint": tseslint.plugin,
|
||||||
|
"simple-import-sort": simpleImportSort,
|
||||||
|
"unused-imports": unusedImports,
|
||||||
|
"path-alias": pathAlias
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
"import/resolver": {
|
||||||
|
map: [
|
||||||
|
["@webpack", "./src/webpack"],
|
||||||
|
["@webpack/common", "./src/webpack/common"],
|
||||||
|
["@utils", "./src/utils"],
|
||||||
|
["@api", "./src/api"],
|
||||||
|
["@components", "./src/components"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
parserOptions: {
|
||||||
|
project: ["./tsconfig.json"],
|
||||||
|
tsconfigRootDir: import.meta.dirname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
/*
|
||||||
|
* Since it's only been a month and Vencord has already been stolen
|
||||||
|
* by random skids who rebranded it to "AlphaCord" and erased all license
|
||||||
|
* information
|
||||||
|
*/
|
||||||
|
"simple-header/header": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
|
||||||
|
"templates": { "author": [".*", "Vendicated and contributors"] }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Style Rules
|
||||||
|
"@stylistic/jsx-quotes": ["error", "prefer-double"],
|
||||||
|
"@stylistic/quotes": ["error", "double", { "avoidEscape": true }],
|
||||||
|
"@stylistic/no-mixed-spaces-and-tabs": "error",
|
||||||
|
"@stylistic/arrow-parens": ["error", "as-needed"],
|
||||||
|
"@stylistic/eol-last": ["error", "always"],
|
||||||
|
"@stylistic/no-multi-spaces": "error",
|
||||||
|
"@stylistic/no-trailing-spaces": "error",
|
||||||
|
"@stylistic/no-whitespace-before-property": "error",
|
||||||
|
"@stylistic/semi": ["error", "always"],
|
||||||
|
"@stylistic/semi-style": ["error", "last"],
|
||||||
|
"@stylistic/space-in-parens": ["error", "never"],
|
||||||
|
"@stylistic/block-spacing": ["error", "always"],
|
||||||
|
"@stylistic/object-curly-spacing": ["error", "always"],
|
||||||
|
"@stylistic/spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||||
|
"@stylistic/no-extra-semi": "error",
|
||||||
|
|
||||||
|
// TS Rules
|
||||||
|
"@stylistic/func-call-spacing": ["error", "never"],
|
||||||
|
|
||||||
|
// ESLint Rules
|
||||||
|
"yoda": "error",
|
||||||
|
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||||
|
"prefer-destructuring": ["error", {
|
||||||
|
"VariableDeclarator": { "array": false, "object": true },
|
||||||
|
"AssignmentExpression": { "array": false, "object": false }
|
||||||
|
}],
|
||||||
|
"operator-assignment": ["error", "always"],
|
||||||
|
"no-useless-computed-key": "error",
|
||||||
|
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
|
"no-duplicate-imports": "error",
|
||||||
|
"@typescript-eslint/dot-notation": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowPrivateClassPropertyAccess": true,
|
||||||
|
"allowProtectedClassPropertyAccess": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-useless-escape": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"extra": "i"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"for-direction": "error",
|
||||||
|
"no-async-promise-executor": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-dupe-else-if": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-loss-of-precision": "error",
|
||||||
|
"no-misleading-character-class": "error",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-unsafe-optional-chaining": "error",
|
||||||
|
"no-useless-backreference": "error",
|
||||||
|
"use-isnan": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-spread": "error",
|
||||||
|
|
||||||
|
// Plugin Rules
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"path-alias/no-relative": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
78
package.json
78
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.9.5",
|
"version": "1.11.4",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -14,9 +14,9 @@
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"author": "Vendicated",
|
"author": "Vendicated",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsx scripts/build/build.mts",
|
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
||||||
"buildStandalone": "pnpm build --standalone",
|
"buildStandalone": "pnpm build --standalone",
|
||||||
"buildWeb": "tsx scripts/build/buildWeb.mts",
|
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
|
||||||
"buildWebStandalone": "pnpm buildWeb --standalone",
|
"buildWebStandalone": "pnpm buildWeb --standalone",
|
||||||
"buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
|
"buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
|
||||||
"buildReporterDesktop": "pnpm build --reporter",
|
"buildReporterDesktop": "pnpm build --reporter",
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||||
"inject": "node scripts/runInstaller.mjs",
|
"inject": "node scripts/runInstaller.mjs",
|
||||||
"uninject": "node scripts/runInstaller.mjs",
|
"uninject": "node scripts/runInstaller.mjs",
|
||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
"lint": "eslint",
|
||||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||||
|
@ -35,54 +35,56 @@
|
||||||
"testTsc": "tsc --noEmit"
|
"testTsc": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.3",
|
"@intrnl/xxhash64": "^0.1.2",
|
||||||
|
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||||
"@vap/core": "0.0.12",
|
"@vap/core": "0.0.12",
|
||||||
"@vap/shiki": "0.10.5",
|
"@vap/shiki": "0.10.5",
|
||||||
"eslint-plugin-simple-header": "^1.0.2",
|
"fflate": "^0.8.2",
|
||||||
"fflate": "^0.7.4",
|
|
||||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||||
"monaco-editor": "^0.50.0",
|
"monaco-editor": "^0.52.2",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^5.0.9",
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/asar": "^3.2.10",
|
"@stylistic/eslint-plugin": "^2.12.1",
|
||||||
"@types/chrome": "^0.0.246",
|
"@types/chrome": "^0.0.287",
|
||||||
"@types/diff": "^5.0.3",
|
"@types/diff": "^6.0.0",
|
||||||
"@types/lodash": "^4.14.194",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^18.16.3",
|
"@types/node": "^22.10.5",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^19.0.2",
|
||||||
"@types/react-dom": "^18.2.1",
|
"@types/react-dom": "^19.0.2",
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
"diff": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^5.59.1",
|
|
||||||
"diff": "^5.1.0",
|
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"esbuild": "^0.23.0",
|
"esbuild": "^0.15.18",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "^1.0.0",
|
"eslint-plugin-path-alias": "2.1.0",
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-react": "^7.37.3",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-simple-header": "^1.2.1",
|
||||||
"highlight.js": "10.6.0",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
|
"highlight.js": "11.7.0",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.22.2",
|
||||||
"puppeteer-core": "^19.11.1",
|
"puppeteer-core": "^23.11.1",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
"stylelint": "^15.6.0",
|
"stylelint": "^16.12.0",
|
||||||
"stylelint-config-standard": "^33.0.0",
|
"stylelint-config-standard": "^36.0.1",
|
||||||
"ts-patch": "^3.1.2",
|
"ts-patch": "^3.3.0",
|
||||||
"tsx": "^4.16.2",
|
"ts-pattern": "^5.6.0",
|
||||||
"type-fest": "^3.9.0",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.4.5",
|
"type-fest": "^4.31.0",
|
||||||
"typescript-transform-paths": "^3.4.7",
|
"typescript": "^5.7.2",
|
||||||
|
"typescript-eslint": "^8.19.0",
|
||||||
|
"typescript-transform-paths": "^3.5.3",
|
||||||
"zip-local": "^0.3.5"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@9.1.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
|
||||||
"eslint@8.46.0": "patches/eslint@8.46.0.patch"
|
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||||
},
|
},
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"ignoreMissing": [
|
"ignoreMissing": [
|
||||||
|
@ -105,7 +107,7 @@
|
||||||
"sourceDir": "./dist/firefox-unpacked"
|
"sourceDir": "./dist/firefox-unpacked"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20",
|
"node": ">=18",
|
||||||
"pnpm": ">=9"
|
"pnpm": ">=9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
diff --git a/lib/rules/no-relative.js b/lib/rules/no-relative.js
|
|
||||||
index 71594c83f1f4f733ffcc6047d7f7084348335dbe..d8623d87c89499c442171db3272cba07c9efabbe 100644
|
|
||||||
--- a/lib/rules/no-relative.js
|
|
||||||
+++ b/lib/rules/no-relative.js
|
|
||||||
@@ -41,7 +41,7 @@ module.exports = {
|
|
||||||
ImportDeclaration(node) {
|
|
||||||
const importPath = node.source.value;
|
|
||||||
|
|
||||||
- if (!/^(\.?\.\/)/.test(importPath)) {
|
|
||||||
+ if (!/^(\.\.\/)/.test(importPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
14
patches/eslint-plugin-path-alias@2.1.0.patch
Normal file
14
patches/eslint-plugin-path-alias@2.1.0.patch
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
diff --git a/dist/index.js b/dist/index.js
|
||||||
|
index 67de6fb139070fd0e49beca65e3b63c531202e16..aa2883c8126e4952a42872ee920f59547a066430 100644
|
||||||
|
--- a/dist/index.js
|
||||||
|
+++ b/dist/index.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.?\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}};
|
||||||
|
+var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}};
|
||||||
|
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||||
|
index 96de18e06d4cc413e11af038cd760e4804c32e59..27e8c4e3e2c942400cc3982e52159904ca6eedfa 100644
|
||||||
|
--- a/dist/index.mjs
|
||||||
|
+++ b/dist/index.mjs
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.?\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default};
|
||||||
|
+var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default};
|
4633
pnpm-lock.yaml
4633
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -17,30 +17,31 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createPackage } from "@electron/asar";
|
import esbuild from "esbuild";
|
||||||
import { BuildOptions, Plugin } from "esbuild";
|
import { readdir } from "fs/promises";
|
||||||
import { existsSync, readdirSync } from "fs";
|
|
||||||
import { readdir, rm, writeFile } from "fs/promises";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { addBuild, BUILD_TIMESTAMP, buildOrWatchAll, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, watch } 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 = {
|
const defines = {
|
||||||
IS_STANDALONE: String(IS_STANDALONE),
|
IS_STANDALONE,
|
||||||
IS_DEV: String(IS_DEV),
|
IS_DEV,
|
||||||
IS_REPORTER: String(IS_REPORTER),
|
IS_REPORTER,
|
||||||
IS_UPDATER_DISABLED: String(IS_UPDATER_DISABLED),
|
IS_UPDATER_DISABLED,
|
||||||
IS_WEB: "false",
|
IS_WEB: false,
|
||||||
IS_EXTENSION: "false",
|
IS_EXTENSION: false,
|
||||||
VERSION: JSON.stringify(VERSION),
|
VERSION: JSON.stringify(VERSION),
|
||||||
BUILD_TIMESTAMP: String(BUILD_TIMESTAMP)
|
BUILD_TIMESTAMP
|
||||||
};
|
};
|
||||||
|
|
||||||
if (defines.IS_STANDALONE === "false")
|
if (defines.IS_STANDALONE === false)
|
||||||
// If this is a local build (not standalone), optimize
|
// If this is a local build (not standalone), optimize
|
||||||
// for the specific platform we're on
|
// for the specific platform we're on
|
||||||
defines["process.platform"] = JSON.stringify(process.platform);
|
defines["process.platform"] = JSON.stringify(process.platform);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.BuildOptions}
|
||||||
|
*/
|
||||||
const nodeCommonOpts = {
|
const nodeCommonOpts = {
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
|
@ -48,12 +49,15 @@ const nodeCommonOpts = {
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
||||||
define: defines
|
define: defines
|
||||||
} satisfies BuildOptions;
|
};
|
||||||
|
|
||||||
const sourceMapFooter = (s: string) => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||||
const sourcemap = watch ? "inline" : "external";
|
const sourcemap = watch ? "inline" : "external";
|
||||||
|
|
||||||
const globNativesPlugin: Plugin = {
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
const globNativesPlugin = {
|
||||||
name: "glob-natives-plugin",
|
name: "glob-natives-plugin",
|
||||||
setup: build => {
|
setup: build => {
|
||||||
const filter = /^~pluginNatives$/;
|
const filter = /^~pluginNatives$/;
|
||||||
|
@ -100,26 +104,26 @@ const globNativesPlugin: Plugin = {
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Discord Desktop main & renderer & preload
|
// Discord Desktop main & renderer & preload
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/main/index.ts"],
|
entryPoints: ["src/main/index.ts"],
|
||||||
outfile: "dist/desktop/patcher.js",
|
outfile: "dist/patcher.js",
|
||||||
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: "true",
|
IS_DISCORD_DESKTOP: true,
|
||||||
IS_VESKTOP: "false"
|
IS_VESKTOP: false
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
...nodeCommonOpts.plugins,
|
...nodeCommonOpts.plugins,
|
||||||
globNativesPlugin
|
globNativesPlugin
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
entryPoints: ["src/Vencord.ts"],
|
entryPoints: ["src/Vencord.ts"],
|
||||||
outfile: "dist/desktop/renderer.js",
|
outfile: "dist/renderer.js",
|
||||||
format: "iife",
|
format: "iife",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
|
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
|
||||||
|
@ -127,111 +131,79 @@ await Promise.all([
|
||||||
sourcemap,
|
sourcemap,
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("discordDesktop"),
|
globPlugins("discordDesktop"),
|
||||||
...commonOpts.plugins
|
...commonRendererPlugins
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: "true",
|
IS_DISCORD_DESKTOP: true,
|
||||||
IS_VESKTOP: "false"
|
IS_VESKTOP: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/preload.ts"],
|
entryPoints: ["src/preload.ts"],
|
||||||
outfile: "dist/desktop/preload.js",
|
outfile: "dist/preload.js",
|
||||||
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: "true",
|
IS_DISCORD_DESKTOP: true,
|
||||||
IS_VESKTOP: "false"
|
IS_VESKTOP: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Vencord Desktop main & renderer & preload
|
// Vencord Desktop main & renderer & preload
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/main/index.ts"],
|
entryPoints: ["src/main/index.ts"],
|
||||||
outfile: "dist/vesktop/main.js",
|
outfile: "dist/vencordDesktopMain.js",
|
||||||
footer: { js: "//# sourceURL=VencordMain\n" + sourceMapFooter("main") },
|
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: "false",
|
IS_DISCORD_DESKTOP: false,
|
||||||
IS_VESKTOP: "true"
|
IS_VESKTOP: true
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
...nodeCommonOpts.plugins,
|
...nodeCommonOpts.plugins,
|
||||||
globNativesPlugin
|
globNativesPlugin
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
entryPoints: ["src/Vencord.ts"],
|
entryPoints: ["src/Vencord.ts"],
|
||||||
outfile: "dist/vesktop/renderer.js",
|
outfile: "dist/vencordDesktopRenderer.js",
|
||||||
format: "iife",
|
format: "iife",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
|
footer: { js: "//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
|
||||||
globalName: "Vencord",
|
globalName: "Vencord",
|
||||||
sourcemap,
|
sourcemap,
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("vencordDesktop"),
|
globPlugins("vencordDesktop"),
|
||||||
...commonOpts.plugins
|
...commonRendererPlugins
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: "false",
|
IS_DISCORD_DESKTOP: false,
|
||||||
IS_VESKTOP: "true"
|
IS_VESKTOP: true
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/preload.ts"],
|
entryPoints: ["src/preload.ts"],
|
||||||
outfile: "dist/vesktop/preload.js",
|
outfile: "dist/vencordDesktopPreload.js",
|
||||||
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("vencordDesktopPreload") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: "false",
|
IS_DISCORD_DESKTOP: false,
|
||||||
IS_VESKTOP: "true"
|
IS_VESKTOP: true
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
]);
|
]).catch(err => {
|
||||||
|
console.error("Build failed");
|
||||||
await buildOrWatchAll();
|
console.error(err.message);
|
||||||
|
// make ci fail
|
||||||
await Promise.all([
|
if (!commonOpts.watch)
|
||||||
writeFile("dist/desktop/package.json", JSON.stringify({
|
process.exitCode = 1;
|
||||||
name: "vencord",
|
});
|
||||||
main: "patcher.js"
|
|
||||||
})),
|
|
||||||
writeFile("dist/vesktop/package.json", JSON.stringify({
|
|
||||||
name: "vencord",
|
|
||||||
main: "main.js"
|
|
||||||
}))
|
|
||||||
]);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
createPackage("dist/desktop", "dist/desktop.asar"),
|
|
||||||
createPackage("dist/vesktop", "dist/vesktop.asar")
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
if (existsSync("dist/renderer.js")) {
|
|
||||||
console.warn("Legacy dist folder. Cleaning up and adding shims.");
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
readdirSync("dist")
|
|
||||||
.filter(f =>
|
|
||||||
f.endsWith(".map") ||
|
|
||||||
f.endsWith(".LEGAL.txt") ||
|
|
||||||
["patcher", "preload", "renderer"].some(name => f.startsWith(name))
|
|
||||||
)
|
|
||||||
.map(file => rm(join("dist", file)))
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
writeFile("dist/patcher.js", 'require("./desktop")'),
|
|
||||||
writeFile("dist/vencordDesktopMain.js", 'require("./vesktop")')
|
|
||||||
]);
|
|
||||||
}
|
|
|
@ -23,12 +23,12 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import Zip from "zip-local";
|
import Zip from "zip-local";
|
||||||
|
|
||||||
import { addBuild, BUILD_TIMESTAMP, buildOrWatchAll, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.BuildOptions}
|
* @type {esbuild.BuildOptions}
|
||||||
*/
|
*/
|
||||||
const commonOptions: esbuild.BuildOptions = {
|
const commonOptions = {
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
entryPoints: ["browser/Vencord.ts"],
|
entryPoints: ["browser/Vencord.ts"],
|
||||||
globalName: "Vencord",
|
globalName: "Vencord",
|
||||||
|
@ -36,20 +36,20 @@ const commonOptions: esbuild.BuildOptions = {
|
||||||
external: ["~plugins", "~git-hash", "/assets/*"],
|
external: ["~plugins", "~git-hash", "/assets/*"],
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("web"),
|
globPlugins("web"),
|
||||||
...commonOpts.plugins,
|
...commonRendererPlugins
|
||||||
],
|
],
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
define: {
|
define: {
|
||||||
IS_WEB: "true",
|
IS_WEB: true,
|
||||||
IS_EXTENSION: "false",
|
IS_EXTENSION: false,
|
||||||
IS_STANDALONE: "true",
|
IS_STANDALONE: true,
|
||||||
IS_DEV: String(IS_DEV),
|
IS_DEV,
|
||||||
IS_REPORTER: String(IS_REPORTER),
|
IS_REPORTER,
|
||||||
IS_DISCORD_DESKTOP: "false",
|
IS_DISCORD_DESKTOP: false,
|
||||||
IS_VESKTOP: "false",
|
IS_VESKTOP: false,
|
||||||
IS_UPDATER_DISABLED: "true",
|
IS_UPDATER_DISABLED: true,
|
||||||
VERSION: JSON.stringify(VERSION),
|
VERSION: JSON.stringify(VERSION),
|
||||||
BUILD_TIMESTAMP: String(BUILD_TIMESTAMP)
|
BUILD_TIMESTAMP
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,39 +67,39 @@ const RnNoiseFiles = [
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[
|
[
|
||||||
addBuild({
|
esbuild.build({
|
||||||
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
format: "iife",
|
format: "iife",
|
||||||
outbase: "node_modules/monaco-editor/esm/",
|
outbase: "node_modules/monaco-editor/esm/",
|
||||||
outdir: "dist/browser/monaco"
|
outdir: "dist/monaco"
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
entryPoints: ["browser/monaco.ts"],
|
entryPoints: ["browser/monaco.ts"],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
format: "iife",
|
format: "iife",
|
||||||
outfile: "dist/browser/monaco/index.js",
|
outfile: "dist/monaco/index.js",
|
||||||
loader: {
|
loader: {
|
||||||
".ttf": "file"
|
".ttf": "file"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
outfile: "dist/browser/browser.js",
|
outfile: "dist/browser.js",
|
||||||
footer: { js: "//# sourceURL=VencordWeb" }
|
footer: { js: "//# sourceURL=VencordWeb" }
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
outfile: "dist/browser/extension.js",
|
outfile: "dist/extension.js",
|
||||||
define: {
|
define: {
|
||||||
...commonOptions?.define,
|
...commonOptions?.define,
|
||||||
IS_EXTENSION: "true",
|
IS_EXTENSION: true,
|
||||||
},
|
},
|
||||||
footer: { js: "//# sourceURL=VencordWeb" }
|
footer: { js: "//# sourceURL=VencordWeb" }
|
||||||
}),
|
}),
|
||||||
addBuild({
|
esbuild.build({
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||||
define: {
|
define: {
|
||||||
|
@ -116,12 +116,18 @@ await Promise.all(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
);
|
).catch(err => {
|
||||||
|
console.error("Build failed");
|
||||||
|
console.error(err.message);
|
||||||
|
if (!commonOpts.watch)
|
||||||
|
process.exit(1);
|
||||||
|
});;
|
||||||
|
|
||||||
await buildOrWatchAll();
|
/**
|
||||||
|
* @type {(dir: string) => Promise<string[]>}
|
||||||
async function globDir(dir: string): Promise<string[]> {
|
*/
|
||||||
const files = [] as string[];
|
async function globDir(dir) {
|
||||||
|
const files = [];
|
||||||
|
|
||||||
for (const child of await readdir(dir, { withFileTypes: true })) {
|
for (const child of await readdir(dir, { withFileTypes: true })) {
|
||||||
const p = join(dir, child.name);
|
const p = join(dir, child.name);
|
||||||
|
@ -134,23 +140,27 @@ async function globDir(dir: string): Promise<string[]> {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadDir(dir: string, basePath = "") {
|
/**
|
||||||
|
* @type {(dir: string, basePath?: string) => Promise<Record<string, string>>}
|
||||||
|
*/
|
||||||
|
async function loadDir(dir, basePath = "") {
|
||||||
const files = await globDir(dir);
|
const files = await globDir(dir);
|
||||||
return Object.fromEntries(await Promise.all(files.map(async f =>
|
return Object.fromEntries(await Promise.all(files.map(async f => [f.slice(basePath.length), await readFile(f)])));
|
||||||
[f.slice(basePath.length), await readFile(f)] as const
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildExtension(target: string, files: string[]): Promise<void> {
|
/**
|
||||||
const entries: Record<string, Buffer> = {
|
* @type {(target: string, files: string[]) => Promise<void>}
|
||||||
"dist/Vencord.js": await readFile("dist/browser/extension.js"),
|
*/
|
||||||
"dist/Vencord.css": await readFile("dist/browser/extension.css"),
|
async function buildExtension(target, files) {
|
||||||
...await loadDir("dist/browser/monaco"),
|
const entries = {
|
||||||
|
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||||
|
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||||
|
...await loadDir("dist/monaco"),
|
||||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
||||||
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)] as const
|
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
||||||
))),
|
))),
|
||||||
...Object.fromEntries(await Promise.all(files.map(async f => {
|
...Object.fromEntries(await Promise.all(files.map(async f => {
|
||||||
let content: Uint8Array | Buffer = await readFile(join("browser", f));
|
let content = await readFile(join("browser", f));
|
||||||
if (f.startsWith("manifest")) {
|
if (f.startsWith("manifest")) {
|
||||||
const json = JSON.parse(content.toString("utf-8"));
|
const json = JSON.parse(content.toString("utf-8"));
|
||||||
json.version = VERSION;
|
json.version = VERSION;
|
||||||
|
@ -160,19 +170,19 @@ async function buildExtension(target: string, files: string[]): Promise<void> {
|
||||||
return [
|
return [
|
||||||
f.startsWith("manifest") ? "manifest.json" : f,
|
f.startsWith("manifest") ? "manifest.json" : f,
|
||||||
content
|
content
|
||||||
] as const;
|
];
|
||||||
})))
|
})))
|
||||||
};
|
};
|
||||||
|
|
||||||
await rm(target, { recursive: true, force: true });
|
await rm(target, { recursive: true, force: true });
|
||||||
await Promise.all(Object.entries(entries).map(async ([file, content]) => {
|
await Promise.all(Object.entries(entries).map(async ([file, content]) => {
|
||||||
const dest = join("dist/browser", target, file);
|
const dest = join("dist", target, file);
|
||||||
const parentDirectory = join(dest, "..");
|
const parentDirectory = join(dest, "..");
|
||||||
await mkdir(parentDirectory, { recursive: true });
|
await mkdir(parentDirectory, { recursive: true });
|
||||||
await writeFile(dest, content);
|
await writeFile(dest, content);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.info("Unpacked Extension written to dist/browser/" + target);
|
console.info("Unpacked Extension written to dist/" + target);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => {
|
const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => {
|
||||||
|
@ -195,14 +205,12 @@ if (!process.argv.includes("--skip-extension")) {
|
||||||
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Zip.zip("dist/browser/chromium-unpacked", (_err, zip) => {
|
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
|
||||||
zip.compress().save("dist/extension-chrome.zip");
|
|
||||||
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||||
});
|
|
||||||
Zip.zip("dist/browser/firefox-unpacked", (_err, zip) => {
|
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||||
zip.compress().save("dist/extension-firefox.zip");
|
|
||||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await appendCssRuntime;
|
await appendCssRuntime;
|
||||||
}
|
}
|
|
@ -20,7 +20,7 @@ import "../suppressExperimentalWarnings.js";
|
||||||
import "../checkNodeVersion.js";
|
import "../checkNodeVersion.js";
|
||||||
|
|
||||||
import { exec, execSync } from "child_process";
|
import { exec, execSync } from "child_process";
|
||||||
import esbuild, { build, BuildOptions, context, Plugin } from "esbuild";
|
import esbuild from "esbuild";
|
||||||
import { constants as FsConstants, readFileSync } from "fs";
|
import { constants as FsConstants, readFileSync } from "fs";
|
||||||
import { access, readdir, readFile } from "fs/promises";
|
import { access, readdir, readFile } from "fs/promises";
|
||||||
import { minify as minifyHtml } from "html-minifier-terser";
|
import { minify as minifyHtml } from "html-minifier-terser";
|
||||||
|
@ -28,8 +28,10 @@ import { join, relative } from "path";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
import { getPluginTarget } from "../utils.mjs";
|
import { getPluginTarget } from "../utils.mjs";
|
||||||
|
import { builtinModules } from "module";
|
||||||
|
|
||||||
const PackageJSON: typeof import("../../package.json") = JSON.parse(readFileSync("package.json", "utf-8"));
|
/** @type {import("../../package.json")} */
|
||||||
|
const PackageJSON = JSON.parse(readFileSync("package.json"));
|
||||||
|
|
||||||
export const VERSION = PackageJSON.version;
|
export const VERSION = PackageJSON.version;
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
@ -53,8 +55,11 @@ export const banner = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
||||||
|
/**
|
||||||
export async function resolvePluginName(base: string, dirent: import("fs").Dirent) {
|
* @param {string} base
|
||||||
|
* @param {import("fs").Dirent} dirent
|
||||||
|
*/
|
||||||
|
export async function resolvePluginName(base, dirent) {
|
||||||
const fullPath = join(base, dirent.name);
|
const fullPath = join(base, dirent.name);
|
||||||
const content = dirent.isFile()
|
const content = dirent.isFile()
|
||||||
? await readFile(fullPath, "utf-8")
|
? await readFile(fullPath, "utf-8")
|
||||||
|
@ -75,13 +80,28 @@ export async function resolvePluginName(base: string, dirent: import("fs").Diren
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exists(path: string) {
|
export async function exists(path) {
|
||||||
return await access(path, FsConstants.F_OK)
|
return await access(path, FsConstants.F_OK)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const globPlugins: (kind: "web" | "discordDesktop" | "vencordDesktop") => Plugin = kind => ({
|
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
||||||
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export const makeAllPackagesExternalPlugin = {
|
||||||
|
name: "make-all-packages-external",
|
||||||
|
setup(build) {
|
||||||
|
const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
|
||||||
|
build.onResolve({ filter }, args => ({ path: args.path, external: true }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {(kind: "web" | "discordDesktop" | "vencordDesktop") => import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export const globPlugins = kind => ({
|
||||||
name: "glob-plugins",
|
name: "glob-plugins",
|
||||||
setup: build => {
|
setup: build => {
|
||||||
const filter = /^~plugins$/;
|
const filter = /^~plugins$/;
|
||||||
|
@ -145,7 +165,10 @@ export const globPlugins: (kind: "web" | "discordDesktop" | "vencordDesktop") =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gitHashPlugin: Plugin = {
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export const gitHashPlugin = {
|
||||||
name: "git-hash-plugin",
|
name: "git-hash-plugin",
|
||||||
setup: build => {
|
setup: build => {
|
||||||
const filter = /^~git-hash$/;
|
const filter = /^~git-hash$/;
|
||||||
|
@ -158,7 +181,10 @@ export const gitHashPlugin: Plugin = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const gitRemotePlugin: Plugin = {
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export const gitRemotePlugin = {
|
||||||
name: "git-remote-plugin",
|
name: "git-remote-plugin",
|
||||||
setup: build => {
|
setup: build => {
|
||||||
const filter = /^~git-remote$/;
|
const filter = /^~git-remote$/;
|
||||||
|
@ -180,7 +206,10 @@ export const gitRemotePlugin: Plugin = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fileUrlPlugin: Plugin = {
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export const fileUrlPlugin = {
|
||||||
name: "file-uri-plugin",
|
name: "file-uri-plugin",
|
||||||
setup: build => {
|
setup: build => {
|
||||||
const filter = /^file:\/\/.+$/;
|
const filter = /^file:\/\/.+$/;
|
||||||
|
@ -200,7 +229,7 @@ export const fileUrlPlugin: Plugin = {
|
||||||
|
|
||||||
const encoding = base64 ? "base64" : "utf-8";
|
const encoding = base64 ? "base64" : "utf-8";
|
||||||
|
|
||||||
let content: string;
|
let content;
|
||||||
if (!minify) {
|
if (!minify) {
|
||||||
content = await readFile(path, encoding);
|
content = await readFile(path, encoding);
|
||||||
if (!noTrim) content = content.trimEnd();
|
if (!noTrim) content = content.trimEnd();
|
||||||
|
@ -240,7 +269,10 @@ export const fileUrlPlugin: Plugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const styleModule = readFileSync("./scripts/build/module/style.js", "utf-8");
|
const styleModule = readFileSync("./scripts/build/module/style.js", "utf-8");
|
||||||
export const stylePlugin: Plugin = {
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
export const stylePlugin = {
|
||||||
name: "style-plugin",
|
name: "style-plugin",
|
||||||
setup: ({ onResolve, onLoad }) => {
|
setup: ({ onResolve, onLoad }) => {
|
||||||
onResolve({ filter: /\.css\?managed$/, namespace: "file" }, ({ path, resolveDir }) => ({
|
onResolve({ filter: /\.css\?managed$/, namespace: "file" }, ({ path, resolveDir }) => ({
|
||||||
|
@ -261,59 +293,47 @@ export const stylePlugin: Plugin = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildsFinished = Promise.resolve();
|
/**
|
||||||
const buildsFinishedPlugin: Plugin = {
|
* @type {(filter: RegExp, message: string) => import("esbuild").Plugin}
|
||||||
name: "builds-finished-plugin",
|
*/
|
||||||
setup({ onEnd }) {
|
export const banImportPlugin = (filter, message) => ({
|
||||||
if (!watch) return;
|
name: "ban-imports",
|
||||||
|
setup: build => {
|
||||||
let resolve: () => void;
|
build.onResolve({ filter }, () => {
|
||||||
const done = new Promise<void>(r => resolve = r);
|
return { errors: [{ text: message }] };
|
||||||
buildsFinished = buildsFinished.then(() => done);
|
});
|
||||||
|
}
|
||||||
onEnd(() => resolve());
|
});
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("esbuild").BuildOptions}
|
||||||
|
*/
|
||||||
export const commonOpts = {
|
export const commonOpts = {
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
bundle: true,
|
bundle: true,
|
||||||
|
watch,
|
||||||
minify: !watch,
|
minify: !watch,
|
||||||
sourcemap: watch ? "inline" : "external",
|
sourcemap: watch ? "inline" : "",
|
||||||
legalComments: "linked",
|
legalComments: "linked",
|
||||||
banner,
|
banner,
|
||||||
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin, buildsFinishedPlugin],
|
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
|
||||||
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
|
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
|
||||||
inject: ["./scripts/build/inject/react.mjs"],
|
inject: ["./scripts/build/inject/react.mjs"],
|
||||||
jsxFactory: "VencordCreateElement",
|
jsxFactory: "VencordCreateElement",
|
||||||
jsxFragment: "VencordFragment",
|
jsxFragment: "VencordFragment",
|
||||||
jsx: "transform"
|
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||||
} satisfies BuildOptions;
|
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
||||||
|
};
|
||||||
|
|
||||||
|
const escapedBuiltinModules = builtinModules
|
||||||
|
.map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
|
||||||
|
.join("|");
|
||||||
|
const builtinModuleRegex = new RegExp(`^(node:)?(${escapedBuiltinModules})$`);
|
||||||
|
|
||||||
const builds = [] as BuildOptions[];
|
export const commonRendererPlugins = [
|
||||||
export function addBuild(options: BuildOptions) {
|
banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"),
|
||||||
builds.push(options);
|
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"),
|
||||||
export async function buildOrWatchAll() {
|
...commonOpts.plugins
|
||||||
if (watch) {
|
];
|
||||||
const contexts = await Promise.all(builds.map(context));
|
|
||||||
await Promise.all(contexts.map(ctx => ctx.watch()));
|
|
||||||
|
|
||||||
await buildsFinished;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await Promise.all(builds.map(build));
|
|
||||||
} catch (err) {
|
|
||||||
const reason = err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: err;
|
|
||||||
|
|
||||||
console.error("Build failed");
|
|
||||||
console.error(reason);
|
|
||||||
// make ci fail
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,8 +36,9 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
|
||||||
const CANARY = process.env.USE_CANARY === "true";
|
const CANARY = process.env.USE_CANARY === "true";
|
||||||
|
|
||||||
const browser = await pup.launch({
|
const browser = await pup.launch({
|
||||||
headless: "new",
|
headless: true,
|
||||||
executablePath: process.env.CHROMIUM_BIN
|
executablePath: process.env.CHROMIUM_BIN,
|
||||||
|
args: ["--no-sandbox"]
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
@ -225,7 +226,7 @@ page.on("console", async e => {
|
||||||
plugin,
|
plugin,
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
|
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
|
||||||
error: await maybeGetError(e.args()[3])
|
error: await maybeGetError(e.args()[3])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,6 @@ try {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
VENCORD_USER_DATA_DIR: BASE_DIR,
|
VENCORD_USER_DATA_DIR: BASE_DIR,
|
||||||
VENCORD_DIRECTORY: join(BASE_DIR, "dist/desktop"),
|
|
||||||
VENCORD_DEV_INSTALL: "1"
|
VENCORD_DEV_INSTALL: "1"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,11 +16,15 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function getPluginTarget(filePath: string) {
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function getPluginTarget(filePath) {
|
||||||
const pathParts = filePath.split(/[/\\]/);
|
const pathParts = filePath.split(/[/\\]/);
|
||||||
if (/^index\.tsx?$/.test(pathParts.at(-1)!)) pathParts.pop();
|
if (/^index\.tsx?$/.test(pathParts.at(-1))) pathParts.pop();
|
||||||
|
|
||||||
const identifier = pathParts.at(-1)!.replace(/\.tsx?$/, "");
|
const identifier = pathParts.at(-1).replace(/\.tsx?$/, "");
|
||||||
const identiferBits = identifier.split(".");
|
const identiferBits = identifier.split(".");
|
||||||
return identiferBits.length === 1 ? null : identiferBits.at(-1);
|
return identiferBits.length === 1 ? null : identiferBits.at(-1);
|
||||||
}
|
}
|
20
setup.bat
Normal file
20
setup.bat
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
@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
Normal file
18
setup.sh
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/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!"
|
||||||
|
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
||||||
* Register a new badge with the Badges API
|
* Register a new badge with the Badges API
|
||||||
* @param badge The badge to register
|
* @param badge The badge to register
|
||||||
*/
|
*/
|
||||||
export function addBadge(badge: ProfileBadge) {
|
export function addProfileBadge(badge: ProfileBadge) {
|
||||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||||
Badges.add(badge);
|
Badges.add(badge);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
||||||
* Unregister a badge from the Badges API
|
* Unregister a badge from the Badges API
|
||||||
* @param badge The badge to remove
|
* @param badge The badge to remove
|
||||||
*/
|
*/
|
||||||
export function removeBadge(badge: ProfileBadge) {
|
export function removeProfileBadge(badge: ProfileBadge) {
|
||||||
return Badges.delete(badge);
|
return Badges.delete(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +100,3 @@ export interface BadgeUserArgs {
|
||||||
userId: string;
|
userId: string;
|
||||||
guildId: 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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ import "./ChatButton.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||||
|
@ -74,9 +74,9 @@ export interface ChatBarProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||||
|
|
||||||
const buttonFactories = new Map<string, ChatBarButton>();
|
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||||
const logger = new Logger("ChatButtons");
|
const logger = new Logger("ChatButtons");
|
||||||
|
|
||||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
|
@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||||
|
|
||||||
export interface ChatBarButtonProps {
|
export interface ChatBarButtonProps {
|
||||||
|
@ -99,7 +99,8 @@ export interface ChatBarButtonProps {
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
|
onAuxClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu" | "onAuxClick">;
|
||||||
}
|
}
|
||||||
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
return (
|
return (
|
||||||
|
@ -109,12 +110,13 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
<Button
|
<Button
|
||||||
aria-label={props.tooltip}
|
aria-label={props.tooltip}
|
||||||
size=""
|
size=""
|
||||||
look={ButtonLooks.BLANK}
|
look={Button.Looks.BLANK}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
onContextMenu={props.onContextMenu}
|
onContextMenu={props.onContextMenu}
|
||||||
|
onAuxClick={props.onAuxClick}
|
||||||
{...props.buttonProps}
|
{...props.buttonProps}
|
||||||
>
|
>
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
|
|
|
@ -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): T & {} | undefined;
|
||||||
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
||||||
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
|
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,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
|
|
||||||
import { sendBotMessage } from "./commandHelpers";
|
import { sendBotMessage } from "./commandHelpers";
|
||||||
|
@ -46,10 +47,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder;
|
||||||
export const _init = function (cmds: Command[]) {
|
export const _init = function (cmds: Command[]) {
|
||||||
try {
|
try {
|
||||||
BUILT_IN = cmds;
|
BUILT_IN = cmds;
|
||||||
OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0];
|
OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
|
||||||
RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0];
|
RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load CommandsApi");
|
new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds);
|
||||||
}
|
}
|
||||||
return cmds;
|
return cmds;
|
||||||
} as never;
|
} as never;
|
||||||
|
@ -109,6 +110,7 @@ function registerSubCommands(cmd: Command, plugin: string) {
|
||||||
const subCmd = {
|
const subCmd = {
|
||||||
...cmd,
|
...cmd,
|
||||||
...o,
|
...o,
|
||||||
|
options: o.options !== undefined ? o.options : undefined,
|
||||||
type: ApplicationCommandType.CHAT_INPUT,
|
type: ApplicationCommandType.CHAT_INPUT,
|
||||||
name: `${cmd.name} ${o.name}`,
|
name: `${cmd.name} ${o.name}`,
|
||||||
id: `${o.name}-${cmd.id}`,
|
id: `${o.name}-${cmd.id}`,
|
||||||
|
@ -138,6 +140,8 @@ export function registerCommand<C extends Command>(command: C, plugin: string) {
|
||||||
throw new Error(`Command '${command.name}' already exists.`);
|
throw new Error(`Command '${command.name}' already exists.`);
|
||||||
|
|
||||||
command.isVencordCommand = true;
|
command.isVencordCommand = true;
|
||||||
|
command.untranslatedName ??= command.name;
|
||||||
|
command.untranslatedDescription ??= command.description;
|
||||||
command.id ??= `-${BUILT_IN.length + 1}`;
|
command.id ??= `-${BUILT_IN.length + 1}`;
|
||||||
command.applicationId ??= "-1"; // BUILT_IN;
|
command.applicationId ??= "-1"; // BUILT_IN;
|
||||||
command.type ??= ApplicationCommandType.CHAT_INPUT;
|
command.type ??= ApplicationCommandType.CHAT_INPUT;
|
||||||
|
|
|
@ -93,8 +93,10 @@ export interface Command {
|
||||||
isVencordCommand?: boolean;
|
isVencordCommand?: boolean;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
|
untranslatedName?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
untranslatedDescription?: string;
|
||||||
displayDescription?: string;
|
displayDescription?: string;
|
||||||
|
|
||||||
options?: Option[];
|
options?: Option[];
|
||||||
|
|
|
@ -24,13 +24,13 @@ import type { ReactElement } from "react";
|
||||||
* @param children The rendered context menu elements
|
* @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
|
* @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 | null>, ...args: Array<any>) => void;
|
export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||||
/**
|
/**
|
||||||
* @param navId The navId of the context menu being patched
|
* @param navId The navId of the context menu being patched
|
||||||
* @param children The rendered context menu elements
|
* @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
|
* @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 | null>, ...args: Array<any>) => void;
|
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||||
|
|
||||||
const ContextMenuLogger = new Logger("ContextMenu");
|
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)
|
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||||
*/
|
*/
|
||||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
const navIds: string[] = Array.isArray(navId) ? navId : [navId];
|
||||||
|
|
||||||
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
||||||
|
|
||||||
|
@ -90,19 +90,20 @@ 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
|
* 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 id The id of the child. If an array is specified, all ids will be tried
|
||||||
* @param children The context menu children
|
* @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 | null>): Array<ReactElement | null> | null {
|
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
|
|
||||||
if (Array.isArray(child)) {
|
if (Array.isArray(child)) {
|
||||||
const found = findGroupChildrenByChildId(id, child);
|
const found = findGroupChildrenByChildId(id, child, matchSubstring);
|
||||||
if (found !== null) return found;
|
if (found !== null) return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(Array.isArray(id) && id.some(id => child.props?.id === id))
|
(Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|
||||||
|| child.props?.id === id
|
|| (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
|
||||||
) return children;
|
) return children;
|
||||||
|
|
||||||
let nextChildren = child.props?.children;
|
let nextChildren = child.props?.children;
|
||||||
|
@ -112,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
child.props.children = nextChildren;
|
child.props.children = nextChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
const found = findGroupChildrenByChildId(id, nextChildren);
|
const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
|
||||||
if (found !== null) return found;
|
if (found !== null) return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,9 +122,9 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
contextMenuApiArguments?: Array<any>;
|
contextMenuAPIArguments?: Array<any>;
|
||||||
navId: string;
|
navId: string;
|
||||||
children: Array<ReactElement | null>;
|
children: Array<ReactElement<any> | null>;
|
||||||
"aria-label": string;
|
"aria-label": string;
|
||||||
onSelect: (() => void) | undefined;
|
onSelect: (() => void) | undefined;
|
||||||
onClose: (callback: (...args: Array<any>) => any) => void;
|
onClose: (callback: (...args: Array<any>) => any) => void;
|
||||||
|
@ -135,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
children: cloneMenuChildren(props.children),
|
children: cloneMenuChildren(props.children),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuAPIArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||||
|
@ -143,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -152,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
|
@ -161,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
|
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map(cloneMenuChildren);
|
return obj.map(cloneMenuChildren);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, User } from "discord-types/general/index.js";
|
import { Channel, User } from "discord-types/general/index.js";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
activities: any[];
|
activities: any[];
|
||||||
|
@ -38,27 +40,39 @@ interface DecoratorProps {
|
||||||
user: User;
|
user: User;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||||
type OnlyIn = "guilds" | "dms";
|
type OnlyIn = "guilds" | "dms";
|
||||||
|
|
||||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||||
|
|
||||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||||
decorators.set(identifier, { decorator, onlyIn });
|
decoratorsFactories.set(identifier, { render, onlyIn });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecorator(identifier: string) {
|
export function removeMemberListDecorator(identifier: string) {
|
||||||
decorators.delete(identifier);
|
decoratorsFactories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): JSX.Element {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return Array.from(decorators.values(), decoratorObj => {
|
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
const decorators = Array.from(
|
||||||
// this can most likely be done cleaner
|
decoratorsFactories.entries(),
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
([key, { render: Decorator, onlyIn }]) => {
|
||||||
return decorator(props);
|
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
});
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||||
|
<Decorator {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="vc-member-list-decorators-wrapper">
|
||||||
|
{decorators}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -16,26 +16,29 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
export type Accessory = {
|
import { JSX, ReactNode } from "react";
|
||||||
callback: AccessoryCallback;
|
|
||||||
|
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||||
|
export type MessageAccessory = {
|
||||||
|
render: MessageAccessoryFactory;
|
||||||
position?: number;
|
position?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessories = new Map<String, Accessory>();
|
export const accessories = new Map<string, MessageAccessory>();
|
||||||
|
|
||||||
export function addAccessory(
|
export function addMessageAccessory(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
callback: AccessoryCallback,
|
render: MessageAccessoryFactory,
|
||||||
position?: number
|
position?: number
|
||||||
) {
|
) {
|
||||||
accessories.set(identifier, {
|
accessories.set(identifier, {
|
||||||
callback,
|
render,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAccessory(identifier: string) {
|
export function removeMessageAccessory(identifier: string) {
|
||||||
accessories.delete(identifier);
|
accessories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,15 +46,12 @@ export function _modifyAccessories(
|
||||||
elements: JSX.Element[],
|
elements: JSX.Element[],
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
) {
|
) {
|
||||||
for (const accessory of accessories.values()) {
|
for (const [key, accessory] of accessories.entries()) {
|
||||||
let accessories = accessory.callback(props);
|
const res = (
|
||||||
if (accessories == null)
|
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||||
continue;
|
<accessory.render {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
if (!Array.isArray(accessories))
|
);
|
||||||
accessories = [accessories];
|
|
||||||
else if (accessories.length === 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
elements.splice(
|
elements.splice(
|
||||||
accessory.position != null
|
accessory.position != null
|
||||||
|
@ -60,7 +60,7 @@ export function _modifyAccessories(
|
||||||
: accessory.position
|
: accessory.position
|
||||||
: elements.length,
|
: elements.length,
|
||||||
0,
|
0,
|
||||||
...accessories.filter(e => e != null) as JSX.Element[]
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, Message } from "discord-types/general/index.js";
|
import { Channel, Message } from "discord-types/general/index.js";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecorationProps {
|
export interface MessageDecorationProps {
|
||||||
author: {
|
author: {
|
||||||
/**
|
/**
|
||||||
* Will be username if the user has no nickname
|
* Will be username if the user has no nickname
|
||||||
|
@ -44,20 +46,31 @@ interface DecorationProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||||
|
|
||||||
export const decorations = new Map<string, Decoration>();
|
export const decorationsFactories = new Map<string, MessageDecorationFactory>();
|
||||||
|
|
||||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||||
decorations.set(identifier, decoration);
|
decorationsFactories.set(identifier, decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecoration(identifier: string) {
|
export function removeMessageDecoration(identifier: string) {
|
||||||
decorations.delete(identifier);
|
decorationsFactories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element {
|
||||||
return [...decorations.values()].map(decoration => {
|
const decorations = Array.from(
|
||||||
return decoration(props);
|
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>
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
||||||
openWarningPopout: (props: any) => any;
|
openWarningPopout: (props: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||||
|
|
||||||
const sendListeners = new Set<SendListener>();
|
const sendListeners = new Set<MessageSendListener>();
|
||||||
const editListeners = new Set<EditListener>();
|
const editListeners = new Set<MessageEditListener>();
|
||||||
|
|
||||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||||
extra.replyOptions = replyOptions;
|
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.
|
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreSendListener(listener: SendListener) {
|
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||||
sendListeners.add(listener);
|
sendListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreEditListener(listener: EditListener) {
|
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||||
editListeners.add(listener);
|
editListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
export function removePreSendListener(listener: SendListener) {
|
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||||
return sendListeners.delete(listener);
|
return sendListeners.delete(listener);
|
||||||
}
|
}
|
||||||
export function removePreEditListener(listener: EditListener) {
|
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||||
return editListeners.delete(listener);
|
return editListeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Message clicks
|
// Message clicks
|
||||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||||
|
|
||||||
const listeners = new Set<ClickListener>();
|
const listeners = new Set<MessageClickListener>();
|
||||||
|
|
||||||
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||||
// message object may be outdated, so (try to) fetch latest one
|
// 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 addClickListener(listener: ClickListener) {
|
export function addMessageClickListener(listener: MessageClickListener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeClickListener(listener: ClickListener) {
|
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||||
return listeners.delete(listener);
|
return listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,54 +16,59 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
import type { MouseEventHandler } from "react";
|
import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
export interface ButtonItem {
|
export interface MessagePopoverButtonItem {
|
||||||
key?: string,
|
key?: string,
|
||||||
label: string,
|
label: string,
|
||||||
icon: React.ComponentType<any>,
|
icon: ComponentType<any>,
|
||||||
message: Message,
|
message: Message,
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>,
|
onClick?: MouseEventHandler<HTMLButtonElement>,
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||||
|
|
||||||
export const buttons = new Map<string, getButtonItem>();
|
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||||
|
|
||||||
export function addButton(
|
export function addMessagePopoverButton(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
item: getButtonItem,
|
item: MessagePopoverButtonFactory,
|
||||||
) {
|
) {
|
||||||
buttons.set(identifier, item);
|
buttons.set(identifier, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeButton(identifier: string) {
|
export function removeMessagePopoverButton(identifier: string) {
|
||||||
buttons.delete(identifier);
|
buttons.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _buildPopoverElements(
|
export function _buildPopoverElements(
|
||||||
msg: Message,
|
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||||
makeButton: (item: ButtonItem) => React.ComponentType
|
message: Message
|
||||||
) {
|
) {
|
||||||
const items = [] as React.ComponentType[];
|
const items: React.ReactNode[] = [];
|
||||||
|
|
||||||
for (const [identifier, getItem] of buttons.entries()) {
|
for (const [identifier, getItem] of buttons.entries()) {
|
||||||
try {
|
try {
|
||||||
const item = getItem(msg);
|
const item = getItem(message);
|
||||||
if (item) {
|
if (item) {
|
||||||
item.key ??= identifier;
|
item.key ??= identifier;
|
||||||
items.push(makeButton(item));
|
items.push(
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<Component {...item} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[${identifier}]`, err);
|
logger.error(`[${identifier}]`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return <>{items}</>;
|
||||||
}
|
}
|
|
@ -16,40 +16,36 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { ComponentType } from "react";
|
||||||
const logger = new Logger("ServerListAPI");
|
|
||||||
|
|
||||||
export const enum ServerListRenderPosition {
|
export const enum ServerListRenderPosition {
|
||||||
Above,
|
Above,
|
||||||
In,
|
In,
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderFunctionsAbove = new Set<Function>();
|
const componentsAbove = new Set<ComponentType>();
|
||||||
const renderFunctionsIn = new Set<Function>();
|
const componentsBelow = new Set<ComponentType>();
|
||||||
|
|
||||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).add(renderFunction);
|
getRenderFunctions(position).add(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).delete(renderFunction);
|
getRenderFunctions(position).delete(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderAll = (position: ServerListRenderPosition) => {
|
export const renderAll = (position: ServerListRenderPosition) => {
|
||||||
const ret: Array<JSX.Element> = [];
|
return Array.from(
|
||||||
|
getRenderFunctions(position),
|
||||||
for (const renderFunction of getRenderFunctions(position)) {
|
(Component, i) => (
|
||||||
try {
|
<ErrorBoundary noop key={i}>
|
||||||
ret.unshift(renderFunction());
|
<Component />
|
||||||
} catch (e) {
|
</ErrorBoundary>
|
||||||
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 { mergeDefaults } from "@utils/mergeDefaults";
|
||||||
import { putCloudSettings } from "@utils/settingsSync";
|
import { putCloudSettings } from "@utils/settingsSync";
|
||||||
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
||||||
import { React } from "@webpack/common";
|
import { React, useEffect } from "@webpack/common";
|
||||||
|
|
||||||
import plugins from "~plugins";
|
import plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ export const Settings = SettingsStore.store;
|
||||||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (paths) {
|
if (paths) {
|
||||||
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||||
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||||
|
@ -200,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
SettingsStore.addGlobalChangeListener(forceUpdate);
|
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||||
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [paths]);
|
||||||
|
|
||||||
return SettingsStore.store;
|
return SettingsStore.store;
|
||||||
}
|
}
|
||||||
|
@ -220,6 +220,17 @@ 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<
|
export function definePluginSettings<
|
||||||
Def extends SettingsDefinition,
|
Def extends SettingsDefinition,
|
||||||
Checks extends SettingsChecks<Def>,
|
Checks extends SettingsChecks<Def>,
|
||||||
|
@ -230,6 +241,10 @@ export function definePluginSettings<
|
||||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||||
return Settings.plugins[definedSettings.pluginName] as any;
|
return Settings.plugins[definedSettings.pluginName] as any;
|
||||||
},
|
},
|
||||||
|
get plain() {
|
||||||
|
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||||
|
return PlainSettings.plugins[definedSettings.pluginName] as any;
|
||||||
|
},
|
||||||
use: settings => useSettings(
|
use: settings => useSettings(
|
||||||
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
||||||
).plugins[definedSettings.pluginName] as any,
|
).plugins[definedSettings.pluginName] as any,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Badge({ text, color }): JSX.Element {
|
export function Badge({ text, color }) {
|
||||||
return (
|
return (
|
||||||
<div className="vc-plugins-badge" style={{
|
<div className="vc-plugins-badge" style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
|
|
|
@ -17,16 +17,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button } from "@webpack/common";
|
import { Button } from "@webpack/common";
|
||||||
|
import { ButtonProps } from "@webpack/types";
|
||||||
|
|
||||||
import { Heart } from "./Heart";
|
import { Heart } from "./Heart";
|
||||||
|
|
||||||
export default function DonateButton(props: any) {
|
export default function DonateButton({
|
||||||
|
look = Button.Looks.LINK,
|
||||||
|
color = Button.Colors.TRANSPARENT,
|
||||||
|
...props
|
||||||
|
}: Partial<ButtonProps>) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
look={Button.Looks.LINK}
|
look={look}
|
||||||
color={Button.Colors.TRANSPARENT}
|
color={color}
|
||||||
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
||||||
|
innerClassName="vc-donate-button"
|
||||||
>
|
>
|
||||||
<Heart />
|
<Heart />
|
||||||
Donate
|
Donate
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface Props<T = any> {
|
||||||
/** Render nothing if an error occurs */
|
/** Render nothing if an error occurs */
|
||||||
noop?: boolean;
|
noop?: boolean;
|
||||||
/** Fallback component to render if an error occurs */
|
/** Fallback component to render if an error occurs */
|
||||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
|
||||||
/** called when an error occurs. The props property is only available if using .wrap */
|
/** called when an error occurs. The props property is only available if using .wrap */
|
||||||
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
||||||
/** Custom error message */
|
/** Custom error message */
|
||||||
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -80,10 +79,14 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
if (this.props.noop) return null;
|
if (this.props.noop) return null;
|
||||||
|
|
||||||
if (this.props.fallback)
|
if (this.props.fallback)
|
||||||
return <this.props.fallback
|
return (
|
||||||
children={this.props.children}
|
<this.props.fallback
|
||||||
|
wrappedProps={this.props.wrappedProps}
|
||||||
{...this.state}
|
{...this.state}
|
||||||
/>;
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</this.props.fallback>
|
||||||
|
);
|
||||||
|
|
||||||
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
.vc-expandableheader-center-flex {
|
|
||||||
display: flex;
|
|
||||||
justify-items: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-expandableheader-btn {
|
|
||||||
all: unset;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
|
@ -1,121 +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 "./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
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CSSProperties } from "react";
|
import { CSSProperties, JSX } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
columns: number;
|
columns: number;
|
||||||
|
|
|
@ -16,18 +16,22 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Heart() {
|
import { classes } from "@utils/misc";
|
||||||
|
import { SVGProps } from "react";
|
||||||
|
|
||||||
|
export function Heart(props: SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
|
height="16"
|
||||||
width="16"
|
width="16"
|
||||||
style={{ marginRight: "0.5em", transform: "translateY(2px)" }}
|
{...props}
|
||||||
|
className={classes("vc-heart-icon", props.className)}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#db61a2"
|
fill="#db61a2"
|
||||||
fill-rule="evenodd"
|
fillRule="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"
|
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>
|
</svg>
|
||||||
|
|
|
@ -18,10 +18,9 @@
|
||||||
|
|
||||||
import "./iconStyles.css";
|
import "./iconStyles.css";
|
||||||
|
|
||||||
import { getTheme, Theme } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { i18n } from "@webpack/common";
|
import type { JSX, PropsWithChildren } from "react";
|
||||||
import type { PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
|
@ -56,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
||||||
className={classes(className, "vc-link-icon")}
|
className={classes(className, "vc-link-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g fill="none" fill-rule="evenodd">
|
<g fill="none" fillRule="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" />
|
<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} />
|
<rect width={width} height={height} />
|
||||||
</g>
|
</g>
|
||||||
|
@ -65,8 +64,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discord's copy icon, as seen in the user popout right of the username when clicking
|
* Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks
|
||||||
* your own username in the bottom left user panel
|
|
||||||
*/
|
*/
|
||||||
export function CopyIcon(props: IconProps) {
|
export function CopyIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -76,8 +74,9 @@ export function CopyIcon(props: IconProps) {
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g fill="currentColor">
|
<g fill="currentColor">
|
||||||
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
|
<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="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" />
|
<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" />
|
||||||
</g>
|
</g>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -123,8 +122,8 @@ export function InfoIcon(props: IconProps) {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
transform="translate(2 2)"
|
fillRule="evenodd"
|
||||||
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
|
||||||
export function OwnerCrownIcon(props: IconProps) {
|
export function OwnerCrownIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
aria-label={i18n.Messages.GUILD_OWNER}
|
aria-label={getIntlMessage("GUILD_OWNER")}
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-owner-crown-icon")}
|
className={classes(props.className, "vc-owner-crown-icon")}
|
||||||
role="img"
|
role="img"
|
||||||
|
@ -212,9 +211,10 @@ export function CogWheel(props: IconProps) {
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
clipRule="evenodd"
|
|
||||||
fill="currentColor"
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
>
|
>
|
||||||
<polygon
|
<polygon
|
||||||
fill-rule="nonzero"
|
fillRule="nonzero"
|
||||||
fill="currentColor"
|
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"
|
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,23 +407,30 @@ export function PencilIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
export function GithubIcon(props: IconProps) {
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
return (
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
<Icon
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
{...props}
|
||||||
|
viewBox="-3 -3 30 30"
|
||||||
export function GithubIcon(props: ImageProps) {
|
>
|
||||||
const src = getTheme() === Theme.Light
|
<path
|
||||||
? GithubIconLight
|
fill={props.fill || "currentColor"}
|
||||||
: GithubIconDark;
|
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"
|
||||||
|
/>
|
||||||
return <img {...props} src={src} />;
|
</Icon>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteIcon(props: ImageProps) {
|
export function WebsiteIcon(props: IconProps) {
|
||||||
const src = getTheme() === Theme.Light
|
return (
|
||||||
? WebsiteIconLight
|
<Icon
|
||||||
: WebsiteIconDark;
|
{...props}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
return <img {...props} src={src} />;
|
>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!profile && !user.bot && user.id)
|
if (!profile && !user.bot && user.id)
|
||||||
fetchUserProfile(user.id);
|
fetchUserProfile(user.id);
|
||||||
}, [user.id]);
|
}, [user.id, user.bot, profile]);
|
||||||
|
|
||||||
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||||
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||||
|
|
|
@ -6,16 +6,19 @@
|
||||||
|
|
||||||
import "./LinkIconButton.css";
|
import "./LinkIconButton.css";
|
||||||
|
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { GithubIcon, WebsiteIcon } from "..";
|
import { GithubIcon, WebsiteIcon } from "..";
|
||||||
|
|
||||||
export function GithubLinkIcon() {
|
export function GithubLinkIcon() {
|
||||||
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
|
||||||
|
return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteLinkIcon() {
|
export function WebsiteLinkIcon() {
|
||||||
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
|
||||||
|
return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { Constructor } from "type-fest";
|
||||||
import { PluginMeta } from "~plugins";
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ISettingCustomElementProps,
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
SettingCustomComponent,
|
SettingCustomComponent,
|
||||||
|
@ -74,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
|
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
|
||||||
[OptionType.STRING]: SettingTextComponent,
|
[OptionType.STRING]: SettingTextComponent,
|
||||||
[OptionType.NUMBER]: SettingNumericComponent,
|
[OptionType.NUMBER]: SettingNumericComponent,
|
||||||
[OptionType.BIGINT]: SettingNumericComponent,
|
[OptionType.BIGINT]: SettingNumericComponent,
|
||||||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
[OptionType.SELECT]: SettingSelectComponent,
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
[OptionType.SLIDER]: SettingSliderComponent,
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
[OptionType.COMPONENT]: SettingCustomComponent
|
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||||
|
[OptionType.CUSTOM]: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
@ -109,7 +111,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
setAuthors(a => [...a, author]);
|
setAuthors(a => [...a, author]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, [plugin.authors]);
|
||||||
|
|
||||||
async function saveAndClose() {
|
async function saveAndClose() {
|
||||||
if (!plugin.options) {
|
if (!plugin.options) {
|
||||||
|
@ -129,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
for (const [key, value] of Object.entries(tempSettings)) {
|
for (const [key, value] of Object.entries(tempSettings)) {
|
||||||
const option = plugin.options[key];
|
const option = plugin.options[key];
|
||||||
pluginSettings[key] = value;
|
pluginSettings[key] = value;
|
||||||
option?.onChange?.(value);
|
|
||||||
|
if (option.type === OptionType.CUSTOM) continue;
|
||||||
if (option?.restartNeeded) restartNeeded = true;
|
if (option?.restartNeeded) restartNeeded = true;
|
||||||
}
|
}
|
||||||
if (restartNeeded) onRestartNeeded();
|
if (restartNeeded) onRestartNeeded();
|
||||||
|
@ -141,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
} else {
|
} else {
|
||||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
if (setting.hidden) return null;
|
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||||
|
|
||||||
function onChange(newValue: any) {
|
function onChange(newValue: any) {
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
import { PluginOptionComponent } from "@utils/types";
|
import { PluginOptionComponent } from "@utils/types";
|
||||||
|
|
||||||
import { ISettingElementProps } from ".";
|
import { ISettingCustomElementProps } from ".";
|
||||||
|
|
||||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
|
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
|
||||||
return option.component({ setValue: onChange, setError: onError, option });
|
return option.component({ setValue: onChange, setError: onError, option });
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||||
import { OptionType, PluginOptionNumber } from "@utils/types";
|
import { OptionType, PluginOptionNumber } from "@utils/types";
|
||||||
import { Forms, React, TextInput } from "@webpack/common";
|
import { Forms, React, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -54,7 +56,8 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="number"
|
type="number"
|
||||||
pattern="-?[0-9]+"
|
pattern="-?[0-9]+"
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||||
import { PluginOptionSelect } from "@utils/types";
|
import { PluginOptionSelect } from "@utils/types";
|
||||||
import { Forms, React, Select } from "@webpack/common";
|
import { Forms, React, Select } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -44,7 +46,8 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
|
||||||
<Select
|
<Select
|
||||||
isDisabled={option.disabled?.call(definedSettings) ?? false}
|
isDisabled={option.disabled?.call(definedSettings) ?? false}
|
||||||
options={option.options}
|
options={option.options}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||||
import { PluginOptionSlider } from "@utils/types";
|
import { PluginOptionSlider } from "@utils/types";
|
||||||
import { Forms, React, Slider } from "@webpack/common";
|
import { Forms, React, Slider } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -50,7 +52,8 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
||||||
<Slider
|
<Slider
|
||||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||||
markers={option.markers}
|
markers={option.markers}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
||||||
import { PluginOptionString } from "@utils/types";
|
import { PluginOptionString } from "@utils/types";
|
||||||
import { Forms, React, TextInput } from "@webpack/common";
|
import { Forms, React, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -41,7 +43,8 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
value={state}
|
value={state}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||||
|
|
||||||
export interface ISettingElementProps<T extends PluginOptionBase> {
|
interface ISettingElementPropsBase<T> {
|
||||||
option: T;
|
option: T;
|
||||||
onChange(newValue: any): void;
|
onChange(newValue: any): void;
|
||||||
pluginSettings: {
|
pluginSettings: {
|
||||||
|
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
definedSettings?: DefinedSettings;
|
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 "../../Badge";
|
||||||
export * from "./SettingBooleanComponent";
|
export * from "./SettingBooleanComponent";
|
||||||
export * from "./SettingCustomComponent";
|
export * from "./SettingCustomComponent";
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
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";
|
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
|
||||||
<Forms.FormText className={cl("dep-text")}>
|
<Forms.FormText className={cl("dep-text")}>
|
||||||
Restart now to apply new plugins and their settings
|
Restart now to apply new plugins and their settings
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Button onClick={() => location.reload()}>
|
<Button onClick={() => location.reload()} className={cl("restart-button")}>
|
||||||
Restart
|
Restart
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
@ -93,7 +94,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
||||||
const settings = Settings.plugins[plugin.name];
|
const settings = Settings.plugins[plugin.name];
|
||||||
|
|
||||||
const isEnabled = () => settings.enabled ?? false;
|
const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);
|
||||||
|
|
||||||
function toggleEnabled() {
|
function toggleEnabled() {
|
||||||
const wasEnabled = isEnabled();
|
const wasEnabled = isEnabled();
|
||||||
|
@ -157,8 +158,8 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
className={classes(ButtonClasses.button, cl("info-button"))}
|
className={classes(ButtonClasses.button, cl("info-button"))}
|
||||||
>
|
>
|
||||||
{plugin.options && !isObjectEmpty(plugin.options)
|
{plugin.options && !isObjectEmpty(plugin.options)
|
||||||
? <CogWheel />
|
? <CogWheel className={cl("info-icon")} />
|
||||||
: <InfoIcon />}
|
: <InfoIcon className={cl("info-icon")} />}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -292,10 +293,10 @@ export default function PluginSettings() {
|
||||||
|
|
||||||
if (!pluginFilter(p)) continue;
|
if (!pluginFilter(p)) continue;
|
||||||
|
|
||||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||||
|
|
||||||
if (isRequired) {
|
if (isRequired) {
|
||||||
const tooltipText = p.required
|
const tooltipText = p.required || !depMap[p.name]
|
||||||
? "This plugin is required for Vencord to function."
|
? "This plugin is required for Vencord to function."
|
||||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||||
|
|
||||||
|
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||||
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,7 @@
|
||||||
height: 8em;
|
height: 8em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
gap: 0.25em;
|
||||||
|
|
||||||
.vc-plugins-info-card div {
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-restart-card {
|
.vc-plugins-restart-card {
|
||||||
|
@ -76,11 +73,11 @@
|
||||||
color: var(--info-warning-text);
|
color: var(--info-warning-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-restart-card button {
|
.vc-plugins-restart-button {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
background: var(--info-warning-foreground) !important;
|
background: var(--info-warning-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-info-button svg:not(:hover, :focus) {
|
.vc-plugins-info-icon:not(:hover, :focus) {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDiff() {
|
function renderDiff() {
|
||||||
return diff?.map(p => {
|
return diff?.map((p, idx) => {
|
||||||
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
||||||
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
|
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
|
||||||
|
|
||||||
if (!parsed.find) throw new Error("No 'find' field");
|
if (!parsed.find) throw new Error("No 'find' field");
|
||||||
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
||||||
|
@ -382,6 +382,7 @@ function PatchHelper() {
|
||||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||||
<CodeBlock lang="js" content={code} />
|
<CodeBlock lang="js" content={code} />
|
||||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
<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>
|
</SettingsTab>
|
||||||
|
|
77
src/components/VencordSettings/SpecialCard.tsx
Normal file
77
src/components/VencordSettings/SpecialCard.tsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -25,10 +25,9 @@ import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
|
||||||
|
@ -45,9 +44,7 @@ type FileInput = ComponentType<{
|
||||||
filters?: { name?: string; extensions: string[]; }[];
|
filters?: { name?: string; extensions: string[]; }[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const InviteActions = findByPropsLazy("resolveInvite");
|
|
||||||
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
||||||
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
const cl = classNameFactory("vc-settings-theme-");
|
||||||
|
|
||||||
|
@ -80,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
||||||
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
<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>
|
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
||||||
<div>
|
<div>
|
||||||
{themeLinks.map(link => (
|
{themeLinks.map(rawLink => {
|
||||||
<Card style={{
|
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={{
|
||||||
padding: ".5em",
|
padding: ".5em",
|
||||||
marginBottom: ".5em",
|
marginBottom: ".5em",
|
||||||
marginTop: ".5em"
|
marginTop: ".5em"
|
||||||
|
@ -89,11 +94,11 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
||||||
<Forms.FormTitle tag="h5" style={{
|
<Forms.FormTitle tag="h5" style={{
|
||||||
overflowWrap: "break-word"
|
overflowWrap: "break-word"
|
||||||
}}>
|
}}>
|
||||||
{link}
|
{label}
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<Validator link={link} />
|
<Validator link={link} />
|
||||||
</Card>
|
</Card>;
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -299,6 +304,7 @@ function ThemesTab() {
|
||||||
<Card className="vc-settings-card vc-text-selectable">
|
<Card className="vc-settings-card vc-text-selectable">
|
||||||
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
<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>
|
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
@ -306,7 +312,7 @@ function ThemesTab() {
|
||||||
<TextArea
|
<TextArea
|
||||||
value={themeText}
|
value={themeText}
|
||||||
onChange={setThemeText}
|
onChange={setThemeText}
|
||||||
className={classes(TextAreaProps.textarea, "vc-settings-theme-links")}
|
className={"vc-settings-theme-links"}
|
||||||
placeholder="Theme Links"
|
placeholder="Theme Links"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
|
|
@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
||||||
title: "Oops!",
|
title: "Oops!",
|
||||||
body: (
|
body: (
|
||||||
<ErrorCard>
|
<ErrorCard>
|
||||||
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
|
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
|
||||||
return (
|
return (
|
||||||
<Card style={{ padding: "0 0.5em" }}>
|
<Card style={{ padding: "0 0.5em" }}>
|
||||||
{updates.map(({ hash, author, message }) => (
|
{updates.map(({ hash, author, message }) => (
|
||||||
<div style={{
|
<div key={hash} style={{
|
||||||
marginTop: "0.5em",
|
marginTop: "0.5em",
|
||||||
marginBottom: "0.5em"
|
marginBottom: "0.5em"
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -20,29 +20,38 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||||
import { useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
|
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import { gitRemote } from "@shared/vencordUserAgent";
|
import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
|
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity, isPluginDev } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Button, Card, Forms, React, Select, Switch } from "@webpack/common";
|
import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
|
import BadgeAPI from "../../plugins/_api/badges";
|
||||||
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
||||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
import { SpecialCard } from "./SpecialCard";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
|
|
||||||
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
|
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
|
||||||
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.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> = {
|
type KeysOfType<Object, Type> = {
|
||||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||||
}[keyof Object];
|
}[keyof Object];
|
||||||
|
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||||
fallbackValue: "Loading..."
|
fallbackValue: "Loading..."
|
||||||
|
@ -55,6 +64,8 @@ function VencordSettings() {
|
||||||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||||
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
||||||
|
|
||||||
|
const user = UserStore.getCurrentUser();
|
||||||
|
|
||||||
const Switches: Array<false | {
|
const Switches: Array<false | {
|
||||||
key: KeysOfType<typeof settings, boolean>;
|
key: KeysOfType<typeof settings, boolean>;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -99,7 +110,44 @@ function VencordSettings() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsTab title="Vencord Settings">
|
<SettingsTab title="Vencord Settings">
|
||||||
<DonateCard image={donateImage} />
|
{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)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Forms.FormSection title="Quick Actions">
|
<Forms.FormSection title="Quick Actions">
|
||||||
<QuickActionCard>
|
<QuickActionCard>
|
||||||
<QuickAction
|
<QuickAction
|
||||||
|
@ -239,31 +287,19 @@ function VencordSettings() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DonateCardProps {
|
function DonateButtonComponent() {
|
||||||
image: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DonateCard({ image }: DonateCardProps) {
|
|
||||||
return (
|
return (
|
||||||
<Card className={cl("card", "donate")}>
|
<DonateButton
|
||||||
<div>
|
look={Button.Looks.FILLED}
|
||||||
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle>
|
color={Button.Colors.WHITE}
|
||||||
<Forms.FormText>Please consider supporting the development of Vencord by donating!</Forms.FormText>
|
style={{ marginTop: "1em" }}
|
||||||
<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");
|
export default wrapTab(VencordSettings, "Vencord Settings");
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
.vc-settings-quickActions-card {
|
.vc-settings-quickActions-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, max-content));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
justify-content: center;
|
padding: 0.5em;
|
||||||
padding: 0.5em 0;
|
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width <=1040px) {
|
||||||
|
.vc-settings-quickActions-card {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill {
|
.vc-settings-quickActions-pill {
|
||||||
all: unset;
|
all: unset;
|
||||||
background: var(--background-secondary);
|
background: var(--background-secondary);
|
||||||
|
@ -14,12 +19,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 8px 12px;
|
padding: 8px 9px;
|
||||||
border-radius: 9999px;
|
border-radius: 8px;
|
||||||
|
transition: 0.1s ease-out;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:hover {
|
.vc-settings-quickActions-pill:hover {
|
||||||
background: var(--background-secondary-alt);
|
background: var(--background-secondary-alt);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--elevation-high);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:focus-visible {
|
.vc-settings-quickActions-pill:focus-visible {
|
||||||
|
|
|
@ -33,6 +33,20 @@
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border: 1px solid var(--background-modifier-accent);
|
border: 1px solid var(--background-modifier-accent);
|
||||||
max-height: unset;
|
max-height: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-theme-links::placeholder {
|
||||||
|
color: var(--header-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-theme-links:focus {
|
||||||
|
background-color: var(--background-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-cloud-settings-sync-grid {
|
.vc-cloud-settings-sync-grid {
|
||||||
|
|
92
src/components/VencordSettings/specialCard.css
Normal file
92
src/components/VencordSettings/specialCard.css
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
.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,3 +5,8 @@
|
||||||
.vc-owner-crown-icon {
|
.vc-owner-crown-icon {
|
||||||
color: var(--text-warning);
|
color: var(--text-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-heart-icon {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
translate: 0 2px;
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ export * from "./CodeBlock";
|
||||||
export * from "./DonateButton";
|
export * from "./DonateButton";
|
||||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||||
export * from "./ErrorCard";
|
export * from "./ErrorCard";
|
||||||
export * from "./ExpandableHeader";
|
|
||||||
export * from "./Flex";
|
export * from "./Flex";
|
||||||
export * from "./Heart";
|
export * from "./Heart";
|
||||||
export * from "./Icons";
|
export * from "./Icons";
|
||||||
|
|
|
@ -15,9 +15,9 @@ export async function loadLazyChunks() {
|
||||||
try {
|
try {
|
||||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||||
|
|
||||||
const validChunks = new Set<string>();
|
const validChunks = new Set<number>();
|
||||||
const invalidChunks = new Set<string>();
|
const invalidChunks = new Set<number>();
|
||||||
const deferredRequires = new Set<string>();
|
const deferredRequires = new Set<number>();
|
||||||
|
|
||||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
||||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||||
|
@ -27,16 +27,19 @@ export async function loadLazyChunks() {
|
||||||
|
|
||||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||||
|
|
||||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
let foundCssDebuggingLoad = false;
|
||||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
|
||||||
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
|
|
||||||
|
|
||||||
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
|
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||||
// the chunk containing the component
|
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
|
||||||
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
|
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
|
||||||
|
|
||||||
|
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||||
|
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
|
||||||
|
|
||||||
|
const shouldForceDefer = false;
|
||||||
|
|
||||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
|
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
|
||||||
|
|
||||||
if (chunkIds.length === 0) {
|
if (chunkIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -45,6 +48,16 @@ export async function loadLazyChunks() {
|
||||||
let invalidChunkGroup = false;
|
let invalidChunkGroup = false;
|
||||||
|
|
||||||
for (const id of chunkIds) {
|
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;
|
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||||
|
|
||||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
|
@ -61,7 +74,7 @@ export async function loadLazyChunks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invalidChunkGroup) {
|
if (!invalidChunkGroup) {
|
||||||
validChunkGroups.add([chunkIds, entryPoint]);
|
validChunkGroups.add([chunkIds, Number(entryPoint)]);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -131,14 +144,14 @@ export async function loadLazyChunks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||||
const allChunks = [] as string[];
|
const allChunks = [] as number[];
|
||||||
|
|
||||||
// Matches "id" or id:
|
// Matches "id" or id:
|
||||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
|
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
||||||
const id = currentMatch[1] ?? currentMatch[2];
|
const id = currentMatch[1] ?? currentMatch[2];
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
|
|
||||||
allChunks.push(id);
|
allChunks.push(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||||
|
|
|
@ -62,14 +62,21 @@ async function runReporter() {
|
||||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
if (args[0].$$vencordProps != null) {
|
||||||
else if (method === "mapMangledModule") {
|
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);
|
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})`;
|
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
}
|
}
|
||||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,11 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
}
|
}
|
||||||
switch (url) {
|
switch (url) {
|
||||||
case "renderer.js.map":
|
case "renderer.js.map":
|
||||||
|
case "vencordDesktopRenderer.js.map":
|
||||||
case "preload.js.map":
|
case "preload.js.map":
|
||||||
|
case "vencordDesktopPreload.js.map":
|
||||||
case "patcher.js.map":
|
case "patcher.js.map":
|
||||||
case "main.js.map":
|
case "vencordDesktopMain.js.map":
|
||||||
cb(join(__dirname, url));
|
cb(join(__dirname, url));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -131,7 +131,7 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, "preload.js"),
|
preload: join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js"),
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
sandbox: false
|
sandbox: false
|
||||||
|
|
|
@ -26,14 +26,14 @@ import { IS_VANILLA } from "./utils/constants";
|
||||||
|
|
||||||
console.log("[Vencord] Starting up...");
|
console.log("[Vencord] Starting up...");
|
||||||
|
|
||||||
// FIXME: remove at some point
|
|
||||||
export const isLegacyNonAsarVencord = IS_STANDALONE && !__dirname.endsWith(".asar");
|
|
||||||
|
|
||||||
// Our injector file at app/index.js
|
// Our injector file at app/index.js
|
||||||
const injectorPath = require.main!.filename;
|
const injectorPath = require.main!.filename;
|
||||||
|
|
||||||
|
// special discord_arch_electron injection method
|
||||||
|
const asarName = require.main!.path.endsWith("app.asar") ? "_app.asar" : "app.asar";
|
||||||
|
|
||||||
// The original app.asar
|
// The original app.asar
|
||||||
const asarPath = join(dirname(injectorPath), "..", "_app.asar");
|
const asarPath = join(dirname(injectorPath), "..", asarName);
|
||||||
|
|
||||||
const discordPkg = require(join(asarPath, "package.json"));
|
const discordPkg = require(join(asarPath, "package.json"));
|
||||||
require.main!.filename = join(asarPath, discordPkg.main);
|
require.main!.filename = join(asarPath, discordPkg.main);
|
||||||
|
@ -41,7 +41,7 @@ require.main!.filename = join(asarPath, discordPkg.main);
|
||||||
// @ts-ignore Untyped method? Dies from cringe
|
// @ts-ignore Untyped method? Dies from cringe
|
||||||
app.setAppPath(asarPath);
|
app.setAppPath(asarPath);
|
||||||
|
|
||||||
if (!IS_VANILLA && !isLegacyNonAsarVencord) {
|
if (!IS_VANILLA) {
|
||||||
const settings = RendererSettings.store;
|
const settings = RendererSettings.store;
|
||||||
// Repatch after host updates on Windows
|
// Repatch after host updates on Windows
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
|
@ -71,7 +71,7 @@ if (!IS_VANILLA && !isLegacyNonAsarVencord) {
|
||||||
constructor(options: BrowserWindowConstructorOptions) {
|
constructor(options: BrowserWindowConstructorOptions) {
|
||||||
if (options?.webPreferences?.preload && options.title) {
|
if (options?.webPreferences?.preload && options.title) {
|
||||||
const original = options.webPreferences.preload;
|
const original = options.webPreferences.preload;
|
||||||
options.webPreferences.preload = join(__dirname, "preload.js");
|
options.webPreferences.preload = join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js");
|
||||||
options.webPreferences.sandbox = false;
|
options.webPreferences.sandbox = false;
|
||||||
// work around discord unloading when in background
|
// work around discord unloading when in background
|
||||||
options.webPreferences.backgroundThrottling = false;
|
options.webPreferences.backgroundThrottling = false;
|
||||||
|
@ -157,7 +157,5 @@ if (!IS_VANILLA && !isLegacyNonAsarVencord) {
|
||||||
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLegacyNonAsarVencord) {
|
console.log("[Vencord] Loading original Discord app.asar");
|
||||||
console.log("[Vencord] Loading original Discord app.asar");
|
require(require.main!.filename);
|
||||||
require(require.main!.filename);
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const ASAR_FILE = IS_VESKTOP
|
export const VENCORD_FILES = [
|
||||||
? "vesktop.asar"
|
IS_DISCORD_DESKTOP ? "patcher.js" : "vencordDesktopMain.js",
|
||||||
: "desktop.asar";
|
IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js",
|
||||||
|
IS_DISCORD_DESKTOP ? "renderer.js" : "vencordDesktopRenderer.js",
|
||||||
|
IS_DISCORD_DESKTOP ? "renderer.css" : "vencordDesktopRenderer.css",
|
||||||
|
];
|
||||||
|
|
||||||
export function serializeErrors(func: (...args: any[]) => any) {
|
export function serializeErrors(func: (...args: any[]) => any) {
|
||||||
return async function () {
|
return async function () {
|
||||||
|
|
|
@ -16,27 +16,20 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isLegacyNonAsarVencord } from "@main/patcher";
|
import { get } from "@main/utils/simpleGet";
|
||||||
import { IpcEvents } from "@shared/IpcEvents";
|
import { IpcEvents } from "@shared/IpcEvents";
|
||||||
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
||||||
import { app, dialog, ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
import {
|
import { writeFile } from "fs/promises";
|
||||||
existsSync as originalExistsSync,
|
|
||||||
renameSync as originalRenameSync,
|
|
||||||
writeFileSync as originalWriteFileSync,
|
|
||||||
} from "original-fs";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import gitRemote from "~git-remote";
|
import gitRemote from "~git-remote";
|
||||||
|
|
||||||
import { get } from "../utils/simpleGet";
|
import { serializeErrors, VENCORD_FILES } from "./common";
|
||||||
import { ASAR_FILE, serializeErrors } from "./common";
|
|
||||||
|
|
||||||
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
||||||
let PendingUpdate: string | null = null;
|
let PendingUpdates = [] as [string, string][];
|
||||||
|
|
||||||
let hasUpdateToApplyOnQuit = false;
|
|
||||||
|
|
||||||
async function githubGet(endpoint: string) {
|
async function githubGet(endpoint: string) {
|
||||||
return get(API_BASE + endpoint, {
|
return get(API_BASE + endpoint, {
|
||||||
|
@ -72,22 +65,22 @@ async function fetchUpdates() {
|
||||||
if (hash === gitHash)
|
if (hash === gitHash)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
data.assets.forEach(({ name, browser_download_url }) => {
|
||||||
const asset = data.assets.find(a => a.name === ASAR_FILE);
|
if (VENCORD_FILES.some(s => name.startsWith(s))) {
|
||||||
PendingUpdate = asset.browser_download_url;
|
PendingUpdates.push([name, browser_download_url]);
|
||||||
|
}
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyUpdates() {
|
async function applyUpdates() {
|
||||||
if (!PendingUpdate) return true;
|
await Promise.all(PendingUpdates.map(
|
||||||
|
async ([name, data]) => writeFile(
|
||||||
const data = await get(PendingUpdate);
|
join(__dirname, name),
|
||||||
originalWriteFileSync(__dirname + ".new", data);
|
await get(data)
|
||||||
hasUpdateToApplyOnQuit = true;
|
)
|
||||||
|
));
|
||||||
PendingUpdate = null;
|
PendingUpdates = [];
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,51 +88,3 @@ ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(() => `https://github.com/${g
|
||||||
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
|
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
|
||||||
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(fetchUpdates));
|
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(fetchUpdates));
|
||||||
ipcMain.handle(IpcEvents.BUILD, serializeErrors(applyUpdates));
|
ipcMain.handle(IpcEvents.BUILD, serializeErrors(applyUpdates));
|
||||||
|
|
||||||
async function migrateLegacyToAsar() {
|
|
||||||
try {
|
|
||||||
const isFlatpak = process.platform === "linux" && !!process.env.FLATPAK_ID;
|
|
||||||
if (isFlatpak) throw "Flatpak Discord can't automatically be migrated.";
|
|
||||||
|
|
||||||
const data = await get(`https://github.com/${gitRemote}/releases/latest/download/desktop.asar`);
|
|
||||||
|
|
||||||
originalWriteFileSync(join(__dirname, "../vencord.asar"), data);
|
|
||||||
originalWriteFileSync(__filename, '// Legacy shim for new asar\n\nrequire("../vencord.asar");');
|
|
||||||
|
|
||||||
app.relaunch();
|
|
||||||
app.exit();
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to migrate to asar", e);
|
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
dialog.showErrorBox(
|
|
||||||
"Legacy Install",
|
|
||||||
"The way Vencord loaded was changed and the updater failed to migrate. Please reinstall using the Vencord Installer!"
|
|
||||||
);
|
|
||||||
app.exit(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPreviousUpdate() {
|
|
||||||
originalRenameSync(__dirname + ".new", __dirname);
|
|
||||||
|
|
||||||
app.relaunch();
|
|
||||||
app.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
app.on("will-quit", () => {
|
|
||||||
if (hasUpdateToApplyOnQuit)
|
|
||||||
originalRenameSync(__dirname + ".new", __dirname);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLegacyNonAsarVencord) {
|
|
||||||
console.warn("This is a legacy non asar install! Migrating to asar and restarting...");
|
|
||||||
migrateLegacyToAsar();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalExistsSync(__dirname + ".new")) {
|
|
||||||
console.warn("Found previous not applied update, applying now and restarting...");
|
|
||||||
applyPreviousUpdate();
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ export const ALLOWED_PROTOCOLS = [
|
||||||
"steam:",
|
"steam:",
|
||||||
"spotify:",
|
"spotify:",
|
||||||
"com.epicgames.launcher:",
|
"com.epicgames.launcher:",
|
||||||
"tidal:"
|
"tidal:",
|
||||||
|
"itunes:",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
||||||
|
|
|
@ -71,13 +71,16 @@ export async function installExt(id: string) {
|
||||||
// React Devtools v4.25
|
// React Devtools v4.25
|
||||||
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
|
// 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
|
// 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://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=32`;
|
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
|
||||||
|
|
||||||
const buf = await get(url, {
|
const buf = await get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)"
|
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await extract(crxToZip(buf), extDir).catch(console.error);
|
await extract(crxToZip(buf), extDir).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
src/modules.d.ts
vendored
1
src/modules.d.ts
vendored
|
@ -16,7 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line spaced-comment
|
|
||||||
/// <reference types="standalone-electron-types"/>
|
/// <reference types="standalone-electron-types"/>
|
||||||
|
|
||||||
declare module "~plugins" {
|
declare module "~plugins" {
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[class*="profileBadges"] {
|
|
||||||
flex: none;
|
|
||||||
}
|
|
5
src/plugins/_api/badges/fixDiscordBadgePadding.css
Normal file
5
src/plugins/_api/badges/fixDiscordBadgePadding.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* 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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./fixBadgeOverflow.css";
|
import "./fixDiscordBadgePadding.css";
|
||||||
|
|
||||||
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
|
@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
@ -62,34 +62,6 @@ export default definePlugin({
|
||||||
authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
|
authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
|
||||||
required: true,
|
required: true,
|
||||||
patches: [
|
patches: [
|
||||||
/* Patch the badge list component on user profiles */
|
|
||||||
{
|
|
||||||
find: 'id:"premium",',
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
|
|
||||||
replace: "$&$1.unshift(...$self.getBadges(arguments[0]));",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// alt: "", aria-hidden: false, src: originalSrc
|
|
||||||
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
|
|
||||||
// ...badge.props, ..., src: badge.image ?? ...
|
|
||||||
replace: "...$1.props,$& $1.image??"
|
|
||||||
},
|
|
||||||
// replace their component with ours if applicable
|
|
||||||
{
|
|
||||||
match: /(?<=text:(\i)\.description,spacing:12,.{0,50})children:/,
|
|
||||||
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
|
|
||||||
},
|
|
||||||
// conditionally override their onClick with badge.onClick if it exists
|
|
||||||
{
|
|
||||||
match: /href:(\i)\.link/,
|
|
||||||
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
/* new profiles */
|
|
||||||
{
|
{
|
||||||
find: ".FULL_SIZE]:26",
|
find: ".FULL_SIZE]:26",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -107,7 +79,7 @@ export default definePlugin({
|
||||||
replace: "...$1.props,$& $1.image??"
|
replace: "...$1.props,$& $1.image??"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=text:(\i)\.description,.{0,50})children:/,
|
match: /(?<="aria-label":(\i)\.description,.{0,200})children:/,
|
||||||
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||||
},
|
},
|
||||||
// conditionally override their onClick with badge.onClick if it exists
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
|
@ -130,8 +102,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userProfileBadge: ContributorBadge,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
|
||||||
await loadBadges();
|
await loadBadges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -171,8 +144,8 @@ export default definePlugin({
|
||||||
closeModal(modalKey);
|
closeModal(modalKey);
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
||||||
}}>
|
}}>
|
||||||
<Modals.ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
<Modals.ModalHeader>
|
<ModalHeader>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<Forms.FormTitle
|
<Forms.FormTitle
|
||||||
tag="h2"
|
tag="h2"
|
||||||
|
@ -186,8 +159,8 @@ export default definePlugin({
|
||||||
Vencord Donor
|
Vencord Donor
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalHeader>
|
</ModalHeader>
|
||||||
<Modals.ModalContent>
|
<ModalContent>
|
||||||
<Flex>
|
<Flex>
|
||||||
<img
|
<img
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
@ -210,13 +183,13 @@ export default definePlugin({
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
</Modals.ModalContent>
|
</ModalContent>
|
||||||
<Modals.ModalFooter>
|
<ModalFooter>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<DonateButton />
|
<DonateButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalFooter>
|
</ModalFooter>
|
||||||
</Modals.ModalRoot>
|
</ModalRoot>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,16 @@ export default definePlugin({
|
||||||
description: "API to add buttons to the chat input",
|
description: "API to add buttons to the chat input",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
patches: [{
|
patches: [
|
||||||
|
{
|
||||||
find: '"sticker")',
|
find: '"sticker")',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
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)||`
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,12 +34,22 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Menu,{",
|
find: "navId:",
|
||||||
all: true,
|
all: true,
|
||||||
replacement: {
|
noWarn: true,
|
||||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
replacement: [
|
||||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
24
src/plugins/_api/dynamicImageModalApi.ts
Normal file
24
src/plugins/_api/dynamicImageModalApi.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "DynamicImageModalAPI",
|
||||||
|
authors: [Devs.sadan, Devs.Nuckyz],
|
||||||
|
description: "Allows you to omit either width or height when opening an image modal",
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "SCALE_DOWN:",
|
||||||
|
replacement: {
|
||||||
|
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
|
||||||
|
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -19,10 +19,15 @@
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import managedStyle from "./style.css?managed";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberListDecoratorsAPI",
|
name: "MemberListDecoratorsAPI",
|
||||||
description: "API to add decorators to member list (both in servers and DMs)",
|
description: "API to add decorators to member list (both in servers and DMs)",
|
||||||
authors: [Devs.TheSun, Devs.Ven],
|
authors: [Devs.TheSun, Devs.Ven],
|
||||||
|
|
||||||
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".lostPermission)",
|
find: ".lostPermission)",
|
||||||
|
@ -31,8 +36,8 @@ export default definePlugin({
|
||||||
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
||||||
replace: "$&vencordProps=$1,"
|
replace: "$&vencordProps=$1,"
|
||||||
}, {
|
}, {
|
||||||
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
||||||
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -40,8 +45,8 @@ export default definePlugin({
|
||||||
find: "PrivateChannel.renderAvatar",
|
find: "PrivateChannel.renderAvatar",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
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]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
11
src/plugins/_api/memberListDecorators/style.css
Normal file
11
src/plugins/_api/memberListDecorators/style.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.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;
|
||||||
|
}
|
68
src/plugins/_api/menuItemDemangler.ts
Normal file
68
src/plugins/_api/menuItemDemangler.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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],
|
authors: [Devs.Cyn],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
||||||
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
||||||
|
|
|
@ -19,17 +19,22 @@
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import managedStyle from "./style.css?managed";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageDecorationsAPI",
|
name: "MessageDecorationsAPI",
|
||||||
description: "API to add decorations to messages",
|
description: "API to add decorations to messages",
|
||||||
authors: [Devs.TheSun],
|
authors: [Devs.TheSun],
|
||||||
|
|
||||||
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"Message Username"',
|
find: '"Message Username"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
|
||||||
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
18
src/plugins/_api/messageDecorations/style.css
Normal file
18
src/plugins/_api/messageDecorations/style.css
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.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,26 +25,23 @@ export default definePlugin({
|
||||||
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.EDIT_TEXTAREA_HELP",
|
find: "#{intl::EDIT_TEXTAREA_HELP}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
||||||
replace: (match, args) => "" +
|
replace: (match, args) => "" +
|
||||||
`async ${match}` +
|
`async ${match}` +
|
||||||
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
||||||
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
|
"return Promise.resolve({shouldClear:false,shouldRefocus:true});"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".handleSendMessage,onResize",
|
find: ".handleSendMessage,onResize",
|
||||||
replacement: {
|
replacement: {
|
||||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
// https://regex101.com/r/hBlXpl/1
|
||||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
|
||||||
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
|
||||||
// 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}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
||||||
"return{shoudClear:true,shouldRefocus:true};"
|
"return{shouldClear:false,shouldRefocus:true};"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,8 +49,7 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||||
replace: (m, message, channel, event) =>
|
replace: (m, message, channel, event) =>
|
||||||
// the message param is shadowed by the event param, so need to alias them
|
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
|
||||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,16 +23,14 @@ export default definePlugin({
|
||||||
name: "MessagePopoverAPI",
|
name: "MessagePopoverAPI",
|
||||||
description: "API to add buttons to message popovers.",
|
description: "API to add buttons to message popovers.",
|
||||||
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
||||||
patches: [{
|
patches: [
|
||||||
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
{
|
||||||
|
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||||
replacement: {
|
replacement: {
|
||||||
// foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other))
|
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||||
match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"/,
|
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||||
replace: (m, makeElement) => {
|
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||||
const msg = m.match(/message:(.{1,3}),/)?.[1];
|
|
||||||
if (!msg) throw new Error("Could not find message variable");
|
|
||||||
return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
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",
|
description: "Api required for plugins that modify the server list",
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "Messages.DISCODO_DISABLED",
|
find: "#{intl::DISCODO_DISABLED}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||||
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.SERVERS,children",
|
find: "#{intl::SERVERS}),children",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
|
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
|
||||||
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".METRICS,",
|
find: ".METRICS",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /this\._intervalId=/,
|
match: /this\._intervalId=/,
|
||||||
|
@ -61,13 +61,13 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".installedLogHooks)",
|
find: ".BetterDiscord||null!=",
|
||||||
replacement: {
|
replacement: {
|
||||||
// if getDebugLogging() returns false, the hooks don't get installed.
|
// Make hasClientMods return false
|
||||||
match: "getDebugLogging(){",
|
match: /(?=let \i=window;)/,
|
||||||
replace: "getDebugLogging(){return false;"
|
replace: "return false;"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
startAt: StartAt.Init,
|
startAt: StartAt.Init,
|
||||||
|
|
|
@ -25,8 +25,9 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
|
||||||
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
||||||
import VencordTab from "@components/VencordSettings/VencordTab";
|
import VencordTab from "@components/VencordSettings/VencordTab";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { i18n, React } from "@webpack/common";
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
|
||||||
|
@ -57,20 +58,21 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: ".SEARCH_NO_RESULTS&&0===",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
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}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
// 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/,
|
||||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
||||||
replace: "$2.open($1);return;"
|
replace: "$2.open($1);return;"
|
||||||
|
@ -148,13 +150,18 @@ export default definePlugin({
|
||||||
|
|
||||||
if (!header) return;
|
if (!header) return;
|
||||||
|
|
||||||
|
try {
|
||||||
const names = {
|
const names = {
|
||||||
top: i18n.Messages.USER_SETTINGS,
|
top: getIntlMessage("USER_SETTINGS"),
|
||||||
aboveNitro: i18n.Messages.BILLING_SETTINGS,
|
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
|
||||||
belowNitro: i18n.Messages.APP_SETTINGS,
|
belowNitro: getIntlMessage("APP_SETTINGS"),
|
||||||
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
|
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
||||||
};
|
};
|
||||||
|
|
||||||
return header === names[settingsLocation];
|
return header === names[settingsLocation];
|
||||||
|
} catch {
|
||||||
|
return firstChild === "PREMIUM";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
patchedSettings: new WeakSet(),
|
patchedSettings: new WeakSet(),
|
||||||
|
@ -197,7 +204,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
get electronVersion() {
|
get electronVersion() {
|
||||||
return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
|
return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
get chromiumVersion() {
|
get chromiumVersion() {
|
||||||
|
|
|
@ -16,14 +16,13 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addAccessory } from "@api/MessageAccessories";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
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 { sendMessage } from "@utils/discord";
|
import { sendMessage } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -34,15 +33,13 @@ import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
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 gitHash from "~git-hash";
|
||||||
import plugins, { PluginMeta } from "~plugins";
|
import plugins, { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import SettingsPlugin from "./settings";
|
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 CodeBlockRe = /```js\n(.+?)```/s;
|
||||||
|
|
||||||
const AllowedChannelIds = [
|
const AllowedChannelIds = [
|
||||||
|
@ -52,9 +49,9 @@ const AllowedChannelIds = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const TrustedRolesIds = [
|
const TrustedRolesIds = [
|
||||||
"1026534353167208489", // contributor
|
CONTRIB_ROLE_ID, // contributor
|
||||||
"1026504932959977532", // regular
|
REGULAR_ROLE_ID, // regular
|
||||||
"1042507929485586532", // donor
|
DONOR_ROLE_ID, // donor
|
||||||
];
|
];
|
||||||
|
|
||||||
const AsyncFunction = async function () { }.constructor;
|
const AsyncFunction = async function () { }.constructor;
|
||||||
|
@ -77,7 +74,7 @@ async function generateDebugInfoMessage() {
|
||||||
const client = (() => {
|
const client = (() => {
|
||||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||||
|
@ -142,15 +139,15 @@ export default definePlugin({
|
||||||
required: true,
|
required: true,
|
||||||
description: "Helps us provide support to you",
|
description: "Helps us provide support to you",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".BEGINNING_DM.format",
|
find: "#{intl::BEGINNING_DM}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
|
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
|
||||||
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
|
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
@ -235,22 +232,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
renderMessageAccessory(props) {
|
||||||
if (!isPluginDev(userId)) return null;
|
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
|
||||||
Please do not private message Vencord plugin developers for support!
|
|
||||||
<br />
|
|
||||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
|
||||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}, { noop: true }),
|
|
||||||
|
|
||||||
start() {
|
|
||||||
addAccessory("vencord-debug", props => {
|
|
||||||
const buttons = [] as JSX.Element[];
|
const buttons = [] as JSX.Element[];
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
const shouldAddUpdateButton =
|
||||||
|
@ -327,6 +309,20 @@ export default definePlugin({
|
||||||
return buttons.length
|
return buttons.length
|
||||||
? <Flex>{buttons}</Flex>
|
? <Flex>{buttons}</Flex>
|
||||||
: null;
|
: null;
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||||
|
const userId = channel.getRecipientId();
|
||||||
|
if (!isPluginDev(userId)) return null;
|
||||||
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
|
Please do not private message Vencord plugin developers for support!
|
||||||
|
<br />
|
||||||
|
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||||
|
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
7
src/plugins/accountPanelServerProfile/README.md
Normal file
7
src/plugins/accountPanelServerProfile/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# AccountPanelServerProfile
|
||||||
|
|
||||||
|
Right click your account panel in the bottom left to view your profile in the current server
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
134
src/plugins/accountPanelServerProfile/index.tsx
Normal file
134
src/plugins/accountPanelServerProfile/index.tsx
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* 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: /(?<=\.avatarWrapper,)/,
|
||||||
|
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
|
// Status emojis
|
||||||
find: ".Messages.GUILD_OWNER,",
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
|
||||||
replace: "!0"
|
replace: (_, rest) => `${rest}!0`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
3
src/plugins/alwaysExpandRoles/README.md
Normal file
3
src/plugins/alwaysExpandRoles/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Always Expand Roles
|
||||||
|
|
||||||
|
Always expands the role list in profile popouts
|
45
src/plugins/alwaysExpandRoles/index.ts
Normal file
45
src/plugins/alwaysExpandRoles/index.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue