Compare commits
45 Commits
v1.2.6
...
componentU
Author | SHA1 | Date | |
---|---|---|---|
|
5d1d054b7e | ||
|
a11d5a9111 | ||
|
d7ac418e05 | ||
|
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 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -50,6 +50,7 @@ jobs:
|
|||||||
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload DevBuild as release
|
- name: Upload DevBuild as release
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
run: |
|
run: |
|
||||||
gh release upload devbuild --clobber dist/*
|
gh release upload devbuild --clobber dist/*
|
||||||
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
|
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
|
||||||
@ -58,6 +59,7 @@ jobs:
|
|||||||
RELEASE_TAG: ${{ env.release_tag }}
|
RELEASE_TAG: ${{ env.release_tag }}
|
||||||
|
|
||||||
- name: Upload DevBuild to builds repo
|
- name: Upload DevBuild to builds repo
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "$USERNAME"
|
git config --global user.name "$USERNAME"
|
||||||
git config --global user.email actions@github.com
|
git config --global user.email actions@github.com
|
||||||
|
4
.github/workflows/codeberg-mirror.yml
vendored
4
.github/workflows/codeberg-mirror.yml
vendored
@ -1,4 +1,7 @@
|
|||||||
name: Sync to Codeberg
|
name: Sync to Codeberg
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -7,6 +10,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
codeberg:
|
codeberg:
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
1
.github/workflows/publish.yml
vendored
1
.github/workflows/publish.yml
vendored
@ -6,6 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Publish:
|
Publish:
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
1
.github/workflows/reportBrokenPlugins.yml
vendored
1
.github/workflows/reportBrokenPlugins.yml
vendored
@ -7,6 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
TestPlugins:
|
TestPlugins:
|
||||||
|
if: github.repository == 'Vendicated/Vencord'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@ -12,5 +12,12 @@
|
|||||||
"javascript.format.semicolons": "insert",
|
"javascript.format.semicolons": "insert",
|
||||||
"typescript.format.semicolons": "insert",
|
"typescript.format.semicolons": "insert",
|
||||||
"typescript.preferences.quoteStyle": "double",
|
"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
|
# 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)
|
![](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)*
|
*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
|
## Vencord Desktop
|
||||||
|
|
||||||
> **Warning**
|
> **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
|
||||||
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
|
|
||||||
|
|
||||||
[![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)
|
[![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
|
## Join our Support/Community Server
|
||||||
|
|
||||||
[![Vencord Discord Server](https://invidget.switchblade.xyz/D9uwnFnqmd?theme=dark)](https://discord.gg/D9uwnFnqmd)
|
https://discord.gg/D9uwnFnqmd
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
@ -41,12 +41,5 @@
|
|||||||
"path": "modifyResponseHeaders.json"
|
"path": "modifyResponseHeaders.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
|
|
||||||
"browser_specific_settings": {
|
|
||||||
"gecko": {
|
|
||||||
"id": "vencord-firefox@vendicated.dev",
|
|
||||||
"strict_min_version": "109.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.2.6",
|
"version": "1.2.9",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@ -24,7 +24,7 @@
|
|||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
||||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
"test": "pnpm 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",
|
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||||
"testTsc": "tsc --noEmit",
|
"testTsc": "tsc --noEmit",
|
||||||
"uninject": "node scripts/runInstaller.mjs",
|
"uninject": "node scripts/runInstaller.mjs",
|
||||||
|
@ -19,11 +19,13 @@
|
|||||||
|
|
||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
|
|
||||||
import { commonOpts, globPlugins, isStandalone, watch } from "./common.mjs";
|
import { commonOpts, globPlugins, isStandalone, VERSION, watch } from "./common.mjs";
|
||||||
|
|
||||||
const defines = {
|
const defines = {
|
||||||
IS_STANDALONE: isStandalone,
|
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 (defines.IS_STANDALONE === "false")
|
||||||
// If this is a local build (not standalone), optimise
|
// 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 { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
// wtf is this assert syntax
|
import { commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
|
||||||
import PackageJSON from "../../package.json" assert { type: "json" };
|
|
||||||
import { commonOpts, globPlugins, watch } from "./common.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.BuildOptions}
|
* @type {esbuild.BuildOptions}
|
||||||
@ -47,7 +45,9 @@ const commonOptions = {
|
|||||||
IS_STANDALONE: "true",
|
IS_STANDALONE: "true",
|
||||||
IS_DEV: JSON.stringify(watch),
|
IS_DEV: JSON.stringify(watch),
|
||||||
IS_DISCORD_DESKTOP: "false",
|
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",
|
outfile: "dist/Vencord.user.js",
|
||||||
banner: {
|
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: {
|
footer: {
|
||||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
// 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));
|
let content = await readFile(join("browser", f));
|
||||||
if (f.startsWith("manifest")) {
|
if (f.startsWith("manifest")) {
|
||||||
const json = JSON.parse(content.toString("utf-8"));
|
const json = JSON.parse(content.toString("utf-8"));
|
||||||
json.version = PackageJSON.version;
|
json.version = VERSION;
|
||||||
content = new TextEncoder().encode(JSON.stringify(json));
|
content = new TextEncoder().encode(JSON.stringify(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "../suppressExperimentalWarnings.js";
|
||||||
import "../checkNodeVersion.js";
|
import "../checkNodeVersion.js";
|
||||||
|
|
||||||
import { exec, execSync } from "child_process";
|
import { exec, execSync } from "child_process";
|
||||||
@ -24,6 +25,11 @@ import { readdir, readFile } from "fs/promises";
|
|||||||
import { join, relative } from "path";
|
import { join, relative } from "path";
|
||||||
import { promisify } from "util";
|
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 watch = process.argv.includes("--watch");
|
||||||
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
|
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
|
||||||
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
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 () => {
|
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||||
const pluginDirs = ["plugins", "userplugins"];
|
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
||||||
let code = "";
|
let code = "";
|
||||||
let plugins = "\n";
|
let plugins = "\n";
|
||||||
let i = 0;
|
let i = 0;
|
||||||
@ -72,8 +78,9 @@ export const globPlugins = kind => ({
|
|||||||
if (!existsSync(`./src/${dir}`)) continue;
|
if (!existsSync(`./src/${dir}`)) continue;
|
||||||
const files = await readdir(`./src/${dir}`);
|
const files = await readdir(`./src/${dir}`);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith(".")) continue;
|
if (file.startsWith("_") || file.startsWith(".")) continue;
|
||||||
if (file === "index.ts") continue;
|
if (file === "index.ts") continue;
|
||||||
|
|
||||||
const fileBits = file.split(".");
|
const fileBits = file.split(".");
|
||||||
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1))) {
|
if (fileBits.length > 2 && ["ts", "tsx"].includes(fileBits.at(-1))) {
|
||||||
const mod = fileBits.at(-2);
|
const mod = fileBits.at(-2);
|
||||||
|
@ -171,8 +171,8 @@ async function parseFile(fileName: string) {
|
|||||||
throw fail("no default export called 'definePlugin' found");
|
throw fail("no default export called 'definePlugin' found");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getEntryPoint(dirent: Dirent) {
|
async function getEntryPoint(dir: string, dirent: Dirent) {
|
||||||
const base = join("./src/plugins", dirent.name);
|
const base = join(dir, dirent.name);
|
||||||
if (!dirent.isDirectory()) return base;
|
if (!dirent.isDirectory()) return base;
|
||||||
|
|
||||||
for (const name of ["index.ts", "index.tsx"]) {
|
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`);
|
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 () => {
|
(async () => {
|
||||||
parseDevs();
|
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) {
|
if (process.argv.length > 2) {
|
||||||
writeFileSync(process.argv[2], data);
|
writeFileSync(process.argv[2], data);
|
||||||
|
@ -22,7 +22,7 @@ import { ComponentType, HTMLProps } from "react";
|
|||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
export enum BadgePosition {
|
export const enum BadgePosition {
|
||||||
START,
|
START,
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ export function _getBadges(args: BadgeUserArgs) {
|
|||||||
: badges.push({ ...badge, ...args });
|
: 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);
|
if (donorBadges) badges.unshift(...donorBadges);
|
||||||
|
|
||||||
return badges;
|
return badges;
|
||||||
|
@ -24,7 +24,7 @@ export interface CommandContext {
|
|||||||
guild?: Guild;
|
guild?: Guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApplicationCommandOptionType {
|
export const enum ApplicationCommandOptionType {
|
||||||
SUB_COMMAND = 1,
|
SUB_COMMAND = 1,
|
||||||
SUB_COMMAND_GROUP = 2,
|
SUB_COMMAND_GROUP = 2,
|
||||||
STRING = 3,
|
STRING = 3,
|
||||||
@ -38,7 +38,7 @@ export enum ApplicationCommandOptionType {
|
|||||||
ATTACHMENT = 11,
|
ATTACHMENT = 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApplicationCommandInputType {
|
export const enum ApplicationCommandInputType {
|
||||||
BUILT_IN = 0,
|
BUILT_IN = 0,
|
||||||
BUILT_IN_TEXT = 1,
|
BUILT_IN_TEXT = 1,
|
||||||
BUILT_IN_INTEGRATION = 2,
|
BUILT_IN_INTEGRATION = 2,
|
||||||
@ -64,7 +64,7 @@ export interface ChoicesOption {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApplicationCommandType {
|
export const enum ApplicationCommandType {
|
||||||
CHAT_INPUT = 1,
|
CHAT_INPUT = 1,
|
||||||
USER = 2,
|
USER = 2,
|
||||||
MESSAGE = 3,
|
MESSAGE = 3,
|
||||||
|
29
src/api/ComponentUpdater.ts
Normal file
29
src/api/ComponentUpdater.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 { proxyLazy } from "@utils/lazy";
|
||||||
|
|
||||||
|
const p = proxyLazy<typeof import("plugins/_api/componentUpdater").default>(() => Vencord.Plugins.plugins.ComponentUpdaterAPI as any);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rerender a specific message
|
||||||
|
* @param messageId The id of the message to rerender
|
||||||
|
*/
|
||||||
|
export function updateMessageComponent(messageId: string) {
|
||||||
|
p.forceUpdaters.get(messageId)?.();
|
||||||
|
}
|
@ -20,7 +20,7 @@ import { Logger } from "@utils/Logger";
|
|||||||
|
|
||||||
const logger = new Logger("ServerListAPI");
|
const logger = new Logger("ServerListAPI");
|
||||||
|
|
||||||
export enum ServerListRenderPosition {
|
export const enum ServerListRenderPosition {
|
||||||
Above,
|
Above,
|
||||||
In,
|
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) {
|
export function definePluginSettings<
|
||||||
const definedSettings: DefinedSettings<D> = {
|
Def extends SettingsDefinition,
|
||||||
|
Checks extends SettingsChecks<Def>,
|
||||||
|
PrivateSettings extends object = {}
|
||||||
|
>(def: Def, checks?: Checks) {
|
||||||
|
const definedSettings: DefinedSettings<Def, Checks, PrivateSettings> = {
|
||||||
get store() {
|
get store() {
|
||||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||||
return Settings.plugins[definedSettings.pluginName] as any;
|
return Settings.plugins[definedSettings.pluginName] as any;
|
||||||
@ -264,11 +268,11 @@ export function definePluginSettings<D extends SettingsDefinition, C extends Set
|
|||||||
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
||||||
).plugins[definedSettings.pluginName] as any,
|
).plugins[definedSettings.pluginName] as any,
|
||||||
def,
|
def,
|
||||||
checks: checks ?? {},
|
checks: checks ?? {} as any,
|
||||||
pluginName: "",
|
pluginName: "",
|
||||||
|
|
||||||
withPrivateSettings<T>() {
|
withPrivateSettings<T extends object>() {
|
||||||
return this as DefinedSettings<D, C> & { store: T; };
|
return this as DefinedSettings<Def, Checks, T>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import * as $Badges from "./Badges";
|
import * as $Badges from "./Badges";
|
||||||
import * as $Commands from "./Commands";
|
import * as $Commands from "./Commands";
|
||||||
|
import * as $ComponentUpdater from "./ComponentUpdater";
|
||||||
import * as $ContextMenu from "./ContextMenu";
|
import * as $ContextMenu from "./ContextMenu";
|
||||||
import * as $DataStore from "./DataStore";
|
import * as $DataStore from "./DataStore";
|
||||||
import * as $MemberListDecorators from "./MemberListDecorators";
|
import * as $MemberListDecorators from "./MemberListDecorators";
|
||||||
@ -109,3 +110,8 @@ export const Notifications = $Notifications;
|
|||||||
* An api allowing you to patch and add/remove items to/from context menus
|
* An api allowing you to patch and add/remove items to/from context menus
|
||||||
*/
|
*/
|
||||||
export const ContextMenu = $ContextMenu;
|
export const ContextMenu = $ContextMenu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An api allowing you to update/rerender components
|
||||||
|
*/
|
||||||
|
export const ComponentUpdater = $ComponentUpdater;
|
||||||
|
@ -146,3 +146,47 @@ export function OwnerCrownIcon(props: IconProps) {
|
|||||||
</Icon>
|
</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 result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
|
||||||
const action = wasEnabled ? "stop" : "start";
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
logger.error(`Failed to ${action} plugin ${plugin.name}`);
|
settings.enabled = false;
|
||||||
showErrorToast(`Failed to ${action} plugin: ${plugin.name}`);
|
|
||||||
|
const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`;
|
||||||
|
logger.error(msg);
|
||||||
|
showErrorToast(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +173,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SearchStatus {
|
const enum SearchStatus {
|
||||||
ALL,
|
ALL,
|
||||||
ENABLED,
|
ENABLED,
|
||||||
DISABLED
|
DISABLED
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vc-text-selectable,
|
.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 */
|
/* make text selectable, silly discord makes the entirety of settings not selectable */
|
||||||
user-select: text;
|
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_STANDALONE: boolean;
|
||||||
export var IS_DISCORD_DESKTOP: boolean;
|
export var IS_DISCORD_DESKTOP: boolean;
|
||||||
export var IS_VENCORD_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 VencordNative: typeof import("./VencordNative").default;
|
||||||
export var Vencord: typeof import("./Vencord");
|
export var Vencord: typeof import("./Vencord");
|
||||||
|
51
src/plugins/_api/componentUpdater.ts
Normal file
51
src/plugins/_api/componentUpdater.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useForceUpdater } from "@utils/react";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { useEffect } from "@webpack/common";
|
||||||
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
|
||||||
|
const forceUpdaters = new Map<string, () => void>();
|
||||||
|
|
||||||
|
function useUpdater(data: { channel: Channel; message: Message; }) {
|
||||||
|
const forceUpdater = useForceUpdater();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
forceUpdaters.set(data.message.id, forceUpdater);
|
||||||
|
return () => void forceUpdaters.delete(data.message.id);
|
||||||
|
}, [data.message.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ComponentUpdaterAPI",
|
||||||
|
description: "API to update / force rerender several components, such as messages",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
|
patches: [{
|
||||||
|
find: ".renderContentOnly;",
|
||||||
|
replacement: {
|
||||||
|
match: /=(\i)\.renderContentOnly;/,
|
||||||
|
replace: "$&$self.useUpdater($1);"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
useUpdater,
|
||||||
|
forceUpdaters
|
||||||
|
});
|
@ -82,6 +82,8 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
customSections: [] as ((ID: Record<string, unknown>) => any)[],
|
||||||
|
|
||||||
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
|
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -123,11 +125,13 @@ export default definePlugin({
|
|||||||
label: "Patch Helper",
|
label: "Patch Helper",
|
||||||
element: require("@components/VencordSettings/PatchHelperTab").default,
|
element: require("@components/VencordSettings/PatchHelperTab").default,
|
||||||
},
|
},
|
||||||
|
// TODO: make this use customSections
|
||||||
IS_VENCORD_DESKTOP && {
|
IS_VENCORD_DESKTOP && {
|
||||||
section: "VencordDesktop",
|
section: "VencordDesktop",
|
||||||
label: "Desktop Settings",
|
label: "Desktop Settings",
|
||||||
element: VencordDesktop.Components.Settings,
|
element: VencordDesktop.Components.Settings,
|
||||||
},
|
},
|
||||||
|
...this.customSections.map(func => func(ID)),
|
||||||
{
|
{
|
||||||
section: ID.DIVIDER
|
section: ID.DIVIDER
|
||||||
}
|
}
|
@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
enum Methods {
|
const enum Methods {
|
||||||
Random,
|
Random,
|
||||||
Consistent,
|
Consistent,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
|
@ -104,6 +104,6 @@ export default definePlugin({
|
|||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); // clear status
|
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
|
// @ts-expect-error
|
||||||
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
|
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) {
|
if (scrollerProps?.children) {
|
||||||
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
||||||
if (servers) scrollerProps.children = 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;
|
flags: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActivityType {
|
const enum ActivityType {
|
||||||
PLAYING = 0,
|
PLAYING = 0,
|
||||||
LISTENING = 2,
|
LISTENING = 2,
|
||||||
WATCHING = 3,
|
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 { get, set } from "@api/DataStore";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore } from "@webpack/common";
|
import { ChannelStore } from "@webpack/common";
|
||||||
@ -26,17 +27,6 @@ let style: HTMLStyleElement;
|
|||||||
|
|
||||||
const KEY = "HideAttachments_HiddenIds";
|
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();
|
let hiddenMessages: Set<string> = new Set();
|
||||||
const getHiddenMessages = () => get(KEY).then(set => {
|
const getHiddenMessages = () => get(KEY).then(set => {
|
||||||
hiddenMessages = set ?? new Set<string>();
|
hiddenMessages = set ?? new Set<string>();
|
||||||
|
@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
|
|||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { Tooltip } from "webpack/common";
|
import { Tooltip } from "webpack/common";
|
||||||
|
|
||||||
enum ActivitiesTypes {
|
const enum ActivitiesTypes {
|
||||||
Game,
|
Game,
|
||||||
Embedded
|
Embedded
|
||||||
}
|
}
|
||||||
|
@ -63,12 +63,12 @@ interface TrackData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only relevant enum values
|
// only relevant enum values
|
||||||
enum ActivityType {
|
const enum ActivityType {
|
||||||
PLAYING = 0,
|
PLAYING = 0,
|
||||||
LISTENING = 2,
|
LISTENING = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActivityFlag {
|
const enum ActivityFlag {
|
||||||
INSTANCE = 1 << 0,
|
INSTANCE = 1 << 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export default definePlugin({
|
|||||||
document.addEventListener("keydown", keydown);
|
document.addEventListener("keydown", keydown);
|
||||||
document.addEventListener("keyup", keyup);
|
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;
|
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||||
if (!isDeletePressed) {
|
if (!isDeletePressed) {
|
||||||
if (event.detail < 2) return;
|
if (event.detail < 2) return;
|
||||||
@ -90,7 +90,16 @@ export default definePlugin({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, channel))) {
|
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, channel))) {
|
||||||
|
if (msg.deleted) {
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "MESSAGE_DELETE",
|
||||||
|
channelId: channel.id,
|
||||||
|
id: msg.id,
|
||||||
|
mlDeleted: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
MessageActions.deleteMessage(channel.id, msg.id);
|
MessageActions.deleteMessage(channel.id, msg.id);
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { updateMessageComponent } from "@api/ComponentUpdater";
|
||||||
import { addAccessory } from "@api/MessageAccessories";
|
import { addAccessory } from "@api/MessageAccessories";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
@ -28,7 +29,6 @@ import { find, findByCode, findByPropsLazy } from "@webpack";
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ChannelStore,
|
ChannelStore,
|
||||||
FluxDispatcher,
|
|
||||||
GuildStore,
|
GuildStore,
|
||||||
MessageStore,
|
MessageStore,
|
||||||
Parser,
|
Parser,
|
||||||
@ -228,10 +228,7 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
|||||||
delete msg.interaction;
|
delete msg.interaction;
|
||||||
|
|
||||||
messageFetchQueue.push(() => fetchMessage(channelID, messageID)
|
messageFetchQueue.push(() => fetchMessage(channelID, messageID)
|
||||||
.then(m => m && FluxDispatcher.dispatch({
|
.then(m => m && updateMessageComponent(message.id))
|
||||||
type: "MESSAGE_UPDATE",
|
|
||||||
message: msg
|
|
||||||
}))
|
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
color: #f04747 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +147,11 @@ export default definePlugin({
|
|||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Whether to ignore messages by yourself",
|
description: "Whether to ignore messages by yourself",
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
ignoreUsers: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Comma-separated list of user IDs to ignore",
|
||||||
|
default: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -154,7 +159,7 @@ export default definePlugin({
|
|||||||
try {
|
try {
|
||||||
if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
|
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;
|
const myId = UserStore.getCurrentUser().id;
|
||||||
|
|
||||||
function mutate(id: string) {
|
function mutate(id: string) {
|
||||||
@ -165,7 +170,8 @@ export default definePlugin({
|
|||||||
const shouldIgnore = data.mlDeleted ||
|
const shouldIgnore = data.mlDeleted ||
|
||||||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
|
(msg.flags & EPHEMERAL) === EPHEMERAL ||
|
||||||
ignoreBots && msg.author?.bot ||
|
ignoreBots && msg.author?.bot ||
|
||||||
ignoreSelf && msg.author?.id === myId;
|
ignoreSelf && msg.author?.id === myId ||
|
||||||
|
ignoreUsers.includes(msg.author?.id);
|
||||||
|
|
||||||
if (shouldIgnore) {
|
if (shouldIgnore) {
|
||||||
cache = cache.remove(id);
|
cache = cache.remove(id);
|
||||||
|
@ -2,16 +2,29 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messagelogger-deleted :is(video, .emoji, [data-type="sticker"]),
|
.messagelogger-deleted
|
||||||
.messagelogger-deleted .messagelogger-deleted-attachment,
|
:is(
|
||||||
.messagelogger-deleted div iframe {
|
video,
|
||||||
|
.emoji,
|
||||||
|
[data-type="sticker"],
|
||||||
|
iframe,
|
||||||
|
.messagelogger-deleted-attachment,
|
||||||
|
[class|="inlineMediaEmbed"]
|
||||||
|
) {
|
||||||
filter: grayscale(1) !important;
|
filter: grayscale(1) !important;
|
||||||
transition: 150ms filter ease-in-out;
|
transition: 150ms filter ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messagelogger-deleted:hover :is(video, .emoji, [data-type="sticker"]),
|
.messagelogger-deleted
|
||||||
.messagelogger-deleted .messagelogger-deleted-attachment:hover,
|
:is(
|
||||||
.messagelogger-deleted iframe:hover {
|
video,
|
||||||
|
.emoji,
|
||||||
|
[data-type="sticker"],
|
||||||
|
iframe,
|
||||||
|
.messagelogger-deleted-attachment,
|
||||||
|
[class|="inlineMediaEmbed"]
|
||||||
|
):hover,
|
||||||
|
.messagelogger-deleted {
|
||||||
filter: grayscale(0) !important;
|
filter: grayscale(0) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,37 +18,8 @@
|
|||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByProps, findStoreLazy } from "@webpack";
|
import { findByProps } 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
guild: {
|
guild: {
|
||||||
@ -92,15 +63,5 @@ export default definePlugin({
|
|||||||
suppress_roles: settings.store.role
|
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;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
@ -19,10 +19,12 @@
|
|||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
||||||
|
import { getUniqueUsername } from "@utils/discord";
|
||||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { ContextMenu, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
import { ContextMenu, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||||
import type { Guild } from "discord-types/general";
|
import type { Guild } from "discord-types/general";
|
||||||
|
|
||||||
|
import { settings } from "..";
|
||||||
import { cl, getPermissionDescription, getPermissionString } from "../utils";
|
import { cl, getPermissionDescription, getPermissionString } from "../utils";
|
||||||
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
|
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
|
||||||
|
|
||||||
@ -108,7 +110,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
|||||||
<div
|
<div
|
||||||
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
|
className={cl("perms-list-item", { "perms-list-item-active": selectedItemIndex === index })}
|
||||||
onContextMenu={e => {
|
onContextMenu={e => {
|
||||||
if (permission.type === PermissionType.Role)
|
if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role)
|
||||||
ContextMenu.open(e, () => (
|
ContextMenu.open(e, () => (
|
||||||
<RoleContextMenu
|
<RoleContextMenu
|
||||||
guild={guild}
|
guild={guild}
|
||||||
@ -135,7 +137,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
|||||||
permission.type === PermissionType.Role
|
permission.type === PermissionType.Role
|
||||||
? role?.name || "Unknown Role"
|
? role?.name || "Unknown Role"
|
||||||
: permission.type === PermissionType.User
|
: permission.type === PermissionType.User
|
||||||
? user?.tag || "Unknown User"
|
? (user && getUniqueUsername(user)) || "Unknown User"
|
||||||
: (
|
: (
|
||||||
<Flex style={{ gap: "0.2em", justifyItems: "center" }}>
|
<Flex style={{ gap: "0.2em", justifyItems: "center" }}>
|
||||||
@owner
|
@owner
|
||||||
|
@ -63,6 +63,7 @@
|
|||||||
grid-area: list;
|
grid-area: list;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
border-right: 2px solid var(--background-modifier-active);
|
border-right: 2px solid var(--background-modifier-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +78,15 @@
|
|||||||
padding: 8px 5px;
|
padding: 8px 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 230px;
|
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 {
|
.vc-permviewer-perms-list-item > div {
|
||||||
@ -85,11 +95,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-permviewer-perms-list-item-active {
|
|
||||||
background-color: var(--background-modifier-selected);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-permviewer-perms-role-circle {
|
.vc-permviewer-perms-role-circle {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
|
@ -52,7 +52,7 @@ export default definePlugin({
|
|||||||
find: ".userTagNoNickname",
|
find: ".userTagNoNickname",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /=(\i)\.pronouns/,
|
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
|
// 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",
|
find: ".USER_PROFILE_ACTIVITY",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\).showPronouns/,
|
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 { Settings } from "@api/Settings";
|
||||||
import { VENCORD_USER_AGENT } from "@utils/constants";
|
import { VENCORD_USER_AGENT } from "@utils/constants";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
import { UserStore } from "@webpack/common";
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
||||||
|
|
||||||
|
const UserProfileStore = findStoreLazy("UserProfileStore");
|
||||||
|
|
||||||
export const enum PronounsFormat {
|
export const enum PronounsFormat {
|
||||||
Lowercase = "LOWERCASE",
|
Lowercase = "LOWERCASE",
|
||||||
Capitalized = "CAPITALIZED"
|
Capitalized = "CAPITALIZED"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum PronounSource {
|
||||||
|
PreferPDB,
|
||||||
|
PreferDiscord
|
||||||
|
}
|
||||||
|
|
||||||
// A map of cached pronouns so the same request isn't sent twice
|
// A map of cached pronouns so the same request isn't sent twice
|
||||||
const cache: Record<string, PronounCode> = {};
|
const cache: Record<string, PronounCode> = {};
|
||||||
// A map of ids and callbacks that should be triggered on fetch
|
// 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 {
|
function getDiscordPronouns(id: string) {
|
||||||
const [result] = useAwaiter(() => fetchPronouns(id), {
|
return (
|
||||||
fallbackValue: getCachedPronouns(id),
|
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)
|
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")
|
||||||
if (result && result !== "unspecified" && PronounMapping[result])
|
return Object.hasOwn(PronounMapping, result)
|
||||||
return formatPronouns(result);
|
? formatPronouns(result) // PronounDB
|
||||||
|
: result; // Discord
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useProfilePronouns(id: string) {
|
export function useProfilePronouns(id: string, discordPronouns: string) {
|
||||||
const pronouns = useFormattedPronouns(id);
|
const pronouns = useFormattedPronouns(id, discordPronouns);
|
||||||
|
|
||||||
if (!settings.store.showInProfile) return null;
|
if (!settings.store.showInProfile) return null;
|
||||||
if (!settings.store.showSelf && id === UserStore.getCurrentUser().id) 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!
|
// Gets the cached pronouns, if you're too impatient for a promise!
|
||||||
export function getCachedPronouns(id: string): PronounCode | null {
|
export function getCachedPronouns(id: string, discordPronouns: string): string | null {
|
||||||
return cache[id] ?? 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
|
// 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 => {
|
return new Promise(res => {
|
||||||
// If cached, return the cached pronouns
|
const cached = getCachedPronouns(id, discordPronouns);
|
||||||
if (id in cache) res(getCachedPronouns(id)!);
|
if (cached) return res(cached);
|
||||||
|
|
||||||
// If there is already a request added, then just add this callback to it
|
// 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
|
// If not already added, then add it and call the debounced function to make sure the request gets executed
|
||||||
else {
|
|
||||||
requestQueue[id] = [res];
|
requestQueue[id] = [res];
|
||||||
bulkFetch();
|
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; };
|
const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; };
|
||||||
// For capitalized pronouns, just return the mapping (it is by default capitalized)
|
// For capitalized pronouns, just return the mapping (it is by default capitalized)
|
||||||
if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns];
|
if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns];
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { OptionType } from "@utils/types";
|
import { OptionType } from "@utils/types";
|
||||||
|
|
||||||
import { PronounsFormat } from "./pronoundbUtils";
|
import { PronounsFormat, PronounSource } from "./pronoundbUtils";
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
pronounsFormat: {
|
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: {
|
showSelf: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Enable or disable showing pronouns for the current user",
|
description: "Enable or disable showing pronouns for the current user",
|
||||||
|
@ -159,9 +159,12 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
|
|||||||
return i === - 1 ? undefined : messages[messages.length - i - 1];
|
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) {
|
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;
|
case MentionOptions.DISABLED: return false;
|
||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
@ -184,7 +187,7 @@ function nextReply(isUp: boolean) {
|
|||||||
type: "CREATE_PENDING_REPLY",
|
type: "CREATE_PENDING_REPLY",
|
||||||
channel,
|
channel,
|
||||||
message,
|
message,
|
||||||
shouldMention: shouldMention(),
|
shouldMention: shouldMention(message),
|
||||||
showMentionToggle: channel.guild_id !== null && message.author.id !== meId,
|
showMentionToggle: channel.guild_id !== null && message.author.id !== meId,
|
||||||
_isQuickReply: true
|
_isQuickReply: true
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { getUniqueUsername, openUserProfile } from "@utils/discord";
|
||||||
import { UserUtils } from "@webpack/common";
|
import { UserUtils } from "@webpack/common";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
@ -43,11 +44,19 @@ export async function onRelationshipRemove({ relationship: { type, id } }: Relat
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case RelationshipType.FRIEND:
|
case RelationshipType.FRIEND:
|
||||||
if (settings.store.friends)
|
if (settings.store.friends)
|
||||||
notify(`${user.tag} removed you as a friend.`, user.getAvatarURL(undefined, undefined, false));
|
notify(
|
||||||
|
`${getUniqueUsername(user)} removed you as a friend.`,
|
||||||
|
user.getAvatarURL(undefined, undefined, false),
|
||||||
|
() => openUserProfile(user.id)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case RelationshipType.FRIEND_REQUEST:
|
case RelationshipType.FRIEND_REQUEST:
|
||||||
if (settings.store.friendRequestCancels)
|
if (settings.store.friendRequestCancels)
|
||||||
notify(`A friend request from ${user.tag} has been removed.`, user.getAvatarURL(undefined, undefined, false));
|
notify(
|
||||||
|
`A friend request from ${getUniqueUsername(user)} has been removed.`,
|
||||||
|
user.getAvatarURL(undefined, undefined, false),
|
||||||
|
() => openUserProfile(user.id)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import { DataStore, Notices } from "@api/index";
|
import { DataStore, Notices } from "@api/index";
|
||||||
import { showNotification } from "@api/Notifications";
|
import { showNotification } from "@api/Notifications";
|
||||||
|
import { getUniqueUsername, openUserProfile } from "@utils/discord";
|
||||||
import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
|
import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
@ -69,7 +70,11 @@ export async function syncAndRunChecks() {
|
|||||||
|
|
||||||
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
||||||
if (user)
|
if (user)
|
||||||
notify(`You are no longer friends with ${user.tag}.`, user.getAvatarURL(undefined, undefined, false));
|
notify(
|
||||||
|
`You are no longer friends with ${getUniqueUsername(user)}.`,
|
||||||
|
user.getAvatarURL(undefined, undefined, false),
|
||||||
|
() => openUserProfile(user.id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,20 +84,25 @@ export async function syncAndRunChecks() {
|
|||||||
|
|
||||||
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
||||||
if (user)
|
if (user)
|
||||||
notify(`Friend request from ${user.tag} has been revoked.`, user.getAvatarURL(undefined, undefined, false));
|
notify(
|
||||||
|
`Friend request from ${getUniqueUsername(user)} has been revoked.`,
|
||||||
|
user.getAvatarURL(undefined, undefined, false),
|
||||||
|
() => openUserProfile(user.id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function notify(text: string, icon?: string) {
|
export function notify(text: string, icon?: string, onClick?: () => void) {
|
||||||
if (settings.store.notices)
|
if (settings.store.notices)
|
||||||
Notices.showNotice(text, "OK", () => Notices.popNotice());
|
Notices.showNotice(text, "OK", () => Notices.popNotice());
|
||||||
|
|
||||||
showNotification({
|
showNotification({
|
||||||
title: "Relationship Notifier",
|
title: "Relationship Notifier",
|
||||||
body: text,
|
body: text,
|
||||||
icon
|
icon,
|
||||||
|
onClick
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import { Devs } from "@utils/constants";
|
|||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
const SpoilerClasses = findByPropsLazy("spoilerText");
|
const SpoilerClasses = findByPropsLazy("spoilerContent");
|
||||||
const MessagesClasses = findByPropsLazy("messagesWrapper", "messages");
|
const MessagesClasses = findByPropsLazy("messagesWrapper", "messages");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
@ -43,14 +43,14 @@ export default definePlugin({
|
|||||||
|
|
||||||
if (!ctrlKey) { return; }
|
if (!ctrlKey) { return; }
|
||||||
|
|
||||||
const { spoilerText, hidden } = SpoilerClasses;
|
const { spoilerContent, hidden } = SpoilerClasses;
|
||||||
const { messagesWrapper } = MessagesClasses;
|
const { messagesWrapper } = MessagesClasses;
|
||||||
|
|
||||||
const parent = shiftKey
|
const parent = shiftKey
|
||||||
? document.querySelector(`div.${messagesWrapper}`)
|
? document.querySelector(`div.${messagesWrapper}`)
|
||||||
: (target as HTMLSpanElement).parentElement;
|
: (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();
|
(spoiler as HTMLSpanElement).click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { openUserProfile } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { LazyComponent } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { filters, findBulk } from "@webpack";
|
import { filters, findBulk } from "@webpack";
|
||||||
@ -24,7 +25,7 @@ import { Alerts, moment, Timestamp, UserStore } from "@webpack/common";
|
|||||||
import { Review, ReviewType } from "../entities";
|
import { Review, ReviewType } from "../entities";
|
||||||
import { deleteReview, reportReview } from "../reviewDbApi";
|
import { deleteReview, reportReview } from "../reviewDbApi";
|
||||||
import { settings } from "../settings";
|
import { settings } from "../settings";
|
||||||
import { canDeleteReview, cl, openUserProfileModal, showToast } from "../utils";
|
import { canDeleteReview, cl, showToast } from "../utils";
|
||||||
import { DeleteButton, ReportButton } from "./MessageButton";
|
import { DeleteButton, ReportButton } from "./MessageButton";
|
||||||
import ReviewBadge from "./ReviewBadge";
|
import ReviewBadge from "./ReviewBadge";
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ export default LazyComponent(() => {
|
|||||||
|
|
||||||
return function ReviewComponent({ review, refetch }: { review: Review; refetch(): void; }) {
|
return function ReviewComponent({ review, refetch }: { review: Review; refetch(): void; }) {
|
||||||
function openModal() {
|
function openModal() {
|
||||||
openUserProfileModal(review.sender.discordID);
|
openUserProfile(review.sender.discordID);
|
||||||
}
|
}
|
||||||
|
|
||||||
function delReview() {
|
function delReview() {
|
||||||
|
@ -79,8 +79,8 @@ export default definePlugin({
|
|||||||
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
||||||
|
|
||||||
if (user.banInfo) {
|
if (user.banInfo) {
|
||||||
const endDate = new Date(user.banInfo.banEndDate).getTime();
|
const endDate = new Date(user.banInfo.banEndDate);
|
||||||
if (endDate > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate) {
|
if (endDate.getTime() > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate.getTime()) {
|
||||||
Alerts.show({
|
Alerts.show({
|
||||||
title: "You have been banned from ReviewDB",
|
title: "You have been banned from ReviewDB",
|
||||||
body: (
|
body: (
|
||||||
|
@ -20,24 +20,13 @@ import { classNameFactory } from "@api/Styles";
|
|||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import { findByProps } from "@webpack";
|
import { findByProps } from "@webpack";
|
||||||
import { FluxDispatcher, React, SelectedChannelStore, Toasts, UserUtils } from "@webpack/common";
|
import { React, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
import { Review, UserType } from "./entities";
|
import { Review, UserType } from "./entities";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
|
|
||||||
export const cl = classNameFactory("vc-rdb-");
|
export const cl = classNameFactory("vc-rdb-");
|
||||||
|
|
||||||
export async function openUserProfileModal(userId: string) {
|
|
||||||
await UserUtils.fetchUser(userId);
|
|
||||||
|
|
||||||
await FluxDispatcher.dispatch({
|
|
||||||
type: "USER_PROFILE_MODAL_OPEN",
|
|
||||||
userId,
|
|
||||||
channelId: SelectedChannelStore.getChannelId(),
|
|
||||||
analyticsLocation: "Explosive Hotel"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function authorize(callback?: any) {
|
export function authorize(callback?: any) {
|
||||||
const { OAuth2AuthorizeModal } = findByProps("OAuth2AuthorizeModal");
|
const { OAuth2AuthorizeModal } = findByProps("OAuth2AuthorizeModal");
|
||||||
|
|
||||||
@ -50,9 +39,9 @@ export function authorize(callback?: any) {
|
|||||||
permissions={0n}
|
permissions={0n}
|
||||||
clientId="915703782174752809"
|
clientId="915703782174752809"
|
||||||
cancelCompletesFlow={false}
|
cancelCompletesFlow={false}
|
||||||
callback={async (u: string) => {
|
callback={async (response: any) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(u);
|
const url = new URL(response.location);
|
||||||
url.searchParams.append("clientMod", "vencord");
|
url.searchParams.append("clientMod", "vencord");
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
headers: new Headers({ Accept: "application/json" })
|
headers: new Headers({ Accept: "application/json" })
|
||||||
|
@ -24,7 +24,7 @@ import { useForceUpdater } from "@utils/react";
|
|||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common";
|
import { GuildStore, PresenceStore, RelationshipStore } from "@webpack/common";
|
||||||
|
|
||||||
enum IndicatorType {
|
const enum IndicatorType {
|
||||||
SERVER = 1 << 0,
|
SERVER = 1 << 0,
|
||||||
FRIEND = 1 << 1,
|
FRIEND = 1 << 1,
|
||||||
BOTH = SERVER | FRIEND,
|
BOTH = SERVER | FRIEND,
|
||||||
|
@ -46,18 +46,18 @@ export type ShikiSpec = {
|
|||||||
}) => Promise<IThemedToken[][]>;
|
}) => Promise<IThemedToken[][]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum StyleSheets {
|
export const enum StyleSheets {
|
||||||
Main = "MAIN",
|
Main = "MAIN",
|
||||||
DevIcons = "DEVICONS",
|
DevIcons = "DEVICONS",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum HljsSetting {
|
export const enum HljsSetting {
|
||||||
Never = "NEVER",
|
Never = "NEVER",
|
||||||
Secondary = "SECONDARY",
|
Secondary = "SECONDARY",
|
||||||
Primary = "PRIMARY",
|
Primary = "PRIMARY",
|
||||||
Always = "ALWAYS",
|
Always = "ALWAYS",
|
||||||
}
|
}
|
||||||
export enum DeviconSetting {
|
export const enum DeviconSetting {
|
||||||
Disabled = "DISABLED",
|
Disabled = "DISABLED",
|
||||||
Greyscale = "GREYSCALE",
|
Greyscale = "GREYSCALE",
|
||||||
Color = "COLOR"
|
Color = "COLOR"
|
||||||
|
@ -29,12 +29,12 @@ import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission
|
|||||||
import { sortPermissionOverwrites } from "../../permissionsViewer/utils";
|
import { sortPermissionOverwrites } from "../../permissionsViewer/utils";
|
||||||
import { settings, VIEW_CHANNEL } from "..";
|
import { settings, VIEW_CHANNEL } from "..";
|
||||||
|
|
||||||
enum SortOrderTypes {
|
const enum SortOrderTypes {
|
||||||
LATEST_ACTIVITY = 0,
|
LATEST_ACTIVITY = 0,
|
||||||
CREATION_DATE = 1
|
CREATION_DATE = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ForumLayoutTypes {
|
const enum ForumLayoutTypes {
|
||||||
DEFAULT = 0,
|
DEFAULT = 0,
|
||||||
LIST = 1,
|
LIST = 1,
|
||||||
GRID = 2
|
GRID = 2
|
||||||
@ -61,7 +61,7 @@ interface ExtendedChannel extends Channel {
|
|||||||
availableTags?: Array<Tag>;
|
availableTags?: Array<Tag>;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChannelTypes {
|
const enum ChannelTypes {
|
||||||
GUILD_TEXT = 0,
|
GUILD_TEXT = 0,
|
||||||
GUILD_VOICE = 2,
|
GUILD_VOICE = 2,
|
||||||
GUILD_ANNOUNCEMENT = 5,
|
GUILD_ANNOUNCEMENT = 5,
|
||||||
@ -69,12 +69,12 @@ enum ChannelTypes {
|
|||||||
GUILD_FORUM = 15
|
GUILD_FORUM = 15
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VideoQualityModes {
|
const enum VideoQualityModes {
|
||||||
AUTO = 1,
|
AUTO = 1,
|
||||||
FULL = 2
|
FULL = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChannelFlags {
|
const enum ChannelFlags {
|
||||||
PINNED = 1 << 1,
|
PINNED = 1 << 1,
|
||||||
REQUIRE_TAG = 1 << 4
|
REQUIRE_TAG = 1 << 4
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted
|
|||||||
export const VIEW_CHANNEL = 1n << 10n;
|
export const VIEW_CHANNEL = 1n << 10n;
|
||||||
const CONNECT = 1n << 20n;
|
const CONNECT = 1n << 20n;
|
||||||
|
|
||||||
enum ShowMode {
|
const enum ShowMode {
|
||||||
LockIcon,
|
LockIcon,
|
||||||
HiddenIconWithMutedStyle
|
HiddenIconWithMutedStyle
|
||||||
}
|
}
|
||||||
@ -107,8 +107,8 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Prevent Discord from trying to connect to hidden channels
|
// Prevent Discord from trying to connect to hidden channels
|
||||||
match: /if\(!\i&&!\i(?=.{0,50}?selectVoiceChannel\((\i)\.id\))/,
|
match: /(?=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\))/,
|
||||||
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
|
replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Make Discord show inside the channel if clicking on a hidden or locked 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)(?=\))/,
|
match: /(?<=getChannels\(\i)(?=\))/,
|
||||||
replace: ",true"
|
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,
|
settings,
|
||||||
|
|
||||||
renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix }: UsernameProps) => {
|
renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix }: UsernameProps) => {
|
||||||
|
if (message.interaction) return author?.nick;
|
||||||
try {
|
try {
|
||||||
const { username } = message.author;
|
const { username } = message.author;
|
||||||
const { nick } = author;
|
const { nick } = author;
|
||||||
|
@ -27,7 +27,7 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
|
|||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import plugins from "~plugins";
|
import plugins from "~plugins";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./_core/settings";
|
||||||
|
|
||||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
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_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||||
if (IS_VENCORD_DESKTOP) return `Vencord Desktop v${VencordDesktopNative.app.getVersion()}`;
|
if (IS_VENCORD_DESKTOP) return `Vencord Desktop v${VencordDesktopNative.app.getVersion()}`;
|
||||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
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;
|
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 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 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 = `
|
const debugInfo = `
|
||||||
**Vencord Debug Info**
|
**Vencord Debug Info**
|
||||||
>>> Discord Branch: ${RELEASE_CHANNEL}
|
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
|
||||||
Client: ${client}
|
|
||||||
Platform: ${window.navigator.platform}
|
|
||||||
Vencord: ${gitHash}${settings.additionalInfo}
|
|
||||||
Outdated: ${isOutdated}
|
|
||||||
OpenAsar: ${"openasar" in window}
|
|
||||||
|
|
||||||
Enabled Plugins (${enabledPlugins.length + enabledApiPlugins.length}):
|
Enabled Plugins (${enabledPlugins.length + enabledApiPlugins.length}):
|
||||||
${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "))}
|
${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "))}
|
||||||
|
@ -19,13 +19,13 @@
|
|||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { openUserProfile } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { GuildMemberStore, React, RelationshipStore, SelectedChannelStore } from "@webpack/common";
|
import { GuildMemberStore, React, RelationshipStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
const Avatar = findByCodeLazy(".typingIndicatorRef", "svg");
|
const Avatar = findByCodeLazy(".typingIndicatorRef", "svg");
|
||||||
const openProfile = findByCodeLazy("friendToken", "USER_PROFILE_MODAL_OPEN");
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showAvatars: {
|
showAvatars: {
|
||||||
@ -64,15 +64,7 @@ const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) {
|
|||||||
<strong
|
<strong
|
||||||
role="button"
|
role="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openProfile({
|
openUserProfile(user.id);
|
||||||
userId: user.id,
|
|
||||||
guildId,
|
|
||||||
channelId: SelectedChannelStore.getChannelId(),
|
|
||||||
analyticsLocation: {
|
|
||||||
page: guildId ? "Guild Channel" : "DM Channel",
|
|
||||||
section: "Profile Popout"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
|
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"],
|
["stupid", "baka"],
|
||||||
["what", "nani"],
|
["what", "nani"],
|
||||||
["meow", "nya~"],
|
["meow", "nya~"],
|
||||||
|
["hello", "hewwo"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
|
@ -24,7 +24,7 @@ import definePlugin from "@utils/types";
|
|||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { UserStore, useState } from "@webpack/common";
|
import { UserStore, useState } from "@webpack/common";
|
||||||
import type { User } from "discord-types/general";
|
import type { User } from "discord-types/general";
|
||||||
import type { ComponentType } from "react";
|
import type { ComponentType, ReactNode } from "react";
|
||||||
|
|
||||||
const fetching = new Set<string>();
|
const fetching = new Set<string>();
|
||||||
const queue = new Queue(5);
|
const queue = new Queue(5);
|
||||||
@ -36,7 +36,7 @@ interface MentionProps {
|
|||||||
channelId?: string;
|
channelId?: string;
|
||||||
content: any;
|
content: any;
|
||||||
};
|
};
|
||||||
parse: (content: any, props: MentionProps["props"]) => string[];
|
parse: (content: any, props: MentionProps["props"]) => ReactNode;
|
||||||
props: {
|
props: {
|
||||||
key: string;
|
key: string;
|
||||||
formatInline: boolean;
|
formatInline: boolean;
|
||||||
@ -72,7 +72,7 @@ function MentionWrapper({ data, UserMention, RoleMention, parse, props }: Mentio
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
const mention = children?.[0];
|
const mention = children?.[0]?.props?.children;
|
||||||
if (typeof mention !== "string") return;
|
if (typeof mention !== "string") return;
|
||||||
|
|
||||||
const id = mention.match(/<@(\d+)>/)?.[1];
|
const id = mention.match(/<@(\d+)>/)?.[1];
|
||||||
|
@ -71,7 +71,7 @@ function clean(str: string) {
|
|||||||
|
|
||||||
function formatText(str: string, user: string, channel: string) {
|
function formatText(str: string, user: string, channel: string) {
|
||||||
return str
|
return str
|
||||||
.replaceAll("{{USER}}", clean(user) || user ? "Someone" : "")
|
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
||||||
.replaceAll("{{CHANNEL}}", clean(channel) || "channel");
|
.replaceAll("{{CHANNEL}}", clean(channel) || "channel");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,15 +20,12 @@ import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatc
|
|||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { ImageIcon } from "@components/Icons";
|
import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { openImageModal } from "@utils/discord";
|
||||||
import { LazyComponent } from "@utils/react";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { find, findByCode, findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { GuildMemberStore, Menu } from "@webpack/common";
|
import { GuildMemberStore, Menu } from "@webpack/common";
|
||||||
import type { Channel, Guild, User } from "discord-types/general";
|
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");
|
const BannerStore = findByPropsLazy("getGuildBannerURL");
|
||||||
|
|
||||||
interface UserContextProps {
|
interface UserContextProps {
|
||||||
@ -60,26 +57,29 @@ const settings = definePluginSettings({
|
|||||||
value: "jpg",
|
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) {
|
function openImage(url: string) {
|
||||||
const format = url.startsWith("/") ? "png" : settings.store.format;
|
const format = url.startsWith("/") ? "png" : settings.store.format;
|
||||||
|
|
||||||
const u = new URL(url, window.location.href);
|
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}`);
|
u.pathname = u.pathname.replace(/\.(png|jpe?g|webp)$/, `.${format}`);
|
||||||
url = u.toString();
|
url = u.toString();
|
||||||
|
|
||||||
openModal(modalProps => (
|
u.searchParams.set("size", "4096");
|
||||||
<ModalRoot size={ModalSize.DYNAMIC} {...modalProps}>
|
const originalUrl = u.toString();
|
||||||
<ImageModal
|
|
||||||
shouldAnimate={true}
|
openImageModal(url, {
|
||||||
original={url}
|
original: originalUrl,
|
||||||
src={url}
|
height: 256
|
||||||
renderLinkComponent={MaskedLink}
|
});
|
||||||
/>
|
|
||||||
</ModalRoot>
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
|
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
|
||||||
@ -90,7 +90,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
|||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="view-avatar"
|
id="view-avatar"
|
||||||
label="View Avatar"
|
label="View Avatar"
|
||||||
action={() => openImage(BannerStore.getUserAvatarURL(user, true, 512))}
|
action={() => openImage(BannerStore.getUserAvatarURL(user, true))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
{memberAvatar && (
|
{memberAvatar && (
|
||||||
@ -122,7 +122,6 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon
|
|||||||
openImage(BannerStore.getGuildIconURL({
|
openImage(BannerStore.getGuildIconURL({
|
||||||
id,
|
id,
|
||||||
icon,
|
icon,
|
||||||
size: 512,
|
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { copyWithToast } from "@utils/misc";
|
import { copyWithToast } from "@utils/misc";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
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 { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
@ -41,22 +42,12 @@ function sortObject<T extends object>(obj: T): T {
|
|||||||
|
|
||||||
function cleanMessage(msg: Message) {
|
function cleanMessage(msg: Message) {
|
||||||
const clone = sortObject(JSON.parse(JSON.stringify(msg)));
|
const clone = sortObject(JSON.parse(JSON.stringify(msg)));
|
||||||
for (const key in clone.author) {
|
for (const key of [
|
||||||
switch (key) {
|
"email",
|
||||||
case "id":
|
"phone",
|
||||||
case "username":
|
"mfaEnabled",
|
||||||
case "usernameNormalized":
|
"personalConnectionId"
|
||||||
case "discriminator":
|
]) delete clone.author[key];
|
||||||
case "avatar":
|
|
||||||
case "bot":
|
|
||||||
case "system":
|
|
||||||
case "publicFlags":
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// phone number, email, etc
|
|
||||||
delete clone.author[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// message logger added properties
|
// message logger added properties
|
||||||
const cloneAny = clone as any;
|
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({
|
export default definePlugin({
|
||||||
name: "ViewRaw",
|
name: "ViewRaw",
|
||||||
description: "Copy and view the raw content/data of any message.",
|
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"],
|
dependencies: ["MessagePopoverAPI"],
|
||||||
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addButton("ViewRaw", msg => {
|
addButton("ViewRaw", msg => {
|
||||||
return {
|
const handleClick = () => {
|
||||||
label: "View Raw (Left Click) / Copy Raw (Right Click)",
|
if (settings.store.clickMethod === "Right") {
|
||||||
icon: CopyIcon,
|
copyWithToast(msg.content);
|
||||||
message: msg,
|
} else {
|
||||||
channel: ChannelStore.getChannel(msg.channel_id),
|
openViewRawModal(msg);
|
||||||
onClick: () => openViewRawModal(msg),
|
}
|
||||||
onContextMenu: e => {
|
};
|
||||||
|
|
||||||
|
const handleContextMenu = e => {
|
||||||
|
if (settings.store.clickMethod === "Left") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
copyWithToast(msg.content);
|
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"
|
description: "Choose the greet mode"
|
||||||
}
|
}
|
||||||
});
|
}).withPrivateSettings<{
|
||||||
|
multiGreetChoices?: string[];
|
||||||
|
unholyMultiGreetEnabled?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
const MessageActions = findByPropsLazy("sendGreetMessage");
|
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; }) {
|
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;
|
const { greetMode, multiGreetChoices = [] } = s;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -112,7 +115,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
|
|||||||
))}
|
))}
|
||||||
</Menu.MenuGroup>
|
</Menu.MenuGroup>
|
||||||
|
|
||||||
{!(settings.store as any).unholyMultiGreetEnabled ? null : (
|
{!settings.store.unholyMultiGreetEnabled ? null : (
|
||||||
<>
|
<>
|
||||||
<Menu.MenuSeparator />
|
<Menu.MenuSeparator />
|
||||||
|
|
||||||
|
@ -77,14 +77,14 @@ export async function authorizeCloud() {
|
|||||||
permissions={0n}
|
permissions={0n}
|
||||||
clientId={clientId}
|
clientId={clientId}
|
||||||
cancelCompletesFlow={false}
|
cancelCompletesFlow={false}
|
||||||
callback={async (callbackUrl: string) => {
|
callback={async ({ location }: any) => {
|
||||||
if (!callbackUrl) {
|
if (!location) {
|
||||||
Settings.cloud.authenticated = false;
|
Settings.cloud.authenticated = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(callbackUrl, {
|
const res = await fetch(location, {
|
||||||
headers: new Headers({ Accept: "application/json" })
|
headers: new Headers({ Accept: "application/json" })
|
||||||
});
|
});
|
||||||
const { secret } = await res.json();
|
const { secret } = await res.json();
|
||||||
|
@ -315,6 +315,26 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
name: "UwU",
|
name: "UwU",
|
||||||
id: 691413039156690994n,
|
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>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// 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
|
// 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.
|
* 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
|
// TODO: Might need to somehow implement this
|
||||||
export enum ApngBlendOp {
|
export const enum ApngBlendOp {
|
||||||
SOURCE,
|
SOURCE,
|
||||||
OVER
|
OVER
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MessageObject } from "@api/MessageEvents";
|
import { MessageObject } from "@api/MessageEvents";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { ChannelStore, ComponentDispatch, GuildStore, PrivateChannelsStore, SelectedChannelStore } from "@webpack/common";
|
import { ChannelStore, ComponentDispatch, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, SelectedChannelStore, SelectedGuildStore, UserUtils } from "@webpack/common";
|
||||||
import { Guild, Message } from "discord-types/general";
|
import { Guild, Message, User } from "discord-types/general";
|
||||||
|
|
||||||
|
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
||||||
|
|
||||||
const PreloadedUserSettings = findLazy(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings"));
|
const PreloadedUserSettings = findLazy(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings"));
|
||||||
const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||||
@ -77,3 +79,48 @@ export function sendMessage(
|
|||||||
|
|
||||||
return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
|
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>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
const openProfile = findByCodeLazy("friendToken", "USER_PROFILE_MODAL_OPEN");
|
||||||
|
|
||||||
|
export async function openUserProfile(id: string) {
|
||||||
|
const user = await UserUtils.fetchUser(id);
|
||||||
|
if (!user) throw new Error("No such user: " + id);
|
||||||
|
|
||||||
|
const guildId = SelectedGuildStore.getGuildId();
|
||||||
|
openProfile({
|
||||||
|
userId: id,
|
||||||
|
guildId,
|
||||||
|
channelId: SelectedChannelStore.getChannelId(),
|
||||||
|
analyticsLocation: {
|
||||||
|
page: guildId ? "Guild Channel" : "DM Channel",
|
||||||
|
section: "Profile Popout"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique username for a user. Returns user.username for pomelo people, user.tag otherwise
|
||||||
|
*/
|
||||||
|
export function getUniqueUsername(user: User) {
|
||||||
|
return user.discriminator === "0" ? user.username : user.tag;
|
||||||
|
}
|
@ -31,4 +31,5 @@ export * from "./onceDefined";
|
|||||||
export * from "./onlyOnce";
|
export * from "./onlyOnce";
|
||||||
export * from "./patches";
|
export * from "./patches";
|
||||||
export * from "./Queue";
|
export * from "./Queue";
|
||||||
|
export * from "./react";
|
||||||
export * from "./text";
|
export * from "./text";
|
||||||
|
@ -16,19 +16,19 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { filters, mapMangledModuleLazy } from "@webpack";
|
import { filters, findByCode, mapMangledModuleLazy } from "@webpack";
|
||||||
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
import { LazyComponent } from "./react";
|
import { LazyComponent } from "./react";
|
||||||
|
|
||||||
export enum ModalSize {
|
export const enum ModalSize {
|
||||||
SMALL = "small",
|
SMALL = "small",
|
||||||
MEDIUM = "medium",
|
MEDIUM = "medium",
|
||||||
LARGE = "large",
|
LARGE = "large",
|
||||||
DYNAMIC = "dynamic",
|
DYNAMIC = "dynamic",
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ModalTransitionState {
|
const enum ModalTransitionState {
|
||||||
ENTERING,
|
ENTERING,
|
||||||
ENTERED,
|
ENTERED,
|
||||||
EXITING,
|
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 ModalRoot = LazyComponent(() => Modals.ModalRoot);
|
||||||
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
||||||
export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
||||||
|
@ -149,12 +149,6 @@ export async function putCloudSettings() {
|
|||||||
VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
|
VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
|
||||||
|
|
||||||
cloudSettingsLogger.info("Settings uploaded to cloud successfully");
|
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) {
|
} catch (e: any) {
|
||||||
cloudSettingsLogger.error("Failed to sync up", e);
|
cloudSettingsLogger.error("Failed to sync up", e);
|
||||||
showNotification({
|
showNotification({
|
||||||
|
@ -117,7 +117,7 @@ export interface PluginDef {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OptionType {
|
export const enum OptionType {
|
||||||
STRING,
|
STRING,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
BIGINT,
|
BIGINT,
|
||||||
@ -260,25 +260,29 @@ type SettingsStore<D extends SettingsDefinition> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** An instance of defined plugin settings */
|
/** 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 */
|
/** 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
|
* React hook for getting the settings for this plugin
|
||||||
* @param filter optional filter to avoid rerenders for irrelavent settings
|
* @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 */
|
/** Definitions of each setting */
|
||||||
def: D;
|
def: Def;
|
||||||
/** Setting methods with return values that could rely on other settings */
|
/** Setting methods with return values that could rely on other settings */
|
||||||
checks: C;
|
checks: Checks;
|
||||||
/**
|
/**
|
||||||
* Name of the plugin these settings belong to,
|
* Name of the plugin these settings belong to,
|
||||||
* will be an empty string until plugin is initialized
|
* will be an empty string until plugin is initialized
|
||||||
*/
|
*/
|
||||||
pluginName: string;
|
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>>;
|
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
|
// eslint-disable-next-line path-alias/no-relative
|
||||||
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
import { filters, waitFor } from "@webpack";
|
||||||
|
|
||||||
import { waitForComponent } from "./internal";
|
import { waitForComponent } from "./internal";
|
||||||
import * as t from "./types/components";
|
import * as t from "./types/components";
|
||||||
@ -44,16 +44,18 @@ export let Popout: t.Popout;
|
|||||||
export let Dialog: t.Dialog;
|
export let Dialog: t.Dialog;
|
||||||
export let TabBar: any;
|
export let TabBar: any;
|
||||||
export let Paginator: t.Paginator;
|
export let Paginator: t.Paginator;
|
||||||
|
export let ScrollerThin: t.ScrollerThin;
|
||||||
|
export let Clickable: t.Clickable;
|
||||||
|
export let Avatar: t.Avatar;
|
||||||
// token lagger real
|
// token lagger real
|
||||||
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
|
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
|
||||||
export let useToken: t.useToken;
|
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 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 Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||||
|
|
||||||
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
|
|
||||||
|
|
||||||
waitFor("FormItem", m => {
|
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;
|
Forms = m;
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from "./classes";
|
||||||
export * from "./components";
|
export * from "./components";
|
||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
export * from "./react";
|
export * from "./react";
|
||||||
@ -24,4 +25,3 @@ export * as ComponentTypes from "./types/components.d";
|
|||||||
export * as MenuTypes from "./types/menu.d";
|
export * as MenuTypes from "./types/menu.d";
|
||||||
export * as UtilTypes from "./types/utils.d";
|
export * as UtilTypes from "./types/utils.d";
|
||||||
export * from "./utils";
|
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;
|
onPageChange?(page: number): void;
|
||||||
hideMaxPage?: boolean;
|
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;
|
onChildrenScroll?: Function;
|
||||||
childRowHeight?: number;
|
childRowHeight?: number;
|
||||||
listClassName?: string;
|
listClassName?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
MenuCheckboxItem: RC<{
|
MenuCheckboxItem: RC<{
|
||||||
id: string;
|
id: string;
|
||||||
|
9
src/webpack/common/types/stores.d.ts
vendored
9
src/webpack/common/types/stores.d.ts
vendored
@ -20,14 +20,23 @@ import { Channel } from "discord-types/general";
|
|||||||
|
|
||||||
import { FluxDispatcher, FluxEvents } from "./utils";
|
import { FluxDispatcher, FluxEvents } from "./utils";
|
||||||
|
|
||||||
|
type GenericFunction = (...args: any[]) => any;
|
||||||
|
|
||||||
export class FluxStore {
|
export class FluxStore {
|
||||||
constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>);
|
constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>);
|
||||||
|
|
||||||
|
addChangeListener(callback: () => void): void;
|
||||||
|
addReactChangeListener(callback: () => void): void;
|
||||||
|
removeChangeListener(callback: () => void): void;
|
||||||
|
removeReactChangeListener(callback: () => void): void;
|
||||||
emitChange(): void;
|
emitChange(): void;
|
||||||
getDispatchToken(): string;
|
getDispatchToken(): string;
|
||||||
getName(): string;
|
getName(): string;
|
||||||
initialize(): void;
|
initialize(): void;
|
||||||
initializeIfNeeded(): void;
|
initializeIfNeeded(): void;
|
||||||
|
registerActionHandlers: GenericFunction;
|
||||||
|
syncWith: GenericFunction;
|
||||||
|
waitFor: GenericFunction;
|
||||||
__getLocalVars(): Record<string, any>;
|
__getLocalVars(): Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user