Compare commits

...

23 Commits

Author SHA1 Message Date
Vendicated
ee943c4284 Bump to v1.1.3 2023-03-28 19:09:48 +02:00
Vendicated
337b3709d6 types: Make ErrorBoundary.wrap explicitly return Function 2023-03-28 19:06:58 +02:00
Elliott Tallis
eb318c678f feat(ViewRaw): Improve View Raw action icon (#720)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-28 16:59:30 +00:00
Vendicated
081df6beb7 Fix SilentMessage/SilentTyping toggles showing in wrong sections
Closes #656
2023-03-28 18:56:12 +02:00
Vendicated
ab911b48b5 TypingTweaks: Make names open profile on click
Closes #718
2023-03-28 18:43:45 +02:00
Skye
8cb3491086 feat(uwuify): improve uwuification algorithm (#706)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-28 16:23:51 +00:00
Lewis Crichton
ee794d140f fix: no more theme box obliteration (#707)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-28 16:20:06 +00:00
Vendicated
a00542b61b MessageLinkEmbeds: Fix weird commas in title 2023-03-26 01:27:30 +01:00
Vendicated
041a13c9d3 DevCompanion: Always use original 2023-03-26 01:27:01 +01:00
Lewis Crichton
24aa90bd9c fix API plugins being force enabled unconditionally (#704)
* only enable dependencies if required

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

* added self

* Apply suggestions from code review

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

* fix patches

---------

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-25 03:54:20 +01:00
Syncx
0b4ae729a3 feat(plugin): SearchReply (#551)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-03-25 02:37:29 +00:00
TheKodeToad
b90392576e PronounDB: Add support for compact mode & clean up (#604) 2023-03-25 01:30:24 +00:00
Ven
e143260891 MessageLogger: Add context menu entry to remove history (#693) 2023-03-25 00:55:40 +00:00
Đỗ Văn Hoài Tuân
644c5c4faa Make Vencord title look consistent with Discord (#685)
closes (#649)
2023-03-25 00:42:18 +00:00
Ven
8d8cedd72c Also add Emote Cloner to Emote picker rightclick menu (#664) 2023-03-24 03:42:38 +01:00
Nuckyz
082ac62eda feat(FakeNitro): Transform fake emojis into real ones (#669) 2023-03-23 10:45:39 +00:00
Nuckyz
7923a790e6 Fix MessagePopoverAPI and any error Fake Nitro client theme bypass might have (#665) 2023-03-23 02:11:28 -03:00
Vendicated
1368c25824 ci: Auto generate plugin json 2023-03-23 04:37:53 +01:00
iwa
d0b3678ad6 fix messagelogger deleted styles (#642)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-22 04:37:04 +01:00
44 changed files with 1308 additions and 412 deletions

View File

@ -37,6 +37,9 @@ jobs:
- name: Build - name: Build
run: pnpm build --standalone run: pnpm build --standalone
- name: Generate plugin list
run: pnpm generatePluginJson dist/plugins.json
- name: Clean up obsolete files - name: Clean up obsolete files
run: | run: |
rm -rf dist/extension* Vencord.user.css rm -rf dist/extension* Vencord.user.css

View File

@ -36,7 +36,7 @@ jobs:
export PATH="$PWD/node_modules/.bin:$PATH" export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=$(which chromium-browser) export CHROMIUM_BIN=$(which chromium-browser)
esbuild test/generateReport.ts > dist/report.mjs esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY node dist/report.mjs >> $GITHUB_STEP_SUMMARY
env: env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}

20
CODE_OF_CONDUCT.md Normal file
View File

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

View File

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.1.2", "version": "1.1.3",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"keywords": [ ], "keywords": [ ],
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
@ -20,6 +20,7 @@
"scripts": { "scripts": {
"build": "node scripts/build/build.mjs", "build": "node scripts/build/build.mjs",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
"generatePluginJson": "tsx scripts/generatePluginList.ts",
"inject": "node scripts/runInstaller.mjs", "inject": "node scripts/runInstaller.mjs",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint-styles": "stylelint \"src/**/*.css\"", "lint-styles": "stylelint \"src/**/*.css\"",
@ -59,6 +60,7 @@
"standalone-electron-types": "^1.0.0", "standalone-electron-types": "^1.0.0",
"stylelint": "^14.16.1", "stylelint": "^14.16.1",
"stylelint-config-standard": "^29.0.0", "stylelint-config-standard": "^29.0.0",
"tsx": "^3.12.6",
"type-fest": "^3.5.3", "type-fest": "^3.5.3",
"typescript": "^4.9.4" "typescript": "^4.9.4"
}, },

290
pnpm-lock.yaml generated
View File

@ -35,6 +35,7 @@ specifiers:
standalone-electron-types: ^1.0.0 standalone-electron-types: ^1.0.0
stylelint: ^14.16.1 stylelint: ^14.16.1
stylelint-config-standard: ^29.0.0 stylelint-config-standard: ^29.0.0
tsx: ^3.12.6
type-fest: ^3.5.3 type-fest: ^3.5.3
typescript: ^4.9.4 typescript: ^4.9.4
@ -67,6 +68,7 @@ devDependencies:
standalone-electron-types: 1.0.0 standalone-electron-types: 1.0.0
stylelint: 14.16.1 stylelint: 14.16.1
stylelint-config-standard: 29.0.0_stylelint@14.16.1 stylelint-config-standard: 29.0.0_stylelint@14.16.1
tsx: 3.12.6
type-fest: 3.5.3 type-fest: 3.5.3
typescript: 4.9.4 typescript: 4.9.4
@ -104,6 +106,27 @@ packages:
postcss-selector-parser: 6.0.11 postcss-selector-parser: 6.0.11
dev: true dev: true
/@esbuild-kit/cjs-loader/2.4.2:
resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==}
dependencies:
'@esbuild-kit/core-utils': 3.1.0
get-tsconfig: 4.4.0
dev: true
/@esbuild-kit/core-utils/3.1.0:
resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==}
dependencies:
esbuild: 0.17.12
source-map-support: 0.5.21
dev: true
/@esbuild-kit/esm-loader/2.5.5:
resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==}
dependencies:
'@esbuild-kit/core-utils': 3.1.0
get-tsconfig: 4.4.0
dev: true
/@esbuild/android-arm/0.15.18: /@esbuild/android-arm/0.15.18:
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -113,6 +136,96 @@ packages:
dev: true dev: true
optional: true optional: true
/@esbuild/android-arm/0.17.12:
resolution: {integrity: sha512-E/sgkvwoIfj4aMAPL2e35VnUJspzVYl7+M1B2cqeubdBhADV4uPon0KCc8p2G+LqSJ6i8ocYPCqY3A4GGq0zkQ==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm64/0.17.12:
resolution: {integrity: sha512-WQ9p5oiXXYJ33F2EkE3r0FRDFVpEdcDiwNX3u7Xaibxfx6vQE0Sb8ytrfQsA5WO6kDn6mDfKLh6KrPBjvkk7xA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64/0.17.12:
resolution: {integrity: sha512-m4OsaCr5gT+se25rFPHKQXARMyAehHTQAz4XX1Vk3d27VtqiX0ALMBPoXZsGaB6JYryCLfgGwUslMqTfqeLU0w==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64/0.17.12:
resolution: {integrity: sha512-O3GCZghRIx+RAN0NDPhyyhRgwa19MoKlzGonIb5hgTj78krqp9XZbYCvFr9N1eUxg0ZQEpiiZ4QvsOQwBpP+lg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64/0.17.12:
resolution: {integrity: sha512-5D48jM3tW27h1qjaD9UNRuN+4v0zvksqZSPZqeSWggfMlsVdAhH3pwSfQIFJwcs9QJ9BRibPS4ViZgs3d2wsCA==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64/0.17.12:
resolution: {integrity: sha512-OWvHzmLNTdF1erSvrfoEBGlN94IE6vCEaGEkEH29uo/VoONqPnoDFfShi41Ew+yKimx4vrmmAJEGNoyyP+OgOQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64/0.17.12:
resolution: {integrity: sha512-A0Xg5CZv8MU9xh4a+7NUpi5VHBKh1RaGJKqjxe4KG87X+mTjDE6ZvlJqpWoeJxgfXHT7IMP9tDFu7IZ03OtJAw==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm/0.17.12:
resolution: {integrity: sha512-WsHyJ7b7vzHdJ1fv67Yf++2dz3D726oO3QCu8iNYik4fb5YuuReOI9OtA+n7Mk0xyQivNTPbl181s+5oZ38gyA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64/0.17.12:
resolution: {integrity: sha512-cK3AjkEc+8v8YG02hYLQIQlOznW+v9N+OI9BAFuyqkfQFR+DnDLhEM5N8QRxAUz99cJTo1rLNXqRrvY15gbQUg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32/0.17.12:
resolution: {integrity: sha512-jdOBXJqcgHlah/nYHnj3Hrnl9l63RjtQ4vn9+bohjQPI2QafASB5MtHAoEv0JQHVb/xYQTFOeuHnNYE1zF7tYw==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64/0.15.18: /@esbuild/linux-loong64/0.15.18:
resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -122,6 +235,114 @@ packages:
dev: true dev: true
optional: true optional: true
/@esbuild/linux-loong64/0.17.12:
resolution: {integrity: sha512-GTOEtj8h9qPKXCyiBBnHconSCV9LwFyx/gv3Phw0pa25qPYjVuuGZ4Dk14bGCfGX3qKF0+ceeQvwmtI+aYBbVA==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el/0.17.12:
resolution: {integrity: sha512-o8CIhfBwKcxmEENOH9RwmUejs5jFiNoDw7YgS0EJTF6kgPgcqLFjgoc5kDey5cMHRVCIWc6kK2ShUePOcc7RbA==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64/0.17.12:
resolution: {integrity: sha512-biMLH6NR/GR4z+ap0oJYb877LdBpGac8KfZoEnDiBKd7MD/xt8eaw1SFfYRUeMVx519kVkAOL2GExdFmYnZx3A==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64/0.17.12:
resolution: {integrity: sha512-jkphYUiO38wZGeWlfIBMB72auOllNA2sLfiZPGDtOBb1ELN8lmqBrlMiucgL8awBw1zBXN69PmZM6g4yTX84TA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x/0.17.12:
resolution: {integrity: sha512-j3ucLdeY9HBcvODhCY4b+Ds3hWGO8t+SAidtmWu/ukfLLG/oYDMaA+dnugTVAg5fnUOGNbIYL9TOjhWgQB8W5g==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64/0.17.12:
resolution: {integrity: sha512-uo5JL3cgaEGotaqSaJdRfFNSCUJOIliKLnDGWaVCgIKkHxwhYMm95pfMbWZ9l7GeW9kDg0tSxcy9NYdEtjwwmA==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64/0.17.12:
resolution: {integrity: sha512-DNdoRg8JX+gGsbqt2gPgkgb00mqOgOO27KnrWZtdABl6yWTST30aibGJ6geBq3WM2TIeW6COs5AScnC7GwtGPg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64/0.17.12:
resolution: {integrity: sha512-aVsENlr7B64w8I1lhHShND5o8cW6sB9n9MUtLumFlPhG3elhNWtE7M1TFpj3m7lT3sKQUMkGFjTQBrvDDO1YWA==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64/0.17.12:
resolution: {integrity: sha512-qbHGVQdKSwi0JQJuZznS4SyY27tYXYF0mrgthbxXrZI3AHKuRvU+Eqbg/F0rmLDpW/jkIZBlCO1XfHUBMNJ1pg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64/0.17.12:
resolution: {integrity: sha512-zsCp8Ql+96xXTVTmm6ffvoTSZSV2B/LzzkUXAY33F/76EajNw1m+jZ9zPfNJlJ3Rh4EzOszNDHsmG/fZOhtqDg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32/0.17.12:
resolution: {integrity: sha512-FfrFjR4id7wcFYOdqbDfDET3tjxCozUgbqdkOABsSFzoZGFC92UK7mg4JKRc/B3NNEf1s2WHxJ7VfTdVDPN3ng==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64/0.17.12:
resolution: {integrity: sha512-JOOxw49BVZx2/5tW3FqkdjSD/5gXYeVGPDcB0lvap0gLQshkh1Nyel1QazC+wNxus3xPlsYAgqU1BUmrmCvWtw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@eslint/eslintrc/1.3.3: /@eslint/eslintrc/1.3.3:
resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -563,6 +784,10 @@ packages:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
dev: true dev: true
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
/buffer/5.7.1: /buffer/5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
dependencies: dependencies:
@ -1047,6 +1272,36 @@ packages:
esbuild-windows-arm64: 0.15.18 esbuild-windows-arm64: 0.15.18
dev: true dev: true
/esbuild/0.17.12:
resolution: {integrity: sha512-bX/zHl7Gn2CpQwcMtRogTTBf9l1nl+H6R8nUbjk+RuKqAE3+8FDulLA+pHvX7aA7Xe07Iwa+CWvy9I8Y2qqPKQ==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.17.12
'@esbuild/android-arm64': 0.17.12
'@esbuild/android-x64': 0.17.12
'@esbuild/darwin-arm64': 0.17.12
'@esbuild/darwin-x64': 0.17.12
'@esbuild/freebsd-arm64': 0.17.12
'@esbuild/freebsd-x64': 0.17.12
'@esbuild/linux-arm': 0.17.12
'@esbuild/linux-arm64': 0.17.12
'@esbuild/linux-ia32': 0.17.12
'@esbuild/linux-loong64': 0.17.12
'@esbuild/linux-mips64el': 0.17.12
'@esbuild/linux-ppc64': 0.17.12
'@esbuild/linux-riscv64': 0.17.12
'@esbuild/linux-s390x': 0.17.12
'@esbuild/linux-x64': 0.17.12
'@esbuild/netbsd-x64': 0.17.12
'@esbuild/openbsd-x64': 0.17.12
'@esbuild/sunos-x64': 0.17.12
'@esbuild/win32-arm64': 0.17.12
'@esbuild/win32-ia32': 0.17.12
'@esbuild/win32-x64': 0.17.12
dev: true
/escape-string-regexp/1.0.5: /escape-string-regexp/1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -1400,6 +1655,14 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1: /function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true dev: true
@ -1411,6 +1674,10 @@ packages:
pump: 3.0.0 pump: 3.0.0
dev: true dev: true
/get-tsconfig/4.4.0:
resolution: {integrity: sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==}
dev: true
/get-value/2.0.6: /get-value/2.0.6:
resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2467,6 +2734,13 @@ packages:
urix: 0.1.0 urix: 0.1.0
dev: true dev: true
/source-map-support/0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
dev: true
/source-map-url/0.4.1: /source-map-url/0.4.1:
resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}
deprecated: See https://github.com/lydell/source-map-url#deprecated deprecated: See https://github.com/lydell/source-map-url#deprecated
@ -2477,6 +2751,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
dev: true
/spdx-correct/3.1.1: /spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
dependencies: dependencies:
@ -2739,6 +3018,17 @@ packages:
typescript: 4.9.4 typescript: 4.9.4
dev: true dev: true
/tsx/3.12.6:
resolution: {integrity: sha512-q93WgS3lBdHlPgS0h1i+87Pt6n9K/qULIMNYZo07nSeu2z5QE2CellcAZfofVXBo2tQg9av2ZcRMQ2S2i5oadQ==}
hasBin: true
dependencies:
'@esbuild-kit/cjs-loader': 2.4.2
'@esbuild-kit/core-utils': 3.1.0
'@esbuild-kit/esm-loader': 2.5.5
optionalDependencies:
fsevents: 2.3.2
dev: true
/type-check/0.4.0: /type-check/0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}

View File

@ -0,0 +1,191 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs";
import { access, readFile } from "fs/promises";
import { join } from "path";
import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript";
interface Dev {
name: string;
id: string;
}
interface PluginData {
name: string;
description: string;
authors: Dev[];
dependencies: string[];
hasPatches: boolean;
hasCommands: boolean;
required: boolean;
enabledByDefault: boolean;
target: "desktop" | "web" | "dev";
}
const devs = {} as Record<string, Dev>;
function getName(node: NamedDeclaration) {
return node.name && isIdentifier(node.name) ? node.name.text : undefined;
}
function hasName(node: NamedDeclaration, name: string) {
return getName(node) === name;
}
function getObjectProp(node: ObjectLiteralExpression, name: string) {
const prop = node.properties.find(p => hasName(p, name));
if (prop && isPropertyAssignment(prop)) return prop.initializer;
return prop;
}
function parseDevs() {
const file = createSourceFile("constants.ts", readFileSync("src/utils/constants.ts", "utf8"), ScriptTarget.Latest);
for (const child of file.getChildAt(0).getChildren()) {
if (!isVariableStatement(child)) continue;
const devsDeclaration = child.declarationList.declarations.find(d => hasName(d, "Devs"));
if (!devsDeclaration?.initializer || !isCallExpression(devsDeclaration.initializer)) continue;
const value = devsDeclaration.initializer.arguments[0];
if (!isObjectLiteralExpression(value)) return;
for (const prop of value.properties) {
const name = (prop.name as Identifier).text;
const value = isPropertyAssignment(prop) ? prop.initializer : prop;
if (!isObjectLiteralExpression(value)) throw new Error(`Failed to parse devs: ${name} is not an object literal`);
devs[name] = {
name: (getObjectProp(value, "name") as StringLiteral).text,
id: (getObjectProp(value, "id") as BigIntLiteral).text.slice(0, -1)
};
}
return;
}
throw new Error("Could not find Devs constant");
}
async function parseFile(fileName: string) {
const file = createSourceFile(fileName, await readFile(fileName, "utf8"), ScriptTarget.Latest);
const fail = (reason: string) => {
return new Error(`Invalid plugin ${fileName}, because ${reason}`);
};
for (const node of file.getChildAt(0).getChildren()) {
if (!isExportAssignment(node) || !isCallExpression(node.expression)) continue;
const call = node.expression;
if (!isIdentifier(call.expression) || call.expression.text !== "definePlugin") continue;
const pluginObj = node.expression.arguments[0];
if (!isObjectLiteralExpression(pluginObj)) throw fail("no object literal passed to definePlugin");
const data = {
hasPatches: false,
hasCommands: false,
enabledByDefault: false,
required: false,
} as PluginData;
for (const prop of pluginObj.properties) {
const key = getName(prop);
const value = isPropertyAssignment(prop) ? prop.initializer : prop;
switch (key) {
case "name":
case "description":
if (!isStringLiteral(value)) throw fail(`${key} is not a string literal`);
data[key] = value.text;
break;
case "patches":
data.hasPatches = true;
break;
case "commands":
data.hasCommands = true;
break;
case "authors":
if (!isArrayLiteralExpression(value)) throw fail("authors is not an array literal");
data.authors = value.elements.map(e => {
if (!isPropertyAccessExpression(e)) throw fail("authors array contains non-property access expressions");
return devs[getName(e)!];
});
break;
case "dependencies":
if (!isArrayLiteralExpression(value)) throw fail("dependencies is not an array literal");
const { elements } = value;
if (elements.some(e => !isStringLiteral(e))) throw fail("dependencies array contains non-string elements");
data.dependencies = (elements as NodeArray<StringLiteral>).map(e => e.text);
break;
case "required":
case "enabledByDefault":
data[key] = value.kind === SyntaxKind.TrueKeyword;
if (!data[key] && value.kind !== SyntaxKind.FalseKeyword) throw fail(`${key} is not a boolean literal`);
break;
}
}
if (!data.name || !data.description || !data.authors) throw fail("name, description or authors are missing");
const fileBits = fileName.split(".");
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1)!)) {
const mod = fileBits.at(-2)!;
if (!["web", "desktop", "dev"].includes(mod)) throw fail(`invalid target ${fileBits.at(-2)}`);
data.target = mod as any;
}
return data;
}
throw fail("no default export called 'definePlugin' found");
}
async function getEntryPoint(dirent: Dirent) {
const base = join("./src/plugins", dirent.name);
if (!dirent.isDirectory()) return base;
for (const name of ["index.ts", "index.tsx"]) {
const full = join(base, name);
try {
await access(full);
return full;
} catch { }
}
throw new Error(`${dirent.name}: Couldn't find entry point`);
}
(async () => {
parseDevs();
const plugins = readdirSync("./src/plugins", { withFileTypes: true }).filter(d => d.name !== "index.ts");
const promises = plugins.map(async dirent => parseFile(await getEntryPoint(dirent)));
const data = JSON.stringify(await Promise.all(promises));
if (process.argv.length > 2) {
writeFileSync(process.argv[2], data);
} else {
console.log(data);
}
})();

View File

@ -122,6 +122,8 @@ export function _patchContextMenu(props: ContextMenuProps) {
props.contextMenuApiArguments ??= []; props.contextMenuApiArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId); const contextMenuPatches = navPatches.get(props.navId);
if (!Array.isArray(props.children)) props.children = [props.children];
if (contextMenuPatches) { if (contextMenuPatches) {
for (const patch of contextMenuPatches) { for (const patch of contextMenuPatches) {
try { try {

View File

@ -19,6 +19,7 @@
import Logger from "@utils/Logger"; import Logger from "@utils/Logger";
import { MessageStore } from "@webpack/common"; import { MessageStore } from "@webpack/common";
import type { Channel, Message } from "discord-types/general"; import type { Channel, Message } from "discord-types/general";
import type { Promisable } from "type-fest";
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890"); const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
@ -41,16 +42,16 @@ export interface MessageExtra {
stickerIds?: string[]; stickerIds?: string[];
} }
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => void | { cancel: boolean; }; export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => void; export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void>;
const sendListeners = new Set<SendListener>(); const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>(); const editListeners = new Set<EditListener>();
export function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra) { export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra) {
for (const listener of sendListeners) { for (const listener of sendListeners) {
try { try {
const result = listener(channelId, messageObj, extra); const result = await listener(channelId, messageObj, extra);
if (result && result.cancel === true) { if (result && result.cancel === true) {
return true; return true;
} }
@ -61,10 +62,10 @@ export function _handlePreSend(channelId: string, messageObj: MessageObject, ext
return false; return false;
} }
export function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) { export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
for (const listener of editListeners) { for (const listener of editListeners) {
try { try {
listener(channelId, messageId, messageObj); await listener(channelId, messageId, messageObj);
} catch (e) { } catch (e) {
MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e); MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e);
} }

View File

@ -105,7 +105,7 @@ const ErrorBoundary = LazyComponent(() => {
}; };
}) as }) as
React.ComponentType<React.PropsWithChildren<Props>> & { React.ComponentType<React.PropsWithChildren<Props>> & {
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.ComponentType<T>; wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.FunctionComponent<T>;
}; };
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => ( ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (

View File

@ -116,13 +116,9 @@ export default ErrorBoundary.wrap(function () {
</Card> </Card>
<Forms.FormTitle tag="h5">Themes</Forms.FormTitle> <Forms.FormTitle tag="h5">Themes</Forms.FormTitle>
<TextArea <TextArea
style={{
padding: ".5em",
border: "1px solid var(--background-modifier-accent)"
}}
value={themeText} value={themeText}
onChange={e => setThemeText(e.currentTarget.value)} onChange={e => setThemeText(e.currentTarget.value)}
className={TextAreaProps.textarea} className={`${TextAreaProps.textarea} vc-settings-theme-links`}
placeholder="Theme Links" placeholder="Theme Links"
spellCheck={false} spellCheck={false}
onBlur={onBlur} onBlur={onBlur}

View File

@ -59,7 +59,7 @@ function Settings(props: SettingsProps) {
const CurrentTab = SettingsTabs[tab]?.component; const CurrentTab = SettingsTabs[tab]?.component;
return <Forms.FormSection> return <Forms.FormSection>
<Text variant="heading-md/normal" tag="h2">Vencord Settings</Text> <Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">Vencord Settings</Text>
<TabBar <TabBar
type="top" type="top"

View File

@ -38,3 +38,11 @@
color: var(--info-warning-text); color: var(--info-warning-text);
margin-top: 0; margin-top: 0;
} }
.vc-settings-theme-links {
/* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */
display: inline-block !important;
color: var(--text-normal) !important;
padding: 0.5em;
border: 1px solid var(--background-modifier-accent);
}

View File

@ -32,10 +32,10 @@ export default definePlugin({
} }
}, },
{ {
find: "\"github.com\":new RegExp(\"\\\\/releases\\\\S*\\\\/download\"),", find: '"7z","ade","adp"',
replacement: { replacement: {
match: /const o=JSON.parse\('\[.+?'\)/, match: /JSON\.parse\('\[.+?'\)/,
replace: "const o=[]" replace: "[]"
} }
} }
] ]

View File

@ -22,22 +22,22 @@ import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "MessageEventsAPI", name: "MessageEventsAPI",
description: "Api required by anything using message events.", description: "Api required by anything using message events.",
authors: [Devs.Arjix], authors: [Devs.Arjix, Devs.hunt],
patches: [ patches: [
{ {
find: "sendMessage:function", find: '"MessageActionCreators"',
replacement: [{ replacement: [{
match: /(?<=_sendMessage:function\([^)]+\)){/, match: /_sendMessage:(function\([^)]+\)){/,
replace: "{if(Vencord.Api.MessageEvents._handlePreSend(...arguments)){return;};" replace: "_sendMessage:async $1{if(await Vencord.Api.MessageEvents._handlePreSend(...arguments))return;"
}, { }, {
match: /(?<=\beditMessage:function\([^)]+\)){/, match: /\beditMessage:(function\([^)]+\)){/,
replace: "{Vencord.Api.MessageEvents._handlePreEdit(...arguments);" replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
}] }]
}, },
{ {
find: '("interactionUsernameProfile', find: '("interactionUsernameProfile',
replacement: { replacement: {
match: /var \w=(\w)\.id,\w=(\w)\.id;return .{1,2}\.useCallback\(\(?function\((.{1,2})\){/, match: /var \i=(\i)\.id,\i=(\i)\.id;return \i\.useCallback\(\(?function\((\i)\){/,
replace: (m, message, channel, event) => replace: (m, message, channel, event) =>
// the message param is shadowed by the event param, so need to alias them // the message param is shadowed by the event param, so need to alias them
`var _msg=${message},_chan=${channel};${m}Vencord.Api.MessageEvents._handleClick(_msg, _chan, ${event});` `var _msg=${message},_chan=${channel};${m}Vencord.Api.MessageEvents._handleClick(_msg, _chan, ${event});`

View File

@ -27,11 +27,11 @@ export default definePlugin({
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL", find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
replacement: { replacement: {
// foo && !bar ? createElement(blah,...makeElement(addReactionData)) // foo && !bar ? createElement(blah,...makeElement(addReactionData))
match: /(\i&&!\i)\?\(0,\i\.jsxs?\)\(.{0,200}renderPopout:.{0,300}?(\i)\(.{3,20}\{key:"add-reaction".+?\}/, match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderPopout:.{0,300}?(\i)\(.{3,20}\{key:"add-reaction".+?\}/,
replace: (m, bools, makeElement) => { replace: (m, makeElement) => {
const msg = m.match(/message:(.{1,3}),/)?.[1]; const msg = m.match(/message:(.{1,3}),/)?.[1];
if (!msg) throw new Error("Could not find message variable"); if (!msg) throw new Error("Could not find message variable");
return `...(${bools}?Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}):[]),${m}`; return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`;
} }
} }
}], }],

View File

@ -37,11 +37,11 @@ export default definePlugin({
}, },
}, },
{ {
find: '"username"===', find: '"dot"===',
all: true, all: true,
predicate: () => Settings.plugins.BetterRoleDot.bothStyles, predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
replacement: { replacement: {
match: /"(?:username|dot)"===\w(?!\.\w)/g, match: /"(?:username|dot)"===\i(?!\.\i)/g,
replace: "true", replace: "true",
}, },
}, },

View File

@ -215,7 +215,8 @@ async function setRpc(disable?: boolean) {
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "LOCAL_ACTIVITY_UPDATE", type: "LOCAL_ACTIVITY_UPDATE",
activity: !disable ? activity : {} activity: !disable ? activity : null,
socketId: "CustomRPC",
}); });
} }

View File

@ -158,7 +158,8 @@ function initWs(isManual = false) {
if (keys.length !== 1) if (keys.length !== 1)
return reply("Expected exactly one 'find' matches, found " + keys.length); return reply("Expected exactly one 'find' matches, found " + keys.length);
let src = String(candidates[keys[0]]); const mod = candidates[keys[0]];
let src = String(mod.original ?? mod);
let i = 0; let i = 0;

View File

@ -17,7 +17,6 @@
*/ */
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/settings";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import Logger from "@utils/Logger";
@ -176,74 +175,78 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str
); );
} }
function buildMenuItem(id: string, name: string, isAnimated: boolean) {
return (
<Menu.MenuItem
id="emote-cloner"
key="emote-cloner"
label="Clone Emote"
action={() =>
openModal(modalProps => (
<ModalRoot {...modalProps}>
<ModalHeader>
<img
role="presentation"
aria-hidden
src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${id}.${isAnimated ? "gif" : "png"}`}
alt=""
height={24}
width={24}
style={{ marginRight: "0.5em" }}
/>
<Forms.FormText>Clone {name}</Forms.FormText>
</ModalHeader>
<ModalContent>
<CloneModal id={id} name={name} isAnimated={isAnimated} />
</ModalContent>
</ModalRoot>
))
}
/>
);
}
function isGifUrl(url: string) {
return new URL(url).pathname.endsWith(".gif");
}
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
if (!props) return; const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
const { favoriteableId, emoteClonerDataAlt, itemHref, itemSrc, favoriteableType } = props;
if (!emoteClonerDataAlt || favoriteableType !== "emoji") return; if (!favoriteableId || favoriteableType !== "emoji") return;
const name = emoteClonerDataAlt.match(/:(.*)(?:~\d+)?:/)?.[1]; const match = props.message.content.match(RegExp(`<a?:(\\w+)(?:~\\d+)?:${favoriteableId}>|https://cdn\\.discordapp\\.com/emojis/${favoriteableId}\\.`));
if (!name || !favoriteableId) return; if (!match) return;
const name = match[1] ?? "FakeNitroEmoji";
const src = itemHref ?? itemSrc;
const isAnimated = new URL(src).pathname.endsWith(".gif");
const group = findGroupChildrenByChildId("copy-link", children); const group = findGroupChildrenByChildId("copy-link", children);
if (group && !group.some(child => child?.props?.id === "emote-cloner")) { if (group && !group.some(child => child?.props?.id === "emote-cloner"))
group.push(( group.push(buildMenuItem(favoriteableId, name, isGifUrl(itemHref ?? itemSrc)));
<Menu.MenuItem };
id="emote-cloner"
key="emote-cloner" const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => {
label="Clone" const { id, name, type } = props?.target?.dataset ?? {};
action={() => if (!id || !name || type !== "emoji") return;
openModal(modalProps => (
<ModalRoot {...modalProps}> const firstChild = props.target.firstChild as HTMLImageElement;
<ModalHeader>
<img if (!children.some(c => c?.props?.id === "emote-cloner"))
role="presentation" children.push(buildMenuItem(id, name, firstChild && isGifUrl(firstChild.src)));
aria-hidden
src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${favoriteableId}.${isAnimated ? "gif" : "png"}`}
alt=""
height={24}
width={24}
style={{ marginRight: "0.5em" }}
/>
<Forms.FormText>Clone {name}</Forms.FormText>
</ModalHeader>
<ModalContent>
<CloneModal id={favoriteableId} name={name} isAnimated={isAnimated} />
</ModalContent>
</ModalRoot>
))
}
>
</Menu.MenuItem>
));
}
}; };
migratePluginSettings("EmoteCloner", "EmoteYoink");
export default definePlugin({ export default definePlugin({
name: "EmoteCloner", name: "EmoteCloner",
description: "Adds a Clone context menu item to emotes to clone them your own server", description: "Adds a Clone context menu item to emotes to clone them your own server",
authors: [Devs.Ven, Devs.Nuckyz], authors: [Devs.Ven, Devs.Nuckyz],
dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"], dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"],
patches: [
{
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
replacement: {
match: /favoriteableType:\i,(?<=(\i)\.getAttribute\("data-type"\).+?)/,
replace: (m, target) => `${m}emoteClonerDataAlt:${target}.alt,`
}
}
],
start() { start() {
addContextMenuPatch("message", messageContextMenuPatch); addContextMenuPatch("message", messageContextMenuPatch);
addContextMenuPatch("expression-picker", expressionPickerPatch);
}, },
stop() { stop() {
removeContextMenuPatch("message", messageContextMenuPatch); removeContextMenuPatch("message", messageContextMenuPatch);
removeContextMenuPatch("expression-picker", expressionPickerPatch);
} }
}); });

View File

@ -21,6 +21,7 @@ import { migratePluginSettings, Settings } from "@api/settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies"; import { ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/proxyLazy";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, PermissionStore, UserStore } from "@webpack/common"; import { ChannelStore, FluxDispatcher, PermissionStore, UserStore } from "@webpack/common";
@ -28,7 +29,21 @@ import { ChannelStore, FluxDispatcher, PermissionStore, UserStore } from "@webpa
const DRAFT_TYPE = 0; const DRAFT_TYPE = 0;
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR"); const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
const ProtoPreloadedUserSettings = findLazy(m => m.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings"); const PreloadedUserSettingsProtoHandler = findLazy(m => m.ProtoClass?.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings");
const ReaderFactory = findByPropsLazy("readerFactory");
function searchProtoClass(localName: string, parentProtoClass: any) {
if (!parentProtoClass) return;
const field = parentProtoClass.fields.find(field => field.localName === localName);
if (!field) return;
const getter: any = Object.values(field).find(value => typeof value === "function");
return getter?.();
}
const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass));
const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto));
const USE_EXTERNAL_EMOJIS = 1n << 18n; const USE_EXTERNAL_EMOJIS = 1n << 18n;
const USE_EXTERNAL_STICKERS = 1n << 37n; const USE_EXTERNAL_STICKERS = 1n << 37n;
@ -167,8 +182,38 @@ export default definePlugin({
{ {
find: "updateTheme:function", find: "updateTheme:function",
replacement: { replacement: {
match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\)/, match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/,
replace: (_, rest, backgroundGradientPresetId, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme});` replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
}
},
{
find: 'jumboable?"jumbo":"default"',
predicate: () => Settings.plugins.FakeNitro.transformEmojis === true,
replacement: {
match: /jumboable\?"jumbo":"default",emojiId.+?}}\)},(?<=(\i)=function\(\i\){var \i=\i\.node.+?)/,
replace: (m, component) => `${m}fakeNitroEmojiComponentExport=($self.EmojiComponent=${component},void 0),`
}
},
{
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
predicate: () => Settings.plugins.FakeNitro.transformEmojis === true,
replacement: [
{
match: /1!==(\i)\.length\|\|1!==\i\.length/,
replace: (m, content) => `${m}||${content}[0].target?.startsWith("https://cdn.discordapp.com/emojis/")`
},
{
match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/,
replace: (_, content) => `${content}=$self.patchFakeNitroEmojis(${content});`
}
]
},
{
find: "renderEmbeds=function",
predicate: () => Settings.plugins.FakeNitro.transformEmojis === true,
replacement: {
match: /renderEmbeds=function\(\i\){.+?embeds\.map\(\(function\((\i)\){/,
replace: (m, embed) => `${m}if(${embed}.url?.startsWith("https://cdn.discordapp.com/emojis/"))return null;`
} }
} }
], ],
@ -186,6 +231,12 @@ export default definePlugin({
default: 48, default: 48,
markers: [32, 48, 64, 128, 160, 256, 512], markers: [32, 48, 64, 128, 160, 256, 512],
}, },
transformEmojis: {
description: "Whether to transform fake emojis into real ones",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true,
},
enableStickerBypass: { enableStickerBypass: {
description: "Allow sending fake stickers", description: "Allow sending fake stickers",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -219,48 +270,56 @@ export default definePlugin({
}, },
handleProtoChange(proto: any, user: any) { handleProtoChange(proto: any, user: any) {
const premiumType: number = user?.premium_type ?? UserStore.getCurrentUser()?.premiumType ?? 0; if ((!proto.appearance && !AppearanceSettingsProto) || !UserSettingsProtoStore) return;
if (premiumType === 0) { const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0;
const appearanceDummyProto = ProtoPreloadedUserSettings.create({
appearance: {}
});
proto.appearance ??= appearanceDummyProto.appearance; if (premiumType !== 2) {
proto.appearance ??= AppearanceSettingsProto.create();
if (UserSettingsProtoStore.settings.appearance?.theme != null) { if (UserSettingsProtoStore.settings.appearance?.theme != null) {
proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme; proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme;
} }
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) { if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null && ClientThemeSettingsProto) {
const clientThemeSettingsDummyProto = ProtoPreloadedUserSettings.create({ const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
appearance: { backgroundGradientPresetId: {
clientThemeSettings: { value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
backgroundGradientPresetId: {
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
}
}
} }
}); });
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto.appearance.clientThemeSettings; proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto;
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.appearance.clientThemeSettings.backgroundGradientPresetId; proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
} }
} }
}, },
handleGradientThemeSelect(backgroundGradientPresetId: number | undefined, theme: number) { handleGradientThemeSelect(backgroundGradientPresetId: number | undefined, theme: number, original: () => void) {
const proto = ProtoPreloadedUserSettings.create({ const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
appearance: { if (premiumType === 2 || backgroundGradientPresetId == null) return original();
theme,
clientThemeSettings: { if (!AppearanceSettingsProto || !ClientThemeSettingsProto || !ReaderFactory) return;
backgroundGradientPresetId: backgroundGradientPresetId != null ? {
value: backgroundGradientPresetId const currentAppearanceProto = PreloadedUserSettingsProtoHandler.getCurrentValue().appearance;
} : void 0
} const newAppearanceProto = currentAppearanceProto != null
? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory)
: AppearanceSettingsProto.create();
newAppearanceProto.theme = theme;
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
backgroundGradientPresetId: {
value: backgroundGradientPresetId
} }
}); });
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto;
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create();
proto.appearance = newAppearanceProto;
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "USER_SETTINGS_PROTO_UPDATE", type: "USER_SETTINGS_PROTO_UPDATE",
local: true, local: true,
@ -272,6 +331,39 @@ export default definePlugin({
}); });
}, },
EmojiComponent: null as any,
patchFakeNitroEmojis(content: Array<any>) {
if (!this.EmojiComponent) return content;
const newContent: Array<any> = [];
for (const element of content) {
if (element.props?.trusted == null) {
newContent.push(element);
continue;
}
const fakeNitroMatch = element.props.href.match(/https:\/\/cdn\.discordapp\.com\/emojis\/(\d+?)\.(png|webp|gif).+?(?=\s|$)/);
if (!fakeNitroMatch) {
newContent.push(element);
continue;
}
newContent.push((
<this.EmojiComponent node={{
type: "customEmoji",
jumboable: content.length === 1,
animated: fakeNitroMatch[2] === "gif",
name: ":FakeNitroEmoji:",
emojiId: fakeNitroMatch[1]
}} />
));
}
return newContent;
},
hasPermissionToUseExternalEmojis(channelId: string) { hasPermissionToUseExternalEmojis(channelId: string) {
const channel = ChannelStore.getChannel(channelId); const channel = ChannelStore.getChannel(channelId);

View File

@ -43,8 +43,11 @@ export function isPluginEnabled(p: string) {
const pluginsValues = Object.values(Plugins); const pluginsValues = Object.values(Plugins);
// First roundtrip to mark and force enable dependencies // First roundtrip to mark and force enable dependencies (only for enabled plugins)
for (const p of pluginsValues) { //
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
// goes for the top level and their children, but for now this works okay with the current API plugins
for (const p of pluginsValues) if (settings[p.name]?.enabled) {
p.dependencies?.forEach(d => { p.dependencies?.forEach(d => {
const dep = Plugins[d]; const dep = Plugins[d];
if (dep) { if (dep) {

View File

@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/settings"; import { definePluginSettings } from "@api/settings";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { FluxDispatcher, Forms } from "@webpack/common"; import { FluxDispatcher, Forms } from "@webpack/common";
@ -30,6 +31,12 @@ interface ActivityAssets {
small_text?: string; small_text?: string;
} }
interface ActivityButton {
label: string;
url: string;
}
interface Activity { interface Activity {
state: string; state: string;
details?: string; details?: string;
@ -66,6 +73,9 @@ enum ActivityFlag {
} }
const applicationId = "1043533871037284423"; const applicationId = "1043533871037284423";
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
const logger = new Logger("LastFMRichPresence");
const presenceStore = findByPropsLazy("getLocalPresence"); const presenceStore = findByPropsLazy("getLocalPresence");
const assetManager = mapMangledModuleLazy( const assetManager = mapMangledModuleLazy(
@ -79,14 +89,64 @@ async function getApplicationAsset(key: string): Promise<string> {
return (await assetManager.getAsset(applicationId, [key, undefined]))[0]; return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
} }
function setActivity(activity?: Activity) { function setActivity(activity: Activity | null) {
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: activity }); FluxDispatcher.dispatch({
type: "LOCAL_ACTIVITY_UPDATE",
activity,
socketId: "LastFM",
});
} }
const settings = definePluginSettings({
username: {
description: "last.fm username",
type: OptionType.STRING,
},
apiKey: {
description: "last.fm api key",
type: OptionType.STRING,
},
shareUsername: {
description: "show link to last.fm profile",
type: OptionType.BOOLEAN,
default: false,
},
hideWithSpotify: {
description: "hide last.fm presence if spotify is running",
type: OptionType.BOOLEAN,
default: true,
},
statusName: {
description: "text shown in status",
type: OptionType.STRING,
default: "some music",
},
useListeningStatus: {
description: 'show "Listening to" status instead of "Playing"',
type: OptionType.BOOLEAN,
default: false,
},
missingArt: {
description: "When album or album art is missing",
type: OptionType.SELECT,
options: [
{
label: "Use large Last.fm logo",
value: "lastfmLogo",
default: true
},
{
label: "Use generic placeholder",
value: "placeholder"
}
],
}
});
export default definePlugin({ export default definePlugin({
name: "LastFMRichPresence", name: "LastFMRichPresence",
description: "Little plugin for Last.fm rich presence", description: "Little plugin for Last.fm rich presence",
authors: [Devs.dzshn], authors: [Devs.dzshn, Devs.RuiNtD],
settingsAboutComponent: () => ( settingsAboutComponent: () => (
<> <>
@ -104,30 +164,9 @@ export default definePlugin({
</> </>
), ),
options: { settings,
username: {
description: "last.fm username",
type: OptionType.STRING,
},
apiKey: {
description: "last.fm api key",
type: OptionType.STRING,
},
hideWithSpotify: {
description: "hide last.fm presence if spotify is running",
type: OptionType.BOOLEAN,
default: true,
},
useListeningStatus: {
description: 'show "Listening to" status instead of "Playing"',
type: OptionType.BOOLEAN,
default: false,
}
},
start() { start() {
this.settings = Settings.plugins.LastFMRichPresence;
this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000); this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000);
}, },
@ -136,73 +175,112 @@ export default definePlugin({
}, },
async fetchTrackData(): Promise<TrackData | null> { async fetchTrackData(): Promise<TrackData | null> {
if (!this.settings.username || !this.settings.apiKey) return null; if (!settings.store.username || !settings.store.apiKey)
return null;
const response = await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&api_key=${this.settings.apiKey}&user=${this.settings.username}&limit=1&format=json`); try {
const trackData = (await response.json()).recenttracks.track[0]; const params = new URLSearchParams({
method: "user.getrecenttracks",
api_key: settings.store.apiKey,
user: settings.store.username,
limit: "1",
format: "json"
});
if (!trackData["@attr"]?.nowplaying) return null; const res = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`);
if (!res.ok) throw `${res.status} ${res.statusText}`;
// why does the json api have xml structure const json = await res.json();
return { if (json.error) {
name: trackData.name || "Unknown", logger.error("Error from Last.fm API", `${json.error}: ${json.message}`);
album: trackData.album["#text"], return null;
artist: trackData.artist["#text"] || "Unknown", }
url: trackData.url,
imageUrl: (trackData.image || []).filter(x => x.size === "large")[0]?.["#text"] const trackData = json.recenttracks?.track[0];
};
if (!trackData || !trackData["@attr"]?.nowplaying)
return null;
// why does the json api have xml structure
return {
name: trackData.name || "Unknown",
album: trackData.album["#text"],
artist: trackData.artist["#text"] || "Unknown",
url: trackData.url,
imageUrl: trackData.image?.find((x: any) => x.size === "large")?.["#text"]
};
} catch (e) {
logger.error("Failed to query Last.fm API", e);
// will clear the rich presence if API fails
return null;
}
}, },
async updatePresence() { async updatePresence() {
if (this.settings.hideWithSpotify) { setActivity(await this.getActivity());
},
getLargeImage(track: TrackData): string | undefined {
if (track.imageUrl && !track.imageUrl.includes(placeholderId))
return track.imageUrl;
if (settings.store.missingArt === "placeholder")
return "placeholder";
},
async getActivity(): Promise<Activity | null> {
if (settings.store.hideWithSpotify) {
for (const activity of presenceStore.getActivities()) { for (const activity of presenceStore.getActivities()) {
if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) { if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) {
// there is already music status (probably only spotify can do this currently) // there is already music status because of Spotify or richerCider (probably more)
setActivity(); return null;
return;
} }
} }
} }
const trackData = await this.fetchTrackData(); const trackData = await this.fetchTrackData();
if (!trackData) return null;
if (!trackData) { const largeImage = this.getLargeImage(trackData);
setActivity(); const assets: ActivityAssets = largeImage ?
return; {
} large_image: await getApplicationAsset(largeImage),
large_text: trackData.album || undefined,
const hideAlbumName = !trackData.album || trackData.album === trackData.name;
let assets: ActivityAssets;
if (trackData.imageUrl) {
assets = {
large_image: await getApplicationAsset(trackData.imageUrl),
large_text: trackData.name,
small_image: await getApplicationAsset("lastfm-small"), small_image: await getApplicationAsset("lastfm-small"),
small_text: "Last.fm", small_text: "Last.fm",
}; } : {
} else {
assets = {
large_image: await getApplicationAsset("lastfm-large"), large_image: await getApplicationAsset("lastfm-large"),
large_text: "Last.fm", large_text: trackData.album || undefined,
}; };
}
setActivity({ const buttons: ActivityButton[] = [
{
label: "View Song",
url: trackData.url,
},
];
if (settings.store.shareUsername)
buttons.push({
label: "Last.fm Profile",
url: `https://www.last.fm/user/${settings.store.username}`,
});
return {
application_id: applicationId, application_id: applicationId,
name: "some music", name: settings.store.statusName,
details: trackData.name, details: trackData.name,
state: hideAlbumName ? trackData.artist : `${trackData.artist} - ${trackData.album}`, state: trackData.artist,
assets, assets,
buttons: ["Open in Last.fm"], buttons: buttons.map(v => v.label),
metadata: { metadata: {
button_urls: [trackData.url] button_urls: buttons.map(v => v.url),
}, },
type: this.settings.useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING, type: settings.store.useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING,
flags: ActivityFlag.INSTANCE, flags: ActivityFlag.INSTANCE,
}); };
} }
}); });

View File

@ -264,7 +264,7 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe
color: "var(--background-secondary)", color: "var(--background-secondary)",
author: { author: {
name: <Text variant="text-xs/medium" tag="span"> name: <Text variant="text-xs/medium" tag="span">
<span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span>, <span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span>
{isDM {isDM
? Parser.parse(`<@${dmReceiver.id}>`) ? Parser.parse(`<@${dmReceiver.id}>`)
: Parser.parse(`<#${channel.id}>`) : Parser.parse(`<#${channel.id}>`)
@ -302,7 +302,7 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
{isDM {isDM
? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) ? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
: parse(`<#${channel.id}>`) : parse(`<#${channel.id}>`)
}, }
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span> <span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
</Text> </Text>
} }

View File

@ -1,3 +1,8 @@
.messagelogger-deleted div { .messagelogger-deleted div {
color: #f04747; color: #f04747;
} }
.messagelogger-deleted a {
color: #be3535;
text-decoration: underline;
}

View File

@ -18,19 +18,19 @@
import "./messageLogger.css"; import "./messageLogger.css";
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/settings"; import { Settings } from "@api/settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import Logger from "@utils/Logger"; import Logger from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { moment, Parser, Timestamp, UserStore } from "@webpack/common"; import { FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
import overlayStyle from "./deleteStyleOverlay.css?managed"; import overlayStyle from "./deleteStyleOverlay.css?managed";
import textStyle from "./deleteStyleText.css?managed"; import textStyle from "./deleteStyleText.css?managed";
const i18n = findLazy(m => m.Messages?.["en-US"]);
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage"); const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
function addDeleteStyle() { function addDeleteStyle() {
@ -43,20 +43,48 @@ function addDeleteStyle() {
} }
} }
const MENU_ITEM_ID = "message-logger-remove-history";
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => {
const { message } = props;
const { deleted, editHistory, id, channel_id } = message;
if (!deleted && !editHistory?.length) return;
if (children.some(c => c?.props?.id === MENU_ITEM_ID)) return;
children.push((
<Menu.MenuItem
id={MENU_ITEM_ID}
key={MENU_ITEM_ID}
label="Remove Message History"
action={() => {
if (message.deleted) {
FluxDispatcher.dispatch({
type: "MESSAGE_DELETE",
channelId: channel_id,
id,
mlDeleted: true
});
} else {
message.editHistory = [];
}
}}
/>
));
};
export default definePlugin({ export default definePlugin({
name: "MessageLogger", name: "MessageLogger",
description: "Temporarily logs deleted and edited messages.", description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven], authors: [Devs.rushii, Devs.Ven],
dependencies: ["ContextMenuAPI", "MenuItemDeobfuscatorAPI"],
start() { start() {
addDeleteStyle(); addDeleteStyle();
addContextMenuPatch("message", patchMessageContextMenu);
}, },
stop() { stop() {
document.querySelectorAll(".messagelogger-deleted").forEach(e => e.remove()); removeContextMenuPatch("message", patchMessageContextMenu);
document.querySelectorAll(".messagelogger-edited").forEach(e => e.remove());
document.body.classList.remove("messagelogger-red-overlay");
document.body.classList.remove("messagelogger-red-text");
}, },
renderEdit(edit: { timestamp: any, content: string; }) { renderEdit(edit: { timestamp: any, content: string; }) {
@ -106,7 +134,7 @@ export default definePlugin({
} }
}, },
handleDelete(cache: any, data: { ids: string[], id: string; }, isBulk: boolean) { handleDelete(cache: any, data: { ids: string[], id: string; mlDeleted?: boolean; }, isBulk: boolean) {
try { try {
if (cache == null || (!isBulk && !cache.has(data.id))) return cache; if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
@ -118,7 +146,8 @@ export default definePlugin({
if (!msg) return; if (!msg) return;
const EPHEMERAL = 64; const EPHEMERAL = 64;
const shouldIgnore = (msg.flags & EPHEMERAL) === EPHEMERAL || const shouldIgnore = data.mlDeleted ||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
ignoreBots && msg.author?.bot || ignoreBots && msg.author?.bot ||
ignoreSelf && msg.author?.id === myId; ignoreSelf && msg.author?.id === myId;
@ -271,15 +300,10 @@ export default definePlugin({
// Module 748241 // Module 748241
find: "Message must not be a thread starter message", find: "Message must not be a thread starter message",
replacement: [ replacement: [
{
// Write message.deleted to deleted var
match: /var (\w)=(\w).id,(?=\w=\w.message)/,
replace: "var $1=$2.id,deleted=$2.message.deleted,"
},
{ {
// Append messagelogger-deleted to classNames if deleted // Append messagelogger-deleted to classNames if deleted
match: /\)\("li",\{(.+?),className:/, match: /\)\("li",\{(.+?),className:/,
replace: ")(\"li\",{$1,className:(deleted ? \"messagelogger-deleted \" : \"\")+" replace: ")(\"li\",{$1,className:(arguments[0].message.deleted ? \"messagelogger-deleted \" : \"\")+"
} }
] ]
}, },

View File

@ -2,12 +2,14 @@
display: none; display: none;
} }
.messagelogger-deleted-attachment { .messagelogger-deleted-attachment,
.messagelogger-deleted div iframe {
filter: grayscale(1); filter: grayscale(1);
transition: 150ms filter ease-in-out; transition: 150ms filter ease-in-out;
} }
.messagelogger-deleted-attachment:hover { .messagelogger-deleted-attachment:hover,
.messagelogger-deleted div iframe:hover {
filter: grayscale(0); filter: grayscale(0);
} }

View File

@ -17,39 +17,63 @@
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/settings";
import { classes, useAwaiter } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
import { fetchPronouns, formatPronouns } from "../pronoundbUtils"; import { awaitAndFormatPronouns } from "../pronoundbUtils";
import { PronounMapping } from "../types";
const styles: Record<string, string> = findByPropsLazy("timestampInline"); const styles: Record<string, string> = findByPropsLazy("timestampInline");
export default function PronounsChatComponentWrapper({ message }: { message: Message; }) { function shouldShow(message: Message): boolean {
// Respect showInMessages
if (!Settings.plugins.PronounDB.showInMessages)
return false;
// Don't bother fetching bot or system users // Don't bother fetching bot or system users
if (message.author.bot || message.author.system) if (message.author.bot || message.author.system)
return null; return false;
// Respect showSelf options // Respect showSelf options
if (!Settings.plugins.PronounDB.showSelf && message.author.id === UserStore.getCurrentUser().id) if (!Settings.plugins.PronounDB.showSelf && message.author.id === UserStore.getCurrentUser().id)
return false;
return true;
}
export function PronounsChatComponentWrapper({ message }: { message: Message; }) {
if (!shouldShow(message))
return null; return null;
return <PronounsChatComponent message={message} />; return <PronounsChatComponent message={message} />;
} }
function PronounsChatComponent({ message }: { message: Message; }) { export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) {
const [result, , isPending] = useAwaiter(() => fetchPronouns(message.author.id), { if (!shouldShow(message))
fallbackValue: null, return null;
onError: e => console.error("Fetching pronouns failed: ", e)
});
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns return <CompactPronounsChatComponent message={message} />;
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) { }
function PronounsChatComponent({ message }: { message: Message; }) {
const result = awaitAndFormatPronouns(message.author.id);
if (result != null) {
return ( return (
<span <span
className={classes(styles.timestampInline, styles.timestamp)} className={classes(styles.timestampInline, styles.timestamp)}
> {formatPronouns(result)}</span> > {result}</span>
);
}
return null;
}
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
const result = awaitAndFormatPronouns(message.author.id);
if (result != null) {
return (
<span
className={classes(styles.timestampInline, styles.timestamp, "vc-pronoundb-compact")}
> {result}</span>
); );
} }

View File

@ -17,16 +17,19 @@
*/ */
import { Settings } from "@api/settings"; import { Settings } from "@api/settings";
import { useAwaiter } from "@utils/misc";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { fetchPronouns, formatPronouns } from "../pronoundbUtils"; import { awaitAndFormatPronouns } from "../pronoundbUtils";
import { PronounMapping, UserProfilePronounsProps, UserProfileProps } from "../types"; import { UserProfilePronounsProps, UserProfileProps } from "../types";
export default function PronounsProfileWrapper(PronounsComponent: React.ElementType<UserProfilePronounsProps>, props: UserProfilePronounsProps, profileProps: UserProfileProps) { export default function PronounsProfileWrapper(PronounsComponent: React.ElementType<UserProfilePronounsProps>, props: UserProfilePronounsProps, profileProps: UserProfileProps) {
const user = UserStore.getUser(profileProps.userId) ?? {}; const user = UserStore.getUser(profileProps.userId) ?? {};
// Respect showInProfile
if (!Settings.plugins.PronounDB.showInProfile)
return null;
// Don't bother fetching bot or system users // Don't bother fetching bot or system users
if (user.bot || user.system) return null; if (user.bot || user.system)
return null;
// Respect showSelf options // Respect showSelf options
if (!Settings.plugins.PronounDB.showSelf && user.id === UserStore.getCurrentUser().id) if (!Settings.plugins.PronounDB.showSelf && user.id === UserStore.getCurrentUser().id)
return null; return null;
@ -45,15 +48,12 @@ function ProfilePronouns(
leProps: UserProfilePronounsProps; leProps: UserProfilePronounsProps;
} }
) { ) {
const [result, , isPending] = useAwaiter(() => fetchPronouns(userId), { const result = awaitAndFormatPronouns(userId);
fallbackValue: null,
onError: e => console.error("Fetching pronouns failed: ", e),
});
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render // If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) { if (result != null) {
// First child is the header, second is a div with the actual text // First child is the header, second is a div with the actual text
leProps.currentPronouns ||= formatPronouns(result); leProps.currentPronouns ||= result;
return <Component {...leProps} />; return <Component {...leProps} />;
} }

View File

@ -16,11 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./styles.css";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import PronounsAboutComponent from "./components/PronounsAboutComponent"; import PronounsAboutComponent from "./components/PronounsAboutComponent";
import PronounsChatComponent from "./components/PronounsChatComponent"; import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } from "./components/PronounsChatComponent";
import PronounsProfileWrapper from "./components/PronounsProfileWrapper"; import PronounsProfileWrapper from "./components/PronounsProfileWrapper";
export enum PronounsFormat { export enum PronounsFormat {
@ -30,22 +32,30 @@ export enum PronounsFormat {
export default definePlugin({ export default definePlugin({
name: "PronounDB", name: "PronounDB",
authors: [Devs.Tyman], authors: [Devs.Tyman, Devs.TheKodeToad],
description: "Adds pronouns to user messages using pronoundb", description: "Adds pronouns to user messages using pronoundb",
patches: [ patches: [
// Patch the chat timestamp element // Add next to username (compact mode)
{ {
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
replacement: { replacement: {
match: /(?<=return\s*\(0,\w{1,3}\.jsxs?\)\(.+!\w{1,3}&&)(\(0,\w{1,3}.jsxs?\)\(.+?\{.+?\}\))/, match: /("span",{id:\i,className:\i,children:\i}\))/,
replace: "[$1, $self.PronounsChatComponent(e)]" replace: "$1, $self.CompactPronounsChatComponentWrapper(e)"
}
},
// Patch the chat timestamp element (normal mode)
{
find: "showCommunicationDisabledStyles",
replacement: {
match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/,
replace: "[$1, $self.PronounsChatComponentWrapper(e)]"
} }
}, },
// Hijack the discord pronouns section and add a wrapper around the text section // Hijack the discord pronouns section and add a wrapper around the text section
{ {
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS", find: ".Messages.BOT_PROFILE_SLASH_COMMANDS",
replacement: { replacement: {
match: /\(0,.\.jsx\)\((?<PronounComponent>.{1,2}\..),(?<pronounProps>{currentPronouns.+?:(?<fullProps>.{1,2})\.pronouns.+?})\)/, match: /\(0,.\.jsx\)\((?<PronounComponent>\i\..),(?<pronounProps>{currentPronouns.+?:(?<fullProps>\i)\.pronouns.+?})\)/,
replace: "$<fullProps>&&$self.PronounsProfileWrapper($<PronounComponent>,$<pronounProps>,$<fullProps>)" replace: "$<fullProps>&&$self.PronounsProfileWrapper($<PronounComponent>,$<pronounProps>,$<fullProps>)"
} }
}, },
@ -79,10 +89,21 @@ export default definePlugin({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Enable or disable showing pronouns for the current user", description: "Enable or disable showing pronouns for the current user",
default: true default: true
},
showInMessages: {
type: OptionType.BOOLEAN,
description: "Show in messages",
default: true
},
showInProfile: {
type: OptionType.BOOLEAN,
description: "Show in profile",
default: true
} }
}, },
settingsAboutComponent: PronounsAboutComponent, settingsAboutComponent: PronounsAboutComponent,
// Re-export the components on the plugin object so it is easily accessible in patches // Re-export the components on the plugin object so it is easily accessible in patches
PronounsChatComponent, PronounsChatComponentWrapper,
CompactPronounsChatComponentWrapper,
PronounsProfileWrapper PronounsProfileWrapper
}); });

View File

@ -19,6 +19,7 @@
import { Settings } from "@api/settings"; import { Settings } from "@api/settings";
import { VENCORD_USER_AGENT } from "@utils/constants"; import { VENCORD_USER_AGENT } from "@utils/constants";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import { useAwaiter } from "@utils/misc";
import { PronounsFormat } from "."; import { PronounsFormat } from ".";
import { PronounCode, PronounMapping, PronounsResponse } from "./types"; import { PronounCode, PronounMapping, PronounsResponse } from "./types";
@ -39,6 +40,19 @@ const bulkFetch = debounce(async () => {
} }
}); });
export function awaitAndFormatPronouns(id: string): string | null {
const [result, , isPending] = useAwaiter(() => fetchPronouns(id), {
fallbackValue: null,
onError: e => console.error("Fetching pronouns failed: ", e)
});
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return the mappings
if (!isPending && result && result !== "unspecified" && PronounMapping[result])
return formatPronouns(result);
return null;
}
// Fetches the pronouns for one id, returning a promise that resolves if it was cached, or once the request is completed // Fetches the pronouns for one id, returning a promise that resolves if it was cached, or once the request is completed
export function fetchPronouns(id: string): Promise<PronounCode> { export function fetchPronouns(id: string): Promise<PronounCode> {
return new Promise(res => { return new Promise(res => {

View File

@ -0,0 +1,9 @@
.vc-pronoundb-compact {
display: none;
}
[class*="compact"] .vc-pronoundb-compact {
display: inline-block;
margin-left: -2px;
margin-right: 0.25rem;
}

View File

@ -1,67 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 OpenAsar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Link } from "@components/Link";
import definePlugin from "@utils/types";
import { Forms } from "@webpack/common";
const appIds = [
"911790844204437504",
"886578863147192350",
"1020414178047041627",
"1032800329332445255"
];
export default definePlugin({
name: "richerCider",
description: "Enhances Cider (More details in info button) by adding the \"Listening to\" type prefix to the user's rich presence when an applicable ID is found.",
authors: [{
id: 191621342473224192n,
name: "cryptofyre",
}],
patches: [
{
find: '.displayName="LocalActivityStore"',
replacement: {
match: /LOCAL_ACTIVITY_UPDATE:function\((\i)\)\{/,
replace: "$&$self.patchActivity($1.activity);",
}
}
],
settingsAboutComponent: () => (
<>
<Forms.FormTitle tag="h3">Install Cider to use this Plugin</Forms.FormTitle>
<Forms.FormText>
<Link href="https://cider.sh">Follow the link to our website</Link> to get Cider up and running, and then enable the plugin.
</Forms.FormText>
<br></br>
<Forms.FormTitle tag="h3">What is Cider?</Forms.FormTitle>
<Forms.FormText>
Cider is an open-source and community oriented Apple Music client for Windows, macOS, and Linux.
</Forms.FormText>
<br></br>
<Forms.FormTitle tag="h3">Recommended Optional Plugins</Forms.FormTitle>
<Forms.FormText>
I'd recommend using TimeBarAllActivities alongside this plugin to give off a much better visual to the eye (Keep in mind this only affects your client and will not show for other users)
</Forms.FormText>
</>
),
patchActivity(activity: any) {
if (appIds.includes(activity.application_id)) {
activity.type = 2; /* LISTENING type */
}
},
});

View File

@ -0,0 +1,80 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/misc";
import definePlugin from "@utils/types";
import { findByCode, findByCodeLazy } from "@webpack";
import { ChannelStore, i18n, Menu, SelectedChannelStore } from "@webpack/common";
import { Message } from "discord-types/general";
const ReplyIcon = LazyComponent(() => findByCode("M10 8.26667V4L3 11.4667L10 18.9333V14.56C15 14.56 18.5 16.2667 21 20C20 14.6667 17 9.33333 10 8.26667Z"));
const replyFn = findByCodeLazy("showMentionToggle", "TEXTAREA_FOCUS", "shiftKey");
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
// make sure the message is in the selected channel
if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
const channel = ChannelStore.getChannel(message?.channel_id);
if (!channel) return;
// dms and group chats
const dmGroup = findGroupChildrenByChildId("pin", children);
if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) {
const pinIndex = dmGroup.findIndex(c => c.props.id === "pin");
return dmGroup.splice(pinIndex + 1, 0, (
<Menu.MenuItem
id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY}
icon={ReplyIcon}
action={(e: React.MouseEvent) => replyFn(channel, message, e)}
/>
));
}
// servers
const serverGroup = findGroupChildrenByChildId("mark-unread", children);
if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) {
return serverGroup.unshift((
<Menu.MenuItem
id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY}
icon={ReplyIcon}
action={(e: React.MouseEvent) => replyFn(channel, message, e)}
/>
));
}
};
export default definePlugin({
name: "SearchReply",
description: "Adds a reply button to search results",
authors: [Devs.Aria],
start() {
addContextMenuPatch("message", messageContextMenuPatch);
},
stop() {
removeContextMenuPatch("message", messageContextMenuPatch);
}
});

View File

@ -22,7 +22,11 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common"; import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common";
function SilentMessageToggle() { function SilentMessageToggle(chatBoxProps: {
type: {
analyticsName: string;
};
}) {
const [enabled, setEnabled] = React.useState(false); const [enabled, setEnabled] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
@ -37,6 +41,8 @@ function SilentMessageToggle() {
return () => void removePreSendListener(listener); return () => void removePreSendListener(listener);
}, [enabled]); }, [enabled]);
if (chatBoxProps.type.analyticsName !== "normal") return null;
return ( return (
<Tooltip text="Toggle Silent Message"> <Tooltip text="Toggle Silent Message">
{tooltipProps => ( {tooltipProps => (
@ -78,7 +84,7 @@ export default definePlugin({
find: ".activeCommandOption", find: ".activeCommandOption",
replacement: { replacement: {
match: /"gift"\)\);(?<=(\i)\.push.+?disabled:(\i),.+?)/, match: /"gift"\)\);(?<=(\i)\.push.+?disabled:(\i),.+?)/,
replace: (m, array, disabled) => `${m}${disabled}||${array}.push($self.SilentMessageToggle());` replace: (m, array, disabled) => `${m};try{${disabled}||${array}.push($self.SilentMessageToggle(arguments[0]));}catch{}`
} }
} }
], ],

View File

@ -37,10 +37,16 @@ const settings = definePluginSettings({
} }
}); });
function SilentTypingToggle() { function SilentTypingToggle(chatBoxProps: {
type: {
analyticsName: string;
};
}) {
const { isEnabled } = settings.use(["isEnabled"]); const { isEnabled } = settings.use(["isEnabled"]);
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled; const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
if (chatBoxProps.type.analyticsName !== "normal") return null;
return ( return (
<Tooltip text={isEnabled ? "Disable silent typing" : "Enable silent typing"}> <Tooltip text={isEnabled ? "Disable silent typing" : "Enable silent typing"}>
{(tooltipProps: any) => ( {(tooltipProps: any) => (
@ -83,7 +89,7 @@ export default definePlugin({
predicate: () => settings.store.showIcon, predicate: () => settings.store.showIcon,
replacement: { replacement: {
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}", replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}",
} }
}, },
], ],

View File

@ -21,10 +21,11 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { GuildMemberStore, React, RelationshipStore } from "@webpack/common"; import { GuildMemberStore, React, RelationshipStore, SelectedChannelStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
const Avatar = findByCodeLazy('"top",spacing:'); const Avatar = findByCodeLazy('"top",spacing:');
const openProfile = findByCodeLazy("friendToken", "USER_PROFILE_MODAL_OPEN");
const settings = definePluginSettings({ const settings = definePluginSettings({
showAvatars: { showAvatars: {
@ -53,6 +54,46 @@ export function buildSeveralUsers({ a, b, c }: { a: string, b: string, c: number
]; ];
} }
interface Props {
user: User;
guildId: string;
}
const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) {
return (
<strong
role="button"
onClick={() => {
openProfile({
userId: user.id,
guildId,
channelId: SelectedChannelStore.getChannelId(),
analyticsLocation: {
page: guildId ? "Guild Channel" : "DM Channel",
section: "Profile Popout"
}
});
}}
style={{
display: "grid",
gridAutoFlow: "column",
gap: "4px",
color: settings.store.showRoleColors ? GuildMemberStore.getMember(guildId, user.id)?.colorString : undefined,
cursor: "pointer"
}}
>
{settings.store.showAvatars && (
<div style={{ marginTop: "4px" }}>
<Avatar
size="SIZE_16"
src={user.getAvatarURL(guildId, 128)} />
</div>
)}
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
</strong>
);
}, { noop: true });
export default definePlugin({ export default definePlugin({
name: "TypingTweaks", name: "TypingTweaks",
description: "Show avatars and role colours in the typing indicator", description: "Show avatars and role colours in the typing indicator",
@ -93,22 +134,10 @@ export default definePlugin({
let element = 0; let element = 0;
return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]} /> : c); return children.map(c =>
c.type === "strong"
? <TypingUser {...props} user={users[element++]} />
: c
);
}, },
TypingUser: ErrorBoundary.wrap(({ user, guildId }: { user: User, guildId: string; }) => {
return <strong style={{
display: "grid",
gridAutoFlow: "column",
gap: "4px",
color: settings.store.showRoleColors ? GuildMemberStore.getMember(guildId, user.id)?.colorString : undefined
}}>
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
<Avatar
size="SIZE_16"
src={user.getAvatarURL(guildId, 128)} />
</div>}
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
</strong>;
}, { noop: true })
}); });

View File

@ -21,87 +21,71 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
const endings = [ const endings = [
"owo", "UwU", ">w<", "^w^", "●w●", "☆w☆", "𝗨𝘄𝗨", "(ᗒᗨᗕ)", "(▰˘v˘▰)", "rawr x3",
"( ´ ▽ ` ).。o♡", "*unbuttons shirt*", ">3<", ">:3", ":3", "murr~", "OwO",
"♥(。U ω U。)", "(˘ε˘)", "*screams*", "*twerks*", "*sweats*", "UwU",
"o.O",
"-.-",
">w<",
"(⑅˘꒳˘)",
"(ꈍᴗꈍ)",
"(˘ω˘)",
"(U ᵕ U❁)",
"σωσ",
"òωó",
"(///ˬ///✿)",
"(U U)",
"( ͡o ω ͡o )",
"ʘwʘ",
":3",
":3", // important enough to have twice
"XD",
"nyaa~~",
"mya",
">_<",
"😳",
"🥺",
"😳😳😳",
"rawr",
"^^",
"^^;;",
"(ˆˆ)♡",
"^•ﻌ•^",
"/(^•ω•^)",
"(✿oωo)"
]; ];
const replacements = [ const replacements = [
["love", "wuv"], ["small", "smol"],
["mr", "mistuh"], ["cute", "kawaii~"],
["dog", "doggo"], ["fluff", "floof"],
["cat", "kitteh"], ["love", "luv"],
["hello", "henwo"], ["stupid", "baka"],
["hell", "heck"], ["what", "nani"],
["fuck", "fwick"], ["meow", "nya~"],
["fuk", "fwick"],
["shit", "shoot"],
["friend", "fwend"],
["stop", "stawp"],
["god", "gosh"],
["dick", "peepee"],
["penis", "bulge"],
["damn", "darn"],
]; ];
function selectRandomElement(arr) {
// generate a random index based on the length of the array
const randomIndex = Math.floor(Math.random() * arr.length);
// return the element at the randomly generated index
return arr[randomIndex];
}
function uwuify(message: string): string { function uwuify(message: string): string {
return message message = message.toLowerCase();
.split(" ") // words
.map(w => { for (const pair of replacements) {
let owofied = false; message = message.replaceAll(pair[0], pair[1]);
const lowerCase = w.toLowerCase(); }
// return if the word is too short - uwuifying short words makes them unreadable message = message
if (w.length < 4) { .replaceAll(/([ \t\n])n/g, "$1ny") // nyaify
return w; .replaceAll(/[lr]/g, "w") // [lr] > w
} .replaceAll(/([ \t\n])([a-z])/g, (_, p1, p2) => Math.random() < .5 ? `${p1}${p2}-${p2}` : `${p1}${p2}`) // stutter
.replaceAll(/([^.,!][.,!])([ \t\n])/g, (_, p1, p2) => `${p1} ${selectRandomElement(endings)}${p2}`); // endings
// replacing the words based on the array on line 29 return message;
for (const [find, replace] of replacements) {
if (w.includes(find)) {
w = w.replace(find, replace);
owofied = true;
}
}
// these are the biggest word changes. if any of these are done we dont do the
// ones after the isowo check. to keep the words somewhat readable
if (lowerCase.includes("u") && !lowerCase.includes("uwu")) {
w = w.replace("u", "UwU");
owofied = true;
}
if (lowerCase.includes("o") && !lowerCase.includes("owo")) {
w = w.replace("o", "OwO");
owofied = true;
}
if (lowerCase.endsWith("y") && w.length < 7) {
w = w + " " + "w" + w.slice(1);
owofied = true;
}
// returning if word has been already uwuified - to prevent over-uwuifying
if (owofied) {
return w;
}
// more tiny changes - to keep the words that passed through the latter changes uwuified
if (!lowerCase.endsWith("n")) {
w = w.replace("n", "ny");
}
if (Math.floor(Math.random() * 2) === 1) {
w.replace("s", "sh");
}
if (Math.floor(Math.random() * 5) === 3 && !owofied) {
w = w[0] + "-" + w[0] + "-" + w;
}
if (Math.floor(Math.random() * 5) === 3) {
w =
w +
" " +
endings[Math.floor(Math.random() * endings.length)];
}
w = w.replaceAll("r", "w").replaceAll("l", "w");
return w;
}).join(" ");
} }
@ -110,7 +94,7 @@ function uwuify(message: string): string {
export default definePlugin({ export default definePlugin({
name: "UwUifier", name: "UwUifier",
description: "Simply uwuify commands", description: "Simply uwuify commands",
authors: [Devs.echo], authors: [Devs.echo, Devs.skyevg],
dependencies: ["CommandsAPI"], dependencies: ["CommandsAPI"],
commands: [ commands: [

View File

@ -19,7 +19,7 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { LazyComponent } from "@utils/misc"; import { LazyComponent } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { PluginDef } from "@utils/types"; import definePlugin from "@utils/types";
import { find, findByCode, findByPropsLazy } from "@webpack"; import { find, findByCode, findByPropsLazy } from "@webpack";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
import type { Guild } from "discord-types/general"; import type { Guild } from "discord-types/general";
@ -30,12 +30,12 @@ const MaskedLink = LazyComponent(() => find(m => m.type?.toString().includes("MA
const GuildBannerStore = findByPropsLazy("getGuildBannerURL"); const GuildBannerStore = findByPropsLazy("getGuildBannerURL");
const OPEN_URL = "Vencord.Plugins.plugins.ViewIcons.openImage("; const OPEN_URL = "Vencord.Plugins.plugins.ViewIcons.openImage(";
export default new class ViewIcons implements PluginDef { export default definePlugin({
name = "ViewIcons"; name: "ViewIcons",
authors = [Devs.Ven]; authors: [Devs.Ven],
description = "Makes Avatars/Banners in user profiles clickable, and adds Guild Context Menu Entries to View Banner/Icon."; description: "Makes Avatars/Banners in user profiles clickable, and adds Guild Context Menu Entries to View Banner/Icon.",
dependencies = ["MenuItemDeobfuscatorAPI"]; dependencies: ["MenuItemDeobfuscatorAPI"],
openImage(url: string) { openImage(url: string) {
const u = new URL(url); const u = new URL(url);
@ -52,9 +52,9 @@ export default new class ViewIcons implements PluginDef {
/> />
</ModalRoot> </ModalRoot>
)); ));
} },
patches = [ patches: [
{ {
find: "onAddFriend:", find: "onAddFriend:",
replacement: { replacement: {
@ -83,7 +83,7 @@ export default new class ViewIcons implements PluginDef {
} }
] ]
} }
]; ],
buildGuildContextMenuEntries(guild: Guild) { buildGuildContextMenuEntries(guild: Guild) {
return ( return (
@ -107,4 +107,4 @@ export default new class ViewIcons implements PluginDef {
</Menu.MenuGroup> </Menu.MenuGroup>
); );
} }
}; });

View File

@ -29,15 +29,9 @@ import { Message } from "discord-types/general";
const CopyIcon = () => { const CopyIcon = () => {
return <svg viewBox="0 0 512.002 512.002" fill="currentColor" aria-hidden="true" width="22" height="22"> return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="22" height="22">
<path d="M462.002,92.002h-42.001V50c0-27.57-22.43-50-50-50h-320c-27.57,0-50,22.43-50,50v320.002c0,27.57,22.43,50,50,50h42.001 v42c0,27.57,22.43,50,50,50h320c27.57,0,50-22.43,50-50v-320C512.001,114.432,489.573,92.002,462.002,92.002z M50.001,400.002 c-16.542,0-30-13.458-30-30V50c0-16.542,13.458-30,30-30h320c16.542,0,30,13.458,30,30v320.002c0,16.542-13.458,30-30,30H50.001z M492.002,462.002c0,16.542-13.458,30-30,30h-320c-16.542,0-30-13.458-30-30v-42h257.999c27.57,0,50-22.43,50-50v-258h42.001 c16.542,0,30,13.458,30,30V462.002z" /> <path d="M12.9297 3.25007C12.7343 3.05261 12.4154 3.05226 12.2196 3.24928L11.5746 3.89824C11.3811 4.09297 11.3808 4.40733 11.5739 4.60245L16.5685 9.64824C16.7614 9.84309 16.7614 10.1569 16.5685 10.3517L11.5739 15.3975C11.3808 15.5927 11.3811 15.907 11.5746 16.1017L12.2196 16.7507C12.4154 16.9477 12.7343 16.9474 12.9297 16.7499L19.2604 10.3517C19.4532 10.1568 19.4532 9.84314 19.2604 9.64832L12.9297 3.25007Z" />
<path d="M462.024,457.002H170.98c-5.522,0-10,4.478-10,10c0,5.523,4.478,10,10,10h291.043c5.522,0,10-4.477,10-10 S467.546,457.002,462.024,457.002z" /> <path d="M8.42616 4.60245C8.6193 4.40733 8.61898 4.09297 8.42545 3.89824L7.78047 3.24928C7.58466 3.05226 7.26578 3.05261 7.07041 3.25007L0.739669 9.64832C0.5469 9.84314 0.546901 10.1568 0.739669 10.3517L7.07041 16.7499C7.26578 16.9474 7.58465 16.9477 7.78047 16.7507L8.42545 16.1017C8.61898 15.907 8.6193 15.5927 8.42616 15.3975L3.43155 10.3517C3.23869 10.1569 3.23869 9.84309 3.43155 9.64824L8.42616 4.60245Z" />
<path d="M142.25,457.002h-0.27c-5.522,0-10,4.478-10,10c0,5.523,4.478,10,10,10h0.27c5.523,0,10-4.477,10-10 S147.773,457.002,142.25,457.002z" />
<path d="M110.035,35h-0.27c-5.522,0-10,4.478-10,10s4.478,10,10,10h0.27c5.522,0,10-4.478,10-10S115.558,35,110.035,35z" />
<path d="M81.036,35H50.001c-5.522,0-10,4.478-10,10s4.478,10,10,10h31.034c5.523,0,10.001-4.478,10.001-10S86.558,35,81.036,35z" />
<path d="M122.084,246.829l-0.008-0.008l-14.8-30.002c5.407-2.305,10.07-5.975,13.466-10.537c3.725-5.006,5.931-11.08,5.931-17.604 c0-8.588-3.248-15.902-9.743-21.965c-3.196-2.992-6.739-5.242-10.627-6.744c-3.891-1.506-8.097-2.258-12.612-2.258H64.936 c-2.793,0-5.327,1.145-7.163,2.996c-1.832,1.85-2.968,4.396-2.968,7.205v82.777c0,2.896,1.137,5.338,2.893,7.105 c0.962,0.969,2.107,1.728,3.349,2.246c1.249,0.52,2.597,0.797,3.953,0.797c2.49,0,5.028-0.92,7.096-2.967 c1.004-0.99,1.761-2.09,2.271-3.297c0.516-1.221,0.771-2.516,0.771-3.885v-31.139h10.844l17.862,36.184l-0.004,0.005 c1.315,2.699,3.416,4.4,5.763,5.201c1.275,0.434,2.616,0.596,3.938,0.502c1.317-0.094,2.618-0.44,3.812-1.022 c2.307-1.123,4.237-3.123,5.179-5.881h0.002c0.45-1.307,0.639-2.609,0.564-3.904C123.023,249.327,122.684,248.058,122.084,246.829 z M106.124,190.85c-0.438,1.795-1.473,3.546-3.189,5.07c-1.205,1.039-2.538,1.838-3.996,2.395c-1.45,0.553-3.02,0.865-4.704,0.934 v-0.006H75.138v-21.221H93.69c3.19,0,5.9,0.893,7.984,2.328c1.83,1.26,3.187,2.939,3.96,4.791 C106.393,186.957,106.591,188.942,106.124,190.85z" />
<path d="M364.199,163.706c-1.12-2.354-3.136-4.285-5.777-5.254l-0.055-0.016c-2.721-0.916-5.49-0.688-7.823,0.439 c-2.349,1.135-4.243,3.172-5.193,5.861v0.008l-19.568,55.744l-19.571-55.816h-0.001c-0.705-2.352-2.14-4.141-3.935-5.322 c-1.589-1.047-3.458-1.609-5.345-1.656c-1.887-0.047-3.801,0.42-5.479,1.43c-1.938,1.164-3.553,3.031-4.445,5.637h0.002 l-19.564,55.73l-19.569-55.746l-0.008-0.016c-0.997-2.779-2.938-4.752-5.235-5.846c-1.222-0.584-2.552-0.918-3.899-0.99 c-1.34-0.074-2.701,0.115-3.991,0.578c-2.347,0.842-4.429,2.57-5.701,5.24c-1.206,2.529-1.354,5.119-0.441,7.766l0.005,0.009 l29.181,83.133l0.016,0.047c1.186,3.139,3.318,5.213,5.775,6.234c1.141,0.473,2.355,0.717,3.581,0.73 c1.219,0.016,2.438-0.197,3.595-0.635c2.602-0.982,4.914-3.084,6.238-6.268l0.039-0.109l19.595-55.779l20.043,56.877l0.047,0.125 c0.967,2.25,2.404,3.912,4.071,4.99c1.666,1.078,3.544,1.563,5.408,1.465c1.854-0.096,3.686-0.764,5.274-1.992 c1.666-1.289,3.059-3.191,3.899-5.686l29.251-83.133C365.546,168.825,365.317,166.053,364.199,163.706z" />
<path d="M226.7,247.37l-0.008-0.031l-8.818-21.24l-0.003,0.002l-25.528-61.617h0.001c-1.024-2.709-3.167-4.654-5.674-5.645 c-1.165-0.461-2.429-0.715-3.708-0.742c-1.28-0.027-2.565,0.178-3.773,0.635c-2.393,0.906-4.479,2.764-5.647,5.719l0.005,0.002 l-25.54,61.719h-0.003l-8.818,21.168v0.008c-1.076,2.607-1,5.383-0.008,7.783c0.994,2.408,2.91,4.433,5.51,5.527l0.038,0.023 c2.619,1.035,5.435,1.064,7.886,0.041c2.295-0.955,4.242-2.813,5.384-5.6l6.147-14.877h37.566l6.148,14.877 c1.175,2.844,3.171,4.674,5.459,5.609c1.261,0.518,2.601,0.752,3.932,0.73c1.324-0.021,2.642-0.295,3.865-0.801 c2.418-0.998,4.496-2.9,5.579-5.547l0.008-0.016C227.723,252.523,227.723,249.945,226.7,247.37z M172.552,219.938l10.344-25.01 l10.416,25.01H172.552z" />
</svg>; </svg>;
}; };

View File

@ -202,8 +202,28 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "lewisakura", name: "lewisakura",
id: 96269247411400704n id: 96269247411400704n
}, },
RuiNtD: {
name: "RuiNtD",
id: 157917665162297344n
},
hunt: {
name: "hunt-g",
id: 222800179697287168n
},
cloudburst: { cloudburst: {
name: "cloudburst", name: "cloudburst",
id: 892128204150685769n id: 892128204150685769n
},
Aria: {
name: "Syncxv",
id: 549244932213309442n,
},
TheKodeToad: {
name: "TheKodeToad",
id: 706152404072267788n
},
skyevg: {
name: "skyevg",
id: 1090310844283363348n
} }
}); });

File diff suppressed because one or more lines are too long

View File

@ -19,6 +19,7 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { FluxEvents } from "./fluxEvents"; import type { FluxEvents } from "./fluxEvents";
import { i18nMessages } from "./i18nMessages";
export { FluxEvents }; export { FluxEvents };
@ -82,3 +83,30 @@ export type RestAPI = Record<"delete" | "get" | "patch" | "post" | "put", (data:
V8APIError: Error; V8APIError: Error;
getAPIBaseURL(withVersion?: boolean): string; getAPIBaseURL(withVersion?: boolean): string;
}; };
export interface Locale {
name: string;
value: string;
localizedName: string;
}
export interface LocaleInfo {
code: string;
enabled: boolean;
name: string;
englishName: string;
postgresLang: string;
}
export interface i18n {
getAvailableLocales(): Locale[];
getLanguages(): LocaleInfo[];
getDefaultLocale(): string;
getLocale(): string;
getLocaleInfo(): LocaleInfo;
setLocale(locale: string): void;
loadPromise: Promise<void>;
Messages: Record<i18nMessages, string>;
}

View File

@ -19,7 +19,7 @@
import type { User } from "discord-types/general"; import type { User } from "discord-types/general";
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
import { _resolveReady,filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy, waitFor } from "../webpack"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack";
import type * as t from "./types/utils"; import type * as t from "./types/utils";
export let FluxDispatcher: t.FluxDispatcher; export let FluxDispatcher: t.FluxDispatcher;
@ -29,6 +29,8 @@ export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYea
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight"); export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight");
export const i18n: t.i18n = findLazy(m => m.Messages?.["en-US"]);
export let SnowflakeUtils: t.SnowflakeUtils; export let SnowflakeUtils: t.SnowflakeUtils;
waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m);