Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
214c101740 | ||
|
5a0e501829 | ||
|
92113da7c0 | ||
|
96f30a5359 | ||
|
ceb1f15188 | ||
|
626eb3613e | ||
|
3020fcc9bb | ||
|
bc0de3926c | ||
|
9820b79dfe | ||
|
ab811470fc | ||
|
e4162e7bd5 | ||
|
7e8397a4da | ||
|
555cf64080 | ||
|
2039e10fd5 | ||
|
e8d90d2b45 | ||
|
55af40ee74 | ||
|
a1fabcdf0a | ||
|
eaeb60308e | ||
|
662c0227eb | ||
|
543fdf4943 | ||
|
1225383723 | ||
|
07a9adbce2 | ||
|
42d8211871 | ||
|
ab3e993274 | ||
|
386dfe363a | ||
|
a4191c9f6c | ||
|
f1349a2787 | ||
|
3680c26f72 | ||
|
683c92f904 | ||
|
3410ed024f | ||
|
dbad10984a | ||
|
55543d8640 | ||
|
263fbc377e | ||
|
c9c0ab5aca | ||
|
7b2bf08b8f | ||
|
43011825af | ||
|
4abcea61f8 | ||
|
cba810cab5 | ||
|
5938c7d67c | ||
|
99d8b8b75f | ||
|
503d49d295 | ||
|
137b79d95b |
40
.github/workflows/build.yml
vendored
40
.github/workflows/build.yml
vendored
@ -42,37 +42,39 @@ jobs:
|
||||
|
||||
- name: Clean up obsolete files
|
||||
run: |
|
||||
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||
rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||
|
||||
- name: Get some values needed for the release
|
||||
id: release_values
|
||||
run: |
|
||||
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload DevBuild as release
|
||||
if: github.repository == 'Vendicated/Vencord'
|
||||
run: |
|
||||
gh release upload devbuild --clobber dist/*
|
||||
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
|
||||
gh release upload devbuild --clobber dist/*
|
||||
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ env.release_tag }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ env.release_tag }}
|
||||
|
||||
- name: Upload DevBuild to builds repo
|
||||
if: github.repository == 'Vendicated/Vencord'
|
||||
run: |
|
||||
git config --global user.name "$USERNAME"
|
||||
git config --global user.email actions@github.com
|
||||
git config --global user.name "$USERNAME"
|
||||
git config --global user.email actions@github.com
|
||||
|
||||
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload
|
||||
cd upload
|
||||
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload
|
||||
cd upload
|
||||
|
||||
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
|
||||
rm -rf *
|
||||
cp -r ../dist/* .
|
||||
GLOBIGNORE=.git:.gitignore:README.md:LICENSE
|
||||
rm -rf *
|
||||
cp -r ../dist/* .
|
||||
|
||||
git add -A
|
||||
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
|
||||
git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
|
||||
git add -A
|
||||
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
|
||||
git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
|
||||
env:
|
||||
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
|
||||
GH_REPO: Vencord/builds
|
||||
USERNAME: GitHub-Actions
|
||||
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
|
||||
GH_REPO: Vencord/builds
|
||||
USERNAME: GitHub-Actions
|
||||
|
4
.github/workflows/codeberg-mirror.yml
vendored
4
.github/workflows/codeberg-mirror.yml
vendored
@ -1,4 +1,7 @@
|
||||
name: Sync to Codeberg
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
@ -7,6 +10,7 @@ on:
|
||||
|
||||
jobs:
|
||||
codeberg:
|
||||
if: github.repository == 'Vendicated/Vencord'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
47
.github/workflows/publish.yml
vendored
47
.github/workflows/publish.yml
vendored
@ -6,6 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
if: github.repository == 'Vendicated/Vencord'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -13,11 +14,11 @@ jobs:
|
||||
|
||||
- name: check that tag matches package.json version
|
||||
run: |
|
||||
pkg_version="v$(jq -r .version < package.json)"
|
||||
if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then
|
||||
echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
pkg_version="v$(jq -r .version < package.json)"
|
||||
if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then
|
||||
echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
|
||||
@ -35,26 +36,26 @@ jobs:
|
||||
|
||||
- name: Publish extension
|
||||
run: |
|
||||
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
|
||||
EXIT_CODE=0
|
||||
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
|
||||
EXIT_CODE=0
|
||||
|
||||
# Chrome
|
||||
cd dist/chromium-unpacked
|
||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
|
||||
# Chrome
|
||||
cd dist/chromium-unpacked
|
||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
|
||||
|
||||
# Firefox
|
||||
cd ../firefox-unpacked
|
||||
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
|
||||
web-ext-submit || EXIT_CODE=$?
|
||||
# Firefox
|
||||
cd ../firefox-unpacked
|
||||
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
|
||||
web-ext-submit || EXIT_CODE=$?
|
||||
|
||||
exit $EXIT_CODE
|
||||
exit $EXIT_CODE
|
||||
env:
|
||||
# Chrome
|
||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
# Chrome
|
||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
|
||||
# Firefox
|
||||
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
|
||||
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
||||
# Firefox
|
||||
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
|
||||
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
||||
|
37
.github/workflows/reportBrokenPlugins.yml
vendored
37
.github/workflows/reportBrokenPlugins.yml
vendored
@ -2,11 +2,12 @@ name: Test Patches
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Every day at midnight
|
||||
- cron: 0 0 * * *
|
||||
# Every day at midnight
|
||||
- cron: 0 0 * * *
|
||||
|
||||
jobs:
|
||||
TestPlugins:
|
||||
if: github.repository == 'Vendicated/Vencord'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -22,10 +23,10 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm add puppeteer
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm add puppeteer
|
||||
|
||||
sudo apt-get install -y chromium-browser
|
||||
sudo apt-get install -y chromium-browser
|
||||
|
||||
- name: Build web
|
||||
run: pnpm buildWeb --standalone
|
||||
@ -33,25 +34,25 @@ jobs:
|
||||
- name: Create Report
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||
export CHROMIUM_BIN=$(which chromium-browser)
|
||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||
export CHROMIUM_BIN=$(which chromium-browser)
|
||||
|
||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
|
||||
- name: Create Report (Canary)
|
||||
timeout-minutes: 10
|
||||
if: success() || failure() # even run if previous one failed
|
||||
run: |
|
||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||
export CHROMIUM_BIN=$(which chromium-browser)
|
||||
export USE_CANARY=true
|
||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||
export CHROMIUM_BIN=$(which chromium-browser)
|
||||
export USE_CANARY=true
|
||||
|
||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -12,5 +12,12 @@
|
||||
"javascript.format.semicolons": "insert",
|
||||
"typescript.format.semicolons": "insert",
|
||||
"typescript.preferences.quoteStyle": "double",
|
||||
"javascript.preferences.quoteStyle": "double"
|
||||
"javascript.preferences.quoteStyle": "double",
|
||||
|
||||
"gitlens.remotes": [
|
||||
{
|
||||
"domain": "codeberg.org",
|
||||
"type": "Gitea"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
25
.vscode/tasks.json
vendored
Normal file
25
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build",
|
||||
"type": "shell",
|
||||
"command": "pnpm build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Watch",
|
||||
"type": "shell",
|
||||
"command": "pnpm watch",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
10
README.md
10
README.md
@ -1,7 +1,8 @@
|
||||
# Vencord
|
||||
|
||||
The cutest Discord client mod
|
||||
[![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Ven/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Ven/cord)
|
||||
|
||||
The cutest Discord client mod
|
||||
|
||||
![](https://user-images.githubusercontent.com/45497981/235015332-0453d3eb-1da6-4601-963e-ef5e454123a1.png)
|
||||
*A screenshot of Vencord featuring the [ClearVision-v6 theme](https://github.com/ClearVision/ClearVision-v6) (Vencord does not come with it pre-installed, it is only an example)*
|
||||
@ -38,10 +39,9 @@ Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Ve
|
||||
## Vencord Desktop
|
||||
|
||||
> **Warning**
|
||||
> This is an alternative app. It currently doesn't support screensharing or keybinds. If you just want to install to the normal Discord Desktop app, scroll up
|
||||
> This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up
|
||||
|
||||
|
||||
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app. It is currently in beta and we have yet to implement some features like screensharing, but you can try the beta nonetheless
|
||||
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app
|
||||
|
||||
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
|
||||
|
||||
@ -49,7 +49,7 @@ As an alternative to the Discord Desktop app, Vencord also has its own standalon
|
||||
|
||||
## Join our Support/Community Server
|
||||
|
||||
[![Vencord Discord Server](https://invidget.switchblade.xyz/D9uwnFnqmd?theme=dark)](https://discord.gg/D9uwnFnqmd)
|
||||
https://discord.gg/D9uwnFnqmd
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
@ -41,12 +41,5 @@
|
||||
"path": "modifyResponseHeaders.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "vencord-firefox@vendicated.dev",
|
||||
"strict_min_version": "109.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.2.6",
|
||||
"version": "1.2.9",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
@ -24,7 +24,7 @@
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc",
|
||||
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||
"testTsc": "tsc --noEmit",
|
||||
"uninject": "node scripts/runInstaller.mjs",
|
||||
|
@ -19,11 +19,13 @@
|
||||
|
||||
import esbuild from "esbuild";
|
||||
|
||||
import { commonOpts, globPlugins, isStandalone, watch } from "./common.mjs";
|
||||
import { commonOpts, globPlugins, isStandalone, VERSION, watch } from "./common.mjs";
|
||||
|
||||
const defines = {
|
||||
IS_STANDALONE: isStandalone,
|
||||
IS_DEV: JSON.stringify(watch)
|
||||
IS_DEV: JSON.stringify(watch),
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
BUILD_TIMESTAMP: Date.now(),
|
||||
};
|
||||
if (defines.IS_STANDALONE === "false")
|
||||
// If this is a local build (not standalone), optimise
|
||||
|
@ -24,9 +24,7 @@ import { readFileSync } from "fs";
|
||||
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
// wtf is this assert syntax
|
||||
import PackageJSON from "../../package.json" assert { type: "json" };
|
||||
import { commonOpts, globPlugins, watch } from "./common.mjs";
|
||||
import { commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
|
||||
|
||||
/**
|
||||
* @type {esbuild.BuildOptions}
|
||||
@ -47,7 +45,9 @@ const commonOptions = {
|
||||
IS_STANDALONE: "true",
|
||||
IS_DEV: JSON.stringify(watch),
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
IS_VENCORD_DESKTOP: "false"
|
||||
IS_VENCORD_DESKTOP: "false",
|
||||
VERSION: JSON.stringify(VERSION),
|
||||
BUILD_TIMESTAMP: Date.now(),
|
||||
}
|
||||
};
|
||||
|
||||
@ -67,7 +67,7 @@ await Promise.all(
|
||||
},
|
||||
outfile: "dist/Vencord.user.js",
|
||||
banner: {
|
||||
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`)
|
||||
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${VERSION}.${new Date().getTime()}`)
|
||||
},
|
||||
footer: {
|
||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||
@ -88,7 +88,7 @@ async function buildPluginZip(target, files, shouldZip) {
|
||||
let content = await readFile(join("browser", f));
|
||||
if (f.startsWith("manifest")) {
|
||||
const json = JSON.parse(content.toString("utf-8"));
|
||||
json.version = PackageJSON.version;
|
||||
json.version = VERSION;
|
||||
content = new TextEncoder().encode(JSON.stringify(json));
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "../suppressExperimentalWarnings.js";
|
||||
import "../checkNodeVersion.js";
|
||||
|
||||
import { exec, execSync } from "child_process";
|
||||
@ -24,6 +25,11 @@ import { readdir, readFile } from "fs/promises";
|
||||
import { join, relative } from "path";
|
||||
import { promisify } from "util";
|
||||
|
||||
// wtf is this assert syntax
|
||||
import PackageJSON from "../../package.json" assert { type: "json" };
|
||||
|
||||
export const VERSION = PackageJSON.version;
|
||||
export const BUILD_TIMESTAMP = Date.now();
|
||||
export const watch = process.argv.includes("--watch");
|
||||
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
|
||||
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
||||
@ -64,7 +70,7 @@ export const globPlugins = kind => ({
|
||||
});
|
||||
|
||||
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||
const pluginDirs = ["plugins", "userplugins"];
|
||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
||||
let code = "";
|
||||
let plugins = "\n";
|
||||
let i = 0;
|
||||
@ -72,8 +78,9 @@ export const globPlugins = kind => ({
|
||||
if (!existsSync(`./src/${dir}`)) continue;
|
||||
const files = await readdir(`./src/${dir}`);
|
||||
for (const file of files) {
|
||||
if (file.startsWith(".")) continue;
|
||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
||||
if (file === "index.ts") continue;
|
||||
|
||||
const fileBits = file.split(".");
|
||||
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1))) {
|
||||
const mod = fileBits.at(-2);
|
||||
|
@ -171,8 +171,8 @@ async function parseFile(fileName: string) {
|
||||
throw fail("no default export called 'definePlugin' found");
|
||||
}
|
||||
|
||||
async function getEntryPoint(dirent: Dirent) {
|
||||
const base = join("./src/plugins", dirent.name);
|
||||
async function getEntryPoint(dir: string, dirent: Dirent) {
|
||||
const base = join(dir, dirent.name);
|
||||
if (!dirent.isDirectory()) return base;
|
||||
|
||||
for (const name of ["index.ts", "index.tsx"]) {
|
||||
@ -186,13 +186,23 @@ async function getEntryPoint(dirent: Dirent) {
|
||||
throw new Error(`${dirent.name}: Couldn't find entry point`);
|
||||
}
|
||||
|
||||
function isPluginFile({ name }: { name: string; }) {
|
||||
if (name === "index.ts") return false;
|
||||
return !name.startsWith("_") && !name.startsWith(".");
|
||||
}
|
||||
|
||||
(async () => {
|
||||
parseDevs();
|
||||
const plugins = readdirSync("./src/plugins", { withFileTypes: true }).filter(d => d.name !== "index.ts");
|
||||
|
||||
const promises = plugins.map(async dirent => parseFile(await getEntryPoint(dirent)));
|
||||
const plugins = ["src/plugins", "src/plugins/_core"].flatMap(dir =>
|
||||
readdirSync(dir, { withFileTypes: true })
|
||||
.filter(isPluginFile)
|
||||
.map(async dirent =>
|
||||
parseFile(await getEntryPoint(dir, dirent))
|
||||
)
|
||||
);
|
||||
|
||||
const data = JSON.stringify(await Promise.all(promises));
|
||||
const data = JSON.stringify(await Promise.all(plugins));
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
writeFileSync(process.argv[2], data);
|
||||
|
@ -22,7 +22,7 @@ import { ComponentType, HTMLProps } from "react";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
export enum BadgePosition {
|
||||
export const enum BadgePosition {
|
||||
START,
|
||||
END
|
||||
}
|
||||
@ -79,7 +79,7 @@ export function _getBadges(args: BadgeUserArgs) {
|
||||
: badges.push({ ...badge, ...args });
|
||||
}
|
||||
}
|
||||
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/apiBadges").default).getDonorBadges(args.user.id);
|
||||
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id);
|
||||
if (donorBadges) badges.unshift(...donorBadges);
|
||||
|
||||
return badges;
|
||||
|
@ -24,7 +24,7 @@ export interface CommandContext {
|
||||
guild?: Guild;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandOptionType {
|
||||
export const enum ApplicationCommandOptionType {
|
||||
SUB_COMMAND = 1,
|
||||
SUB_COMMAND_GROUP = 2,
|
||||
STRING = 3,
|
||||
@ -38,7 +38,7 @@ export enum ApplicationCommandOptionType {
|
||||
ATTACHMENT = 11,
|
||||
}
|
||||
|
||||
export enum ApplicationCommandInputType {
|
||||
export const enum ApplicationCommandInputType {
|
||||
BUILT_IN = 0,
|
||||
BUILT_IN_TEXT = 1,
|
||||
BUILT_IN_INTEGRATION = 2,
|
||||
@ -64,7 +64,7 @@ export interface ChoicesOption {
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandType {
|
||||
export const enum ApplicationCommandType {
|
||||
CHAT_INPUT = 1,
|
||||
USER = 2,
|
||||
MESSAGE = 3,
|
||||
|
@ -20,7 +20,7 @@ import { Logger } from "@utils/Logger";
|
||||
|
||||
const logger = new Logger("ServerListAPI");
|
||||
|
||||
export enum ServerListRenderPosition {
|
||||
export const enum ServerListRenderPosition {
|
||||
Above,
|
||||
In,
|
||||
}
|
||||
|
@ -254,8 +254,12 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function definePluginSettings<D extends SettingsDefinition, C extends SettingsChecks<D>>(def: D, checks?: C) {
|
||||
const definedSettings: DefinedSettings<D> = {
|
||||
export function definePluginSettings<
|
||||
Def extends SettingsDefinition,
|
||||
Checks extends SettingsChecks<Def>,
|
||||
PrivateSettings extends object = {}
|
||||
>(def: Def, checks?: Checks) {
|
||||
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
|
||||
get store() {
|
||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||
return Settings.plugins[definedSettings.pluginName] as any;
|
||||
@ -264,11 +268,11 @@ export function definePluginSettings<D extends SettingsDefinition, C extends Set
|
||||
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
||||
).plugins[definedSettings.pluginName] as any,
|
||||
def,
|
||||
checks: checks ?? {},
|
||||
checks: checks ?? {} as any,
|
||||
pluginName: "",
|
||||
|
||||
withPrivateSettings<T>() {
|
||||
return this as DefinedSettings<D, C> & { store: T; };
|
||||
withPrivateSettings<T extends object>() {
|
||||
return this as DefinedSettings<Def, Checks, T>;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -146,3 +146,47 @@ export function OwnerCrownIcon(props: IconProps) {
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord's screenshare icon, as seen in the connection panel
|
||||
*/
|
||||
export function ScreenshareIcon(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
className={classes(props.className, "vc-screenshare-icon")}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function ImageVisible(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
className={classes(props.className, "vc-image-visible")}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
export function ImageInvisible(props: IconProps) {
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
className={classes(props.className, "vc-image-invisible")}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path fill="currentColor" d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
@ -138,11 +138,13 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
|
||||
}
|
||||
|
||||
const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
|
||||
const action = wasEnabled ? "stop" : "start";
|
||||
|
||||
if (!result) {
|
||||
logger.error(`Failed to ${action} plugin ${plugin.name}`);
|
||||
showErrorToast(`Failed to ${action} plugin: ${plugin.name}`);
|
||||
settings.enabled = false;
|
||||
|
||||
const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`;
|
||||
logger.error(msg);
|
||||
showErrorToast(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -171,7 +173,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
|
||||
);
|
||||
}
|
||||
|
||||
enum SearchStatus {
|
||||
const enum SearchStatus {
|
||||
ALL,
|
||||
ENABLED,
|
||||
DISABLED
|
||||
|
@ -57,7 +57,7 @@
|
||||
}
|
||||
|
||||
.vc-text-selectable,
|
||||
.vc-text-selectable :not(a, button, a *, button *, input, input *) {
|
||||
.vc-text-selectable :where([class*="text" i], [class*="title" i]) {
|
||||
/* make text selectable, silly discord makes the entirety of settings not selectable */
|
||||
user-select: text;
|
||||
|
||||
|
2
src/globals.d.ts
vendored
2
src/globals.d.ts
vendored
@ -37,6 +37,8 @@ declare global {
|
||||
export var IS_STANDALONE: boolean;
|
||||
export var IS_DISCORD_DESKTOP: boolean;
|
||||
export var IS_VENCORD_DESKTOP: boolean;
|
||||
export var VERSION: string;
|
||||
export var BUILD_TIMESTAMP: number;
|
||||
|
||||
export var VencordNative: typeof import("./VencordNative").default;
|
||||
export var Vencord: typeof import("./Vencord");
|
||||
|
@ -82,6 +82,8 @@ export default definePlugin({
|
||||
}
|
||||
}],
|
||||
|
||||
customSections: [] as ((ID: Record<string, unknown>) => any)[],
|
||||
|
||||
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
|
||||
return [
|
||||
{
|
||||
@ -123,11 +125,13 @@ export default definePlugin({
|
||||
label: "Patch Helper",
|
||||
element: require("@components/VencordSettings/PatchHelperTab").default,
|
||||
},
|
||||
// TODO: make this use customSections
|
||||
IS_VENCORD_DESKTOP && {
|
||||
section: "VencordDesktop",
|
||||
label: "Desktop Settings",
|
||||
element: VencordDesktop.Components.Settings,
|
||||
},
|
||||
...this.customSections.map(func => func(ID)),
|
||||
{
|
||||
section: ID.DIVIDER
|
||||
}
|
@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
enum Methods {
|
||||
const enum Methods {
|
||||
Random,
|
||||
Consistent,
|
||||
Timestamp,
|
||||
|
@ -104,6 +104,6 @@ export default definePlugin({
|
||||
|
||||
stop() {
|
||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); // clear status
|
||||
ws.close(); // close WebSocket
|
||||
ws?.close(); // close WebSocket
|
||||
}
|
||||
});
|
||||
|
@ -36,7 +36,8 @@ function Guilds(props: {
|
||||
// @ts-expect-error
|
||||
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
|
||||
|
||||
const scrollerProps = res.props.children?.props?.children?.[1]?.props;
|
||||
// TODO: Make this better
|
||||
const scrollerProps = res.props.children?.props?.children?.props?.children?.[1]?.props;
|
||||
if (scrollerProps?.children) {
|
||||
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
||||
if (servers) scrollerProps.children = servers;
|
||||
|
100
src/plugins/biggerStreamPreview/index.tsx
Normal file
100
src/plugins/biggerStreamPreview/index.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { ScreenshareIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openImageModal } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu } from "@webpack/common";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
|
||||
import { ApplicationStreamingStore, ApplicationStreamPreviewStore } from "./webpack/stores";
|
||||
import { ApplicationStream, Stream } from "./webpack/types/stores";
|
||||
|
||||
export interface UserContextProps {
|
||||
channel: Channel,
|
||||
channelSelected: boolean,
|
||||
className: string,
|
||||
config: { context: string; };
|
||||
context: string,
|
||||
onHeightUpdate: Function,
|
||||
position: string,
|
||||
target: HTMLElement,
|
||||
theme: string,
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface StreamContextProps {
|
||||
appContext: string,
|
||||
className: string,
|
||||
config: { context: string; };
|
||||
context: string,
|
||||
exitFullscreen: Function,
|
||||
onHeightUpdate: Function,
|
||||
position: string,
|
||||
target: HTMLElement,
|
||||
stream: Stream,
|
||||
theme: string,
|
||||
}
|
||||
|
||||
export const handleViewPreview = async ({ guildId, channelId, ownerId }: ApplicationStream | Stream) => {
|
||||
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
||||
if (!previewUrl) return;
|
||||
|
||||
openImageModal(previewUrl);
|
||||
};
|
||||
|
||||
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => () => {
|
||||
const stream = ApplicationStreamingStore.getAnyStreamForUser(userId);
|
||||
if (!stream) return;
|
||||
|
||||
const streamPreviewItem = (
|
||||
<Menu.MenuItem
|
||||
label="View Stream Preview"
|
||||
id="view-stream-preview"
|
||||
icon={ScreenshareIcon}
|
||||
action={() => stream && handleViewPreview(stream)}
|
||||
disabled={!stream}
|
||||
/>
|
||||
);
|
||||
|
||||
children.push(<Menu.MenuSeparator />, streamPreviewItem);
|
||||
};
|
||||
|
||||
export const streamContextPatch: NavContextMenuPatchCallback = (children, { stream }: StreamContextProps) => {
|
||||
return addViewStreamContext(children, { userId: stream.ownerId });
|
||||
};
|
||||
|
||||
export const userContextPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
|
||||
return addViewStreamContext(children, { userId: user.id });
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "BiggerStreamPreview",
|
||||
description: "This plugin allows you to enlarge stream previews",
|
||||
authors: [Devs.phil],
|
||||
start: () => {
|
||||
addContextMenuPatch("user-context", userContextPatch);
|
||||
addContextMenuPatch("stream-context", streamContextPatch);
|
||||
},
|
||||
stop: () => {
|
||||
removeContextMenuPatch("user-context", userContextPatch);
|
||||
removeContextMenuPatch("stream-context", streamContextPatch);
|
||||
}
|
||||
});
|
25
src/plugins/biggerStreamPreview/webpack/stores.ts
Normal file
25
src/plugins/biggerStreamPreview/webpack/stores.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { findStoreLazy } from "@webpack";
|
||||
|
||||
import * as t from "./types/stores";
|
||||
|
||||
export const ApplicationStreamPreviewStore: t.ApplicationStreamPreviewStore = findStoreLazy("ApplicationStreamPreviewStore");
|
||||
export const ApplicationStreamingStore: t.ApplicationStreamingStore = findStoreLazy("ApplicationStreamingStore");
|
77
src/plugins/biggerStreamPreview/webpack/types/stores.ts
Normal file
77
src/plugins/biggerStreamPreview/webpack/types/stores.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { FluxStore } from "@webpack/types";
|
||||
|
||||
export interface ApplicationStreamPreviewStore extends FluxStore {
|
||||
getIsPreviewLoading: (guildId: string | bigint | null, channelId: string | bigint, ownerId: string | bigint) => boolean;
|
||||
getPreviewURL: (guildId: string | bigint | null, channelId: string | bigint, ownerId: string | bigint) => Promise<string | null>;
|
||||
getPreviewURLForStreamKey: (streamKey: string) => ReturnType<ApplicationStreamPreviewStore["getPreviewURL"]>;
|
||||
}
|
||||
|
||||
export interface ApplicationStream {
|
||||
streamType: string;
|
||||
guildId: string | null;
|
||||
channelId: string;
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export interface Stream extends ApplicationStream {
|
||||
state: string;
|
||||
}
|
||||
|
||||
export interface RTCStream {
|
||||
region: string,
|
||||
streamKey: string,
|
||||
viewerIds: string[];
|
||||
}
|
||||
|
||||
export interface StreamMetadata {
|
||||
id: string | null,
|
||||
pid: number | null,
|
||||
sourceName: string | null;
|
||||
}
|
||||
|
||||
export interface StreamingStoreState {
|
||||
activeStreams: [string, Stream][];
|
||||
rtcStreams: { [key: string]: RTCStream; };
|
||||
streamerActiveStreamMetadatas: { [key: string]: StreamMetadata | null; };
|
||||
streamsByUserAndGuild: { [key: string]: { [key: string]: ApplicationStream; }; };
|
||||
}
|
||||
|
||||
/**
|
||||
* example how a stream key could look like: `call(type of connection):1116549917987192913(channelId):305238513941667851(ownerId)`
|
||||
*/
|
||||
export interface ApplicationStreamingStore extends FluxStore {
|
||||
getActiveStreamForApplicationStream: (stream: ApplicationStream) => Stream | null;
|
||||
getActiveStreamForStreamKey: (streamKey: string) => Stream | null;
|
||||
getActiveStreamForUser: (userId: string | bigint, guildId?: string | bigint | null) => Stream | null;
|
||||
getAllActiveStreams: () => Stream[];
|
||||
getAllApplicationStreams: () => ApplicationStream[];
|
||||
getAllApplicationStreamsForChannel: (channelId: string | bigint) => ApplicationStream[];
|
||||
getAllActiveStreamsForChannel: (channelId: string | bigint) => Stream[];
|
||||
getAnyStreamForUser: (userId: string | bigint) => Stream | ApplicationStream | null;
|
||||
getStreamForUser: (userId: string | bigint, guildId?: string | bigint | null) => Stream | null;
|
||||
getCurrentUserActiveStream: () => Stream | null;
|
||||
getLastActiveStream: () => Stream | null;
|
||||
getState: () => StreamingStoreState;
|
||||
getRTCStream: (streamKey: string) => RTCStream | null;
|
||||
getStreamerActiveStreamMetadata: () => StreamMetadata;
|
||||
getViewerIds: (stream: ApplicationStream) => string[];
|
||||
isSelfStreamHidden: (channelId: string | bigint | null) => boolean;
|
||||
}
|
@ -74,7 +74,7 @@ interface Activity {
|
||||
flags: number;
|
||||
}
|
||||
|
||||
enum ActivityType {
|
||||
const enum ActivityType {
|
||||
PLAYING = 0,
|
||||
LISTENING = 2,
|
||||
WATCHING = 3,
|
||||
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ApplicationCommandOptionType } from "@api/Commands";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { makeRange } from "@components/PluginSettings/components";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "Fart2",
|
||||
authors: [Devs.Animal],
|
||||
description: "Enable farting v2, a slash command that allows you to perform or request that someone perform a little toot.",
|
||||
dependencies: ["CommandsAPI"],
|
||||
commands: [{
|
||||
name: "fart",
|
||||
description: "A simple command in which you may either request that a user do a little toot for you, or conduct one yourself.",
|
||||
options: [
|
||||
{
|
||||
type: ApplicationCommandOptionType.USER,
|
||||
name: "user",
|
||||
description: "A Discord™ user of which you would humbly request a toot from.",
|
||||
required: false
|
||||
}
|
||||
],
|
||||
|
||||
execute(args) {
|
||||
const fart = new Audio("https://raw.githubusercontent.com/ItzOnlyAnimal/AliuPlugins/main/fart.mp3");
|
||||
fart.volume = Settings.plugins.Fart2.volume;
|
||||
fart.play();
|
||||
|
||||
return {
|
||||
content: (args[0]) ? `<@${args[0].value}> fart` : "fart"
|
||||
};
|
||||
},
|
||||
}],
|
||||
options: {
|
||||
volume: {
|
||||
description: "how loud you wanna fart (aka volume)",
|
||||
type: OptionType.SLIDER,
|
||||
markers: makeRange(0, 1, 0.1),
|
||||
default: 0.5,
|
||||
stickToMarkers: false,
|
||||
}
|
||||
}
|
||||
});
|
@ -18,6 +18,7 @@
|
||||
|
||||
import { get, set } from "@api/DataStore";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore } from "@webpack/common";
|
||||
@ -26,17 +27,6 @@ let style: HTMLStyleElement;
|
||||
|
||||
const KEY = "HideAttachments_HiddenIds";
|
||||
|
||||
const ImageVisible = () => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
|
||||
</svg>
|
||||
);
|
||||
const ImageInvisible = () => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
let hiddenMessages: Set<string> = new Set();
|
||||
const getHiddenMessages = () => get(KEY).then(set => {
|
||||
hiddenMessages = set ?? new Set<string>();
|
||||
|
@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Tooltip } from "webpack/common";
|
||||
|
||||
enum ActivitiesTypes {
|
||||
const enum ActivitiesTypes {
|
||||
Game,
|
||||
Embedded
|
||||
}
|
||||
|
@ -63,12 +63,12 @@ interface TrackData {
|
||||
}
|
||||
|
||||
// only relevant enum values
|
||||
enum ActivityType {
|
||||
const enum ActivityType {
|
||||
PLAYING = 0,
|
||||
LISTENING = 2,
|
||||
}
|
||||
|
||||
enum ActivityFlag {
|
||||
const enum ActivityFlag {
|
||||
INSTANCE = 1 << 0,
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ export default definePlugin({
|
||||
document.addEventListener("keydown", keydown);
|
||||
document.addEventListener("keyup", keyup);
|
||||
|
||||
this.onClick = addClickListener((msg, channel, event) => {
|
||||
this.onClick = addClickListener((msg: any, channel, event) => {
|
||||
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||
if (!isDeletePressed) {
|
||||
if (event.detail < 2) return;
|
||||
@ -90,7 +90,16 @@ export default definePlugin({
|
||||
});
|
||||
}
|
||||
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, channel))) {
|
||||
MessageActions.deleteMessage(channel.id, msg.id);
|
||||
if (msg.deleted) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: channel.id,
|
||||
id: msg.id,
|
||||
mlDeleted: true
|
||||
});
|
||||
} else {
|
||||
MessageActions.deleteMessage(channel.id, msg.id);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,10 @@
|
||||
.messagelogger-deleted :is(div, h1, h2, h3, p) {
|
||||
/* Message content highlighting */
|
||||
.messagelogger-deleted [class*="contents-"] > :is(div, h1, h2, h3, p) {
|
||||
color: #f04747 !important;
|
||||
}
|
||||
|
||||
/* Embed highlighting */
|
||||
.messagelogger-deleted article :is(div, span, h1, h2, h3, p) {
|
||||
color: #f04747 !important;
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,11 @@ export default definePlugin({
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Whether to ignore messages by yourself",
|
||||
default: false
|
||||
},
|
||||
ignoreUsers: {
|
||||
type: OptionType.STRING,
|
||||
description: "Comma-separated list of user IDs to ignore",
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
@ -154,7 +159,7 @@ export default definePlugin({
|
||||
try {
|
||||
if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
|
||||
|
||||
const { ignoreBots, ignoreSelf } = Settings.plugins.MessageLogger;
|
||||
const { ignoreBots, ignoreSelf, ignoreUsers } = Settings.plugins.MessageLogger;
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
function mutate(id: string) {
|
||||
@ -165,7 +170,8 @@ export default definePlugin({
|
||||
const shouldIgnore = data.mlDeleted ||
|
||||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
|
||||
ignoreBots && msg.author?.bot ||
|
||||
ignoreSelf && msg.author?.id === myId;
|
||||
ignoreSelf && msg.author?.id === myId ||
|
||||
ignoreUsers.includes(msg.author?.id);
|
||||
|
||||
if (shouldIgnore) {
|
||||
cache = cache.remove(id);
|
||||
|
@ -2,16 +2,29 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.messagelogger-deleted :is(video, .emoji, [data-type="sticker"]),
|
||||
.messagelogger-deleted .messagelogger-deleted-attachment,
|
||||
.messagelogger-deleted div iframe {
|
||||
.messagelogger-deleted
|
||||
:is(
|
||||
video,
|
||||
.emoji,
|
||||
[data-type="sticker"],
|
||||
iframe,
|
||||
.messagelogger-deleted-attachment,
|
||||
[class|="inlineMediaEmbed"]
|
||||
) {
|
||||
filter: grayscale(1) !important;
|
||||
transition: 150ms filter ease-in-out;
|
||||
}
|
||||
|
||||
.messagelogger-deleted:hover :is(video, .emoji, [data-type="sticker"]),
|
||||
.messagelogger-deleted .messagelogger-deleted-attachment:hover,
|
||||
.messagelogger-deleted iframe:hover {
|
||||
.messagelogger-deleted
|
||||
:is(
|
||||
video,
|
||||
.emoji,
|
||||
[data-type="sticker"],
|
||||
iframe,
|
||||
.messagelogger-deleted-attachment,
|
||||
[class|="inlineMediaEmbed"]
|
||||
):hover,
|
||||
.messagelogger-deleted {
|
||||
filter: grayscale(0) !important;
|
||||
}
|
||||
|
||||
|
@ -18,37 +18,8 @@
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByProps, findStoreLazy } from "@webpack";
|
||||
import { Button, Text } from "@webpack/common";
|
||||
|
||||
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
|
||||
|
||||
function NoDMNotificationsModal({ modalProps }: { modalProps: ModalProps; }) {
|
||||
return (
|
||||
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||
<ModalContent>
|
||||
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center", "alignItems": "center", textAlign: "center", height: "100%", padding: "8px 0", gap: "16px" }}>
|
||||
<Text variant="text-lg/semibold">You seem to have been affected by a bug that caused DM notifications to be muted and break if you used the MuteNewGuild plugin.</Text>
|
||||
<Text variant="text-lg/semibold">If you haven't received any notifications for private messages, this is why. This issue is now fixed, so they should work again. Please verify, and in case they are still broken, ask for help in the Vencord support channel!</Text>
|
||||
<Text variant="text-lg/semibold">We're very sorry for any inconvenience caused by this issue :(</Text>
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<div style={{ display: "flex", justifyContent: "center", width: "100%" }}>
|
||||
<Button
|
||||
onClick={modalProps.onClose}
|
||||
size={Button.Sizes.MEDIUM}
|
||||
color={Button.Colors.BRAND}
|
||||
>
|
||||
Understood!
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
import { findByProps } from "@webpack";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
guild: {
|
||||
@ -92,15 +63,5 @@ export default definePlugin({
|
||||
suppress_roles: settings.store.role
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
start() {
|
||||
const [isMuted, isEveryoneSupressed, isRolesSupressed] = [UserGuildSettingsStore.isMuted(null), UserGuildSettingsStore.isSuppressEveryoneEnabled(null), UserGuildSettingsStore.isSuppressRolesEnabled(null)];
|
||||
|
||||
if (isMuted || isEveryoneSupressed || isRolesSupressed) {
|
||||
findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(null, { muted: false, suppress_everyone: false, suppress_roles: false });
|
||||
|
||||
openModal(modalProps => <NoDMNotificationsModal modalProps={modalProps} />);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
104
src/plugins/mutualGroupDMs.tsx
Normal file
104
src/plugins/mutualGroupDMs.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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 { Devs } from "@utils/constants";
|
||||
import { isNonNullish } from "@utils/guards";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
|
||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||
const AvatarUtils = findByPropsLazy("getChannelIconURL");
|
||||
const UserUtils = findByPropsLazy("getGlobalName");
|
||||
|
||||
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
|
||||
const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon");
|
||||
|
||||
function getGroupDMName(channel: Channel) {
|
||||
return channel.name ||
|
||||
channel.recipients
|
||||
.map(UserStore.getUser)
|
||||
.filter(isNonNullish)
|
||||
.map(c => RelationshipStore.getNickname(c.id) || UserUtils.getName(c))
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "MutualGroupDMs",
|
||||
description: "Shows mutual group dms in profiles",
|
||||
authors: [Devs.amia],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
|
||||
replace: '$1?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
},
|
||||
{
|
||||
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
renderMutualGDMs(user: User, onClose: () => void) {
|
||||
const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => (
|
||||
<Clickable
|
||||
className={ProfileListClasses.listRow}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
SelectedChannelActionCreators.selectPrivateChannel(c.id);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={AvatarUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
||||
size="SIZE_40"
|
||||
className={ProfileListClasses.listAvatar}
|
||||
>
|
||||
</Avatar>
|
||||
<div className={ProfileListClasses.listRowContent}>
|
||||
<div className={ProfileListClasses.listName}>{getGroupDMName(c)}</div>
|
||||
<div className={GuildLabelClasses.guildNick}>{c.recipients.length + 1} Members</div>
|
||||
</div>
|
||||
</Clickable>
|
||||
));
|
||||
|
||||
return (
|
||||
<ScrollerThin
|
||||
className={ProfileListClasses.listScroller}
|
||||
fade={true}
|
||||
onClose={onClose}
|
||||
>
|
||||
{entries.length > 0
|
||||
? entries
|
||||
: (
|
||||
<div className={ProfileListClasses.empty}>
|
||||
<div className={ProfileListClasses.emptyIconFriends}></div>
|
||||
<div className={ProfileListClasses.emptyText}>No group dms in common</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</ScrollerThin>
|
||||
);
|
||||
}
|
||||
});
|
97
src/plugins/noPendingCount.ts
Normal file
97
src/plugins/noPendingCount.ts
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
const MessageRequestStore = findByPropsLazy("getMessageRequestsCount");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
hideFriendRequestsCount: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Hide incoming friend requests count",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
hideMessageRequestsCount: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Hide message requests count",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
hidePremiumOffersCount: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Hide nitro offers count",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoPendingCount",
|
||||
description: "Removes the ping count of incoming friend requests, message requests, and nitro offers.",
|
||||
authors: [Devs.amia],
|
||||
|
||||
settings: settings,
|
||||
|
||||
// Functions used to determine the top left count indicator can be found in the single module that calls getUnacknowledgedOffers(...)
|
||||
// or by searching for "showProgressBadge:"
|
||||
patches: [
|
||||
{
|
||||
find: ".getPendingCount=",
|
||||
predicate: () => settings.store.hideFriendRequestsCount,
|
||||
replacement: {
|
||||
match: /(?<=\.getPendingCount=function\(\)\{)/,
|
||||
replace: "return 0;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".getMessageRequestsCount=",
|
||||
predicate: () => settings.store.hideMessageRequestsCount,
|
||||
replacement: {
|
||||
match: /(?<=\.getMessageRequestsCount=function\(\)\{)/,
|
||||
replace: "return 0;"
|
||||
}
|
||||
},
|
||||
// This prevents the Message Requests tab from always hiding due to the previous patch (and is compatible with spam requests)
|
||||
// In short, only the red badge is hidden. Button visibility behavior isn't changed.
|
||||
{
|
||||
find: ".getSpamChannelsCount(),",
|
||||
predicate: () => settings.store.hideMessageRequestsCount,
|
||||
replacement: {
|
||||
match: /(?<=getSpamChannelsCount\(\),\i=)\i\.getMessageRequestsCount\(\)/,
|
||||
replace: "$self.getRealMessageRequestCount()"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "showProgressBadge:",
|
||||
predicate: () => settings.store.hidePremiumOffersCount,
|
||||
replacement: {
|
||||
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
|
||||
replace: "(function(){return 0})"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
getRealMessageRequestCount() {
|
||||
return MessageRequestStore.getMessageRequestChannelIds().size;
|
||||
}
|
||||
});
|
53
src/plugins/noProfileThemes.ts
Normal file
53
src/plugins/noProfileThemes.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoProfileThemes",
|
||||
description: "Completely removes Nitro profile themes",
|
||||
authors: [Devs.TheKodeToad],
|
||||
patches: [
|
||||
{
|
||||
find: ".NITRO_BANNER,",
|
||||
replacement: {
|
||||
// = isPremiumAtLeast(user.premiumType, TIER_2)
|
||||
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
|
||||
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
|
||||
replace: "=$1?.banner&&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "().avatarPositionPremiumNoBanner,default:",
|
||||
replacement: {
|
||||
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
|
||||
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\(\)\.(\i))/,
|
||||
// premiumUserWithoutBanner: foo().avatarPositionNormal...
|
||||
replace: ".$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".hasThemeColors=function(){",
|
||||
replacement: {
|
||||
match: /(?<=key:"canUsePremiumProfileCustomization",get:function\(\){return)/,
|
||||
replace: " false;"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
@ -23,6 +23,7 @@ import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, Mod
|
||||
import { ContextMenu, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||
import type { Guild } from "discord-types/general";
|
||||
|
||||
import { settings } from "..";
|
||||
import { cl, getPermissionDescription, getPermissionString } from "../utils";
|
||||
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
|
||||
|
||||
@ -108,7 +109,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||
<div
|
||||
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
|
||||
onContextMenu={e => {
|
||||
if (permission.type === PermissionType.Role)
|
||||
if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role)
|
||||
ContextMenu.open(e, () => (
|
||||
<RoleContextMenu
|
||||
guild={guild}
|
||||
|
@ -63,6 +63,7 @@
|
||||
grid-area: list;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
border-right: 2px solid var(--background-modifier-active);
|
||||
}
|
||||
|
||||
@ -77,6 +78,15 @@
|
||||
padding: 8px 5px;
|
||||
cursor: pointer;
|
||||
width: 230px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-list-item:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-list-item-active {
|
||||
background-color: var(--background-modifier-selected);
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-list-item > div {
|
||||
@ -85,11 +95,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-list-item-active {
|
||||
background-color: var(--background-modifier-selected);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.vc-permviewer-perms-role-circle {
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
|
@ -52,7 +52,7 @@ export default definePlugin({
|
||||
find: ".userTagNoNickname",
|
||||
replacement: {
|
||||
match: /=(\i)\.pronouns/,
|
||||
replace: "=$self.useProfilePronouns($1.user.id)"
|
||||
replace: "=$self.useProfilePronouns($1.user.id,$1.pronouns)"
|
||||
}
|
||||
},
|
||||
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
|
||||
@ -60,7 +60,7 @@ export default definePlugin({
|
||||
find: ".USER_PROFILE_ACTIVITY",
|
||||
replacement: {
|
||||
match: /\).showPronouns/,
|
||||
replace: ").showPronouns||true;const vcPronounce=$self.useProfilePronouns(arguments[0].user.id);if(arguments[0].displayProfile)arguments[0].displayProfile.pronouns=vcPronounce"
|
||||
replace: ").showPronouns||true;const vcPronounce=$self.useProfilePronouns(arguments[0].user.id,arguments[0].displayProfile?.pronouns);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -19,17 +19,26 @@
|
||||
import { Settings } from "@api/Settings";
|
||||
import { VENCORD_USER_AGENT } from "@utils/constants";
|
||||
import { debounce } from "@utils/debounce";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
||||
|
||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
||||
|
||||
export const enum PronounsFormat {
|
||||
Lowercase = "LOWERCASE",
|
||||
Capitalized = "CAPITALIZED"
|
||||
}
|
||||
|
||||
export const enum PronounSource {
|
||||
PreferPDB,
|
||||
PreferDiscord
|
||||
}
|
||||
|
||||
// A map of cached pronouns so the same request isn't sent twice
|
||||
const cache: Record<string, PronounCode> = {};
|
||||
// A map of ids and callbacks that should be triggered on fetch
|
||||
@ -46,21 +55,29 @@ const bulkFetch = debounce(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
export function useFormattedPronouns(id: string): string | null {
|
||||
const [result] = useAwaiter(() => fetchPronouns(id), {
|
||||
fallbackValue: getCachedPronouns(id),
|
||||
function getDiscordPronouns(id: string) {
|
||||
return (
|
||||
UserProfileStore.getGuildMemberProfile(id, getCurrentChannel()?.guild_id)?.pronouns
|
||||
|| UserProfileStore.getUserProfile(id)?.pronouns
|
||||
);
|
||||
}
|
||||
|
||||
export function useFormattedPronouns(id: string, discordPronouns: string = getDiscordPronouns(id)): string | null {
|
||||
const [result] = useAwaiter(() => fetchPronouns(id, discordPronouns), {
|
||||
fallbackValue: getCachedPronouns(id, discordPronouns),
|
||||
onError: e => console.error("Fetching pronouns failed: ", e)
|
||||
});
|
||||
|
||||
// If the result is present and not "unspecified", and there is a mapping for the code, then return the mappings
|
||||
if (result && result !== "unspecified" && PronounMapping[result])
|
||||
return formatPronouns(result);
|
||||
if (result && result !== "unspecified")
|
||||
return Object.hasOwn(PronounMapping, result)
|
||||
? formatPronouns(result) // PronounDB
|
||||
: result; // Discord
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useProfilePronouns(id: string) {
|
||||
const pronouns = useFormattedPronouns(id);
|
||||
export function useProfilePronouns(id: string, discordPronouns: string) {
|
||||
const pronouns = useFormattedPronouns(id, discordPronouns);
|
||||
|
||||
if (!settings.store.showInProfile) return null;
|
||||
if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) return null;
|
||||
@ -70,22 +87,28 @@ export function useProfilePronouns(id: string) {
|
||||
|
||||
|
||||
// Gets the cached pronouns, if you're too impatient for a promise!
|
||||
export function getCachedPronouns(id: string): PronounCode | null {
|
||||
return cache[id] ?? null;
|
||||
export function getCachedPronouns(id: string, discordPronouns: string): string | null {
|
||||
if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns)
|
||||
return discordPronouns;
|
||||
|
||||
const cached = cache[id];
|
||||
if (cached && cached !== "unspecified") return cached;
|
||||
|
||||
return discordPronouns || cached || null;
|
||||
}
|
||||
|
||||
// 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, discordPronouns: string): Promise<string> {
|
||||
return new Promise(res => {
|
||||
// If cached, return the cached pronouns
|
||||
if (id in cache) res(getCachedPronouns(id)!);
|
||||
const cached = getCachedPronouns(id, discordPronouns);
|
||||
if (cached) return res(cached);
|
||||
|
||||
// If there is already a request added, then just add this callback to it
|
||||
else if (id in requestQueue) requestQueue[id].push(res);
|
||||
if (id in requestQueue) return requestQueue[id].push(res);
|
||||
|
||||
// If not already added, then add it and call the debounced function to make sure the request gets executed
|
||||
else {
|
||||
requestQueue[id] = [res];
|
||||
bulkFetch();
|
||||
}
|
||||
requestQueue[id] = [res];
|
||||
bulkFetch();
|
||||
});
|
||||
}
|
||||
|
||||
@ -116,7 +139,7 @@ async function bulkFetchPronouns(ids: string[]): Promise<PronounsResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatPronouns(pronouns: PronounCode): string {
|
||||
export function formatPronouns(pronouns: string): string {
|
||||
const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; };
|
||||
// For capitalized pronouns, just return the mapping (it is by default capitalized)
|
||||
if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns];
|
||||
|
@ -19,7 +19,7 @@
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
import { PronounsFormat } from "./pronoundbUtils";
|
||||
import { PronounsFormat, PronounSource } from "./pronoundbUtils";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
pronounsFormat: {
|
||||
@ -37,6 +37,21 @@ export const settings = definePluginSettings({
|
||||
}
|
||||
]
|
||||
},
|
||||
pronounSource: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Where to source pronouns from",
|
||||
options: [
|
||||
{
|
||||
label: "Prefer PronounDB, fall back to Discord",
|
||||
value: PronounSource.PreferPDB,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "Prefer Discord, fall back to PronounDB (might lead to inconsistency between pronouns in chat and profile)",
|
||||
value: PronounSource.PreferDiscord
|
||||
}
|
||||
]
|
||||
},
|
||||
showSelf: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable or disable showing pronouns for the current user",
|
||||
|
@ -159,9 +159,12 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
|
||||
return i === - 1 ? undefined : messages[messages.length - i - 1];
|
||||
}
|
||||
|
||||
function shouldMention() {
|
||||
function shouldMention(message) {
|
||||
const { enabled, userList, shouldPingListed } = Settings.plugins.NoReplyMention;
|
||||
const shouldPing = !enabled || (shouldPingListed === userList.includes(message.author.id));
|
||||
|
||||
switch (settings.store.shouldMention) {
|
||||
case MentionOptions.NO_REPLY_MENTION_PLUGIN: return !Settings.plugins.NoReplyMention.enabled;
|
||||
case MentionOptions.NO_REPLY_MENTION_PLUGIN: return shouldPing;
|
||||
case MentionOptions.DISABLED: return false;
|
||||
default: return true;
|
||||
}
|
||||
@ -184,7 +187,7 @@ function nextReply(isUp: boolean) {
|
||||
type: "CREATE_PENDING_REPLY",
|
||||
channel,
|
||||
message,
|
||||
shouldMention: shouldMention(),
|
||||
shouldMention: shouldMention(message),
|
||||
showMentionToggle: channel.guild_id !== null && message.author.id !== meId,
|
||||
_isQuickReply: true
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
const SpoilerClasses = findByPropsLazy("spoilerText");
|
||||
const SpoilerClasses = findByPropsLazy("spoilerContent");
|
||||
const MessagesClasses = findByPropsLazy("messagesWrapper", "messages");
|
||||
|
||||
export default definePlugin({
|
||||
@ -43,14 +43,14 @@ export default definePlugin({
|
||||
|
||||
if (!ctrlKey) { return; }
|
||||
|
||||
const { spoilerText, hidden } = SpoilerClasses;
|
||||
const { spoilerContent, hidden } = SpoilerClasses;
|
||||
const { messagesWrapper } = MessagesClasses;
|
||||
|
||||
const parent = shiftKey
|
||||
? document.querySelector(`div.${messagesWrapper}`)
|
||||
: (target as HTMLSpanElement).parentElement;
|
||||
|
||||
for (const spoiler of parent!.querySelectorAll(`span.${spoilerText}.${hidden}`)) {
|
||||
for (const spoiler of parent!.querySelectorAll(`span.${spoilerContent}.${hidden}`)) {
|
||||
(spoiler as HTMLSpanElement).click();
|
||||
}
|
||||
}
|
||||
|
@ -79,8 +79,8 @@ export default definePlugin({
|
||||
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
||||
|
||||
if (user.banInfo) {
|
||||
const endDate = new Date(user.banInfo.banEndDate).getTime();
|
||||
if (endDate > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate) {
|
||||
const endDate = new Date(user.banInfo.banEndDate);
|
||||
if (endDate.getTime() > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate.getTime()) {
|
||||
Alerts.show({
|
||||
title: "You have been banned from ReviewDB",
|
||||
body: (
|
||||
|
@ -50,9 +50,9 @@ export function authorize(callback?: any) {
|
||||
permissions={0n}
|
||||
clientId="915703782174752809"
|
||||
cancelCompletesFlow={false}
|
||||
callback={async (u: string) => {
|
||||
callback={async (response: any) => {
|
||||
try {
|
||||
const url = new URL(u);
|
||||
const url = new URL(response.location);
|
||||
url.searchParams.append("clientMod", "vencord");
|
||||
const res = await fetch(url, {
|
||||
headers: new Headers({ Accept: "application/json" })
|
||||
|
@ -24,7 +24,7 @@ import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common";
|
||||
|
||||
enum IndicatorType {
|
||||
const enum IndicatorType {
|
||||
SERVER = 1 << 0,
|
||||
FRIEND = 1 << 1,
|
||||
BOTH = SERVER | FRIEND,
|
||||
|
@ -46,18 +46,18 @@ export type ShikiSpec = {
|
||||
}) => Promise<IThemedToken[][]>;
|
||||
};
|
||||
|
||||
export enum StyleSheets {
|
||||
export const enum StyleSheets {
|
||||
Main = "MAIN",
|
||||
DevIcons = "DEVICONS",
|
||||
}
|
||||
|
||||
export enum HljsSetting {
|
||||
export const enum HljsSetting {
|
||||
Never = "NEVER",
|
||||
Secondary = "SECONDARY",
|
||||
Primary = "PRIMARY",
|
||||
Always = "ALWAYS",
|
||||
}
|
||||
export enum DeviconSetting {
|
||||
export const enum DeviconSetting {
|
||||
Disabled = "DISABLED",
|
||||
Greyscale = "GREYSCALE",
|
||||
Color = "COLOR"
|
||||
|
@ -29,12 +29,12 @@ import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission
|
||||
import { sortPermissionOverwrites } from "../../permissionsViewer/utils";
|
||||
import { settings, VIEW_CHANNEL } from "..";
|
||||
|
||||
enum SortOrderTypes {
|
||||
const enum SortOrderTypes {
|
||||
LATEST_ACTIVITY = 0,
|
||||
CREATION_DATE = 1
|
||||
}
|
||||
|
||||
enum ForumLayoutTypes {
|
||||
const enum ForumLayoutTypes {
|
||||
DEFAULT = 0,
|
||||
LIST = 1,
|
||||
GRID = 2
|
||||
@ -61,7 +61,7 @@ interface ExtendedChannel extends Channel {
|
||||
availableTags?: Array<Tag>;
|
||||
}
|
||||
|
||||
enum ChannelTypes {
|
||||
const enum ChannelTypes {
|
||||
GUILD_TEXT = 0,
|
||||
GUILD_VOICE = 2,
|
||||
GUILD_ANNOUNCEMENT = 5,
|
||||
@ -69,12 +69,12 @@ enum ChannelTypes {
|
||||
GUILD_FORUM = 15
|
||||
}
|
||||
|
||||
enum VideoQualityModes {
|
||||
const enum VideoQualityModes {
|
||||
AUTO = 1,
|
||||
FULL = 2
|
||||
}
|
||||
|
||||
enum ChannelFlags {
|
||||
const enum ChannelFlags {
|
||||
PINNED = 1 << 1,
|
||||
REQUIRE_TAG = 1 << 4
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted
|
||||
export const VIEW_CHANNEL = 1n << 10n;
|
||||
const CONNECT = 1n << 20n;
|
||||
|
||||
enum ShowMode {
|
||||
const enum ShowMode {
|
||||
LockIcon,
|
||||
HiddenIconWithMutedStyle
|
||||
}
|
||||
@ -107,8 +107,8 @@ export default definePlugin({
|
||||
},
|
||||
{
|
||||
// Prevent Discord from trying to connect to hidden channels
|
||||
match: /if\(!\i&&!\i(?=.{0,50}?selectVoiceChannel\((\i)\.id\))/,
|
||||
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
|
||||
match: /(?=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\))/,
|
||||
replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
|
||||
},
|
||||
{
|
||||
// Make Discord show inside the channel if clicking on a hidden or locked channel
|
||||
@ -419,6 +419,14 @@ export default definePlugin({
|
||||
match: /(?<=getChannels\(\i)(?=\))/,
|
||||
replace: ",true"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '.displayName="NowPlayingViewStore"',
|
||||
replacement: {
|
||||
// Make active now voice states on hiddenl channels
|
||||
match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/,
|
||||
replace: "$1"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -63,6 +63,7 @@ export default definePlugin({
|
||||
settings,
|
||||
|
||||
renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix }: UsernameProps) => {
|
||||
if (message.interaction) return author?.nick;
|
||||
try {
|
||||
const { username } = message.author;
|
||||
const { nick } = author;
|
||||
|
@ -27,7 +27,7 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
|
||||
import gitHash from "~git-hash";
|
||||
import plugins from "~plugins";
|
||||
|
||||
import settings from "./settings";
|
||||
import settings from "./_core/settings";
|
||||
|
||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
||||
|
||||
@ -55,7 +55,10 @@ export default definePlugin({
|
||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||
if (IS_VENCORD_DESKTOP) return `Vencord Desktop v${VencordDesktopNative.app.getVersion()}`;
|
||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
||||
return `Web (${navigator.userAgent})`;
|
||||
|
||||
// @ts-expect-error
|
||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||
return `${name} (${navigator.userAgent})`;
|
||||
})();
|
||||
|
||||
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
|
||||
@ -63,14 +66,18 @@ export default definePlugin({
|
||||
const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));
|
||||
const enabledApiPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && isApiPlugin(p));
|
||||
|
||||
const info = {
|
||||
Vencord: `v${VERSION} • ${gitHash}${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
||||
"Discord Branch": RELEASE_CHANNEL,
|
||||
Client: client,
|
||||
Platform: window.navigator.platform,
|
||||
Outdated: isOutdated,
|
||||
OpenAsar: "openasar" in window,
|
||||
};
|
||||
|
||||
const debugInfo = `
|
||||
**Vencord Debug Info**
|
||||
>>> Discord Branch: ${RELEASE_CHANNEL}
|
||||
Client: ${client}
|
||||
Platform: ${window.navigator.platform}
|
||||
Vencord: ${gitHash}${settings.additionalInfo}
|
||||
Outdated: ${isOutdated}
|
||||
OpenAsar: ${"openasar" in window}
|
||||
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
|
||||
|
||||
Enabled Plugins (${enabledPlugins.length + enabledApiPlugins.length}):
|
||||
${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "))}
|
||||
|
67
src/plugins/unsuppressEmbeds.tsx
Normal file
67
src/plugins/unsuppressEmbeds.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
||||
|
||||
const EMBED_SUPPRESSED = 1 << 2;
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => () => {
|
||||
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
|
||||
if (!isEmbedSuppressed && !embeds.length) return;
|
||||
|
||||
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
|
||||
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;
|
||||
|
||||
const menuGroup = findGroupChildrenByChildId("delete", children);
|
||||
const deleteIndex = menuGroup?.findIndex(i => i?.props?.id === "delete");
|
||||
if (!deleteIndex || !menuGroup) return;
|
||||
|
||||
menuGroup.splice(deleteIndex - 1, 0, (
|
||||
<Menu.MenuItem
|
||||
id="unsuppress-embeds"
|
||||
key="unsuppress-embeds"
|
||||
label={isEmbedSuppressed ? "Unsuppress Embeds" : "Suppress Embeds"}
|
||||
color={isEmbedSuppressed ? undefined : "danger"}
|
||||
icon={isEmbedSuppressed ? ImageVisible : ImageInvisible}
|
||||
action={() =>
|
||||
RestAPI.patch({
|
||||
url: `/channels/${channel.id}/messages/${messageId}`,
|
||||
body: { flags: isEmbedSuppressed ? flags & ~EMBED_SUPPRESSED : flags | EMBED_SUPPRESSED }
|
||||
})
|
||||
}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "UnsuppressEmbeds",
|
||||
authors: [Devs.rad, Devs.HypedDomi],
|
||||
description: "Allows you to unsuppress embeds in messages",
|
||||
|
||||
start() {
|
||||
addContextMenuPatch("message", messageContextMenuPatch);
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
||||
},
|
||||
});
|
@ -65,6 +65,7 @@ const replacements = [
|
||||
["stupid", "baka"],
|
||||
["what", "nani"],
|
||||
["meow", "nya~"],
|
||||
["hello", "hewwo"],
|
||||
];
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { UserStore, useState } from "@webpack/common";
|
||||
import type { User } from "discord-types/general";
|
||||
import type { ComponentType } from "react";
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
|
||||
const fetching = new Set<string>();
|
||||
const queue = new Queue(5);
|
||||
@ -36,7 +36,7 @@ interface MentionProps {
|
||||
channelId?: string;
|
||||
content: any;
|
||||
};
|
||||
parse: (content: any, props: MentionProps["props"]) => string[];
|
||||
parse: (content: any, props: MentionProps["props"]) => ReactNode;
|
||||
props: {
|
||||
key: string;
|
||||
formatInline: boolean;
|
||||
@ -72,7 +72,7 @@ function MentionWrapper({ data, UserMention, RoleMention, parse, props }: Mentio
|
||||
>
|
||||
<span
|
||||
onMouseEnter={() => {
|
||||
const mention = children?.[0];
|
||||
const mention = children?.[0]?.props?.children;
|
||||
if (typeof mention !== "string") return;
|
||||
|
||||
const id = mention.match(/<@(\d+)>/)?.[1];
|
||||
|
@ -71,7 +71,7 @@ function clean(str: string) {
|
||||
|
||||
function formatText(str: string, user: string, channel: string) {
|
||||
return str
|
||||
.replaceAll("{{USER}}", clean(user) || user ? "Someone" : "")
|
||||
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
||||
.replaceAll("{{CHANNEL}}", clean(channel) || "channel");
|
||||
}
|
||||
|
||||
|
@ -20,15 +20,12 @@ import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatc
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { ImageIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { openImageModal } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { find, findByCode, findByPropsLazy } from "@webpack";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { GuildMemberStore, Menu } from "@webpack/common";
|
||||
import type { Channel, Guild, User } from "discord-types/general";
|
||||
|
||||
const ImageModal = LazyComponent(() => findByCode(".MEDIA_MODAL_CLOSE,"));
|
||||
const MaskedLink = LazyComponent(() => find(m => m.type?.toString().includes("MASKED_LINK)")));
|
||||
const BannerStore = findByPropsLazy("getGuildBannerURL");
|
||||
|
||||
interface UserContextProps {
|
||||
@ -60,26 +57,29 @@ const settings = definePluginSettings({
|
||||
value: "jpg",
|
||||
}
|
||||
]
|
||||
},
|
||||
imgSize: {
|
||||
type: OptionType.SELECT,
|
||||
description: "The image size to use",
|
||||
options: ["128", "256", "512", "1024", "2048", "4096"].map(n => ({ label: n, value: n, default: n === "1024" }))
|
||||
}
|
||||
});
|
||||
|
||||
function openImage(url: string) {
|
||||
const format = url.startsWith("/") ? "png" : settings.store.format;
|
||||
|
||||
const u = new URL(url, window.location.href);
|
||||
u.searchParams.set("size", "512");
|
||||
u.searchParams.set("size", settings.store.imgSize);
|
||||
u.pathname = u.pathname.replace(/\.(png|jpe?g|webp)$/, `.${format}`);
|
||||
url = u.toString();
|
||||
|
||||
openModal(modalProps => (
|
||||
<ModalRoot size={ModalSize.DYNAMIC} {...modalProps}>
|
||||
<ImageModal
|
||||
shouldAnimate={true}
|
||||
original={url}
|
||||
src={url}
|
||||
renderLinkComponent={MaskedLink}
|
||||
/>
|
||||
</ModalRoot>
|
||||
));
|
||||
u.searchParams.set("size", "4096");
|
||||
const originalUrl = u.toString();
|
||||
|
||||
openImageModal(url, {
|
||||
original: originalUrl,
|
||||
height: 256
|
||||
});
|
||||
}
|
||||
|
||||
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
|
||||
@ -90,7 +90,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
||||
<Menu.MenuItem
|
||||
id="view-avatar"
|
||||
label="View Avatar"
|
||||
action={() => openImage(BannerStore.getUserAvatarURL(user, true, 512))}
|
||||
action={() => openImage(BannerStore.getUserAvatarURL(user, true))}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
{memberAvatar && (
|
||||
@ -122,7 +122,6 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon
|
||||
openImage(BannerStore.getGuildIconURL({
|
||||
id,
|
||||
icon,
|
||||
size: 512,
|
||||
canAnimate: true
|
||||
}))
|
||||
}
|
||||
|
@ -17,13 +17,14 @@
|
||||
*/
|
||||
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
@ -41,22 +42,12 @@ function sortObject<T extends object>(obj: T): T {
|
||||
|
||||
function cleanMessage(msg: Message) {
|
||||
const clone = sortObject(JSON.parse(JSON.stringify(msg)));
|
||||
for (const key in clone.author) {
|
||||
switch (key) {
|
||||
case "id":
|
||||
case "username":
|
||||
case "usernameNormalized":
|
||||
case "discriminator":
|
||||
case "avatar":
|
||||
case "bot":
|
||||
case "system":
|
||||
case "publicFlags":
|
||||
break;
|
||||
default:
|
||||
// phone number, email, etc
|
||||
delete clone.author[key];
|
||||
}
|
||||
}
|
||||
for (const key of [
|
||||
"email",
|
||||
"phone",
|
||||
"mfaEnabled",
|
||||
"personalConnectionId"
|
||||
]) delete clone.author[key];
|
||||
|
||||
// message logger added properties
|
||||
const cloneAny = clone as any;
|
||||
@ -116,26 +107,58 @@ function openViewRawModal(msg: Message) {
|
||||
));
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
clickMethod: {
|
||||
description: "Change the button to view the raw content/data of any message.",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{ label: "Left Click to view the raw content.", value: "Left", default: true },
|
||||
{ label: "Right click to view the raw content.", value: "Right" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "ViewRaw",
|
||||
description: "Copy and view the raw content/data of any message.",
|
||||
authors: [Devs.KingFish, Devs.Ven],
|
||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
settings,
|
||||
|
||||
start() {
|
||||
addButton("ViewRaw", msg => {
|
||||
return {
|
||||
label: "View Raw (Left Click) / Copy Raw (Right Click)",
|
||||
icon: CopyIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: () => openViewRawModal(msg),
|
||||
onContextMenu: e => {
|
||||
const handleClick = () => {
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
openViewRawModal(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = e => {
|
||||
if (settings.store.clickMethod === "Left") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openViewRawModal(msg);
|
||||
}
|
||||
};
|
||||
|
||||
const label = settings.store.clickMethod === "Right"
|
||||
? "Copy Raw (Left Click) / View Raw (Right Click)"
|
||||
: "View Raw (Left Click) / Copy Raw (Right Click)";
|
||||
|
||||
return {
|
||||
label,
|
||||
icon: CopyIcon,
|
||||
message: msg,
|
||||
channel: ChannelStore.getChannel(msg.channel_id),
|
||||
onClick: handleClick,
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -44,7 +44,10 @@ const settings = definePluginSettings({
|
||||
],
|
||||
description: "Choose the greet mode"
|
||||
}
|
||||
});
|
||||
}).withPrivateSettings<{
|
||||
multiGreetChoices?: string[];
|
||||
unholyMultiGreetEnabled?: boolean;
|
||||
}>();
|
||||
|
||||
const MessageActions = findByPropsLazy("sendGreetMessage");
|
||||
|
||||
@ -73,7 +76,7 @@ function greet(channel: Channel, message: Message, stickers: string[]) {
|
||||
|
||||
|
||||
function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], message: Message, channel: Channel; }) {
|
||||
const s = settings.use(["greetMode", "multiGreetChoices"] as any) as { greetMode: GreetMode, multiGreetChoices: string[]; };
|
||||
const s = settings.use(["greetMode", "multiGreetChoices"]);
|
||||
const { greetMode, multiGreetChoices = [] } = s;
|
||||
|
||||
return (
|
||||
@ -112,7 +115,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
|
||||
))}
|
||||
</Menu.MenuGroup>
|
||||
|
||||
{!(settings.store as any).unholyMultiGreetEnabled ? null : (
|
||||
{!settings.store.unholyMultiGreetEnabled ? null : (
|
||||
<>
|
||||
<Menu.MenuSeparator />
|
||||
|
||||
|
@ -77,14 +77,14 @@ export async function authorizeCloud() {
|
||||
permissions={0n}
|
||||
clientId={clientId}
|
||||
cancelCompletesFlow={false}
|
||||
callback={async (callbackUrl: string) => {
|
||||
if (!callbackUrl) {
|
||||
callback={async ({ location }: any) => {
|
||||
if (!location) {
|
||||
Settings.cloud.authenticated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(callbackUrl, {
|
||||
const res = await fetch(location, {
|
||||
headers: new Headers({ Accept: "application/json" })
|
||||
});
|
||||
const { secret } = await res.json();
|
||||
|
@ -315,6 +315,26 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||
name: "UwU",
|
||||
id: 691413039156690994n,
|
||||
},
|
||||
amia: {
|
||||
name: "amia",
|
||||
id: 142007603549962240n
|
||||
},
|
||||
phil: {
|
||||
name: "phil",
|
||||
id: 305288513941667851n
|
||||
},
|
||||
ImLvna: {
|
||||
name: "Luna <3",
|
||||
id: 174200708818665472n
|
||||
},
|
||||
rad: {
|
||||
name: "rad",
|
||||
id: 113027285765885952n
|
||||
},
|
||||
HypedDomi: {
|
||||
name: "HypedDomi",
|
||||
id: 354191516979429376n
|
||||
}
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
@ -37,7 +37,7 @@ export const importApngJs = makeLazy(async () => {
|
||||
});
|
||||
|
||||
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
|
||||
export enum ApngDisposeOp {
|
||||
export const enum ApngDisposeOp {
|
||||
/**
|
||||
* no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
|
||||
*/
|
||||
@ -53,7 +53,7 @@ export enum ApngDisposeOp {
|
||||
}
|
||||
|
||||
// TODO: Might need to somehow implement this
|
||||
export enum ApngBlendOp {
|
||||
export const enum ApngBlendOp {
|
||||
SOURCE,
|
||||
OVER
|
||||
}
|
||||
|
@ -18,9 +18,11 @@
|
||||
|
||||
import { MessageObject } from "@api/MessageEvents";
|
||||
import { findByPropsLazy, findLazy } from "@webpack";
|
||||
import { ChannelStore, ComponentDispatch, GuildStore, PrivateChannelsStore, SelectedChannelStore } from "@webpack/common";
|
||||
import { ChannelStore, ComponentDispatch, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, SelectedChannelStore } from "@webpack/common";
|
||||
import { Guild, Message } from "discord-types/general";
|
||||
|
||||
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
||||
|
||||
const PreloadedUserSettings = findLazy(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings"));
|
||||
const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||
|
||||
@ -77,3 +79,23 @@ export function sendMessage(
|
||||
|
||||
return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
|
||||
}
|
||||
|
||||
export function openImageModal(url: string, props?: Partial<React.ComponentProps<ImageModal>>): string {
|
||||
return openModal(modalProps => (
|
||||
<ModalRoot
|
||||
{...modalProps}
|
||||
className={ModalImageClasses.modal}
|
||||
size={ModalSize.DYNAMIC}>
|
||||
<ImageModal
|
||||
className={ModalImageClasses.image}
|
||||
original={url}
|
||||
placeholder={url}
|
||||
src={url}
|
||||
renderLinkComponent={props => <MaskedLink {...props} />}
|
||||
shouldHideMediaOptions={false}
|
||||
shouldAnimate
|
||||
{...props}
|
||||
/>
|
||||
</ModalRoot>
|
||||
));
|
||||
}
|
@ -31,4 +31,5 @@ export * from "./onceDefined";
|
||||
export * from "./onlyOnce";
|
||||
export * from "./patches";
|
||||
export * from "./Queue";
|
||||
export * from "./react";
|
||||
export * from "./text";
|
||||
|
@ -16,19 +16,19 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { filters, mapMangledModuleLazy } from "@webpack";
|
||||
import { filters, findByCode, mapMangledModuleLazy } from "@webpack";
|
||||
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
||||
|
||||
import { LazyComponent } from "./react";
|
||||
|
||||
export enum ModalSize {
|
||||
export const enum ModalSize {
|
||||
SMALL = "small",
|
||||
MEDIUM = "medium",
|
||||
LARGE = "large",
|
||||
DYNAMIC = "dynamic",
|
||||
}
|
||||
|
||||
enum ModalTransitionState {
|
||||
const enum ModalTransitionState {
|
||||
ENTERING,
|
||||
ENTERED,
|
||||
EXITING,
|
||||
@ -107,6 +107,25 @@ export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", {
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ImageModal = ComponentType<{
|
||||
className?: string;
|
||||
src: string;
|
||||
placeholder: string;
|
||||
original: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
animated?: boolean;
|
||||
responsive?: boolean;
|
||||
renderLinkComponent(props: any): ReactNode;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
shouldAnimate?: boolean;
|
||||
onClose?(): void;
|
||||
shouldHideMediaOptions?: boolean;
|
||||
}>;
|
||||
|
||||
export const ImageModal = LazyComponent(() => findByCode(".renderLinkComponent", ".responsive") as ImageModal);
|
||||
|
||||
export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
|
||||
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
||||
export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
||||
|
@ -149,12 +149,6 @@ export async function putCloudSettings() {
|
||||
VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
|
||||
|
||||
cloudSettingsLogger.info("Settings uploaded to cloud successfully");
|
||||
showNotification({
|
||||
title: "Cloud Settings",
|
||||
body: "Synchronized your settings to the cloud!",
|
||||
color: "var(--green-360)",
|
||||
noPersist: true
|
||||
});
|
||||
} catch (e: any) {
|
||||
cloudSettingsLogger.error("Failed to sync up", e);
|
||||
showNotification({
|
||||
|
@ -117,7 +117,7 @@ export interface PluginDef {
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export enum OptionType {
|
||||
export const enum OptionType {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BIGINT,
|
||||
@ -260,25 +260,29 @@ type SettingsStore<D extends SettingsDefinition> = {
|
||||
};
|
||||
|
||||
/** An instance of defined plugin settings */
|
||||
export interface DefinedSettings<D extends SettingsDefinition = SettingsDefinition, C extends SettingsChecks<D> = {}> {
|
||||
export interface DefinedSettings<
|
||||
Def extends SettingsDefinition = SettingsDefinition,
|
||||
Checks extends SettingsChecks<Def> = {},
|
||||
PrivateSettings extends object = {}
|
||||
> {
|
||||
/** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */
|
||||
store: SettingsStore<D>;
|
||||
store: SettingsStore<Def> & PrivateSettings;
|
||||
/**
|
||||
* React hook for getting the settings for this plugin
|
||||
* @param filter optional filter to avoid rerenders for irrelavent settings
|
||||
*/
|
||||
use<F extends Extract<keyof D, string>>(filter?: F[]): Pick<SettingsStore<D>, F>;
|
||||
use<F extends Extract<keyof Def | keyof PrivateSettings, string>>(filter?: F[]): Pick<SettingsStore<Def> & PrivateSettings, F>;
|
||||
/** Definitions of each setting */
|
||||
def: D;
|
||||
def: Def;
|
||||
/** Setting methods with return values that could rely on other settings */
|
||||
checks: C;
|
||||
checks: Checks;
|
||||
/**
|
||||
* Name of the plugin these settings belong to,
|
||||
* will be an empty string until plugin is initialized
|
||||
*/
|
||||
pluginName: string;
|
||||
|
||||
withPrivateSettings<T>(): this & { store: T; };
|
||||
withPrivateSettings<T extends object>(): DefinedSettings<Def, Checks, T>;
|
||||
}
|
||||
|
||||
export type PartialExcept<T, R extends keyof T> = Partial<T> & Required<Pick<T, R>>;
|
||||
|
24
src/webpack/common/classes.ts
Normal file
24
src/webpack/common/classes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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 { findByPropsLazy } from "@webpack";
|
||||
|
||||
import * as t from "./types/classes";
|
||||
|
||||
export const ModalImageClasses: t.ImageModalClasses = findByPropsLazy("image", "modal");
|
||||
export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent");
|
@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line path-alias/no-relative
|
||||
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
||||
import { filters, waitFor } from "@webpack";
|
||||
|
||||
import { waitForComponent } from "./internal";
|
||||
import * as t from "./types/components";
|
||||
@ -44,16 +44,18 @@ export let Popout: t.Popout;
|
||||
export let Dialog: t.Dialog;
|
||||
export let TabBar: any;
|
||||
export let Paginator: t.Paginator;
|
||||
export let ScrollerThin: t.ScrollerThin;
|
||||
export let Clickable: t.Clickable;
|
||||
export let Avatar: t.Avatar;
|
||||
// token lagger real
|
||||
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
|
||||
export let useToken: t.useToken;
|
||||
|
||||
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", m => m?.type?.toString().includes("MASKED_LINK)"));
|
||||
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
||||
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||
|
||||
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
|
||||
|
||||
waitFor("FormItem", m => {
|
||||
({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator } = m);
|
||||
({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar } = m);
|
||||
Forms = m;
|
||||
});
|
||||
|
@ -16,6 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from "./classes";
|
||||
export * from "./components";
|
||||
export * from "./menu";
|
||||
export * from "./react";
|
||||
@ -24,4 +25,3 @@ export * as ComponentTypes from "./types/components.d";
|
||||
export * as MenuTypes from "./types/menu.d";
|
||||
export * as UtilTypes from "./types/utils.d";
|
||||
export * from "./utils";
|
||||
|
||||
|
40
src/webpack/common/types/classes.d.ts
vendored
Normal file
40
src/webpack/common/types/classes.d.ts
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export interface ImageModalClasses {
|
||||
image: string,
|
||||
modal: string,
|
||||
responsiveWidthMobile: string;
|
||||
}
|
||||
|
||||
export interface ButtonWrapperClasses {
|
||||
hoverScale: string;
|
||||
buttonWrapper: string;
|
||||
button: string;
|
||||
iconMask: string;
|
||||
buttonContent: string;
|
||||
icon: string;
|
||||
pulseIcon: string;
|
||||
pulseButton: string;
|
||||
notificationDot: string;
|
||||
sparkleContainer: string;
|
||||
sparkleStar: string;
|
||||
sparklePlus: string;
|
||||
sparkle: string;
|
||||
active: string;
|
||||
}
|
50
src/webpack/common/types/components.d.ts
vendored
50
src/webpack/common/types/components.d.ts
vendored
@ -397,3 +397,53 @@ export type Paginator = ComponentType<{
|
||||
onPageChange?(page: number): void;
|
||||
hideMaxPage?: boolean;
|
||||
}>;
|
||||
|
||||
export type MaskedLink = ComponentType<{
|
||||
onClick(): void;
|
||||
trusted: boolean;
|
||||
title: string,
|
||||
href: string;
|
||||
}>;
|
||||
|
||||
export type ScrollerThin = ComponentType<PropsWithChildren<{
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
|
||||
dir?: "ltr";
|
||||
orientation?: "horizontal" | "vertical";
|
||||
paddingFix?: boolean;
|
||||
fade?: boolean;
|
||||
|
||||
onClose?(): void;
|
||||
onScroll?(): void;
|
||||
}>>;
|
||||
|
||||
export type Clickable = ComponentType<PropsWithChildren<{
|
||||
className?: string;
|
||||
|
||||
href?: string;
|
||||
ignoreKeyPress?: boolean;
|
||||
|
||||
onClick?(): void;
|
||||
onKeyPress?(): void;
|
||||
}>>;
|
||||
|
||||
export type Avatar = ComponentType<PropsWithChildren<{
|
||||
className?: string;
|
||||
|
||||
src?: string;
|
||||
size?: "SIZE_16" | "SIZE_20" | "SIZE_24" | "SIZE_32" | "SIZE_40" | "SIZE_48" | "SIZE_56" | "SIZE_80" | "SIZE_120";
|
||||
|
||||
statusColor?: string;
|
||||
statusTooltip?: string;
|
||||
statusBackdropColor?: string;
|
||||
|
||||
isMobile?: boolean;
|
||||
isTyping?: boolean;
|
||||
isSpeaking?: boolean;
|
||||
|
||||
typingIndicatorRef?: unknown;
|
||||
|
||||
"aria-hidden"?: boolean;
|
||||
"aria-label"?: string;
|
||||
}>>;
|
||||
|
1
src/webpack/common/types/menu.d.ts
vendored
1
src/webpack/common/types/menu.d.ts
vendored
@ -44,6 +44,7 @@ export interface Menu {
|
||||
onChildrenScroll?: Function;
|
||||
childRowHeight?: number;
|
||||
listClassName?: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
MenuCheckboxItem: RC<{
|
||||
id: string;
|
||||
|
Reference in New Issue
Block a user