Merge branch 'dev' into tray-stuff
This commit is contained in:
commit
bc87f72421
58 changed files with 1038 additions and 353 deletions
16
.github/workflows/reportBrokenPlugins.yml
vendored
16
.github/workflows/reportBrokenPlugins.yml
vendored
|
|
@ -28,12 +28,8 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
if: ${{ github.event_name == 'schedule' }}
|
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: ${{ github.event_name == 'schedule' && 'dev' || github.ref }}
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
|
||||||
|
|
||||||
- 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
|
||||||
|
|
||||||
|
|
@ -45,13 +41,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile --prefer-offline
|
||||||
|
|
||||||
- name: Install Google Chrome
|
|
||||||
id: setup-chrome
|
|
||||||
uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2
|
|
||||||
with:
|
|
||||||
chrome-version: stable
|
|
||||||
|
|
||||||
- name: Build Equicord Reporter Version
|
- name: Build Equicord Reporter Version
|
||||||
run: pnpm buildReporter
|
run: pnpm buildReporter
|
||||||
|
|
@ -60,7 +50,7 @@ jobs:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
run: |
|
run: |
|
||||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||||
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
|
export CHROMIUM_BIN=/usr/bin/google-chrome
|
||||||
|
|
||||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "equicord",
|
"name": "equicord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.14.3.1",
|
"version": "1.14.4.0",
|
||||||
"description": "The other cutest Discord client mod",
|
"description": "The other cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Equicord/Equicord#readme",
|
"homepage": "https://github.com/Equicord/Equicord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
"@ffmpeg/ffmpeg": "^0.12.10",
|
"@ffmpeg/ffmpeg": "^0.12.10",
|
||||||
"@ffmpeg/util": "^0.12.1",
|
"@ffmpeg/util": "^0.12.1",
|
||||||
"@intrnl/xxhash64": "^0.1.2",
|
"@intrnl/xxhash64": "^0.1.2",
|
||||||
"@song-spotlight/api": "^2.0.0",
|
"@song-spotlight/api": "^2.0.1",
|
||||||
"@streamparser/json": "^0.0.22",
|
"@streamparser/json": "^0.0.22",
|
||||||
"@types/less": "^3.0.8",
|
"@types/less": "^3.0.8",
|
||||||
"@types/stylus": "^0.48.42",
|
"@types/stylus": "^0.48.42",
|
||||||
|
|
|
||||||
164
pnpm-lock.yaml
generated
164
pnpm-lock.yaml
generated
|
|
@ -26,8 +26,8 @@ importers:
|
||||||
specifier: ^0.1.2
|
specifier: ^0.1.2
|
||||||
version: 0.1.2
|
version: 0.1.2
|
||||||
'@song-spotlight/api':
|
'@song-spotlight/api':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.1
|
||||||
version: 2.0.0(typescript@5.9.3)
|
version: 2.0.1(typescript@5.9.3)
|
||||||
'@streamparser/json':
|
'@streamparser/json':
|
||||||
specifier: ^0.0.22
|
specifier: ^0.0.22
|
||||||
version: 0.0.22
|
version: 0.0.22
|
||||||
|
|
@ -721,8 +721,8 @@ packages:
|
||||||
'@rtsao/scc@1.1.0':
|
'@rtsao/scc@1.1.0':
|
||||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||||
|
|
||||||
'@song-spotlight/api@2.0.0':
|
'@song-spotlight/api@2.0.1':
|
||||||
resolution: {integrity: sha512-a3gyrSojd3BzwloxZHyDXA7HhjmCNQZOYATh7AZJyCXIqilBMwFPgQ2FVkUpzf6MzZhN9emeN6OGZOJb/rkECw==}
|
resolution: {integrity: sha512-nxBJch8cGp10NYeJSp3DCPS/LYmlpcs9VCHrHzgCHFRICTKWhlpiUZgrACP9TmQ8RQsE4B9e23BcQDaqWqPCGg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ^5
|
typescript: ^5
|
||||||
|
|
||||||
|
|
@ -941,6 +941,10 @@ packages:
|
||||||
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
|
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
array-includes@3.1.9:
|
||||||
|
resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
array-union@2.1.0:
|
array-union@2.1.0:
|
||||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -957,6 +961,10 @@ packages:
|
||||||
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
|
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
array.prototype.flat@1.3.3:
|
||||||
|
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
array.prototype.flatmap@1.3.3:
|
array.prototype.flatmap@1.3.3:
|
||||||
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
|
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -1258,6 +1266,10 @@ packages:
|
||||||
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
|
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-abstract@1.24.1:
|
||||||
|
resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
es-define-property@1.0.1:
|
es-define-property@1.0.1:
|
||||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -1322,8 +1334,8 @@ packages:
|
||||||
eslint-import-resolver-node@0.3.9:
|
eslint-import-resolver-node@0.3.9:
|
||||||
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
||||||
|
|
||||||
eslint-module-utils@2.12.0:
|
eslint-module-utils@2.12.1:
|
||||||
resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==}
|
resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': '*'
|
'@typescript-eslint/parser': '*'
|
||||||
|
|
@ -1757,6 +1769,10 @@ packages:
|
||||||
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-negative-zero@2.0.3:
|
||||||
|
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-number-object@1.1.1:
|
is-number-object@1.1.1:
|
||||||
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
|
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -1947,6 +1963,9 @@ packages:
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||||
|
|
||||||
|
minimatch@3.1.5:
|
||||||
|
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||||
|
|
||||||
minimatch@9.0.5:
|
minimatch@9.0.5:
|
||||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
@ -2217,6 +2236,11 @@ packages:
|
||||||
resolve-pkg-maps@1.0.0:
|
resolve-pkg-maps@1.0.0:
|
||||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||||
|
|
||||||
|
resolve@1.22.11:
|
||||||
|
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
@ -2340,6 +2364,10 @@ packages:
|
||||||
standalone-electron-types@34.2.0:
|
standalone-electron-types@34.2.0:
|
||||||
resolution: {integrity: sha512-+BIrNe0TdZBBBRS3G/F7cPbuBipSZylSjSrJu9+sjw84+vz36a5oZ7l9NeH7aXgMWmPzPZKsTm+K0nOIvhENJQ==}
|
resolution: {integrity: sha512-+BIrNe0TdZBBBRS3G/F7cPbuBipSZylSjSrJu9+sjw84+vz36a5oZ7l9NeH7aXgMWmPzPZKsTm+K0nOIvhENJQ==}
|
||||||
|
|
||||||
|
stop-iteration-iterator@1.1.0:
|
||||||
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
streamx@2.22.0:
|
streamx@2.22.0:
|
||||||
resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==}
|
resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==}
|
||||||
|
|
||||||
|
|
@ -2588,6 +2616,10 @@ packages:
|
||||||
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
|
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
which-typed-array@1.1.20:
|
||||||
|
resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
which@1.3.1:
|
which@1.3.1:
|
||||||
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
@ -3006,7 +3038,7 @@ snapshots:
|
||||||
|
|
||||||
'@rtsao/scc@1.1.0': {}
|
'@rtsao/scc@1.1.0': {}
|
||||||
|
|
||||||
'@song-spotlight/api@2.0.0(typescript@5.9.3)':
|
'@song-spotlight/api@2.0.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|
@ -3271,6 +3303,17 @@ snapshots:
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
is-string: 1.1.1
|
is-string: 1.1.1
|
||||||
|
|
||||||
|
array-includes@3.1.9:
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.8
|
||||||
|
call-bound: 1.0.4
|
||||||
|
define-properties: 1.2.1
|
||||||
|
es-abstract: 1.24.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
is-string: 1.1.1
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
array-union@2.1.0: {}
|
array-union@2.1.0: {}
|
||||||
|
|
||||||
array.prototype.findlast@1.2.5:
|
array.prototype.findlast@1.2.5:
|
||||||
|
|
@ -3287,7 +3330,7 @@ snapshots:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-abstract: 1.23.9
|
es-abstract: 1.24.1
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
es-shim-unscopables: 1.1.0
|
es-shim-unscopables: 1.1.0
|
||||||
|
|
@ -3299,6 +3342,13 @@ snapshots:
|
||||||
es-abstract: 1.23.9
|
es-abstract: 1.23.9
|
||||||
es-shim-unscopables: 1.1.0
|
es-shim-unscopables: 1.1.0
|
||||||
|
|
||||||
|
array.prototype.flat@1.3.3:
|
||||||
|
dependencies:
|
||||||
|
call-bind: 1.0.8
|
||||||
|
define-properties: 1.2.1
|
||||||
|
es-abstract: 1.24.1
|
||||||
|
es-shim-unscopables: 1.1.0
|
||||||
|
|
||||||
array.prototype.flatmap@1.3.3:
|
array.prototype.flatmap@1.3.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
|
|
@ -3644,6 +3694,63 @@ snapshots:
|
||||||
unbox-primitive: 1.1.0
|
unbox-primitive: 1.1.0
|
||||||
which-typed-array: 1.1.19
|
which-typed-array: 1.1.19
|
||||||
|
|
||||||
|
es-abstract@1.24.1:
|
||||||
|
dependencies:
|
||||||
|
array-buffer-byte-length: 1.0.2
|
||||||
|
arraybuffer.prototype.slice: 1.0.4
|
||||||
|
available-typed-arrays: 1.0.7
|
||||||
|
call-bind: 1.0.8
|
||||||
|
call-bound: 1.0.4
|
||||||
|
data-view-buffer: 1.0.2
|
||||||
|
data-view-byte-length: 1.0.2
|
||||||
|
data-view-byte-offset: 1.0.1
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
es-to-primitive: 1.3.0
|
||||||
|
function.prototype.name: 1.1.8
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
get-proto: 1.0.1
|
||||||
|
get-symbol-description: 1.1.0
|
||||||
|
globalthis: 1.0.4
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-property-descriptors: 1.0.2
|
||||||
|
has-proto: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
internal-slot: 1.1.0
|
||||||
|
is-array-buffer: 3.0.5
|
||||||
|
is-callable: 1.2.7
|
||||||
|
is-data-view: 1.0.2
|
||||||
|
is-negative-zero: 2.0.3
|
||||||
|
is-regex: 1.2.1
|
||||||
|
is-set: 2.0.3
|
||||||
|
is-shared-array-buffer: 1.0.4
|
||||||
|
is-string: 1.1.1
|
||||||
|
is-typed-array: 1.1.15
|
||||||
|
is-weakref: 1.1.1
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
object-keys: 1.1.1
|
||||||
|
object.assign: 4.1.7
|
||||||
|
own-keys: 1.0.1
|
||||||
|
regexp.prototype.flags: 1.5.4
|
||||||
|
safe-array-concat: 1.1.3
|
||||||
|
safe-push-apply: 1.0.0
|
||||||
|
safe-regex-test: 1.1.0
|
||||||
|
set-proto: 1.0.0
|
||||||
|
stop-iteration-iterator: 1.1.0
|
||||||
|
string.prototype.trim: 1.2.10
|
||||||
|
string.prototype.trimend: 1.0.9
|
||||||
|
string.prototype.trimstart: 1.0.8
|
||||||
|
typed-array-buffer: 1.0.3
|
||||||
|
typed-array-byte-length: 1.0.3
|
||||||
|
typed-array-byte-offset: 1.0.4
|
||||||
|
typed-array-length: 1.0.7
|
||||||
|
unbox-primitive: 1.1.0
|
||||||
|
which-typed-array: 1.1.20
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
|
|
@ -3767,11 +3874,11 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
resolve: 1.22.8
|
resolve: 1.22.11
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1):
|
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|
@ -3784,19 +3891,19 @@ snapshots:
|
||||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1):
|
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.9
|
||||||
array.prototype.findlastindex: 1.2.6
|
array.prototype.findlastindex: 1.2.6
|
||||||
array.prototype.flat: 1.3.2
|
array.prototype.flat: 1.3.3
|
||||||
array.prototype.flatmap: 1.3.3
|
array.prototype.flatmap: 1.3.3
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 9.39.1
|
eslint: 9.39.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1)
|
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1)
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.5
|
||||||
object.fromentries: 2.0.8
|
object.fromentries: 2.0.8
|
||||||
object.groupby: 1.0.3
|
object.groupby: 1.0.3
|
||||||
object.values: 1.2.1
|
object.values: 1.2.1
|
||||||
|
|
@ -4276,6 +4383,8 @@ snapshots:
|
||||||
|
|
||||||
is-map@2.0.3: {}
|
is-map@2.0.3: {}
|
||||||
|
|
||||||
|
is-negative-zero@2.0.3: {}
|
||||||
|
|
||||||
is-number-object@1.1.1:
|
is-number-object@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
|
@ -4450,6 +4559,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.12
|
brace-expansion: 1.1.12
|
||||||
|
|
||||||
|
minimatch@3.1.5:
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 1.1.12
|
||||||
|
|
||||||
minimatch@9.0.5:
|
minimatch@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.2
|
brace-expansion: 2.0.2
|
||||||
|
|
@ -4524,7 +4637,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-abstract: 1.23.9
|
es-abstract: 1.24.1
|
||||||
|
|
||||||
object.values@1.2.1:
|
object.values@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -4734,6 +4847,12 @@ snapshots:
|
||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|
||||||
|
resolve@1.22.11:
|
||||||
|
dependencies:
|
||||||
|
is-core-module: 2.16.1
|
||||||
|
path-parse: 1.0.7
|
||||||
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
|
|
@ -4875,6 +4994,11 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.13
|
'@types/node': 22.13.13
|
||||||
|
|
||||||
|
stop-iteration-iterator@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
internal-slot: 1.1.0
|
||||||
|
|
||||||
streamx@2.22.0:
|
streamx@2.22.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-fifo: 1.3.2
|
fast-fifo: 1.3.2
|
||||||
|
|
@ -5239,6 +5363,16 @@ snapshots:
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
|
|
||||||
|
which-typed-array@1.1.20:
|
||||||
|
dependencies:
|
||||||
|
available-typed-arrays: 1.0.7
|
||||||
|
call-bind: 1.0.8
|
||||||
|
call-bound: 1.0.4
|
||||||
|
for-each: 0.3.5
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
|
||||||
which@1.3.1:
|
which@1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ export interface Settings {
|
||||||
themeNames: Record<string, string>;
|
themeNames: Record<string, string>;
|
||||||
enableReactDevtools: boolean;
|
enableReactDevtools: boolean;
|
||||||
themeLinks: string[];
|
themeLinks: string[];
|
||||||
|
mainWindowFrameless: boolean;
|
||||||
frameless: boolean;
|
frameless: boolean;
|
||||||
transparent: boolean;
|
transparent: boolean;
|
||||||
winCtrlQ: boolean;
|
winCtrlQ: boolean;
|
||||||
|
|
@ -116,6 +117,7 @@ const DefaultSettings: Settings = {
|
||||||
pinnedThemes: [],
|
pinnedThemes: [],
|
||||||
themeNames: {},
|
themeNames: {},
|
||||||
enableReactDevtools: false,
|
enableReactDevtools: false,
|
||||||
|
mainWindowFrameless: false,
|
||||||
frameless: false,
|
frameless: false,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
winCtrlQ: false,
|
winCtrlQ: false,
|
||||||
|
|
|
||||||
|
|
@ -749,3 +749,23 @@ export const QrCodeIcon = (props?: any) => {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ComponentsIcon = (props?: any) => {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill={props.fill || "currentColor"}
|
||||||
|
d="M3 15.5V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v.5a.5.5 0 0 1-.5.5H17a4 4 0 0 0-4 4v4.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5ZM12.5 18H2a1 1 0 1 0 0 2h10.48c.33 0 .57-.3.54-.63A4.08 4.08 0 0 1 13 19v-.5a.5.5 0 0 0-.5-.5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
fill={props.fill || "currentColor"}
|
||||||
|
d="M15 11c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8Zm2 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -67,3 +67,22 @@ export function InviteButton({
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function TranslateButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: Partial<ButtonProps>) {
|
||||||
|
const link = "https://weblate.equicord.org/projects/equicord/";
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
variant="none"
|
||||||
|
size="medium"
|
||||||
|
type="button"
|
||||||
|
onClick={() => VencordNative.native.openExternal(link)}
|
||||||
|
className={className || "vc-translate-button"}
|
||||||
|
>
|
||||||
|
Translate Here
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,18 @@
|
||||||
background-color: color-mix(in srgb, #c3a3ce 50%, rgb(240 178 240));
|
background-color: color-mix(in srgb, #c3a3ce 50%, rgb(240 178 240));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-translate-button {
|
||||||
|
overflow: visible !important;
|
||||||
|
color: black;
|
||||||
|
background-color: color-mix(in srgb, #575757 50%, rgb(255 255 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-translate-button:hover {
|
||||||
|
overflow: visible !important;
|
||||||
|
color: black;
|
||||||
|
background-color: color-mix(in srgb, #575757 50%, rgb(201 201 201));
|
||||||
|
}
|
||||||
|
|
||||||
.vc-invite-link {
|
.vc-invite-link {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|
@ -142,4 +154,14 @@
|
||||||
|
|
||||||
.vc-special-image {
|
.vc-special-image {
|
||||||
width: 65%;
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-translate-modal-icon {
|
||||||
|
margin: auto;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-translate-modal-paragraph {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
@ -5,17 +5,19 @@
|
||||||
.vc-author-modal-header {
|
.vc-author-modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-name {
|
.vc-author-modal-name {
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
margin: 0;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
background: var(--background-base-lowest);
|
background: var(--background-base-lowest);
|
||||||
border-radius: 0 9999px 9999px 0;
|
border-radius: 0 9999px 9999px 0;
|
||||||
padding: 6px 0.8em 6px 0.5em;
|
padding: 6px 0.8em 6px 0.5em;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 20px;
|
line-height: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
@ -35,9 +37,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-avatar {
|
.vc-author-modal-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
transform: translateX(4px);
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--background-primary);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgb(255 255 255 / 18%),
|
||||||
|
0 2px 6px rgb(0 0 0 / 35%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-root+[class*="footer"] {
|
.vc-author-modal-root+[class*="footer"] {
|
||||||
|
|
|
||||||
|
|
@ -123,14 +123,15 @@ function CloudTab() {
|
||||||
Choose which cloud backend to use for storing your settings. You can switch between Equicord's and Vencord's cloud services, or use a self-hosted instance.
|
Choose which cloud backend to use for storing your settings. You can switch between Equicord's and Vencord's cloud services, or use a self-hosted instance.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<SearchableSelect
|
<div className={Margins.bottom8}>
|
||||||
options={cloudBackendOptions}
|
<SearchableSelect
|
||||||
value={cloudBackendOptions.find(o => o.value === cloud.url)}
|
options={cloudBackendOptions}
|
||||||
onChange={v => changeUrl(v)}
|
value={cloudBackendOptions.find(o => o.value === cloud.url)?.value}
|
||||||
className={Margins.bottom16}
|
onChange={v => changeUrl(v)}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
renderOptionPrefix={o => o?.value?.includes("equicord") ? <EquicordIcon /> : <VencordIcon />}
|
renderOptionPrefix={o => o?.value?.includes("equicord") ? <EquicordIcon /> : <VencordIcon />}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Flex gap="8px" alignItems="center">
|
<Flex gap="8px" alignItems="center">
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
|
|
|
||||||
|
|
@ -81,11 +81,18 @@ function EquicordSettings() {
|
||||||
restartRequired: true,
|
restartRequired: true,
|
||||||
warning: { enabled: false },
|
warning: { enabled: false },
|
||||||
},
|
},
|
||||||
|
(!IS_WEB && !IS_DISCORD_DESKTOP || !IS_WINDOWS) && {
|
||||||
|
key: "mainWindowFrameless",
|
||||||
|
title: "Disable the Main Window Frame",
|
||||||
|
description: "Remove the native window frame for a cleaner look. You can still move the window by dragging the title bar area.",
|
||||||
|
restartRequired: true,
|
||||||
|
warning: { enabled: false },
|
||||||
|
},
|
||||||
!IS_WEB &&
|
!IS_WEB &&
|
||||||
(!IS_DISCORD_DESKTOP || !IS_WINDOWS
|
(!IS_DISCORD_DESKTOP || !IS_WINDOWS
|
||||||
? {
|
? {
|
||||||
key: "frameless",
|
key: "frameless",
|
||||||
title: "Disable the Window Frame",
|
title: "Disable All Window Frames",
|
||||||
description: "Remove the native window frame for a cleaner look. You can still move the window by dragging the title bar area.",
|
description: "Remove the native window frame for a cleaner look. You can still move the window by dragging the title bar area.",
|
||||||
restartRequired: true,
|
restartRequired: true,
|
||||||
warning: { enabled: false },
|
warning: { enabled: false },
|
||||||
|
|
@ -96,7 +103,8 @@ function EquicordSettings() {
|
||||||
description: "Replace Discord's custom title bar with the standard Windows title bar. This may improve compatibility with some window management tools.",
|
description: "Replace Discord's custom title bar with the standard Windows title bar. This may improve compatibility with some window management tools.",
|
||||||
restartRequired: true,
|
restartRequired: true,
|
||||||
warning: { enabled: false },
|
warning: { enabled: false },
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
!IS_WEB && {
|
!IS_WEB && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable Window Transparency",
|
title: "Enable Window Transparency",
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
|
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=className:(\i)\.\i,style:\i,)children:\[/,
|
match: /(?<=className:(\i)\.\i,style:\i,)children:\[/,
|
||||||
|
|
|
||||||
187
src/equicordplugins/clickableRoles/index.tsx
Normal file
187
src/equicordplugins/clickableRoles/index.tsx
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./style.css";
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { classNameFactory } from "@utils/css";
|
||||||
|
import { openUserProfile } from "@utils/discord";
|
||||||
|
import { isTruthy } from "@utils/guards";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import type { Role } from "@vencord/discord-types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Constants, GuildRoleStore, IconUtils, Popout, RestAPI, ScrollerThin, useEffect, useRef, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
|
const logger = new Logger("ClickableRoles");
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-clickableroles-");
|
||||||
|
|
||||||
|
const GuildActions = findByPropsLazy("requestMembersById", "banUser");
|
||||||
|
|
||||||
|
const MAX_VISIBLE_MEMBERS = 20;
|
||||||
|
const CACHE_TTL = 60_000;
|
||||||
|
|
||||||
|
const memberCache = new Map<string, { ids: string[]; timestamp: number; }>();
|
||||||
|
|
||||||
|
function getCachedMemberIds(guildId: string, roleId: string) {
|
||||||
|
const key = `${guildId}-${roleId}`;
|
||||||
|
const cached = memberCache.get(key);
|
||||||
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL) return cached.ids;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCachedMemberIds(guildId: string, roleId: string, ids: string[]) {
|
||||||
|
memberCache.set(`${guildId}-${roleId}`, { ids, timestamp: Date.now() });
|
||||||
|
}
|
||||||
|
|
||||||
|
function RoleMembersList({ roleId, guildId, closePopout, setPopoutRef }: { roleId: string; guildId: string; closePopout(): void; setPopoutRef(ref: HTMLDivElement | null): void; }) {
|
||||||
|
const [memberIds, setMemberIds] = useState<string[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const role = useStateFromStores([GuildRoleStore], () => GuildRoleStore.getRole(guildId, roleId));
|
||||||
|
|
||||||
|
const [totalCount, setTotalCount] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
const cached = getCachedMemberIds(guildId, roleId);
|
||||||
|
if (cached) {
|
||||||
|
setTotalCount(cached.length);
|
||||||
|
const visible = cached.slice(0, MAX_VISIBLE_MEMBERS);
|
||||||
|
setMemberIds(visible);
|
||||||
|
if (visible.length) GuildActions.requestMembersById(guildId, visible, false);
|
||||||
|
setLoading(false);
|
||||||
|
} else {
|
||||||
|
RestAPI.get({
|
||||||
|
url: Constants.Endpoints.GUILD_ROLE_MEMBER_IDS(guildId, roleId),
|
||||||
|
}).then(res => {
|
||||||
|
if (cancelled) return;
|
||||||
|
const ids = res.body as string[];
|
||||||
|
setCachedMemberIds(guildId, roleId, ids);
|
||||||
|
setTotalCount(ids.length);
|
||||||
|
const visible = ids.slice(0, MAX_VISIBLE_MEMBERS);
|
||||||
|
setMemberIds(visible);
|
||||||
|
if (visible.length) GuildActions.requestMembersById(guildId, visible, false);
|
||||||
|
setLoading(false);
|
||||||
|
}).catch(e => {
|
||||||
|
if (cancelled) return;
|
||||||
|
logger.error("Failed to fetch role members", e);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, [guildId, roleId]);
|
||||||
|
|
||||||
|
const users = useStateFromStores(
|
||||||
|
[UserStore],
|
||||||
|
() => memberIds.map(id => UserStore.getUser(id)).filter(isTruthy),
|
||||||
|
[memberIds]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cl("popout")} ref={setPopoutRef}>
|
||||||
|
<div className={cl("header")}>
|
||||||
|
<span className={cl("color")} style={{ backgroundColor: role?.colorString ?? "var(--background-mod-strong)" }} />
|
||||||
|
<span className={cl("name")}>{role?.name ?? "Unknown Role"}</span>
|
||||||
|
<span className={cl("count")}>{totalCount}</span>
|
||||||
|
</div>
|
||||||
|
<ScrollerThin className={cl("list")} fade>
|
||||||
|
{loading ? (
|
||||||
|
<div className={cl("empty")}>Loading members...</div>
|
||||||
|
) : users.length === 0 ? (
|
||||||
|
<div className={cl("empty")}>No members found.</div>
|
||||||
|
) : users.map(user => (
|
||||||
|
<div
|
||||||
|
key={user.id}
|
||||||
|
className={cl("user")}
|
||||||
|
onClick={() => {
|
||||||
|
closePopout();
|
||||||
|
openUserProfile(user.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={IconUtils.getUserAvatarURL(user)} alt="" className={cl("avatar")} />
|
||||||
|
<span>{user.globalName ?? user.username}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{totalCount > MAX_VISIBLE_MEMBERS && (
|
||||||
|
<div className={cl("overflow")}>and {totalCount - MAX_VISIBLE_MEMBERS} more</div>
|
||||||
|
)}
|
||||||
|
</ScrollerThin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ClickableRole({ roleId, guildId, children }: { roleId: string; guildId: string; children: React.ReactNode; }) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popout
|
||||||
|
targetElementRef={ref}
|
||||||
|
position="right"
|
||||||
|
align="center"
|
||||||
|
autoInvert
|
||||||
|
nudgeAlignIntoViewport
|
||||||
|
renderPopout={({ closePopout, setPopoutRef }) => (
|
||||||
|
<RoleMembersList roleId={roleId} guildId={guildId} closePopout={closePopout} setPopoutRef={setPopoutRef} />
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{popoutProps => (
|
||||||
|
<div ref={ref} {...popoutProps} className={cl("trigger")}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Popout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const WrappedClickableRole = ErrorBoundary.wrap(ClickableRole, { noop: true });
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ClickableRoles",
|
||||||
|
description: "Click on roles in user profiles and the member list to see which members have them.",
|
||||||
|
authors: [Devs.prism],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "#{intl::T+3Adg::raw}",
|
||||||
|
replacement: {
|
||||||
|
match: /(colors:\i\}\);)return(.*?enableTooltip:!1\}\):null,\i\]\}\))/,
|
||||||
|
replace: "$1return $self.wrapRolePill(arguments[0],()=>$2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: 'tutorialId:"whos-online"',
|
||||||
|
replacement: {
|
||||||
|
match: /\((function\(\i\)\{let\{id:.*?#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.*?\}\))\}\);/,
|
||||||
|
replace: "($self.wrapRoleGroup($1}));",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
wrapRoleGroup(originalFn: (props: { id: string; guildId: string; }) => React.ReactNode) {
|
||||||
|
return (props: { id: string; guildId: string; }) => {
|
||||||
|
const result = originalFn(props);
|
||||||
|
if (!GuildRoleStore.getRole(props.guildId, props.id)) return result;
|
||||||
|
return (
|
||||||
|
<WrappedClickableRole roleId={props.id} guildId={props.guildId}>
|
||||||
|
{result}
|
||||||
|
</WrappedClickableRole>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
wrapRolePill(props: { role: Role; guildId: string; }, renderOriginal: () => React.ReactNode) {
|
||||||
|
return (
|
||||||
|
<WrappedClickableRole roleId={props.role.id} guildId={props.guildId}>
|
||||||
|
{renderOriginal()}
|
||||||
|
</WrappedClickableRole>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
80
src/equicordplugins/clickableRoles/style.css
Normal file
80
src/equicordplugins/clickableRoles/style.css
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
.vc-clickableroles-trigger {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-popout {
|
||||||
|
background-color: var(--background-surface-highest);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
width: 250px;
|
||||||
|
max-height: var(--reference-position-layer-max-height, 350px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-list {
|
||||||
|
max-height: 260px;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-color {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--interactive-text-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-count {
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-empty {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-user {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--interactive-text-default);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-user:hover {
|
||||||
|
background-color: var(--interactive-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-avatar {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-clickableroles-overflow {
|
||||||
|
padding: 6px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { ComponentsIcon } from "@components/Icons";
|
||||||
import SettingsPlugin from "@plugins/_core/settings";
|
import SettingsPlugin from "@plugins/_core/settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { removeFromArray } from "@utils/misc";
|
import { removeFromArray } from "@utils/misc";
|
||||||
|
|
@ -14,30 +15,6 @@ import { SettingsRouter } from "@webpack/common";
|
||||||
|
|
||||||
import ComponentsTab from "./components/ComponentsTab";
|
import ComponentsTab from "./components/ComponentsTab";
|
||||||
|
|
||||||
function ComponentsIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
{...props}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M3 15.5V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v.5a.5.5 0 0 1-.5.5H17a4 4 0 0 0-4 4v4.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5ZM12.5 18H2a1 1 0 1 0 0 2h10.48c.33 0 .57-.3.54-.63A4.08 4.08 0 0 1 13 19v-.5a.5.5 0 0 0-.5-.5Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
fillRule="evenodd"
|
|
||||||
d="M15 11c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8Zm2 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z"
|
|
||||||
clipRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Components",
|
name: "Components",
|
||||||
description: "Adds a new tab to settings to browse Discord components.",
|
description: "Adds a new tab to settings to browse Discord components.",
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,13 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./style.css";
|
||||||
|
|
||||||
import { definePluginSettings, migratePluginSetting, migratePluginSettings } from "@api/Settings";
|
import { definePluginSettings, migratePluginSetting, migratePluginSettings } from "@api/Settings";
|
||||||
|
import { Divider } from "@components/Divider";
|
||||||
import { HeadingSecondary } from "@components/Heading";
|
import { HeadingSecondary } from "@components/Heading";
|
||||||
|
import { Notice } from "@components/Notice";
|
||||||
|
import { classNameFactory } from "@utils/css";
|
||||||
import { Devs, EquicordDevs } from "@utils/index";
|
import { Devs, EquicordDevs } from "@utils/index";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
|
@ -24,17 +29,13 @@ for (const [oldKey, newKey] of migrationsAnammox) {
|
||||||
migratePluginSetting("Declutter", newKey, oldKey);
|
migratePluginSetting("Declutter", newKey, oldKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-declutter-");
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
userProfileHeader: {
|
userProfileHeader: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
component: () => SectionSeparator("User Profile"),
|
component: () => SectionSeparator("User Profile"),
|
||||||
},
|
},
|
||||||
removeAvatarDecoration: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Remove avatar decorations.",
|
|
||||||
default: true,
|
|
||||||
restartNeeded: true,
|
|
||||||
},
|
|
||||||
removeNameplate: {
|
removeNameplate: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Remove nameplates.",
|
description: "Remove nameplates.",
|
||||||
|
|
@ -59,11 +60,13 @@ export const settings = definePluginSettings({
|
||||||
default: true,
|
default: true,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
},
|
},
|
||||||
removeUsernameStyles: {
|
accessibilityNotice: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.COMPONENT,
|
||||||
description: "Remove username colors and effects.",
|
component: () => (
|
||||||
default: true,
|
<Notice.Info className={cl("accessibility-notice")}>
|
||||||
restartNeeded: true
|
Discord already has a built-in username style option in Accessibility settings.
|
||||||
|
</Notice.Info>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
friendsListHeader: {
|
friendsListHeader: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
|
|
@ -72,7 +75,7 @@ export const settings = definePluginSettings({
|
||||||
removeShopAboveDM: {
|
removeShopAboveDM: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Remove shops above DMs list.",
|
description: "Remove shops above DMs list.",
|
||||||
default: true,
|
default: false,
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
},
|
},
|
||||||
removeQuestsAboveDM: {
|
removeQuestsAboveDM: {
|
||||||
|
|
@ -125,29 +128,21 @@ export const settings = definePluginSettings({
|
||||||
|
|
||||||
function SectionSeparator(title: string) {
|
function SectionSeparator(title: string) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={cl("section-separator")}>
|
||||||
<hr style={{ width: "100%" }} />
|
<Divider />
|
||||||
<HeadingSecondary>{title}</HeadingSecondary>
|
<HeadingSecondary className={cl("section-title")}>
|
||||||
<hr style={{ width: "100%" }} />
|
{title}
|
||||||
</>
|
</HeadingSecondary>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Declutter",
|
name: "Declutter",
|
||||||
description: "Declutter discord ui by removing unwanted elements such as profile customizations, shops, quests and more.",
|
description: "Cleans up Discord by removing non-essential UI elements like profile effects, shop tabs, boosts, and more.",
|
||||||
authors: [EquicordDevs.Leon135, Devs.prism, Devs.Kyuuhachi],
|
authors: [EquicordDevs.Leon135, Devs.prism, Devs.Kyuuhachi],
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
|
||||||
// Avatar decoration
|
|
||||||
find: "getAvatarDecorationURL:",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=function \i\((\i)\){)(?=.{0,150}let{avatarDecoration)/,
|
|
||||||
replace: "$&return null;"
|
|
||||||
},
|
|
||||||
predicate: () => settings.store.removeAvatarDecoration,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// Nameplate
|
// Nameplate
|
||||||
find: "#{intl::AVATAR_MALLOW}",
|
find: "#{intl::AVATAR_MALLOW}",
|
||||||
|
|
@ -176,20 +171,13 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.removeClanTag,
|
predicate: () => settings.store.removeClanTag,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Username styles and allways show username
|
// Always show username
|
||||||
find: ".NITRO_PRIVACY_PERK_BETA_COACHMARK));",
|
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||||
replacement: [
|
replacement: {
|
||||||
{
|
match: /hoverText:(\i),forceHover:\i,children:/g,
|
||||||
match: /displayNameStyles:(\i),/,
|
replace: "hoverText:$1,forceHover:!0,children:"
|
||||||
replace: "displayNameStyles:void 0,",
|
},
|
||||||
predicate: () => settings.store.removeUsernameStyles
|
predicate: () => settings.store.alwaysShowUsername
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /hoverText:(\i),forceHover:\i,children:/g,
|
|
||||||
replace: "hoverText:$1,forceHover:!0,children:",
|
|
||||||
predicate: () => settings.store.alwaysShowUsername
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Button tooltips in user area
|
// Button tooltips in user area
|
||||||
|
|
|
||||||
19
src/equicordplugins/declutter/style.css
Normal file
19
src/equicordplugins/declutter/style.css
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
.vc-declutter-accessibility-notice {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-declutter-section-separator {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-declutter-section-title {
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
@ -188,16 +188,6 @@ export default definePlugin({
|
||||||
replace: "{_enableHangStatus:$1=false,"
|
replace: "{_enableHangStatus:$1=false,"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Always show open legacy settings
|
|
||||||
{
|
|
||||||
find: ".DEVELOPER_SECTION,",
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /\i\.\i\.isDeveloper/,
|
|
||||||
replace: "true"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// Force Role Icon
|
// Force Role Icon
|
||||||
{
|
{
|
||||||
find: "Message Username",
|
find: "Message Username",
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ function GuildSelector() {
|
||||||
return (
|
return (
|
||||||
<SearchableSelect
|
<SearchableSelect
|
||||||
options={options}
|
options={options}
|
||||||
value={options.find(o => o.value === soundGuildId)}
|
value={options.find(o => o.value === soundGuildId)?.value}
|
||||||
placeholder="Select a server..."
|
placeholder="Select a server..."
|
||||||
maxVisibleItems={6}
|
maxVisibleItems={6}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
import { BaseText } from "@components/BaseText";
|
import { BaseText } from "@components/BaseText";
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
import { LazyComponentWrapper } from "@utils/lazyReact";
|
import { LazyComponentWrapper } from "@utils/lazyReact";
|
||||||
import { Channel, Message, MessageAttachment, ScrollerBaseRef } from "@vencord/discord-types";
|
import { Message, MessageAttachment, ScrollerBaseRef } from "@vencord/discord-types";
|
||||||
import { ChannelType } from "@vencord/discord-types/enums";
|
import { ChannelType } from "@vencord/discord-types/enums";
|
||||||
import { findByCodeLazy, findByPropsLazy, findComponentByCode, findComponentByCodeLazy, findCssClassesLazy, proxyLazyWebpack } from "@webpack";
|
import { findByCodeLazy, findComponentByCode, findComponentByCodeLazy, findCssClassesLazy, proxyLazyWebpack } from "@webpack";
|
||||||
import { ChannelStore, ExpressionPickerStore, ListScrollerThin, lodash, PermissionsBits, PermissionStore, React, useCallback, useEffect, useMemo, useRef, useState, useStateFromStores } from "@webpack/common";
|
import { ChannelStore, ExpressionPickerStore, ListScrollerThin, lodash, PermissionsBits, PermissionStore, React, useCallback, useEffect, useMemo, useRef, useState, useStateFromStores } from "@webpack/common";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
|
@ -22,11 +22,24 @@ const ManaSearchBar = findComponentByCodeLazy<ManaSearchBarProps>("#{intl::SEARC
|
||||||
const FavoriteButton = findComponentByCodeLazy<FavoriteButtonProps>("#{intl::GIF_TOOLTIP_ADD_TO_FAVORITES}");
|
const FavoriteButton = findComponentByCodeLazy<FavoriteButtonProps>("#{intl::GIF_TOOLTIP_ADD_TO_FAVORITES}");
|
||||||
const SendIcon = findComponentByCodeLazy("M6.6 10.02 14 11.4a.6.6");
|
const SendIcon = findComponentByCodeLazy("M6.6 10.02 14 11.4a.6.6");
|
||||||
|
|
||||||
const DmChannel: Channel & { new(base?: Partial<Channel>): Channel; } = findByPropsLazy("fromServer", "sortRecipients");
|
const createChannelRecordFromServer = findByCodeLazy(".GUILD_TEXT]", "fromServer)");
|
||||||
const MessageClass: Message & { new(base?: Partial<Message>): Message; } = findByCodeLazy(";mentionChannels;mentionGames;");
|
const createMessageRecord = findByCodeLazy(".createFromServer(", ".isBlockedForMessage", "messageReference:");
|
||||||
|
|
||||||
const Classes = findCssClassesLazy("gifFavoriteButton", "ctaButtonContainer");
|
const Classes = findCssClassesLazy("gifFavoriteButton", "ctaButtonContainer");
|
||||||
|
|
||||||
|
function createPreviewMessage(attachment: MessageAttachment, channelId: string) {
|
||||||
|
const previewMessage = {
|
||||||
|
id: `favourite-anything-preview-${attachment.id}`,
|
||||||
|
attachments: [attachment],
|
||||||
|
channel_id: channelId,
|
||||||
|
content: "",
|
||||||
|
type: 0,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
return createMessageRecord(previewMessage) as Message;
|
||||||
|
}
|
||||||
|
|
||||||
export const AttachmentPreview = proxyLazyWebpack(() => {
|
export const AttachmentPreview = proxyLazyWebpack(() => {
|
||||||
// findComponentByCodeLazy doesn't work properly with component classes, this must be kept within the lazy scope
|
// findComponentByCodeLazy doesn't work properly with component classes, this must be kept within the lazy scope
|
||||||
const MessageComponent = findComponentByCode("this.renderAttachments") as LazyComponentWrapper<MessageComponentClass>;
|
const MessageComponent = findComponentByCode("this.renderAttachments") as LazyComponentWrapper<MessageComponentClass>;
|
||||||
|
|
@ -37,12 +50,12 @@ export const AttachmentPreview = proxyLazyWebpack(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = Object.freeze(new DmChannel({ id: "0", type: ChannelType.GUILD_TEXT }));
|
const channel = Object.freeze(createChannelRecordFromServer({ id: "0", type: ChannelType.GUILD_TEXT }));
|
||||||
|
|
||||||
return function AttachmentPreview({ attachment }: AttachmentsComponentProps) {
|
return function AttachmentPreview({ attachment }: AttachmentsComponentProps) {
|
||||||
const message = useMemo(
|
const message = useMemo(
|
||||||
() => new MessageClass({ attachments: [attachment], channel_id: channel.id }),
|
() => createPreviewMessage(attachment, channel.id),
|
||||||
[attachment]
|
[attachment, channel.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "HopOn",
|
name: "HopOn",
|
||||||
description: "Hop on Fortnite or Hop on bloons :3",
|
description: "Hop on! Opens a configurable URL when a message matches a custom regex in the current channel.",
|
||||||
authors: [Devs.ImLvna],
|
authors: [Devs.ImLvna],
|
||||||
settings,
|
settings,
|
||||||
flux: {
|
flux: {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ import { ChannelStore, MediaEngineStore, PermissionsBits, PermissionStore, Selec
|
||||||
|
|
||||||
import { getCurrentMedia, settings } from "./utils";
|
import { getCurrentMedia, settings } from "./utils";
|
||||||
|
|
||||||
let hasStreamed;
|
let hasStreamed, isStreaming, streamKey;
|
||||||
const startStream = findByCodeLazy('type:"STREAM_START"');
|
const startStream = findByCodeLazy('type:"STREAM_START"');
|
||||||
|
const stopStream = findByCodeLazy('type:"STREAM_STOP"');
|
||||||
const StreamPreviewSettings = getUserSettingLazy("voiceAndVideo", "disableStreamPreviews")!;
|
const StreamPreviewSettings = getUserSettingLazy("voiceAndVideo", "disableStreamPreviews")!;
|
||||||
const ApplicationStreamingSettingsStore = findStoreLazy("ApplicationStreamingSettingsStore");
|
const ApplicationStreamingSettingsStore = findStoreLazy("ApplicationStreamingSettingsStore");
|
||||||
|
|
||||||
|
|
@ -44,14 +45,18 @@ async function autoStartStream(instant = true) {
|
||||||
let sourceId = streamMedia.id;
|
let sourceId = streamMedia.id;
|
||||||
if (streamMedia.type === "video_device") sourceId = `camera:${streamMedia.id}`;
|
if (streamMedia.type === "video_device") sourceId = `camera:${streamMedia.id}`;
|
||||||
|
|
||||||
startStream(channel.guild_id ?? null, selected, {
|
if (isStreaming && streamKey.endsWith(UserStore.getCurrentUser().id)) {
|
||||||
"pid": null,
|
stopStream(streamKey);
|
||||||
"sourceId": sourceId,
|
} else {
|
||||||
"sourceName": streamMedia.name,
|
startStream(channel.guild_id ?? null, selected, {
|
||||||
"audioSourceId": streamMedia.name,
|
"pid": null,
|
||||||
"sound": soundshareEnabled,
|
"sourceId": sourceId,
|
||||||
"previewDisabled": preview
|
"sourceName": streamMedia.name,
|
||||||
});
|
"audioSourceId": streamMedia.name,
|
||||||
|
"sound": soundshareEnabled,
|
||||||
|
"previewDisabled": preview
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
|
@ -122,6 +127,14 @@ export default definePlugin({
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
STREAM_CREATE: d => {
|
||||||
|
streamKey = d;
|
||||||
|
isStreaming = true;
|
||||||
|
},
|
||||||
|
STREAM_DELETE: d => {
|
||||||
|
streamKey = d;
|
||||||
|
isStreaming = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ function StreamSimplePicker({ streamMediaSelection, streamMedia }: PickerProps)
|
||||||
placeholder="Select a media source to stream "
|
placeholder="Select a media source to stream "
|
||||||
maxVisibleItems={5}
|
maxVisibleItems={5}
|
||||||
options={options}
|
options={options}
|
||||||
value={options.find(o => o.value === streamMedia)}
|
value={options.find(o => o.value === streamMedia)?.value}
|
||||||
onChange={v => settings.store.streamMedia = v}
|
onChange={v => settings.store.streamMedia = v}
|
||||||
closeOnSelect
|
closeOnSelect
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -129,4 +129,4 @@ export default definePlugin({
|
||||||
"user-context": UserMenuPatch,
|
"user-context": UserMenuPatch,
|
||||||
"message": MessageMenuPatch
|
"message": MessageMenuPatch
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ function Timestamp({ channel }: { channel: Channel; }) {
|
||||||
if (!lastMessage) return null;
|
if (!lastMessage) return null;
|
||||||
|
|
||||||
const timestamp = SnowflakeUtils.extractTimestamp(lastMessage.id);
|
const timestamp = SnowflakeUtils.extractTimestamp(lastMessage.id);
|
||||||
const className = ExperimentStore.getUserExperimentBucket("2026-01-favorites-server") >= 1 ? cl("timestamp-favorites") : cl("timestamp");
|
const className = ExperimentStore.getUserExperimentBucket("2026-01-favorites-server") > 0 ? cl("timestamp-favorites") : cl("timestamp");
|
||||||
return <span className={className}>{formatRelativeTime(timestamp)}</span>;
|
return <span className={className}>{formatRelativeTime(timestamp)}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const settings = definePluginSettings({
|
||||||
addFreakyEnding: {
|
addFreakyEnding: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Add 👅 or ❤️ at the end",
|
description: "Add 👅 or ❤️ at the end",
|
||||||
default: true
|
default: false
|
||||||
},
|
},
|
||||||
uwuEveryMessage: {
|
uwuEveryMessage: {
|
||||||
description: "Make every single message uwuified",
|
description: "Make every single message uwuified",
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ const settings = definePluginSettings({
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Moyai",
|
name: "Moyai",
|
||||||
authors: [Devs.Megu, Devs.Nuckyz],
|
authors: [Devs.Megu, Devs.Nuckyz],
|
||||||
description: "🗿🗿🗿🗿🗿🗿🗿🗿",
|
description: "Plays a 🗿 sound effect whenever a moyai emoji is sent, reacted, or used as a voice effect in your current channel.",
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
|
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||||
replacement: {
|
replacement: {
|
||||||
// react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
|
// react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
|
||||||
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,occluded:)/,
|
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,occluded:)/,
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,6 @@ export default definePlugin({
|
||||||
replace: "$1{...$2,...$self.getPlatform(true)},presence"
|
replace: "$1{...$2,...$self.getPlatform(true)},presence"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: "#{intl::POPOUT_STAY_ON_TOP}),icon:",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=CallTile.{0,15}\.memo\((\i)=>\{)/,
|
|
||||||
replace: "$1.platform = $self.getPlatform(false, $1?.participantUserId)?.vcIcon || $1?.platform;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: '("AppSkeleton");',
|
find: '("AppSkeleton");',
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
@ -88,19 +81,19 @@ export default definePlugin({
|
||||||
if (bypass || userId === UserStore.getCurrentUser().id) {
|
if (bypass || userId === UserStore.getCurrentUser().id) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "desktop":
|
case "desktop":
|
||||||
return { browser: "Discord Client", vcIcon: 0 };
|
return { browser: "Discord Client" };
|
||||||
case "web":
|
case "web":
|
||||||
return { browser: "Discord Web", vcIcon: 0 };
|
return { browser: "Discord Web" };
|
||||||
case "ios":
|
case "ios":
|
||||||
return { browser: "Discord iOS", vcIcon: 1 };
|
return { browser: "Discord iOS" };
|
||||||
case "android":
|
case "android":
|
||||||
return { browser: "Discord Android", vcIcon: 1 };
|
return { browser: "Discord Android" };
|
||||||
case "xbox":
|
case "xbox":
|
||||||
return { browser: "Discord Embedded", vcIcon: 2 };
|
return { browser: "Discord Embedded" };
|
||||||
case "playstation":
|
case "playstation":
|
||||||
return { browser: "Discord Embedded", vcIcon: 3 };
|
return { browser: "Discord Embedded" };
|
||||||
case "vr":
|
case "vr":
|
||||||
return { browser: "Discord VR", vcIcon: 4 };
|
return { browser: "Discord VR" };
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,14 @@
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { EquicordDevs } from "@utils/constants";
|
import { EquicordDevs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
import { useAuthorizationStore } from "./lib/stores/AuthorizationStore";
|
import { useAuthorizationStore } from "./lib/stores/AuthorizationStore";
|
||||||
import { useSongStore } from "./lib/stores/SongStore";
|
import { useSongStore } from "./lib/stores/SongStore";
|
||||||
import { Native } from "./service";
|
import { Native } from "./service";
|
||||||
import Settings from "./ui/settings";
|
import settings from "./settings";
|
||||||
import ProfileSongs from "./ui/songs/ProfileSongs";
|
import ProfileSongs from "./ui/songs/ProfileSongs";
|
||||||
import WidgetSongs from "./ui/songs/WidgetSongs";
|
import WidgetSongs from "./ui/songs/WidgetSongs";
|
||||||
|
|
||||||
|
|
@ -22,12 +21,7 @@ export default definePlugin({
|
||||||
name: "SongSpotlight",
|
name: "SongSpotlight",
|
||||||
description: "Show off songs on your profile",
|
description: "Show off songs on your profile",
|
||||||
authors: [EquicordDevs.nexpid],
|
authors: [EquicordDevs.nexpid],
|
||||||
settings: definePluginSettings({
|
settings,
|
||||||
manager: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
component: () => <Settings />,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
patches: [
|
patches: [
|
||||||
// Personal profile popout
|
// Personal profile popout
|
||||||
{
|
{
|
||||||
|
|
@ -50,7 +44,7 @@ export default definePlugin({
|
||||||
find: ".SIDEBAR}),nicknameIcons:",
|
find: ".SIDEBAR}),nicknameIcons:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /{userId:(\i)\.id}\)}\).{0,100}]}\)(?=\]\}\))/,
|
match: /{userId:(\i)\.id}\)}\).{0,100}]}\)(?=\]\}\))/,
|
||||||
replace: "$&,$self.renderProfileSongs({userId:$1.id})",
|
replace: "$&,$self.renderProfileSongs({userId:$1.id,isSidebar:true})",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Full profile modal sections (lazy loaded)
|
// Full profile modal sections (lazy loaded)
|
||||||
|
|
@ -58,14 +52,14 @@ export default definePlugin({
|
||||||
find: ".MUTUAL_GUILDS})),",
|
find: ".MUTUAL_GUILDS})),",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i).push\({text.{0,50}}\);/,
|
match: /(\i).push\({text.{0,50}}\);/,
|
||||||
replace: "$&$1.push({text:\"Song Spotlight\",section:\"SONG_SPOTLIGHT\"});",
|
replace: '$&$1.push({text:"Song Spotlight",section:"SONG_SPOTLIGHT"});',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".hasUnsavedChanges()&&",
|
find: ".hasUnsavedChanges()&&",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /({user:(\i),.{0,80}return (\i===))/,
|
match: /({user:(\i),.{0,80}return (\i===))/,
|
||||||
replace: "$1\"SONG_SPOTLIGHT\"?$self.renderWidgetSongs({user:$2}):$3",
|
replace: '$1"SONG_SPOTLIGHT"?$self.renderWidgetSongs({user:$2}):$3',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { showToast, Toasts, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { useAuthorizationStore } from "./stores/AuthorizationStore";
|
import { useAuthorizationStore } from "./stores/AuthorizationStore";
|
||||||
import { useSongStore } from "./stores/SongStore";
|
import { useSongStore } from "./stores/SongStore";
|
||||||
import { logger } from "./utils";
|
|
||||||
|
|
||||||
export const apiConstants = {
|
export const apiConstants = {
|
||||||
api: "https://dc.songspotlight.nexpid.xyz/",
|
api: "https://dc.songspotlight.nexpid.xyz/",
|
||||||
|
|
@ -20,16 +19,36 @@ export const apiConstants = {
|
||||||
songLimit: 6,
|
songLimit: 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function authFetch(_url: string | URL, options?: RequestInit) {
|
let refreshPromise: Promise<boolean> | undefined;
|
||||||
const url = new URL(_url);
|
async function refreshAccessToken() {
|
||||||
|
const token = useAuthorizationStore.getState().getToken();
|
||||||
|
if (!token) return false;
|
||||||
|
|
||||||
|
return refreshPromise ??= fetch(new URL("api/auth/refresh", apiConstants.api), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-Refresh-Token": token.refresh,
|
||||||
|
},
|
||||||
|
body: token.access,
|
||||||
|
}).then(async res => {
|
||||||
|
if (!res.ok) return false;
|
||||||
|
|
||||||
|
const access = await res.text();
|
||||||
|
useAuthorizationStore.getState().setToken(access, token.refresh);
|
||||||
|
return true;
|
||||||
|
}).finally(() => refreshPromise = undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authFetch(url: string | URL, options?: RequestInit, retried = false) {
|
||||||
|
url = new URL(url);
|
||||||
try {
|
try {
|
||||||
|
const token = useAuthorizationStore.getState().getToken();
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
...options?.headers,
|
...options?.headers,
|
||||||
Authorization: useAuthorizationStore.getState().getToken()!,
|
Authorization: token?.access,
|
||||||
},
|
} as HeadersInit,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) return res;
|
if (res.ok) return res;
|
||||||
|
|
@ -37,36 +56,29 @@ export async function authFetch(_url: string | URL, options?: RequestInit) {
|
||||||
// not modified
|
// not modified
|
||||||
if (res.status === 304) return null;
|
if (res.status === 304) return null;
|
||||||
|
|
||||||
|
const text = await res.text();
|
||||||
|
|
||||||
// unauthorized
|
// unauthorized
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
useAuthorizationStore.getState().deleteToken();
|
const retry = !retried && await refreshAccessToken();
|
||||||
|
if (retry) return await authFetch(url, options, true);
|
||||||
|
else {
|
||||||
|
useAuthorizationStore.getState().deleteTokens();
|
||||||
|
showToast("You have been signed out from Song Spotlight. Please sign in again.", Toasts.Type.FAILURE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(
|
||||||
|
!text.includes("<body>") && res.status >= 400 && res.status <= 599
|
||||||
|
? `Song Spotlight: ${text}`
|
||||||
|
: `Song Spotlight fetch error at ${url.pathname}`,
|
||||||
|
Toasts.Type.FAILURE,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = await res.text();
|
|
||||||
showToast(
|
|
||||||
!text.includes("<body>") && res.status >= 400 && res.status <= 599
|
|
||||||
? `Song Spotlight: ${text}`
|
|
||||||
: `Song Spotlight fetch error at ${url.pathname}`,
|
|
||||||
Toasts.Type.FAILURE,
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
"Got an authFetch response error",
|
|
||||||
options?.method ?? "GET",
|
|
||||||
url.toString(),
|
|
||||||
res.status,
|
|
||||||
text,
|
|
||||||
);
|
|
||||||
throw new Error(text);
|
throw new Error(text);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(`Song Spotlight: ${error}`, Toasts.Type.FAILURE);
|
showToast(`Song Spotlight: ${error}`, Toasts.Type.FAILURE);
|
||||||
|
|
||||||
logger.error(
|
|
||||||
"Got an authFetch request error",
|
|
||||||
options?.method ?? "GET",
|
|
||||||
url.toString(),
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,22 @@ export function presentOAuth2Modal() {
|
||||||
const url = new URL(location);
|
const url = new URL(location);
|
||||||
url.searchParams.append("whois", "equicord");
|
url.searchParams.append("whois", "equicord");
|
||||||
|
|
||||||
const token = await (await authFetch(url))?.text();
|
const res = await authFetch(url);
|
||||||
if (!token) return;
|
if (!res) throw "Response wasn't ok";
|
||||||
|
|
||||||
useAuthorizationStore.getState().setToken(token);
|
const access = await res.text();
|
||||||
|
if (!access) throw "Access token is missing";
|
||||||
|
|
||||||
|
const refresh = res.headers.get("X-Refresh-Token");
|
||||||
|
if (!refresh) throw "Refresh token is missing";
|
||||||
|
|
||||||
|
useAuthorizationStore.getState().setToken(access, refresh);
|
||||||
getData();
|
getData();
|
||||||
|
|
||||||
showToast("Successfully authorized!", Toasts.Type.SUCCESS);
|
showToast("Successfully authorized!", Toasts.Type.SUCCESS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Got an error during OAuth2", error);
|
logger.error("Got an error during OAuth2", error);
|
||||||
|
if (typeof error === "string") showToast(error, Toasts.Type.FAILURE);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,16 @@ import { UserStore, zustandCreate, zustandPersist } from "@webpack/common";
|
||||||
|
|
||||||
import { PersistedZustandStore, ZustandDefinition } from "./zustand";
|
import { PersistedZustandStore, ZustandDefinition } from "./zustand";
|
||||||
|
|
||||||
|
export interface Token {
|
||||||
|
access: string;
|
||||||
|
refresh: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface AuthorizationState {
|
interface AuthorizationState {
|
||||||
tokens: Record<string, string>;
|
tokens: Record<string, Token>;
|
||||||
getToken(): string | undefined;
|
getToken(): Token | undefined;
|
||||||
setToken(token: string): void;
|
setToken(access: string, refresh: string): void;
|
||||||
deleteToken(): void;
|
deleteTokens(): void;
|
||||||
isAuthorized(): boolean;
|
isAuthorized(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,17 +31,18 @@ export const useAuthorizationStore: PersistedZustandStore<AuthorizationState> =
|
||||||
getToken() {
|
getToken() {
|
||||||
return get().tokens[UserStore.getCurrentUser()?.id];
|
return get().tokens[UserStore.getCurrentUser()?.id];
|
||||||
},
|
},
|
||||||
setToken(token) {
|
setToken(access, refresh) {
|
||||||
const userId = UserStore.getCurrentUser()?.id;
|
const userId = UserStore.getCurrentUser()?.id;
|
||||||
if (userId) {
|
if (userId) {
|
||||||
set({
|
set({
|
||||||
tokens: Object.assign(get().tokens, {
|
tokens: {
|
||||||
[userId]: token,
|
...get().tokens,
|
||||||
}),
|
[userId]: { access, refresh },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteToken() {
|
deleteTokens() {
|
||||||
set({ tokens: {} });
|
set({ tokens: {} });
|
||||||
},
|
},
|
||||||
isAuthorized() {
|
isAuthorized() {
|
||||||
|
|
@ -45,11 +51,24 @@ export const useAuthorizationStore: PersistedZustandStore<AuthorizationState> =
|
||||||
})) as ZustandDefinition<AuthorizationState>,
|
})) as ZustandDefinition<AuthorizationState>,
|
||||||
{
|
{
|
||||||
name: "songspotlight-auth",
|
name: "songspotlight-auth",
|
||||||
|
version: 1,
|
||||||
|
migrate(persisted: any, version: number) {
|
||||||
|
if (version === 0) {
|
||||||
|
persisted.tokens = Object.fromEntries(
|
||||||
|
Object.entries(persisted.tokens).map(([userId, access]) => [userId, {
|
||||||
|
access,
|
||||||
|
refresh: "",
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return persisted;
|
||||||
|
},
|
||||||
storage: {
|
storage: {
|
||||||
async getItem(name: string) {
|
async getItem(name: string) {
|
||||||
return (await DataStore.get(name)) ?? null;
|
return (await DataStore.get(name)) ?? null;
|
||||||
},
|
},
|
||||||
async setItem(name: string, value: string) {
|
async setItem(name: string, value: unknown) {
|
||||||
return await DataStore.set(name, value);
|
return await DataStore.set(name, value);
|
||||||
},
|
},
|
||||||
async removeItem(name: string) {
|
async removeItem(name: string) {
|
||||||
|
|
|
||||||
35
src/equicordplugins/songSpotlight.desktop/settings.tsx
Normal file
35
src/equicordplugins/songSpotlight.desktop/settings.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { makeRange, OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
import { apiConstants } from "./lib/api";
|
||||||
|
import Settings from "./ui/settings";
|
||||||
|
|
||||||
|
export default definePluginSettings({
|
||||||
|
collapseSongList: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Collapses the song list on user profiles to a button which opens a separate menu",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
profileSongsLimit: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "How many songs are shown when initially clicking on a user",
|
||||||
|
default: apiConstants.songLimit,
|
||||||
|
markers: makeRange(1, apiConstants.songLimit),
|
||||||
|
},
|
||||||
|
manager: {
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
component: () => <Settings />,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
profileSongsLimit: {
|
||||||
|
disabled() {
|
||||||
|
return this.store.collapseSongList;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -23,7 +23,10 @@
|
||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
line-clamp: 1;
|
line-clamp: 1;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* components */
|
/* components */
|
||||||
|
|
@ -137,13 +140,13 @@
|
||||||
--list-height: calc(var(--base-height) + 80px);
|
--list-height: calc(var(--base-height) + 80px);
|
||||||
--number-size: 32px;
|
--number-size: 32px;
|
||||||
|
|
||||||
padding: 8px;
|
display: flex;
|
||||||
gap: 12px;
|
flex-direction: column;
|
||||||
overflow: visible;
|
gap: 6px;
|
||||||
}
|
|
||||||
|
|
||||||
.user-profile-sidebar .vc-songspotlight-songs-container {
|
&.vc-songspotlight-songs-container-sidebar {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-songspotlight-songs-more {
|
.vc-songspotlight-songs-more {
|
||||||
|
|
@ -194,6 +197,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-profile-sidebar .vc-songspotlight-song-container {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.user-profile-popout:not(.custom-user-profile-theme) .vc-songspotlight-song-container {
|
.user-profile-popout:not(.custom-user-profile-theme) .vc-songspotlight-song-container {
|
||||||
background: var(--opacity-16);
|
background: var(--opacity-16);
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +218,6 @@
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
overflow: visible;
|
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,7 +226,7 @@
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
overflow: visible;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-songspotlight-song-cover {
|
.vc-songspotlight-song-cover {
|
||||||
|
|
@ -232,7 +238,6 @@
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-shadow: 0 3px 12px 0 rgb(0 0 0 / 16%);
|
box-shadow: 0 3px 12px 0 rgb(0 0 0 / 16%);
|
||||||
transition: 0.18s transform;
|
transition: 0.18s transform;
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.03);
|
transform: scale(1.03);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,14 @@ import { Tooltip } from "@webpack/common";
|
||||||
import { ComponentType, HTMLAttributes, JSX } from "react";
|
import { ComponentType, HTMLAttributes, JSX } from "react";
|
||||||
|
|
||||||
export const OverlayClasses = findCssClassesLazy("overlay", "inner", "outer");
|
export const OverlayClasses = findCssClassesLazy("overlay", "inner", "outer");
|
||||||
|
export const ContainerClasses = findCssClassesLazy(
|
||||||
|
"breadcrumb",
|
||||||
|
"innerContainer",
|
||||||
|
"icons",
|
||||||
|
"icon",
|
||||||
|
"displayCount",
|
||||||
|
"displayCountText",
|
||||||
|
);
|
||||||
export const CardClasses = findCssClassesLazy("card", "headerText", "headerContextMenu");
|
export const CardClasses = findCssClassesLazy("card", "headerText", "headerContextMenu");
|
||||||
export const WidgetClasses = findCssClassesLazy("tabPanelScroller", "tabListScroller");
|
export const WidgetClasses = findCssClassesLazy("tabPanelScroller", "tabListScroller");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ interface SettingsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Settings({ templateData }: SettingsProps) {
|
export default function Settings({ templateData }: SettingsProps) {
|
||||||
const { isAuthorized, deleteToken } = useAuthorizationStore();
|
const { isAuthorized, deleteTokens } = useAuthorizationStore();
|
||||||
const { self } = useSongStore();
|
const { self } = useSongStore();
|
||||||
|
|
||||||
const ticked = useRef(false);
|
const ticked = useRef(false);
|
||||||
|
|
@ -172,7 +172,7 @@ export default function Settings({ templateData }: SettingsProps) {
|
||||||
<Button
|
<Button
|
||||||
variant="dangerPrimary"
|
variant="dangerPrimary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deleteToken();
|
deleteTokens();
|
||||||
showToast("Successfully signed out!", Toasts.Type.SUCCESS);
|
showToast("Successfully signed out!", Toasts.Type.SUCCESS);
|
||||||
}}
|
}}
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
|
|
@ -189,7 +189,7 @@ export default function Settings({ templateData }: SettingsProps) {
|
||||||
setPending(true);
|
setPending(true);
|
||||||
try {
|
try {
|
||||||
await deleteData();
|
await deleteData();
|
||||||
deleteToken();
|
deleteTokens();
|
||||||
|
|
||||||
showToast("Successfully deleted songs!", Toasts.Type.SUCCESS);
|
showToast("Successfully deleted songs!", Toasts.Type.SUCCESS);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseText } from "@components/index";
|
||||||
|
import { Native } from "@equicordplugins/songSpotlight.desktop/service";
|
||||||
|
import {
|
||||||
|
CardClasses,
|
||||||
|
ContainerClasses,
|
||||||
|
OverlayClasses,
|
||||||
|
Spinner,
|
||||||
|
} from "@equicordplugins/songSpotlight.desktop/ui/common";
|
||||||
|
import { RenderSongInfo } from "@song-spotlight/api/handlers";
|
||||||
|
import { UserData } from "@song-spotlight/api/structs";
|
||||||
|
import { sid } from "@song-spotlight/api/util";
|
||||||
|
import { classes } from "@utils/index";
|
||||||
|
import {
|
||||||
|
SelectedChannelStore,
|
||||||
|
SelectedGuildStore,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
UserProfileActions,
|
||||||
|
UserStore,
|
||||||
|
useState,
|
||||||
|
} from "@webpack/common";
|
||||||
|
|
||||||
|
import { ProfileSongsProps } from "./ProfileSongs";
|
||||||
|
|
||||||
|
const shownSongs = 4;
|
||||||
|
|
||||||
|
interface CollapsedProfileSongsProps extends ProfileSongsProps {
|
||||||
|
data?: UserData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CollapsedProfileSongs({ data, userId, isSidebar }: CollapsedProfileSongsProps) {
|
||||||
|
const [renders, setRenders] = useState(new Map<string, RenderSongInfo>());
|
||||||
|
const previews = useMemo(() => data?.slice(0, shownSongs), [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRenders(new Map());
|
||||||
|
if (!previews) return;
|
||||||
|
|
||||||
|
for (const song of previews) {
|
||||||
|
Native.renderSong(song)
|
||||||
|
.catch(() => null)
|
||||||
|
.then(info => {
|
||||||
|
if (!info) return;
|
||||||
|
setRenders(renders => new Map(renders).set(sid(song), info));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [previews]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={ContainerClasses.breadcrumb}
|
||||||
|
aria-label="Song Spotlight"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
const user = UserStore.getUser(userId);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const guildId = SelectedGuildStore.getGuildId();
|
||||||
|
UserProfileActions.openUserProfileModal({
|
||||||
|
userId,
|
||||||
|
guildId,
|
||||||
|
channelId: SelectedChannelStore.getChannelId(),
|
||||||
|
analyticsLocation: {
|
||||||
|
page: guildId ? "Guild Channel" : "DM Channel",
|
||||||
|
section: "Profile Popout",
|
||||||
|
},
|
||||||
|
tabSection: "SONG_SPOTLIGHT",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={classes(OverlayClasses.overlay, ContainerClasses.innerContainer, CardClasses.card)}>
|
||||||
|
<BaseText size={isSidebar ? "sm" : "xs"} weight="medium">Song Spotlight</BaseText>
|
||||||
|
<div className={ContainerClasses.icons}>
|
||||||
|
{previews && data
|
||||||
|
? previews.map((song, i) => {
|
||||||
|
const render = renders.get(sid(song));
|
||||||
|
const extra = i === shownSongs - 1 && data.length > shownSongs;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ContainerClasses.icon} key={i}>
|
||||||
|
{render?.thumbnailUrl && (
|
||||||
|
<img
|
||||||
|
src={render.thumbnailUrl}
|
||||||
|
alt={render.label}
|
||||||
|
className={classes(extra && ContainerClasses.displayCount)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{extra && (
|
||||||
|
<div className={ContainerClasses.displayCountText}>
|
||||||
|
<BaseText size="xs" weight="medium">+{data.length - shownSongs}</BaseText>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: <Spinner type={Spinner.Type.SPINNING_CIRCLE} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -7,10 +7,12 @@
|
||||||
import { BaseText } from "@components/BaseText";
|
import { BaseText } from "@components/BaseText";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { LinkIcon, PencilIcon } from "@components/Icons";
|
import { LinkIcon, PencilIcon } from "@components/Icons";
|
||||||
|
import { Link } from "@components/Link";
|
||||||
import { listData } from "@equicordplugins/songSpotlight.desktop/lib/api";
|
import { listData } from "@equicordplugins/songSpotlight.desktop/lib/api";
|
||||||
import { useAuthorizationStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore";
|
import { useAuthorizationStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore";
|
||||||
import { useSongStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/SongStore";
|
import { useSongStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/SongStore";
|
||||||
import { cl } from "@equicordplugins/songSpotlight.desktop/lib/utils";
|
import { cl } from "@equicordplugins/songSpotlight.desktop/lib/utils";
|
||||||
|
import settings from "@equicordplugins/songSpotlight.desktop/settings";
|
||||||
import {
|
import {
|
||||||
CardClasses,
|
CardClasses,
|
||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
|
|
@ -20,40 +22,75 @@ import {
|
||||||
import { openSettingsModal } from "@equicordplugins/songSpotlight.desktop/ui/settings";
|
import { openSettingsModal } from "@equicordplugins/songSpotlight.desktop/ui/settings";
|
||||||
import { sid } from "@song-spotlight/api/util";
|
import { sid } from "@song-spotlight/api/util";
|
||||||
import { copyWithToast } from "@utils/discord";
|
import { copyWithToast } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/index";
|
||||||
import { ContextMenuApi, FluxDispatcher, Menu, React, Tooltip, useEffect, UserStore, useState } from "@webpack/common";
|
import {
|
||||||
|
ContextMenuApi,
|
||||||
|
FluxDispatcher,
|
||||||
|
Menu,
|
||||||
|
React,
|
||||||
|
Tooltip,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
UserStore,
|
||||||
|
useState,
|
||||||
|
} from "@webpack/common";
|
||||||
|
|
||||||
import Song from ".";
|
import Song from ".";
|
||||||
|
import CollapsedProfileSongs from "./CollapsedProfileSongs";
|
||||||
|
|
||||||
interface ProfileSongsProps {
|
export interface ProfileSongsProps {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
isSidebar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProfileSongs({ userId }: ProfileSongsProps) {
|
export default function ProfileSongs({ userId, isSidebar }: ProfileSongsProps) {
|
||||||
const [failed, setFailed] = useState(false);
|
const [failed, setFailed] = useState(false);
|
||||||
const { isAuthorized } = useAuthorizationStore();
|
const { isAuthorized } = useAuthorizationStore();
|
||||||
const { users } = useSongStore();
|
const { users } = useSongStore();
|
||||||
|
const { profileSongsLimit, collapseSongList } = settings.use();
|
||||||
|
|
||||||
const data = users[userId]?.data, at = users[userId]?.at;
|
const data = users[userId]?.data;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthorized() && !data) listData(userId).catch(() => setFailed(true));
|
if (isAuthorized() && !data) listData(userId).catch(() => setFailed(true));
|
||||||
}, [isAuthorized()]);
|
}, [isAuthorized()]);
|
||||||
|
|
||||||
|
const [clamped, setClamped] = useState(true);
|
||||||
|
const clampedData = useMemo(() => data && (clamped ? data.slice(0, profileSongsLimit) : data), [
|
||||||
|
data,
|
||||||
|
clamped,
|
||||||
|
profileSongsLimit,
|
||||||
|
]);
|
||||||
|
|
||||||
const owned = UserStore.getCurrentUser().id === userId;
|
const owned = UserStore.getCurrentUser().id === userId;
|
||||||
|
|
||||||
if (isAuthorized() && !data && !failed) {
|
const pending = isAuthorized() && !clampedData && !data && !failed;
|
||||||
|
if (!pending && !clampedData?.[0]) return null;
|
||||||
|
|
||||||
|
if (collapseSongList) {
|
||||||
|
return (
|
||||||
|
<CollapsedProfileSongs
|
||||||
|
data={data}
|
||||||
|
userId={userId}
|
||||||
|
isSidebar={isSidebar}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (pending) {
|
||||||
return <Spinner type={Spinner.Type.WANDERING_CUBES} />;
|
return <Spinner type={Spinner.Type.WANDERING_CUBES} />;
|
||||||
} else if (!data?.[0]) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classes(OverlayClasses.overlay, CardClasses.card, cl("songs-container"))}
|
className={classes(
|
||||||
|
isSidebar && OverlayClasses.overlay,
|
||||||
|
isSidebar && CardClasses.card,
|
||||||
|
cl("songs-container", isSidebar && "songs-container-sidebar"),
|
||||||
|
)}
|
||||||
key="song-spotlight-profile-songs"
|
key="song-spotlight-profile-songs"
|
||||||
>
|
>
|
||||||
<Flex justifyContent="space-between">
|
<Flex justifyContent="space-between">
|
||||||
<BaseText size="xs" weight="semibold" className={cl("header")}>Song Spotlight</BaseText>
|
<BaseText size="xs" weight={isSidebar ? "semibold" : "medium"} className={cl("header")}>
|
||||||
|
Song Spotlight
|
||||||
|
</BaseText>
|
||||||
<Tooltip text="More">
|
<Tooltip text="More">
|
||||||
{props => (
|
{props => (
|
||||||
<MoreHorizontalIcon
|
<MoreHorizontalIcon
|
||||||
|
|
@ -89,8 +126,17 @@ export default function ProfileSongs({ userId }: ProfileSongsProps) {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flexDirection="column" gap="6px">
|
<Flex flexDirection="column" gap="6px">
|
||||||
{data.map((song, i) => <Song owned={owned} song={song} index={i} key={sid(song)} />)}
|
{clampedData.map((song, i) => <Song owned={owned} song={song} index={i} key={sid(song)} />)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
{clamped && data.length > profileSongsLimit && (
|
||||||
|
<Link
|
||||||
|
onClick={() => setClamped(false)}
|
||||||
|
>
|
||||||
|
<BaseText size="sm" weight="medium" className={cl("sub")} style={{ textAlign: "center" }}>
|
||||||
|
Show {data.length - profileSongsLimit} more
|
||||||
|
</BaseText>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ function SongInfo({ owned, song, render, big }: SongInfoProps) {
|
||||||
icon={LinkIcon}
|
icon={LinkIcon}
|
||||||
action={() => copyWithToast(render.link)}
|
action={() => copyWithToast(render.link)}
|
||||||
/>
|
/>
|
||||||
{owned
|
{!owned
|
||||||
? (
|
? (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="steal-song"
|
id="steal-song"
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str
|
||||||
|
|
||||||
<SearchableSelect
|
<SearchableSelect
|
||||||
options={options}
|
options={options}
|
||||||
value={options.find(o => o.value === currentValue)}
|
value={options.find(o => o.value === currentValue)?.value}
|
||||||
placeholder={"Select a Timezone"}
|
placeholder={"Select a Timezone"}
|
||||||
maxVisibleItems={5}
|
maxVisibleItems={5}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
|
|
|
||||||
|
|
@ -794,7 +794,7 @@ function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user:
|
||||||
<HeadingSecondary>Select voice for {user.username}</HeadingSecondary>
|
<HeadingSecondary>Select voice for {user.username}</HeadingSecondary>
|
||||||
<SearchableSelect
|
<SearchableSelect
|
||||||
options={options}
|
options={options}
|
||||||
value={options.find(o => o.value === currentValue)}
|
value={options.find(o => o.value === currentValue)?.value}
|
||||||
placeholder="Select a voice"
|
placeholder="Select a voice"
|
||||||
maxVisibleItems={6}
|
maxVisibleItems={6}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\[\i\.\i\]:\i\}\),children:\[/,
|
match: /\[\i\.\i\]:\i\}\),children:\[/,
|
||||||
replace: "$&$self.renderButtons(arguments[0].user),"
|
replace: "$&$self.renderButtons(arguments[0]?.user),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ function LanguageSelectionModal(props: { modalProps: ModalProps, src: string; })
|
||||||
</BaseText>
|
</BaseText>
|
||||||
<SearchableSelect
|
<SearchableSelect
|
||||||
options={languageOptions}
|
options={languageOptions}
|
||||||
value={languageOptions.find(o => o.value === language)}
|
value={languageOptions.find(o => o.value === language)?.value}
|
||||||
onChange={setLanguage}
|
onChange={setLanguage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ if (!IS_VANILLA) {
|
||||||
constructor(options: BrowserWindowConstructorOptions) {
|
constructor(options: BrowserWindowConstructorOptions) {
|
||||||
if (options?.webPreferences?.preload && options.title) {
|
if (options?.webPreferences?.preload && options.title) {
|
||||||
const original = options.webPreferences.preload;
|
const original = options.webPreferences.preload;
|
||||||
|
const isMainWindow = options.title === "Discord";
|
||||||
options.webPreferences.preload = join(__dirname, "preload.js");
|
options.webPreferences.preload = join(__dirname, "preload.js");
|
||||||
options.webPreferences.sandbox = false;
|
options.webPreferences.sandbox = false;
|
||||||
// work around discord unloading when in background
|
// work around discord unloading when in background
|
||||||
|
|
@ -79,6 +80,8 @@ if (!IS_VANILLA) {
|
||||||
|
|
||||||
if (settings.frameless) {
|
if (settings.frameless) {
|
||||||
options.frame = false;
|
options.frame = false;
|
||||||
|
} else if (settings.mainWindowFrameless && isMainWindow) {
|
||||||
|
options.frame = false;
|
||||||
} else if (process.platform === "win32" && settings.winNativeTitleBar) {
|
} else if (process.platform === "win32" && settings.winNativeTitleBar) {
|
||||||
delete options.frame;
|
delete options.frame;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins, { PluginMeta } from "~plugins";
|
import Plugins, { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import { EquicordDonorModal, VencordDonorModal } from "./modals";
|
import { EquicordDonorModal, EquicordTranslatorModal, VencordDonorModal } from "./modals";
|
||||||
|
|
||||||
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64";
|
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64";
|
||||||
const EQUICORD_CONTRIBUTOR_BADGE = "https://equicord.org/assets/favicon.png";
|
const EQUICORD_CONTRIBUTOR_BADGE = "https://equicord.org/assets/favicon.png";
|
||||||
|
|
@ -253,7 +253,7 @@ export default definePlugin({
|
||||||
ContextMenuApi.openContextMenu(event, () => <BadgeContextMenu badge={badge} />);
|
ContextMenuApi.openContextMenu(event, () => <BadgeContextMenu badge={badge} />);
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
return EquicordDonorModal();
|
return badge.tooltip === "Equicord Translator" ? EquicordTranslatorModal() : EquicordDonorModal();
|
||||||
},
|
},
|
||||||
} satisfies ProfileBadge));
|
} satisfies ProfileBadge));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { Flex } from "@components/Flex";
|
||||||
import { HeadingPrimary } from "@components/Heading";
|
import { HeadingPrimary } from "@components/Heading";
|
||||||
import { Heart } from "@components/Heart";
|
import { Heart } from "@components/Heart";
|
||||||
import { Paragraph } from "@components/Paragraph";
|
import { Paragraph } from "@components/Paragraph";
|
||||||
import { DonateButton } from "@components/settings";
|
import { DonateButton, TranslateButton } from "@components/settings";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
|
|
||||||
|
|
@ -72,7 +72,6 @@ export function EquicordDonorModal() {
|
||||||
const modalKey = openModal(props => (
|
const modalKey = openModal(props => (
|
||||||
<ErrorBoundary noop onError={() => {
|
<ErrorBoundary noop onError={() => {
|
||||||
closeModal(modalKey);
|
closeModal(modalKey);
|
||||||
// Will get my own in the future
|
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/thororen1234");
|
VencordNative.native.openExternal("https://github.com/sponsors/thororen1234");
|
||||||
}}>
|
}}>
|
||||||
<ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
|
|
@ -123,3 +122,47 @@ export function EquicordDonorModal() {
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function EquicordTranslatorModal() {
|
||||||
|
const modalKey = openModal(props => (
|
||||||
|
<ErrorBoundary noop onError={() => {
|
||||||
|
closeModal(modalKey);
|
||||||
|
}}>
|
||||||
|
<ModalRoot {...props}>
|
||||||
|
<ModalHeader>
|
||||||
|
<HeadingPrimary
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center",
|
||||||
|
margin: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justifyContent="center" alignItems="center" gap="0.5em">
|
||||||
|
Equicord Translator
|
||||||
|
</Flex>
|
||||||
|
</HeadingPrimary>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalContent>
|
||||||
|
<Flex>
|
||||||
|
<img
|
||||||
|
className="vc-translate-modal-icon"
|
||||||
|
role="presentation"
|
||||||
|
src="https://badge.equicord.org/translator.png"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<div className="vc-translate-modal-paragraph">
|
||||||
|
<Paragraph>
|
||||||
|
Awarded to contributors who expand Equicord’s language support by translating content for the community.
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Flex justifyContent="center" style={{ width: "100%" }}>
|
||||||
|
<TranslateButton />
|
||||||
|
</Flex>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
</ErrorBoundary>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,16 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::USER_PROFILE_PRONOUNS}",
|
find: "#{intl::USER_PROFILE_PRONOUNS}",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(?<=shouldUnderlineOnHover:null.{0,150})children:(\i)/,
|
{
|
||||||
replace: "children:[...Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id}),$1]"
|
match: /(?<=,\i\]\}\)\),)null!=\i/,
|
||||||
}
|
replace: "($&||Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id})?.length)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<=shouldUnderlineOnHover:null.{0,300})children:(\i)/,
|
||||||
|
replace: "children:[...Vencord.Api.NicknameIcons._renderIcons({userId:arguments[0].user?.id}),$1]"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -152,14 +152,6 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
|
||||||
replacement: {
|
|
||||||
// Skip the check Discord performs to make sure the section being selected in the user settings context menu is valid
|
|
||||||
match: /null!=\(\i=Object.values\(\i\.\i\).{0,50}?&&(?=\(0,\i\.openUserSettings\)\(\i,\{section:\i)/,
|
|
||||||
replace: ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: ".buildLayout().map",
|
find: ".buildLayout().map",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ export default definePlugin({
|
||||||
find: ".MINI_PREVIEW,[",
|
find: ".MINI_PREVIEW,[",
|
||||||
predicate: () => settings.store.nameplates,
|
predicate: () => settings.store.nameplates,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /animate:\i,loop:.{0,15}===\i/,
|
match: /animate:\i,loop:(?:!0===\i(?:&&)?){2}/,
|
||||||
replace: "animate:true,loop:true"
|
replace: "animate:true,loop:true"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ export default definePlugin({
|
||||||
// Create the isBetterFolders and betterFoldersExpandedIds variables in the GuildsBar component
|
// Create the isBetterFolders and betterFoldersExpandedIds variables in the GuildsBar component
|
||||||
// Needed because we access this from a non-arrow closure so we can't use arguments[0]
|
// Needed because we access this from a non-arrow closure so we can't use arguments[0]
|
||||||
{
|
{
|
||||||
match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i,)/,
|
match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i)/,
|
||||||
replace: "$&,isBetterFolders,betterFoldersExpandedIds"
|
replace: "$&,isBetterFolders,betterFoldersExpandedIds"
|
||||||
},
|
},
|
||||||
// Export the isBetterFolders and betterFoldersExpandedIds variable to the Guild List component
|
// Export the isBetterFolders and betterFoldersExpandedIds variable to the Guild List component
|
||||||
|
|
|
||||||
|
|
@ -7,29 +7,16 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import { buildPluginMenuEntries, buildThemeMenuEntries } from "@equicordplugins/equicordToolbox/menu";
|
import { buildPluginMenuEntries, buildThemeMenuEntries } from "@equicordplugins/equicordToolbox/menu";
|
||||||
import SettingsPlugin from "@plugins/_core/settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { classNameFactory } from "@utils/css";
|
import { classNameFactory } from "@utils/css";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findCssClassesLazy } from "@webpack";
|
import { findCssClassesLazy } from "@webpack";
|
||||||
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
|
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
|
||||||
import type { HTMLAttributes, ReactElement } from "react";
|
import type { HTMLAttributes, ReactNode } from "react";
|
||||||
|
|
||||||
import fullHeightStyle from "./fullHeightContext.css?managed";
|
import fullHeightStyle from "./fullHeightContext.css?managed";
|
||||||
|
|
||||||
type SettingsEntry = {
|
|
||||||
ariaLabel?: string;
|
|
||||||
element?: any;
|
|
||||||
label?: string;
|
|
||||||
newIndicator?: any;
|
|
||||||
newIndicatorDismissibleContentTypes?: number[];
|
|
||||||
predicate?: () => boolean;
|
|
||||||
searchableTitles?: string[];
|
|
||||||
section: string,
|
|
||||||
tabPredicate?: () => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cl = classNameFactory("");
|
const cl = classNameFactory("");
|
||||||
const Classes = findCssClassesLazy("animating", "baseLayer", "bg", "layer", "layers");
|
const Classes = findCssClassesLazy("animating", "baseLayer", "bg", "layer", "layers");
|
||||||
|
|
||||||
|
|
@ -54,11 +41,6 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface TransformedSettingsEntry {
|
|
||||||
section: string;
|
|
||||||
items: SettingsEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LayerProps extends HTMLAttributes<HTMLDivElement> {
|
interface LayerProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
mode: "SHOWN" | "HIDDEN";
|
mode: "SHOWN" | "HIDDEN";
|
||||||
baseLayer?: boolean;
|
baseLayer?: boolean;
|
||||||
|
|
@ -169,15 +151,12 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{ // Settings cog context menu
|
{ // Settings cog context menu
|
||||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||||
|
predicate: () => settings.store.organizeMenu,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /=\[\];(\i)(?=\.forEach.{0,200}?"logout"===\i.{0,100}?(\i)\.get\(\i\))/,
|
match: /children:\[(\i),(?<=\1=.{0,30}\.openUserSettings.+?)/,
|
||||||
replace: "=$self.wrapMap([]);$self.transformSettingsEntries($1,$2)",
|
replace: "children:[$self.transformSettingsEntries($1),",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
|
|
||||||
replace: "$&case 'EquicordPlugins':return $self.buildPluginMenuEntries(true);case 'EquicordThemes':return $self.buildThemeMenuEntries();"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -200,72 +179,34 @@ export default definePlugin({
|
||||||
return <Layer {...props} />;
|
return <Layer {...props} />;
|
||||||
},
|
},
|
||||||
|
|
||||||
transformSettingsEntries(list: SettingsEntry[], keyMap: Map<string, string>) {
|
transformSettingsEntries(list) {
|
||||||
const items: any[] = [];
|
const items: ReactNode[] = [];
|
||||||
const equicordItems: SettingsEntry[] = [
|
|
||||||
{ section: "EquicordSettings", label: "Settings" },
|
|
||||||
{ section: "EquicordPlugins", label: "Plugins" },
|
|
||||||
{ section: "EquicordThemes", label: "Themes" },
|
|
||||||
{ section: "EquicordUpdater", label: "Updater" },
|
|
||||||
{ section: "EquicordChangelog", label: "Changelog" },
|
|
||||||
{ section: "EquicordCloud", label: "Cloud" },
|
|
||||||
{ section: "EquicordBackupAndRestore", label: "Backup & Restore" },
|
|
||||||
{ section: "EquicordPatchHelper", label: "Patch Helper" },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [section, key] of SettingsPlugin.settingsSectionMap) {
|
for (const item of list) {
|
||||||
const entry = SettingsPlugin.customEntries.find(e => key.endsWith(e.key));
|
const { key, props } = item;
|
||||||
if (entry) equicordItems.push({ section, label: entry.title });
|
if (!props) continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.store.organizeMenu) {
|
if (key === "equicord_plugins" || key === "equicord_themes") {
|
||||||
for (const item of list) {
|
const children = key === "equicord_plugins"
|
||||||
if (!item.label || item.predicate != null && !item.predicate()) continue;
|
? buildPluginMenuEntries()
|
||||||
|
: buildThemeMenuEntries();
|
||||||
|
|
||||||
if (item.section === "HEADER") {
|
items.push(
|
||||||
keyMap.set(item.label, item.label);
|
<Menu.MenuItem key={key} label={props.label} id={props.label} {...props}>
|
||||||
items.push({ section: item.label, items: [] });
|
{children}
|
||||||
} else if (item.section !== "DIVIDER" && keyMap.has(item.section)) {
|
</Menu.MenuItem>
|
||||||
items.at(-1)?.items.push(item);
|
);
|
||||||
}
|
} else if (key.endsWith("_section") && props.label) {
|
||||||
|
items.push(
|
||||||
|
<Menu.MenuItem key={key} label={props.label} id={props.label}>
|
||||||
|
{this.transformSettingsEntries(props.children)}
|
||||||
|
</Menu.MenuItem>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyMap.set("Equicord", "Equicord");
|
|
||||||
items.push({
|
|
||||||
section: "Equicord",
|
|
||||||
items: equicordItems
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
items.push(...list, ...equicordItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
},
|
|
||||||
|
|
||||||
wrapMap(toWrap: TransformedSettingsEntry[]) {
|
|
||||||
if (!settings.store.organizeMenu) return toWrap;
|
|
||||||
// @ts-expect-error
|
|
||||||
toWrap.map = function (render: (item: SettingsEntry) => ReactElement<any>) {
|
|
||||||
return this
|
|
||||||
.filter(a => a.items.length > 0)
|
|
||||||
.map(({ section, items }) => {
|
|
||||||
const children = items.map(render);
|
|
||||||
if (section) {
|
|
||||||
return (
|
|
||||||
<Menu.MenuItem
|
|
||||||
key={section}
|
|
||||||
id={section.replace(/\W/, "_")}
|
|
||||||
label={section}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Menu.MenuItem>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return toWrap;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Current user area, at bottom of channels/dm list
|
// Current user area, at bottom of channels/dm list
|
||||||
{
|
{
|
||||||
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
|
find: ".DISPLAY_NAME_STYLES_COACHMARK),",
|
||||||
replacement: [
|
replacement: [
|
||||||
// Use Decor avatar decoration hook
|
// Use Decor avatar decoration hook
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -87,14 +87,6 @@ export default definePlugin({
|
||||||
replace: (_, rest) => `${rest}onClick:()=>{}`
|
replace: (_, rest) => `${rest}onClick:()=>{}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Make the Favourites Server experiment allow favouriting DMs and threads
|
|
||||||
{
|
|
||||||
find: "useCanFavoriteChannel",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=isFavorite\(\i\.id\).{0,5})\i\.isThread\(\)/,
|
|
||||||
replace: "false",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Enable experiment embed on sent experiment links
|
// Enable experiment embed on sent experiment links
|
||||||
{
|
{
|
||||||
find: "Clear Treatment ",
|
find: "Clear Treatment ",
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ const settings = definePluginSettings({
|
||||||
description: "Whether to disable the embed permission check when sending fake emojis and stickers",
|
description: "Whether to disable the embed permission check when sending fake emojis and stickers",
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: false
|
default: false
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function hasPermission(channelId: string, permission: bigint) {
|
function hasPermission(channelId: string, permission: bigint) {
|
||||||
|
|
@ -376,7 +376,7 @@ export default definePlugin({
|
||||||
match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)\i\.available/g,
|
match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)\i\.available/g,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
get guildId() {
|
get guildId() {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "setDevtoolsCallbacks",
|
find: "setDevtoolsCallbacks",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\(null!=\i&&"0.0.0"===\i\.remoteApp\.getVersion\(\)\)/,
|
match: /if\(null!=\i&&"0.0.0"===\i\.app\.getVersion\(\)\)/,
|
||||||
replace: "if(true)"
|
replace: "if(true)"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '=>"IMAGE"===',
|
find: '"PLAINTEXT_PREVIEW":"OTHER"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i(?:\|\|("VISUAL_PLACEHOLDER"===\i))?;/,
|
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i(?:\|\|("VISUAL_PLACEHOLDER"===\i))?;/,
|
||||||
replace: (_, visualPlaceholderPred) => visualPlaceholderPred != null ? `=>${visualPlaceholderPred};` : "=>false;"
|
replace: (_, visualPlaceholderPred) => visualPlaceholderPred != null ? `=>${visualPlaceholderPred};` : "=>false;"
|
||||||
|
|
|
||||||
|
|
@ -1263,7 +1263,15 @@ export const EquicordDevs = Object.freeze({
|
||||||
Leon135: {
|
Leon135: {
|
||||||
name: "Leon135",
|
name: "Leon135",
|
||||||
id: 309275452231385088n,
|
id: 309275452231385088n,
|
||||||
}
|
},
|
||||||
|
bbpltergiest: {
|
||||||
|
name: "bbpltergiest",
|
||||||
|
id: 279448683672502274n,
|
||||||
|
},
|
||||||
|
mshl: {
|
||||||
|
name: "m.shl",
|
||||||
|
id: 1025245410224263258n,
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ waitFor(m => {
|
||||||
|
|
||||||
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
|
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
|
||||||
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
|
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
|
||||||
export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode("hasContentBackground", "oauth2_authorize"));
|
export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode("hasContentBackground", "nextStep", "onClose?.()"));
|
||||||
|
|
||||||
export const Animations = mapMangledModuleLazy(".assign({colorNames:", {
|
export const Animations = mapMangledModuleLazy(".assign({colorNames:", {
|
||||||
Transition: filters.componentByCode('["items","children"]', ",null,"),
|
Transition: filters.componentByCode('["items","children"]', ",null,"),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue