Compare commits
1 commit
20cf1eb6a2
...
beaf164aa8
Author | SHA1 | Date | |
---|---|---|---|
beaf164aa8 |
97 changed files with 977 additions and 3170 deletions
2
.github/workflows/codeberg-mirror.yml
vendored
2
.github/workflows/codeberg-mirror.yml
vendored
|
@ -18,5 +18,5 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
|
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
|
||||||
with:
|
with:
|
||||||
target_repo_url: "git@codeberg.org:Vee/cord.git"
|
target_repo_url: "git@codeberg.org:Ven/cord.git"
|
||||||
ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }}
|
ssh_private_key: ${{ secrets.CODEBERG_SSH_PRIVATE_KEY }}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Vencord
|
# Vencord
|
||||||
|
|
||||||
[![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/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/Vee/cord)
|
[![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
|
The cutest Discord client mod
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.6.9",
|
"version": "1.6.5",
|
||||||
"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": {
|
||||||
|
|
|
@ -350,7 +350,7 @@ function runTime(token: string) {
|
||||||
let invalidEntryPoint = false;
|
let invalidEntryPoint = false;
|
||||||
|
|
||||||
for (const id of chunkIds) {
|
for (const id of chunkIds) {
|
||||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
if (!wreq.u(id)) continue;
|
||||||
|
|
||||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
|
@ -376,22 +376,9 @@ function runTime(token: string) {
|
||||||
} catch (err) { }
|
} catch (err) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches "id" or id:
|
const allChunks = Function("return " + (wreq.u.toString().match(/(?<=\()\{.+?\}/s)?.[0] ?? "null"))() as Record<string | number, string[]> | null;
|
||||||
const chunkIdRegex = /(?:"(\d+?)")|(?:(\d+?):)/g;
|
if (!allChunks) throw new Error("Failed to get all chunks");
|
||||||
const wreqU = wreq.u.toString();
|
const chunksLeft = Object.keys(allChunks).filter(id => {
|
||||||
|
|
||||||
const allChunks = [] as string[];
|
|
||||||
let currentMatch: RegExpExecArray | null;
|
|
||||||
|
|
||||||
while ((currentMatch = chunkIdRegex.exec(wreqU)) != null) {
|
|
||||||
const id = currentMatch[1] ?? currentMatch[2];
|
|
||||||
if (id == null) continue;
|
|
||||||
|
|
||||||
allChunks.push(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
|
||||||
const chunksLeft = allChunks.filter(id => {
|
|
||||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,11 @@ const ETAG_FILE = join(FILE_DIR, "etag.txt");
|
||||||
function getFilename() {
|
function getFilename() {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case "win32":
|
case "win32":
|
||||||
return "VencordInstallerCli.exe";
|
return "VencordInstaller.exe";
|
||||||
case "darwin":
|
case "darwin":
|
||||||
return "VencordInstaller.MacOS.zip";
|
return "VencordInstaller.MacOS.zip";
|
||||||
case "linux":
|
case "linux":
|
||||||
return "VencordInstallerCli-linux";
|
return "VencordInstaller-" + (process.env.WAYLAND_DISPLAY ? "wayland" : "x11");
|
||||||
default:
|
default:
|
||||||
throw new Error("Unsupported platform: " + process.platform);
|
throw new Error("Unsupported platform: " + process.platform);
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,6 @@ const installerBin = await ensureBinary();
|
||||||
|
|
||||||
console.log("Now running Installer...");
|
console.log("Now running Installer...");
|
||||||
|
|
||||||
try {
|
|
||||||
execFileSync(installerBin, {
|
execFileSync(installerBin, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
env: {
|
env: {
|
||||||
|
@ -127,6 +126,3 @@ try {
|
||||||
VENCORD_DEV_INSTALL: "1"
|
VENCORD_DEV_INSTALL: "1"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
console.error("Something went wrong. Please check the logs above.");
|
|
||||||
}
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ async function syncSettings() {
|
||||||
// pre-check for local shared settings
|
// pre-check for local shared settings
|
||||||
if (
|
if (
|
||||||
Settings.cloud.authenticated &&
|
Settings.cloud.authenticated &&
|
||||||
!await dsGet("Vencord_cloudSecret") // this has been enabled due to local settings share or some other bug
|
await dsGet("Vencord_cloudSecret") === null // this has been enabled due to local settings share or some other bug
|
||||||
) {
|
) {
|
||||||
// show a notification letting them know and tell them how to fix it
|
// show a notification letting them know and tell them how to fix it
|
||||||
showNotification({
|
showNotification({
|
||||||
|
@ -145,3 +145,4 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
.vc-chatbar-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "./ChatButton.css";
|
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { waitFor } from "@webpack";
|
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
|
||||||
import { Channel } from "discord-types/general";
|
|
||||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
|
||||||
|
|
||||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
|
||||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
|
||||||
|
|
||||||
export interface ChatBarProps {
|
|
||||||
channel: Channel;
|
|
||||||
disabled: boolean;
|
|
||||||
isEmpty: boolean;
|
|
||||||
type: {
|
|
||||||
analyticsName: string;
|
|
||||||
attachments: boolean;
|
|
||||||
autocomplete: {
|
|
||||||
addReactionShortcut: boolean,
|
|
||||||
forceChatLayer: boolean,
|
|
||||||
reactions: boolean;
|
|
||||||
},
|
|
||||||
commands: {
|
|
||||||
enabled: boolean;
|
|
||||||
},
|
|
||||||
drafts: {
|
|
||||||
type: number,
|
|
||||||
commandType: number,
|
|
||||||
autoSave: boolean;
|
|
||||||
},
|
|
||||||
emojis: {
|
|
||||||
button: boolean;
|
|
||||||
},
|
|
||||||
gifs: {
|
|
||||||
button: boolean,
|
|
||||||
allowSending: boolean;
|
|
||||||
},
|
|
||||||
gifts: {
|
|
||||||
button: boolean;
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
requireSendMessages: boolean;
|
|
||||||
},
|
|
||||||
showThreadPromptOnReply: boolean,
|
|
||||||
stickers: {
|
|
||||||
button: boolean,
|
|
||||||
allowSending: boolean,
|
|
||||||
autoSuggest: boolean;
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
allowMentioning: boolean;
|
|
||||||
},
|
|
||||||
submit: {
|
|
||||||
button: boolean,
|
|
||||||
ignorePreference: boolean,
|
|
||||||
disableEnterToSubmit: boolean,
|
|
||||||
clearOnSubmit: boolean,
|
|
||||||
useDisabledStylesOnSubmit: boolean;
|
|
||||||
},
|
|
||||||
uploadLongMessages: boolean,
|
|
||||||
upsellLongMessages: {
|
|
||||||
iconOnly: boolean;
|
|
||||||
},
|
|
||||||
showCharacterCount: boolean,
|
|
||||||
sedReplace: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
|
||||||
|
|
||||||
const buttonFactories = new Map<string, ChatBarButton>();
|
|
||||||
const logger = new Logger("ChatButtons");
|
|
||||||
|
|
||||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
|
||||||
if (props.disabled) return;
|
|
||||||
|
|
||||||
for (const [key, Button] of buttonFactories) {
|
|
||||||
buttons.push(
|
|
||||||
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
|
|
||||||
<Button {...props} isMainChat={props.type.analyticsName === "normal"} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
|
||||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
|
||||||
|
|
||||||
export interface ChatBarButtonProps {
|
|
||||||
children: ReactNode;
|
|
||||||
tooltip: string;
|
|
||||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
|
||||||
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
|
|
||||||
}
|
|
||||||
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
|
||||||
return (
|
|
||||||
<Tooltip text={props.tooltip}>
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<div className={`expression-picker-chat-input-button ${ChannelTextAreaClasses?.buttonContainer ?? ""} vc-chatbar-button`}>
|
|
||||||
<Button
|
|
||||||
aria-label={props.tooltip}
|
|
||||||
size=""
|
|
||||||
look={ButtonLooks.BLANK}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
|
||||||
onClick={props.onClick}
|
|
||||||
onContextMenu={props.onContextMenu}
|
|
||||||
{...props.buttonProps}
|
|
||||||
>
|
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}, { noop: true });
|
|
|
@ -21,7 +21,7 @@ import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import type { DispatchWithoutAction } from "react";
|
import type { DispatchWithoutAction } from "react";
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
|
||||||
richBody={
|
richBody={
|
||||||
<div className={cl("body")}>
|
<div className={cl("body")}>
|
||||||
{data.body}
|
{data.body}
|
||||||
<Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
|
<Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as $Badges from "./Badges";
|
import * as $Badges from "./Badges";
|
||||||
import * as $ChatButtons from "./ChatButtons";
|
|
||||||
import * as $Commands from "./Commands";
|
import * as $Commands from "./Commands";
|
||||||
import * as $ContextMenu from "./ContextMenu";
|
import * as $ContextMenu from "./ContextMenu";
|
||||||
import * as $DataStore from "./DataStore";
|
import * as $DataStore from "./DataStore";
|
||||||
|
@ -105,8 +104,3 @@ 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 add buttons to the chat input
|
|
||||||
*/
|
|
||||||
export const ChatButtons = $ChatButtons;
|
|
||||||
|
|
|
@ -21,11 +21,9 @@ import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon } from "@components/Icons";
|
import { DeleteIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { openModal } from "@utils/modal";
|
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
|
@ -250,21 +248,6 @@ function ThemesTab() {
|
||||||
>
|
>
|
||||||
Edit QuickCSS
|
Edit QuickCSS
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{Vencord.Settings.plugins.ClientTheme.enabled && (
|
|
||||||
<Button
|
|
||||||
onClick={() => openModal(modalProps => (
|
|
||||||
<PluginModal
|
|
||||||
{...modalProps}
|
|
||||||
plugin={Vencord.Plugins.plugins.ClientTheme}
|
|
||||||
onRestartNeeded={() => { }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
>
|
|
||||||
Edit ClientTheme
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
|
@ -81,12 +81,9 @@ function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string
|
||||||
|
|
||||||
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
|
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
|
||||||
return (
|
return (
|
||||||
<Card style={{ padding: "0 0.5em" }}>
|
<Card style={{ padding: ".5em" }}>
|
||||||
{updates.map(({ hash, author, message }) => (
|
{updates.map(({ hash, author, message }) => (
|
||||||
<div style={{
|
<div>
|
||||||
marginTop: "0.5em",
|
|
||||||
marginBottom: "0.5em"
|
|
||||||
}}>
|
|
||||||
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
|
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
|
||||||
<span style={{
|
<span style={{
|
||||||
marginLeft: "0.5em",
|
marginLeft: "0.5em",
|
||||||
|
@ -116,7 +113,7 @@ function Updatable(props: CommonProps) {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
<Forms.FormText className={Margins.bottom8}>
|
||||||
{isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
|
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -83,10 +83,10 @@ function VencordSettings() {
|
||||||
title: "Use Windows' native title bar instead of Discord's custom one",
|
title: "Use Windows' native title bar instead of Discord's custom one",
|
||||||
note: "Requires a full restart"
|
note: "Requires a full restart"
|
||||||
}),
|
}),
|
||||||
!IS_WEB && {
|
!IS_WEB && false /* This causes electron to freeze / white screen for some people */ && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable window transparency.",
|
title: "Enable window transparency",
|
||||||
note: "You need a theme that supports transparency or this will do nothing. Will stop the window from being resizable. Requires a full restart"
|
note: "Requires a full restart"
|
||||||
},
|
},
|
||||||
!IS_WEB && isWindows && {
|
!IS_WEB && isWindows && {
|
||||||
key: "winCtrlQ",
|
key: "winCtrlQ",
|
||||||
|
|
|
@ -139,15 +139,8 @@ export function initIpc(mainWindow: BrowserWindow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
||||||
const title = "Vencord QuickCSS Editor";
|
|
||||||
const existingWindow = BrowserWindow.getAllWindows().find(w => w.title === title);
|
|
||||||
if (existingWindow && !existingWindow.isDestroyed()) {
|
|
||||||
existingWindow.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
title,
|
title: "Vencord QuickCSS Editor",
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
|
|
@ -79,7 +79,8 @@ if (!IS_VANILLA) {
|
||||||
delete options.frame;
|
delete options.frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.transparent) {
|
// This causes electron to freeze / white screen for some people
|
||||||
|
if ((settings as any).transparentUNSAFE_USE_AT_OWN_RISK) {
|
||||||
options.transparent = true;
|
options.transparent = true;
|
||||||
options.backgroundColor = "#00000000";
|
options.backgroundColor = "#00000000";
|
||||||
}
|
}
|
||||||
|
@ -129,15 +130,6 @@ if (!IS_VANILLA) {
|
||||||
});
|
});
|
||||||
|
|
||||||
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||||
|
|
||||||
// Monkey patch commandLine to disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
|
||||||
const originalAppend = app.commandLine.appendSwitch;
|
|
||||||
app.commandLine.appendSwitch = function (...args) {
|
|
||||||
if (args[0] === "disable-features" && !args[1]?.includes("WidgetLayering")) {
|
|
||||||
args[1] += ",WidgetLayering";
|
|
||||||
}
|
|
||||||
return originalAppend.apply(this, args);
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,6 @@ async function build() {
|
||||||
const command = isFlatpak ? "flatpak-spawn" : "node";
|
const command = isFlatpak ? "flatpak-spawn" : "node";
|
||||||
const args = isFlatpak ? ["--host", "node", "scripts/build/build.mjs"] : ["scripts/build/build.mjs"];
|
const args = isFlatpak ? ["--host", "node", "scripts/build/build.mjs"] : ["scripts/build/build.mjs"];
|
||||||
|
|
||||||
if (IS_DEV) args.push("--dev");
|
|
||||||
|
|
||||||
const res = await execFile(command, args, opts);
|
const res = await execFile(command, args, opts);
|
||||||
|
|
||||||
return !res.stderr.includes("Build failed");
|
return !res.stderr.includes("Build failed");
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "ChatInputButtonAPI",
|
|
||||||
description: "API to add buttons to the chat input",
|
|
||||||
authors: [Devs.Ven],
|
|
||||||
|
|
||||||
patches: [{
|
|
||||||
find: 'location:"ChannelTextAreaButtons"',
|
|
||||||
replacement: {
|
|
||||||
match: /if\(!\i\.isMobile\)\{(?=.+?&&(\i)\.push\(.{0,50}"gift")/,
|
|
||||||
replace: "$&Vencord.Api.ChatButtons._injectButtons($1,arguments[0]);"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
94
src/plugins/anonymiseFileNames/index.ts
Normal file
94
src/plugins/anonymiseFileNames/index.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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 { Settings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
const enum Methods {
|
||||||
|
Random,
|
||||||
|
Consistent,
|
||||||
|
Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
const tarExtMatcher = /\.tar\.\w+$/;
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "AnonymiseFileNames",
|
||||||
|
authors: [Devs.obscurity],
|
||||||
|
description: "Anonymise uploaded file names",
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "instantBatchUpload:function",
|
||||||
|
replacement: {
|
||||||
|
match: /uploadFiles:(.{1,2}),/,
|
||||||
|
replace:
|
||||||
|
"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f.filename)),$1(...args)),",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
method: {
|
||||||
|
description: "Anonymising method",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [
|
||||||
|
{ label: "Random Characters", value: Methods.Random, default: true },
|
||||||
|
{ label: "Consistent", value: Methods.Consistent },
|
||||||
|
{ label: "Timestamp (4chan-like)", value: Methods.Timestamp },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
randomisedLength: {
|
||||||
|
description: "Random characters length",
|
||||||
|
type: OptionType.NUMBER,
|
||||||
|
default: 7,
|
||||||
|
disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Random,
|
||||||
|
},
|
||||||
|
consistent: {
|
||||||
|
description: "Consistent filename",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "image",
|
||||||
|
disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Consistent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
anonymise(file: string) {
|
||||||
|
let name = "image";
|
||||||
|
const tarMatch = tarExtMatcher.exec(file);
|
||||||
|
const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
|
||||||
|
const ext = extIdx !== -1 ? file.slice(extIdx) : "";
|
||||||
|
|
||||||
|
switch (Settings.plugins.AnonymiseFileNames.method) {
|
||||||
|
case Methods.Random:
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
name = Array.from(
|
||||||
|
{ length: Settings.plugins.AnonymiseFileNames.randomisedLength },
|
||||||
|
() => chars[Math.floor(Math.random() * chars.length)]
|
||||||
|
).join("");
|
||||||
|
break;
|
||||||
|
case Methods.Consistent:
|
||||||
|
name = Settings.plugins.AnonymiseFileNames.consistent;
|
||||||
|
break;
|
||||||
|
case Methods.Timestamp:
|
||||||
|
// UNIX timestamp in nanos, i could not find a better dependency-less way
|
||||||
|
name = `${Math.floor(Date.now() / 1000)}${Math.floor(window.performance.now())}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return name + ext;
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,130 +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 { Upload } from "@api/MessageEvents";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
|
||||||
|
|
||||||
type AnonUpload = Upload & { anonymise?: boolean; };
|
|
||||||
|
|
||||||
const ActionBarIcon = findByCodeLazy(".actionBarIcon)");
|
|
||||||
const UploadDraft = findByPropsLazy("popFirstFile", "update");
|
|
||||||
|
|
||||||
const enum Methods {
|
|
||||||
Random,
|
|
||||||
Consistent,
|
|
||||||
Timestamp,
|
|
||||||
}
|
|
||||||
|
|
||||||
const tarExtMatcher = /\.tar\.\w+$/;
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
anonymiseByDefault: {
|
|
||||||
description: "Whether to anonymise file names by default",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
method: {
|
|
||||||
description: "Anonymising method",
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
options: [
|
|
||||||
{ label: "Random Characters", value: Methods.Random, default: true },
|
|
||||||
{ label: "Consistent", value: Methods.Consistent },
|
|
||||||
{ label: "Timestamp", value: Methods.Timestamp },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
randomisedLength: {
|
|
||||||
description: "Random characters length",
|
|
||||||
type: OptionType.NUMBER,
|
|
||||||
default: 7,
|
|
||||||
disabled: () => settings.store.method !== Methods.Random,
|
|
||||||
},
|
|
||||||
consistent: {
|
|
||||||
description: "Consistent filename",
|
|
||||||
type: OptionType.STRING,
|
|
||||||
default: "image",
|
|
||||||
disabled: () => settings.store.method !== Methods.Consistent,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "AnonymiseFileNames",
|
|
||||||
authors: [Devs.obscurity],
|
|
||||||
description: "Anonymise uploaded file names",
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "instantBatchUpload:function",
|
|
||||||
replacement: {
|
|
||||||
match: /uploadFiles:(.{1,2}),/,
|
|
||||||
replace:
|
|
||||||
"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f)),$1(...args)),",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
|
|
||||||
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settings,
|
|
||||||
|
|
||||||
renderIcon: ErrorBoundary.wrap(({ upload, channelId, draftType }: { upload: AnonUpload; draftType: unknown; channelId: string; }) => {
|
|
||||||
const anonymise = upload.anonymise ?? settings.store.anonymiseByDefault;
|
|
||||||
return (
|
|
||||||
<ActionBarIcon
|
|
||||||
tooltip={anonymise ? "Using anonymous file name" : "Using normal file name"}
|
|
||||||
onClick={() => {
|
|
||||||
upload.anonymise = !anonymise;
|
|
||||||
UploadDraft.update(channelId, upload.id, draftType, {}); // dummy update so component rerenders
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{anonymise
|
|
||||||
? <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17.06 13C15.2 13 13.64 14.33 13.24 16.1C12.29 15.69 11.42 15.8 10.76 16.09C10.35 14.31 8.79 13 6.94 13C4.77 13 3 14.79 3 17C3 19.21 4.77 21 6.94 21C9 21 10.68 19.38 10.84 17.32C11.18 17.08 12.07 16.63 13.16 17.34C13.34 19.39 15 21 17.06 21C19.23 21 21 19.21 21 17C21 14.79 19.23 13 17.06 13M6.94 19.86C5.38 19.86 4.13 18.58 4.13 17S5.39 14.14 6.94 14.14C8.5 14.14 9.75 15.42 9.75 17S8.5 19.86 6.94 19.86M17.06 19.86C15.5 19.86 14.25 18.58 14.25 17S15.5 14.14 17.06 14.14C18.62 14.14 19.88 15.42 19.88 17S18.61 19.86 17.06 19.86M22 10.5H2V12H22V10.5M15.53 2.63C15.31 2.14 14.75 1.88 14.22 2.05L12 2.79L9.77 2.05L9.72 2.04C9.19 1.89 8.63 2.17 8.43 2.68L6 9H18L15.56 2.68L15.53 2.63Z" /></svg>
|
|
||||||
: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ transform: "scale(-1,1)" }}><path fill="currentColor" d="M22.11 21.46L2.39 1.73L1.11 3L6.31 8.2L6 9H7.11L8.61 10.5H2V12H10.11L13.5 15.37C13.38 15.61 13.3 15.85 13.24 16.1C12.29 15.69 11.41 15.8 10.76 16.09C10.35 14.31 8.79 13 6.94 13C4.77 13 3 14.79 3 17C3 19.21 4.77 21 6.94 21C9 21 10.68 19.38 10.84 17.32C11.18 17.08 12.07 16.63 13.16 17.34C13.34 19.39 15 21 17.06 21C17.66 21 18.22 20.86 18.72 20.61L20.84 22.73L22.11 21.46M6.94 19.86C5.38 19.86 4.13 18.58 4.13 17C4.13 15.42 5.39 14.14 6.94 14.14C8.5 14.14 9.75 15.42 9.75 17C9.75 18.58 8.5 19.86 6.94 19.86M17.06 19.86C15.5 19.86 14.25 18.58 14.25 17C14.25 16.74 14.29 16.5 14.36 16.25L17.84 19.73C17.59 19.81 17.34 19.86 17.06 19.86M22 12H15.2L13.7 10.5H22V12M17.06 13C19.23 13 21 14.79 21 17C21 17.25 20.97 17.5 20.93 17.73L19.84 16.64C19.68 15.34 18.66 14.32 17.38 14.17L16.29 13.09C16.54 13.03 16.8 13 17.06 13M12.2 9L7.72 4.5L8.43 2.68C8.63 2.17 9.19 1.89 9.72 2.04L9.77 2.05L12 2.79L14.22 2.05C14.75 1.88 15.32 2.14 15.54 2.63L15.56 2.68L18 9H12.2Z" /></svg>
|
|
||||||
}
|
|
||||||
</ActionBarIcon>
|
|
||||||
);
|
|
||||||
}, { noop: true }),
|
|
||||||
|
|
||||||
anonymise(upload: AnonUpload) {
|
|
||||||
if ((upload.anonymise ?? settings.store.anonymiseByDefault) === false) return upload.filename;
|
|
||||||
|
|
||||||
const file = upload.filename;
|
|
||||||
const tarMatch = tarExtMatcher.exec(file);
|
|
||||||
const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
|
|
||||||
const ext = extIdx !== -1 ? file.slice(extIdx) : "";
|
|
||||||
|
|
||||||
switch (settings.store.method) {
|
|
||||||
case Methods.Random:
|
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
return Array.from(
|
|
||||||
{ length: settings.store.randomisedLength },
|
|
||||||
() => chars[Math.floor(Math.random() * chars.length)]
|
|
||||||
).join("") + ext;
|
|
||||||
case Methods.Consistent:
|
|
||||||
return settings.store.consistent + ext;
|
|
||||||
case Methods.Timestamp:
|
|
||||||
return Date.now() + ext;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "BetterGifPicker",
|
|
||||||
description: "Makes the gif picker open the favourite category by default",
|
|
||||||
authors: [Devs.Samwich],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: ".GIFPickerResultTypes.SEARCH",
|
|
||||||
replacement: [{
|
|
||||||
match: "this.state={resultType:null}",
|
|
||||||
replace: 'this.state={resultType:"Favorites"}'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
|
@ -82,7 +82,7 @@ export const streamContextPatch: NavContextMenuPatchCallback = (children, { stre
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userContextPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
|
export const userContextPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
|
||||||
if (user) return addViewStreamContext(children, { userId: user.id });
|
return addViewStreamContext(children, { userId: user.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
|
|
@ -45,8 +45,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".embedWrapper,embed",
|
find: ".embedWrapper,embed",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /\.embedWrapper(?=.+?channel_id:(\i)\.id)/g,
|
match: /\.embedWrapper/g,
|
||||||
replace: "$&+($1.nsfw?' vc-nsfw-img':'')"
|
replace: "$&+(this.props.channel.nsfw?' vc-nsfw-img':'')"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -140,11 +140,11 @@ export const defaultRules = [
|
||||||
"tt_content",
|
"tt_content",
|
||||||
"lr@yandex.*",
|
"lr@yandex.*",
|
||||||
"redircnt@yandex.*",
|
"redircnt@yandex.*",
|
||||||
"feature@*.youtube.com",
|
"feature@youtube.com",
|
||||||
"kw@*.youtube.com",
|
"kw@youtube.com",
|
||||||
"si@*.youtube.com",
|
"si@youtube.com",
|
||||||
"pp@*.youtube.com",
|
"pp@youtube.com",
|
||||||
"si@*.youtu.be",
|
"si@youtu.be",
|
||||||
"wt_zmc",
|
"wt_zmc",
|
||||||
"utm_source",
|
"utm_source",
|
||||||
"utm_content",
|
"utm_content",
|
||||||
|
@ -153,6 +153,5 @@ export const defaultRules = [
|
||||||
"utm_term",
|
"utm_term",
|
||||||
"si@open.spotify.com",
|
"si@open.spotify.com",
|
||||||
"igshid",
|
"igshid",
|
||||||
"igsh",
|
|
||||||
"share_id@reddit.com",
|
"share_id@reddit.com",
|
||||||
];
|
];
|
||||||
|
|
|
@ -19,16 +19,6 @@
|
||||||
border: thin solid var(--background-modifier-accent) !important;
|
border: thin solid var(--background-modifier-accent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-warning * {
|
.client-theme-warning {
|
||||||
color: var(--text-danger);
|
color: var(--text-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-contrast-warning {
|
|
||||||
background-color: var(--background-primary);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: .5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,19 +8,19 @@ import "./clientTheme.css";
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Forms, lodash as _, useStateFromStores } from "@webpack/common";
|
import { Button, Forms } from "@webpack/common";
|
||||||
|
|
||||||
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||||
|
|
||||||
const colorPresets = [
|
const colorPresets = [
|
||||||
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
||||||
"#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42",
|
"#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42",
|
||||||
"#3C2E42", "#422938", "#b6908f", "#bfa088", "#d3c77d",
|
"#3C2E42", "#422938"
|
||||||
"#86ac86", "#88aab3", "#8693b5", "#8a89ba", "#ad94bb",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function onPickColor(color: number) {
|
function onPickColor(color: number) {
|
||||||
|
@ -30,35 +30,9 @@ function onPickColor(color: number) {
|
||||||
updateColorVars(hexColor);
|
updateColorVars(hexColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { saveClientTheme } = findByPropsLazy("saveClientTheme");
|
|
||||||
|
|
||||||
function setTheme(theme: string) {
|
|
||||||
saveClientTheme({ theme });
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeStore = findStoreLazy("ThemeStore");
|
|
||||||
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
|
|
||||||
|
|
||||||
function ThemeSettings() {
|
function ThemeSettings() {
|
||||||
const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme);
|
const lightnessWarning = hexToLightness(settings.store.color) > 45;
|
||||||
const isLightTheme = theme === "light";
|
const lightModeWarning = getTheme() === Theme.Light;
|
||||||
const oppositeTheme = isLightTheme ? "dark" : "light";
|
|
||||||
|
|
||||||
const nitroTheme = useStateFromStores([NitroThemeStore], () => NitroThemeStore.gradientPreset);
|
|
||||||
const nitroThemeEnabled = nitroTheme !== undefined;
|
|
||||||
|
|
||||||
const selectedLuminance = relativeLuminance(settings.store.color);
|
|
||||||
|
|
||||||
let contrastWarning = false, fixableContrast = true;
|
|
||||||
if ((isLightTheme && selectedLuminance < 0.26) || !isLightTheme && selectedLuminance > 0.12)
|
|
||||||
contrastWarning = true;
|
|
||||||
if (selectedLuminance < 0.26 && selectedLuminance > 0.12)
|
|
||||||
fixableContrast = false;
|
|
||||||
// light mode with values greater than 65 leads to background colors getting crushed together and poor text contrast for muted channels
|
|
||||||
if (isLightTheme && selectedLuminance > 0.65) {
|
|
||||||
contrastWarning = true;
|
|
||||||
fixableContrast = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="client-theme-settings">
|
<div className="client-theme-settings">
|
||||||
|
@ -74,18 +48,15 @@ function ThemeSettings() {
|
||||||
suggestedColors={colorPresets}
|
suggestedColors={colorPresets}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{(contrastWarning || nitroThemeEnabled) && (<>
|
{lightnessWarning || lightModeWarning
|
||||||
|
? <div>
|
||||||
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
|
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
|
||||||
<div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}>
|
<Forms.FormText className="client-theme-warning">Your theme won't look good:</Forms.FormText>
|
||||||
<div className="client-theme-warning">
|
{lightnessWarning && <Forms.FormText className="client-theme-warning">Selected color is very light</Forms.FormText>}
|
||||||
<Forms.FormText>Warning, your theme won't look good:</Forms.FormText>
|
{lightModeWarning && <Forms.FormText className="client-theme-warning">Light mode isn't supported</Forms.FormText>}
|
||||||
{contrastWarning && <Forms.FormText>Selected color won't contrast well with text</Forms.FormText>}
|
|
||||||
{nitroThemeEnabled && <Forms.FormText>Nitro themes aren't supported</Forms.FormText>}
|
|
||||||
</div>
|
</div>
|
||||||
{(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>}
|
: null
|
||||||
{(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>}
|
}
|
||||||
</div>
|
|
||||||
</>)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -116,12 +87,9 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
startAt: StartAt.DOMContentLoaded,
|
startAt: StartAt.DOMContentLoaded,
|
||||||
async start() {
|
start() {
|
||||||
updateColorVars(settings.store.color);
|
updateColorVars(settings.store.color);
|
||||||
|
generateColorOffsets();
|
||||||
const styles = await getStyles();
|
|
||||||
generateColorOffsets(styles);
|
|
||||||
generateLightModeFixes(styles);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
@ -130,86 +98,56 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const variableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g;
|
const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g;
|
||||||
const lightVariableRegex = /^--primary-[1-5]\d{2}-hsl/g;
|
|
||||||
const darkVariableRegex = /^--primary-[5-9]\d{2}-hsl/g;
|
|
||||||
|
|
||||||
// generates variables per theme by:
|
async function generateColorOffsets() {
|
||||||
// - matching regex (so we can limit what variables are included in light/dark theme, otherwise text becomes unreadable)
|
|
||||||
// - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600)
|
const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
|
||||||
function genThemeSpecificOffsets(variableLightness: Record<string, number>, regex: RegExp, centerVariable: string): string {
|
const variableLightness = {} as Record<string, number>;
|
||||||
return Object.entries(variableLightness).filter(([key]) => key.search(regex) > -1)
|
|
||||||
|
// Search all stylesheets for color variables
|
||||||
|
for (const styleLinkNode of styleLinkNodes) {
|
||||||
|
const cssLink = styleLinkNode.getAttribute("href");
|
||||||
|
if (!cssLink) continue;
|
||||||
|
|
||||||
|
const res = await fetch(cssLink);
|
||||||
|
const cssString = await res.text();
|
||||||
|
|
||||||
|
// Get lightness values of --primary variables >=500
|
||||||
|
let variableMatch = variableRegex.exec(cssString);
|
||||||
|
while (variableMatch !== null) {
|
||||||
|
const [, variable, lightness] = variableMatch;
|
||||||
|
variableLightness[variable] = parseFloat(lightness);
|
||||||
|
variableMatch = variableRegex.exec(cssString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate offsets
|
||||||
|
const lightnessOffsets = Object.entries(variableLightness)
|
||||||
.map(([key, lightness]) => {
|
.map(([key, lightness]) => {
|
||||||
const lightnessOffset = lightness - variableLightness[centerVariable];
|
const lightnessOffset = lightness - variableLightness["--primary-600-hsl"];
|
||||||
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
|
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
|
||||||
return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`;
|
return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`;
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
|
||||||
|
|
||||||
|
const style = document.createElement("style");
|
||||||
function generateColorOffsets(styles) {
|
style.setAttribute("id", "clientThemeOffsets");
|
||||||
const variableLightness = {} as Record<string, number>;
|
style.textContent = `:root:root {
|
||||||
|
${lightnessOffsets}
|
||||||
// Get lightness values of --primary variables
|
}`;
|
||||||
let variableMatch = variableRegex.exec(styles);
|
document.head.appendChild(style);
|
||||||
while (variableMatch !== null) {
|
|
||||||
const [, variable, lightness] = variableMatch;
|
|
||||||
variableLightness[variable] = parseFloat(lightness);
|
|
||||||
variableMatch = variableRegex.exec(styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
createStyleSheet("clientThemeOffsets", [
|
|
||||||
`.theme-light {\n ${genThemeSpecificOffsets(variableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`,
|
|
||||||
`.theme-dark {\n ${genThemeSpecificOffsets(variableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`,
|
|
||||||
].join("\n\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateLightModeFixes(styles) {
|
|
||||||
const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm;
|
|
||||||
// get light capturing groups that mention --white-500
|
|
||||||
const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat();
|
|
||||||
|
|
||||||
const groupBackgroundRegex = /^([^{]*)\{background:var\(--white-500\)/m;
|
|
||||||
const groupBackgroundColorRegex = /^([^{]*)\{background-color:var\(--white-500\)/m;
|
|
||||||
// find all capturing groups that assign background or background-color directly to w500
|
|
||||||
const backgroundGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundRegex)).join(",\n");
|
|
||||||
const backgroundColorGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundColorRegex)).join(",\n");
|
|
||||||
// create css to reassign them to --primary-100
|
|
||||||
const reassignBackgrounds = `${backgroundGroups} {\n background: var(--primary-100) \n}`;
|
|
||||||
const reassignBackgroundColors = `${backgroundColorGroups} {\n background-color: var(--primary-100) \n}`;
|
|
||||||
|
|
||||||
const groupBgVarRegex = /\.theme-light\{([^}]*--[^:}]*(?:background|bg)[^:}]*:var\(--white-500\)[^}]*)\}/m;
|
|
||||||
const bgVarRegex = /^(--[^:]*(?:background|bg)[^:]*):var\(--white-500\)/m;
|
|
||||||
// get all global variables used for backgrounds
|
|
||||||
const lightVars = mapReject(relevantStyles, style => captureOne(style, groupBgVarRegex)) // get the insides of capture groups that have at least one background var with w500
|
|
||||||
.map(str => str.split(";")).flat(); // captureGroupInsides[] -> cssRule[]
|
|
||||||
const lightBgVars = mapReject(lightVars, variable => captureOne(variable, bgVarRegex)); // remove vars that aren't for backgrounds or w500
|
|
||||||
// create css to reassign every var
|
|
||||||
const reassignVariables = `.theme-light {\n ${lightBgVars.map(variable => `${variable}: var(--primary-100);`).join("\n")} \n}`;
|
|
||||||
|
|
||||||
createStyleSheet("clientThemeLightModeFixes", [
|
|
||||||
reassignBackgrounds,
|
|
||||||
reassignBackgroundColors,
|
|
||||||
reassignVariables,
|
|
||||||
].join("\n\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureOne(str, regex) {
|
|
||||||
const result = str.match(regex);
|
|
||||||
return (result === null) ? null : result[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapReject(arr, mapFunc, rejectFunc = _.isNull) {
|
|
||||||
return _.reject(arr.map(mapFunc), rejectFunc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateColorVars(color: string) {
|
function updateColorVars(color: string) {
|
||||||
const { hue, saturation, lightness } = hexToHSL(color);
|
const { hue, saturation, lightness } = hexToHSL(color);
|
||||||
|
|
||||||
let style = document.getElementById("clientThemeVars");
|
let style = document.getElementById("clientThemeVars");
|
||||||
if (!style)
|
if (!style) {
|
||||||
style = createStyleSheet("clientThemeVars");
|
style = document.createElement("style");
|
||||||
|
style.setAttribute("id", "clientThemeVars");
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
style.textContent = `:root {
|
style.textContent = `:root {
|
||||||
--theme-h: ${hue};
|
--theme-h: ${hue};
|
||||||
|
@ -218,28 +156,6 @@ function updateColorVars(color: string) {
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStyleSheet(id, content = "") {
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.setAttribute("id", id);
|
|
||||||
style.textContent = content.split("\n").map(line => line.trim()).join("\n");
|
|
||||||
document.body.appendChild(style);
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns all of discord's native styles in a single string
|
|
||||||
async function getStyles(): Promise<string> {
|
|
||||||
let out = "";
|
|
||||||
const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
|
|
||||||
for (const styleLinkNode of styleLinkNodes) {
|
|
||||||
const cssLink = styleLinkNode.getAttribute("href");
|
|
||||||
if (!cssLink) continue;
|
|
||||||
|
|
||||||
const res = await fetch(cssLink);
|
|
||||||
out += await res.text();
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://css-tricks.com/converting-color-spaces-in-javascript/
|
// https://css-tricks.com/converting-color-spaces-in-javascript/
|
||||||
function hexToHSL(hexCode: string) {
|
function hexToHSL(hexCode: string) {
|
||||||
// Hex => RGB normalized to 0-1
|
// Hex => RGB normalized to 0-1
|
||||||
|
@ -282,14 +198,17 @@ function hexToHSL(hexCode: string) {
|
||||||
return { hue, saturation, lightness };
|
return { hue, saturation, lightness };
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
// Minimized math just for lightness, lowers lag when changing colors
|
||||||
function relativeLuminance(hexCode: string) {
|
function hexToLightness(hexCode: string) {
|
||||||
const normalize = (x: number) =>
|
// Hex => RGB normalized to 0-1
|
||||||
x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
const r = parseInt(hexCode.substring(0, 2), 16) / 255;
|
||||||
|
const g = parseInt(hexCode.substring(2, 4), 16) / 255;
|
||||||
|
const b = parseInt(hexCode.substring(4, 6), 16) / 255;
|
||||||
|
|
||||||
const r = normalize(parseInt(hexCode.substring(0, 2), 16) / 255);
|
const cMax = Math.max(r, g, b);
|
||||||
const g = normalize(parseInt(hexCode.substring(2, 4), 16) / 255);
|
const cMin = Math.min(r, g, b);
|
||||||
const b = normalize(parseInt(hexCode.substring(4, 6), 16) / 255);
|
|
||||||
|
|
||||||
return r * 0.2126 + g * 0.7152 + b * 0.0722;
|
const lightness = 100 * ((cMax + cMin) / 2);
|
||||||
|
|
||||||
|
return lightness;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,6 @@ interface UserContextProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
|
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
|
||||||
if (!user) return;
|
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-copy-user-url"
|
id="vc-copy-user-url"
|
||||||
|
|
|
@ -25,6 +25,7 @@ import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { maybePromptToUpdate } from "@utils/updater";
|
import { maybePromptToUpdate } from "@utils/updater";
|
||||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||||
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
|
||||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||||
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
|
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
|
||||||
|
@ -56,13 +57,13 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let hasCrashedOnce = false;
|
let crashCount: number = 0;
|
||||||
let isRecovering = false;
|
let lastCrashTimestamp: number = 0;
|
||||||
let shouldAttemptRecover = true;
|
let shouldAttemptNextHandle = false;
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "CrashHandler",
|
name: "CrashHandler",
|
||||||
description: "Utility plugin for handling and possibly recovering from crashes without a restart",
|
description: "Utility plugin for handling and possibly recovering from Crashes without a restart",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
enabledByDefault: true,
|
enabledByDefault: true,
|
||||||
|
|
||||||
|
@ -72,67 +73,61 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /this\.setState\((.+?)\)/,
|
match: /(?=this\.setState\()/,
|
||||||
replace: "$self.handleCrash(this,$1);"
|
replace: "$self.handleCrash(this)||"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
handleCrash(_this: any, errorState: any) {
|
handleCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||||
_this.setState(errorState);
|
if (Date.now() - lastCrashTimestamp <= 1_000 && !shouldAttemptNextHandle) return true;
|
||||||
|
|
||||||
// Already recovering, prevent error which happens more than once too fast to trigger another recover
|
shouldAttemptNextHandle = false;
|
||||||
if (isRecovering) return;
|
|
||||||
isRecovering = true;
|
|
||||||
|
|
||||||
// 1 ms timeout to avoid react breaking when re-rendering
|
if (++crashCount > 5) {
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
// Prevent a crash loop with an error that could not be handled
|
|
||||||
if (!shouldAttemptRecover) {
|
|
||||||
try {
|
try {
|
||||||
showNotification({
|
showNotification({
|
||||||
color: "#eed202",
|
color: "#eed202",
|
||||||
title: "Discord has crashed!",
|
title: "Discord has crashed!",
|
||||||
body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.",
|
body: "Awn :( Discord has crashed more than five times, not attempting to recover.",
|
||||||
noPersist: true
|
noPersist: true,
|
||||||
});
|
});
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
return;
|
lastCrashTimestamp = Date.now();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldAttemptRecover = false;
|
setTimeout(() => crashCount--, 60_000);
|
||||||
// This is enough to avoid a crash loop
|
|
||||||
setTimeout(() => shouldAttemptRecover = true, 500);
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!hasCrashedOnce) {
|
if (crashCount === 1) maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
||||||
hasCrashedOnce = true;
|
|
||||||
maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
|
||||||
}
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (settings.store.attemptToPreventCrashes) {
|
if (settings.store.attemptToPreventCrashes) {
|
||||||
this.handlePreventCrash(_this);
|
this.handlePreventCrash(_this);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
CrashHandlerLogger.error("Failed to handle crash", err);
|
CrashHandlerLogger.error("Failed to handle crash", err);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
lastCrashTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}, 1);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handlePreventCrash(_this: any) {
|
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||||
|
if (Date.now() - lastCrashTimestamp >= 1_000) {
|
||||||
try {
|
try {
|
||||||
showNotification({
|
showNotification({
|
||||||
color: "#eed202",
|
color: "#eed202",
|
||||||
title: "Discord has crashed!",
|
title: "Discord has crashed!",
|
||||||
body: "Attempting to recover...",
|
body: "Attempting to recover...",
|
||||||
noPersist: true
|
noPersist: true,
|
||||||
});
|
});
|
||||||
} catch { }
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const channelId = SelectedChannelStore.getChannelId();
|
const channelId = SelectedChannelStore.getChannelId();
|
||||||
|
@ -181,12 +176,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens
|
|
||||||
setImmediate(() => isRecovering = false);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_this.setState({ error: null, info: null });
|
shouldAttemptNextHandle = true;
|
||||||
|
_this.forceUpdate();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
// patch componentDidMount to replace embed thumbnail and title
|
// patch componentDidMount to replace embed thumbnail and title
|
||||||
{
|
{
|
||||||
match: /render\(\)\{.{0,30}let\{embed:/,
|
match: /render\(\)\{let\{embed:/,
|
||||||
replace: "componentDidMount=$self.embedDidMount;$&"
|
replace: "componentDidMount=$self.embedDidMount;$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,21 @@
|
||||||
|
|
||||||
import "./ui/styles.css";
|
import "./ui/styles.css";
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import { Margins } from "@utils/margins";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
|
import { closeAllModals } from "@utils/modal";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { UserStore } from "@webpack/common";
|
import { FluxDispatcher, Forms, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants";
|
import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants";
|
||||||
import { useAuthorizationStore } from "./lib/stores/AuthorizationStore";
|
import { useAuthorizationStore } from "./lib/stores/AuthorizationStore";
|
||||||
import { useCurrentUserDecorationsStore } from "./lib/stores/CurrentUserDecorationsStore";
|
import { useCurrentUserDecorationsStore } from "./lib/stores/CurrentUserDecorationsStore";
|
||||||
import { useUserDecorAvatarDecoration, useUsersDecorationsStore } from "./lib/stores/UsersDecorationsStore";
|
import { useUserDecorAvatarDecoration, useUsersDecorationsStore } from "./lib/stores/UsersDecorationsStore";
|
||||||
import { settings } from "./settings";
|
|
||||||
import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components";
|
import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components";
|
||||||
import DecorSection from "./ui/components/DecorSection";
|
import DecorSection from "./ui/components/DecorSection";
|
||||||
|
|
||||||
|
@ -26,6 +30,27 @@ export interface AvatarDecoration {
|
||||||
skuId: string;
|
skuId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
changeDecoration: {
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
description: "Change your avatar decoration",
|
||||||
|
component() {
|
||||||
|
return <div>
|
||||||
|
<DecorSection hideTitle hideDivider noMargin />
|
||||||
|
<Forms.FormText type="description" className={classes(Margins.top8, Margins.bottom8)}>
|
||||||
|
You can also access Decor decorations from the <Link
|
||||||
|
href="/settings/profile-customization"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
closeAllModals();
|
||||||
|
FluxDispatcher.dispatch({ type: "USER_SETTINGS_MODAL_SET_SECTION", section: "Profile Customization" });
|
||||||
|
}}
|
||||||
|
>Profiles</Link> page.
|
||||||
|
</Forms.FormText>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Decor",
|
name: "Decor",
|
||||||
description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.",
|
description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.",
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Link } from "@components/Link";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { closeAllModals } from "@utils/modal";
|
|
||||||
import { OptionType } from "@utils/types";
|
|
||||||
import { FluxDispatcher, Forms } from "@webpack/common";
|
|
||||||
|
|
||||||
import DecorSection from "./ui/components/DecorSection";
|
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
|
||||||
changeDecoration: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "Change your avatar decoration",
|
|
||||||
component() {
|
|
||||||
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
|
||||||
Enable Decor and restart your client to change your avatar decoration.
|
|
||||||
</Forms.FormText>;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<DecorSection hideTitle hideDivider noMargin />
|
|
||||||
<Forms.FormText type="description" className={classes(Margins.top8, Margins.bottom8)}>
|
|
||||||
You can also access Decor decorations from the <Link
|
|
||||||
href="/settings/profile-customization"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
closeAllModals();
|
|
||||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_MODAL_SET_SECTION", section: "Profile Customization" });
|
|
||||||
}}
|
|
||||||
>Profiles</Link> page.
|
|
||||||
</Forms.FormText>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
agreedToGuidelines: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Agreed to guidelines",
|
|
||||||
hidden: true,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -19,7 +19,7 @@ export let DecorationGridItem: DecorationGridItemComponent;
|
||||||
export const setDecorationGridItem = v => DecorationGridItem = v;
|
export const setDecorationGridItem = v => DecorationGridItem = v;
|
||||||
|
|
||||||
export const AvatarDecorationModalPreview = LazyComponentWebpack(() => {
|
export const AvatarDecorationModalPreview = LazyComponentWebpack(() => {
|
||||||
const component = findComponentByCode(".shopPreviewBanner");
|
const component = findComponentByCode("AvatarDecorationModalPreview");
|
||||||
return React.memo(component);
|
return React.memo(component);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
|
import { extractAndLoadChunksLazy } from "@webpack";
|
||||||
|
|
||||||
export const cl = classNameFactory("vc-decor-");
|
export const cl = classNameFactory("vc-decor-");
|
||||||
export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
|
|
||||||
|
|
||||||
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]);
|
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]);
|
||||||
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);
|
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);
|
||||||
|
|
|
@ -4,13 +4,12 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
|
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -19,17 +18,16 @@ import { GUILD_ID, INVITE_KEY } from "../../lib/constants";
|
||||||
import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore";
|
import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore";
|
||||||
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
||||||
import { decorationToAvatarDecoration } from "../../lib/utils/decoration";
|
import { decorationToAvatarDecoration } from "../../lib/utils/decoration";
|
||||||
import { settings } from "../../settings";
|
import { cl, requireAvatarDecorationModal } from "../";
|
||||||
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "../";
|
|
||||||
import { AvatarDecorationModalPreview } from "../components";
|
import { AvatarDecorationModalPreview } from "../components";
|
||||||
import DecorationGridCreate from "../components/DecorationGridCreate";
|
import DecorationGridCreate from "../components/DecorationGridCreate";
|
||||||
import DecorationGridNone from "../components/DecorationGridNone";
|
import DecorationGridNone from "../components/DecorationGridNone";
|
||||||
import DecorDecorationGridDecoration from "../components/DecorDecorationGridDecoration";
|
import DecorDecorationGridDecoration from "../components/DecorDecorationGridDecoration";
|
||||||
import SectionedGridList from "../components/SectionedGridList";
|
import SectionedGridList from "../components/SectionedGridList";
|
||||||
import { openCreateDecorationModal } from "./CreateDecorationModal";
|
import { openCreateDecorationModal } from "./CreateDecorationModal";
|
||||||
import { openGuidelinesModal } from "./GuidelinesModal";
|
|
||||||
|
|
||||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||||
|
const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
|
||||||
|
|
||||||
function usePresets() {
|
function usePresets() {
|
||||||
const [presets, setPresets] = useState<Preset[]>([]);
|
const [presets, setPresets] = useState<Preset[]>([]);
|
||||||
|
@ -85,7 +83,7 @@ function SectionHeader({ section }: { section: Section; }) {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChangeDecorationModal(props: ModalProps) {
|
export default function ChangeDecorationModal(props: any) {
|
||||||
// undefined = not trying, null = none, Decoration = selected
|
// undefined = not trying, null = none, Decoration = selected
|
||||||
const [tryingDecoration, setTryingDecoration] = useState<Decoration | null | undefined>(undefined);
|
const [tryingDecoration, setTryingDecoration] = useState<Decoration | null | undefined>(undefined);
|
||||||
const isTryingDecoration = typeof tryingDecoration !== "undefined";
|
const isTryingDecoration = typeof tryingDecoration !== "undefined";
|
||||||
|
@ -118,7 +116,6 @@ function ChangeDecorationModal(props: ModalProps) {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
title: "Your Decorations",
|
title: "Your Decorations",
|
||||||
subtitle: "You can delete your own decorations by right clicking on them.",
|
|
||||||
sectionKey: "ownDecorations",
|
sectionKey: "ownDecorations",
|
||||||
items: ["none", ...ownDecorations, "create"]
|
items: ["none", ...ownDecorations, "create"]
|
||||||
},
|
},
|
||||||
|
@ -151,7 +148,6 @@ function ChangeDecorationModal(props: ModalProps) {
|
||||||
className={cl("change-decoration-modal-content")}
|
className={cl("change-decoration-modal-content")}
|
||||||
scrollbarType="none"
|
scrollbarType="none"
|
||||||
>
|
>
|
||||||
<ErrorBoundary>
|
|
||||||
<SectionedGridList
|
<SectionedGridList
|
||||||
renderItem={item => {
|
renderItem={item => {
|
||||||
if (typeof item === "string") {
|
if (typeof item === "string") {
|
||||||
|
@ -167,7 +163,7 @@ function ChangeDecorationModal(props: ModalProps) {
|
||||||
{tooltipProps => <DecorationGridCreate
|
{tooltipProps => <DecorationGridCreate
|
||||||
className={cl("change-decoration-modal-decoration")}
|
className={cl("change-decoration-modal-decoration")}
|
||||||
{...tooltipProps}
|
{...tooltipProps}
|
||||||
onSelect={!hasDecorationPendingReview ? (settings.store.agreedToGuidelines ? openCreateDecorationModal : openGuidelinesModal) : () => { }}
|
onSelect={!hasDecorationPendingReview ? openCreateDecorationModal : () => { }}
|
||||||
/>}
|
/>}
|
||||||
</Tooltip>;
|
</Tooltip>;
|
||||||
}
|
}
|
||||||
|
@ -206,7 +202,6 @@ function ChangeDecorationModal(props: ModalProps) {
|
||||||
}
|
}
|
||||||
{activeDecorationHasAuthor && <Text key={`createdBy-${activeSelectedDecoration.authorId}`}>Created by {Parser.parse(`<@${activeSelectedDecoration.authorId}>`)}</Text>}
|
{activeDecorationHasAuthor && <Text key={`createdBy-${activeSelectedDecoration.authorId}`}>Created by {Parser.parse(`<@${activeSelectedDecoration.authorId}>`)}</Text>}
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter className={classes(cl("change-decoration-modal-footer", cl("modal-footer")))}>
|
<ModalFooter className={classes(cl("change-decoration-modal-footer", cl("modal-footer")))}>
|
||||||
<div className={cl("change-decoration-modal-footer-btn-container")}>
|
<div className={cl("change-decoration-modal-footer-btn-container")}>
|
||||||
|
|
|
@ -4,22 +4,22 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common";
|
import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
|
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
|
||||||
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
||||||
import { cl, DecorationModalStyles, requireAvatarDecorationModal, requireCreateStickerModal } from "../";
|
import { cl, requireAvatarDecorationModal, requireCreateStickerModal } from "../";
|
||||||
import { AvatarDecorationModalPreview } from "../components";
|
import { AvatarDecorationModalPreview } from "../components";
|
||||||
|
|
||||||
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
|
|
||||||
|
|
||||||
const { default: HelpMessage, HelpMessageTypes } = findByPropsLazy("HelpMessageTypes");
|
const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
|
||||||
|
|
||||||
|
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
|
||||||
|
|
||||||
function useObjectURL(object: Blob | MediaSource | null) {
|
function useObjectURL(object: Blob | MediaSource | null) {
|
||||||
const [url, setUrl] = useState<string | null>(null);
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
|
@ -39,7 +39,7 @@ function useObjectURL(object: Blob | MediaSource | null) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateDecorationModal(props: ModalProps) {
|
export default function CreateDecorationModal(props) {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
@ -75,14 +75,6 @@ function CreateDecorationModal(props: ModalProps) {
|
||||||
className={cl("create-decoration-modal-content")}
|
className={cl("create-decoration-modal-content")}
|
||||||
scrollbarType="none"
|
scrollbarType="none"
|
||||||
>
|
>
|
||||||
<ErrorBoundary>
|
|
||||||
<HelpMessage messageType={HelpMessageTypes.WARNING}>
|
|
||||||
Make sure your decoration does not violate <Link
|
|
||||||
href="https://github.com/decor-discord/.github/blob/main/GUIDELINES.md"
|
|
||||||
>
|
|
||||||
the guidelines
|
|
||||||
</Link> before submitting it.
|
|
||||||
</HelpMessage>
|
|
||||||
<div className={cl("create-decoration-modal-form-preview-container")}>
|
<div className={cl("create-decoration-modal-form-preview-container")}>
|
||||||
<div className={cl("create-decoration-modal-form")}>
|
<div className={cl("create-decoration-modal-form")}>
|
||||||
{error !== null && <Text color="text-danger" variant="text-xs/normal">{error.message}</Text>}
|
{error !== null && <Text color="text-danger" variant="text-xs/normal">{error.message}</Text>}
|
||||||
|
@ -117,6 +109,11 @@ function CreateDecorationModal(props: ModalProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Forms.FormText type="description" className={Margins.bottom16}>
|
<Forms.FormText type="description" className={Margins.bottom16}>
|
||||||
|
Make sure your decoration does not violate <Link
|
||||||
|
href="https://github.com/decor-discord/.github/blob/main/GUIDELINES.md"
|
||||||
|
>
|
||||||
|
the guidelines
|
||||||
|
</Link> before creating your decoration.
|
||||||
<br />You can receive updates on your decoration's review by joining <Link
|
<br />You can receive updates on your decoration's review by joining <Link
|
||||||
href={`https://discord.gg/${INVITE_KEY}`}
|
href={`https://discord.gg/${INVITE_KEY}`}
|
||||||
onClick={async e => {
|
onClick={async e => {
|
||||||
|
@ -137,7 +134,6 @@ function CreateDecorationModal(props: ModalProps) {
|
||||||
Decor's Discord server
|
Decor's Discord server
|
||||||
</Link>.
|
</Link>.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</ErrorBoundary>
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter className={cl("modal-footer")}>
|
<ModalFooter className={cl("modal-footer")}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -149,7 +145,7 @@ function CreateDecorationModal(props: ModalProps) {
|
||||||
disabled={!file || !name}
|
disabled={!file || !name}
|
||||||
submitting={submitting}
|
submitting={submitting}
|
||||||
>
|
>
|
||||||
Submit for Review
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Link } from "@components/Link";
|
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
|
||||||
import { Button, Forms, Text } from "@webpack/common";
|
|
||||||
|
|
||||||
import { settings } from "../../settings";
|
|
||||||
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "../";
|
|
||||||
import { openCreateDecorationModal } from "./CreateDecorationModal";
|
|
||||||
|
|
||||||
function GuidelinesModal(props: ModalProps) {
|
|
||||||
return <ModalRoot
|
|
||||||
{...props}
|
|
||||||
size={ModalSize.SMALL}
|
|
||||||
className={DecorationModalStyles.modal}
|
|
||||||
>
|
|
||||||
<ModalHeader separator={false} className={cl("modal-header")}>
|
|
||||||
<Text
|
|
||||||
color="header-primary"
|
|
||||||
variant="heading-lg/semibold"
|
|
||||||
tag="h1"
|
|
||||||
style={{ flexGrow: 1 }}
|
|
||||||
>
|
|
||||||
Hold on
|
|
||||||
</Text>
|
|
||||||
<ModalCloseButton onClick={props.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalContent
|
|
||||||
scrollbarType="none"
|
|
||||||
>
|
|
||||||
<Forms.FormText>
|
|
||||||
By submitting a decoration, you agree to <Link
|
|
||||||
href="https://github.com/decor-discord/.github/blob/main/GUIDELINES.md"
|
|
||||||
>
|
|
||||||
the guidelines
|
|
||||||
</Link>. Not reading these guidelines may get your account suspended from creating more decorations in the future.
|
|
||||||
</Forms.FormText>
|
|
||||||
</ModalContent>
|
|
||||||
<ModalFooter className={cl("modal-footer")}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
settings.store.agreedToGuidelines = true;
|
|
||||||
props.onClose();
|
|
||||||
openCreateDecorationModal();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Continue
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={props.onClose}
|
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
look={Button.Looks.LINK}
|
|
||||||
>
|
|
||||||
Go Back
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalRoot>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const openGuidelinesModal = () =>
|
|
||||||
requireAvatarDecorationModal().then(() => openModal(props => <GuidelinesModal {...props} />));
|
|
|
@ -8,7 +8,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
gap: 4px;
|
gap: 4px
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-decor-change-decoration-modal-preview {
|
.vc-decor-change-decoration-modal-preview {
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
.vc-decor-sectioned-grid-list-grid {
|
.vc-decor-sectioned-grid-list-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-decor-section-remove-margin {
|
.vc-decor-section-remove-margin {
|
||||||
|
|
|
@ -16,27 +16,18 @@
|
||||||
* 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 { migratePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "DisableCallIdle",
|
name: "DisableDMCallIdle",
|
||||||
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
|
description: "Disables automatically getting kicked from a DM voice call after 3 minutes.",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,?(?=this\.idleTimeout=new \i\.Timeout)/,
|
match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
||||||
replace: ";return;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "handleIdleUpdate(){",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=_initialize\(\){)/,
|
|
||||||
replace: "return;"
|
replace: "return;"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -108,7 +108,6 @@ const enum FakeNoticeType {
|
||||||
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
|
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
|
||||||
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
|
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
|
||||||
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
|
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
|
||||||
const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
enableEmojiBypass: {
|
enableEmojiBypass: {
|
||||||
|
@ -157,11 +156,6 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true,
|
default: true,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
},
|
|
||||||
useHyperLinks: {
|
|
||||||
description: "Whether to use hyperlinks when sending fake emojis and stickers",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -351,7 +345,7 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.transformEmojis,
|
predicate: () => settings.store.transformEmojis,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Add the fake nitro emoji notice
|
// Add the fake nitro emoji notice
|
||||||
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
||||||
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -453,23 +447,13 @@ export default definePlugin({
|
||||||
|
|
||||||
trimContent(content: Array<any>) {
|
trimContent(content: Array<any>) {
|
||||||
const firstContent = content[0];
|
const firstContent = content[0];
|
||||||
if (typeof firstContent === "string") {
|
if (typeof firstContent === "string") content[0] = firstContent.trimStart();
|
||||||
content[0] = firstContent.trimStart();
|
if (content[0] === "") content.shift();
|
||||||
content[0] || content.shift();
|
|
||||||
} else if (firstContent?.type === "span") {
|
|
||||||
firstContent.props.children = firstContent.props.children.trimStart();
|
|
||||||
firstContent.props.children || content.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastIndex = content.length - 1;
|
const lastIndex = content.length - 1;
|
||||||
const lastContent = content[lastIndex];
|
const lastContent = content[lastIndex];
|
||||||
if (typeof lastContent === "string") {
|
if (typeof lastContent === "string") content[lastIndex] = lastContent.trimEnd();
|
||||||
content[lastIndex] = lastContent.trimEnd();
|
if (content[lastIndex] === "") content.pop();
|
||||||
content[lastIndex] || content.pop();
|
|
||||||
} else if (lastContent?.type === "span") {
|
|
||||||
lastContent.props.children = lastContent.props.children.trimEnd();
|
|
||||||
lastContent.props.children || content.pop();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
clearEmptyArrayItems(array: Array<any>) {
|
clearEmptyArrayItems(array: Array<any>) {
|
||||||
|
@ -481,7 +465,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) {
|
patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) {
|
||||||
// If content has more than one child or it's a single ReactElement like a header, list or span
|
// If content has more than one child or it's a single ReactElement like a header or list
|
||||||
if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content;
|
if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content;
|
||||||
|
|
||||||
let nextIndex = content.length;
|
let nextIndex = content.length;
|
||||||
|
@ -590,7 +574,7 @@ export default definePlugin({
|
||||||
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
|
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
|
||||||
|
|
||||||
for (const item of itemsToMaybePush) {
|
for (const item of itemsToMaybePush) {
|
||||||
if (!settings.store.transformCompoundSentence && !item.startsWith("http") && !hyperLinkRegex.test(item)) continue;
|
if (!settings.store.transformCompoundSentence && !item.startsWith("http")) continue;
|
||||||
|
|
||||||
const imgMatch = item.match(fakeNitroStickerRegex);
|
const imgMatch = item.match(fakeNitroStickerRegex);
|
||||||
if (imgMatch) {
|
if (imgMatch) {
|
||||||
|
@ -635,7 +619,8 @@ export default definePlugin({
|
||||||
case "image": {
|
case "image": {
|
||||||
if (
|
if (
|
||||||
!settings.store.transformCompoundSentence
|
!settings.store.transformCompoundSentence
|
||||||
&& !contentItems.some(item => item === embed.url! || item.match(hyperLinkRegex)?.[1] === embed.url!)
|
&& !contentItems.includes(embed.url!)
|
||||||
|
&& !contentItems.includes(embed.image?.proxyURL!)
|
||||||
) return false;
|
) return false;
|
||||||
|
|
||||||
if (settings.store.transformEmojis) {
|
if (settings.store.transformEmojis) {
|
||||||
|
@ -713,7 +698,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
getStickerLink(stickerId: string) {
|
getStickerLink(stickerId: string) {
|
||||||
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`;
|
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
|
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
|
||||||
|
@ -810,16 +795,12 @@ export default definePlugin({
|
||||||
if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
|
if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
|
||||||
link = link.replace(".png", ".gif");
|
link = link.replace(".png", ".gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sticker.format_type === StickerType.APNG) {
|
if (sticker.format_type === StickerType.APNG) {
|
||||||
this.sendAnimatedSticker(link, sticker.id, channelId);
|
this.sendAnimatedSticker(link, sticker.id, channelId);
|
||||||
return { cancel: true };
|
return { cancel: true };
|
||||||
} else {
|
} else {
|
||||||
const url = new URL(link);
|
|
||||||
url.searchParams.set("name", sticker.name);
|
|
||||||
|
|
||||||
messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`;
|
|
||||||
extra.stickers!.length = 0;
|
extra.stickers!.length = 0;
|
||||||
|
messageObj.content += ` ${link}&name=${encodeURIComponent(sticker.name)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,13 +813,12 @@ export default definePlugin({
|
||||||
if (emoji.guildId === guildId && !emoji.animated) continue;
|
if (emoji.guildId === guildId && !emoji.animated) continue;
|
||||||
|
|
||||||
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
||||||
|
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
|
||||||
const url = new URL(emoji.url);
|
size: Settings.plugins.FakeNitro.emojiSize,
|
||||||
url.searchParams.set("size", s.emojiSize.toString());
|
name: encodeURIComponent(emoji.name)
|
||||||
url.searchParams.set("name", emoji.name);
|
}));
|
||||||
|
|
||||||
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
|
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
|
||||||
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
|
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -860,11 +840,11 @@ export default definePlugin({
|
||||||
if (emoji.available !== false && canUseEmotes) return emojiStr;
|
if (emoji.available !== false && canUseEmotes) return emojiStr;
|
||||||
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
|
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
|
||||||
|
|
||||||
const url = new URL(emoji.url);
|
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
|
||||||
url.searchParams.set("size", s.emojiSize.toString());
|
size: Settings.plugins.FakeNitro.emojiSize,
|
||||||
url.searchParams.set("name", emoji.name);
|
name: encodeURIComponent(emoji.name)
|
||||||
|
}));
|
||||||
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
|
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "FixCodeblockGap",
|
|
||||||
description: "Removes the gap between codeblocks and text below it",
|
|
||||||
authors: [Devs.Grzesiek11],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: ".default.Messages.DELETED_ROLE_PLACEHOLDER",
|
|
||||||
replacement: {
|
|
||||||
match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
|
|
||||||
replace: "$&\\n?",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
25
src/plugins/fixImagesQuality/index.ts
Normal file
25
src/plugins/fixImagesQuality/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "FixImagesQuality",
|
||||||
|
description: "Fixes the quality of images in the chat being horrible.",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "handleImageLoad=",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(?<=getSrc\(\i\){.+?return )\i\.SUPPORTS_WEBP.+?:(?=\i&&\(\i="png"\))/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -1,5 +0,0 @@
|
||||||
# FixYoutubeEmbeds
|
|
||||||
|
|
||||||
Bypasses youtube videos being blocked from display on Discord (for example by UMG)
|
|
||||||
|
|
||||||
![](https://github.com/Vendicated/Vencord/assets/45497981/7a5fdcaa-217c-4c63-acae-f0d6af2f79be)
|
|
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "FixYoutubeEmbeds",
|
|
||||||
description: "Bypasses youtube videos being blocked from display on Discord (for example by UMG)",
|
|
||||||
authors: [Devs.coolelectronics]
|
|
||||||
});
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { app } from "electron";
|
|
||||||
import { getSettings } from "main/ipcMain";
|
|
||||||
|
|
||||||
app.on("browser-window-created", (_, win) => {
|
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
|
||||||
frame.once("dom-ready", () => {
|
|
||||||
if (frame.url.startsWith("https://www.youtube.com/")) {
|
|
||||||
const settings = getSettings().plugins?.FixYoutubeEmbeds;
|
|
||||||
if (!settings?.enabled) return;
|
|
||||||
|
|
||||||
frame.executeJavaScript(`
|
|
||||||
new MutationObserver(() => {
|
|
||||||
if(
|
|
||||||
document.querySelector('div.ytp-error-content-wrap-subreason a[href*="www.youtube.com/watch?v="]')
|
|
||||||
) location.reload()
|
|
||||||
}).observe(document.body, { childList: true, subtree:true });
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -16,11 +16,10 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { StatusSettingsStores } from "@webpack/common";
|
import { StatusSettingsStores } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -29,31 +28,22 @@ import style from "./style.css?managed";
|
||||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||||
|
|
||||||
function makeIcon(showCurrentGame?: boolean) {
|
function makeIcon(showCurrentGame?: boolean) {
|
||||||
const { oldIcon } = settings.use(["oldIcon"]);
|
const controllerIcon = "M3.06 20.4q-1.53 0-2.37-1.065T.06 16.74l1.26-9q.27-1.8 1.605-2.97T6.06 3.6h11.88q1.8 0 3.135 1.17t1.605 2.97l1.26 9q.21 1.53-.63 2.595T20.94 20.4q-.63 0-1.17-.225T18.78 19.5l-2.7-2.7H7.92l-2.7 2.7q-.45.45-.99.675t-1.17.225Zm14.94-7.2q.51 0 .855-.345T19.2 12q0-.51-.345-.855T18 10.8q-.51 0-.855.345T16.8 12q0 .51.345 .855T18 13.2Zm-2.4-3.6q.51 0 .855-.345T16.8 8.4q0-.51-.345-.855T15.6 7.2q-.51 0-.855.345T14.4 8.4q0 .51.345 .855T15.6 9.6ZM6.9 13.2h1.8v-2.1h2.1v-1.8h-2.1v-2.1h-1.8v2.1h-2.1v1.8h2.1v2.1Z";
|
||||||
|
|
||||||
const redLinePath = !oldIcon
|
|
||||||
? "M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4Z"
|
|
||||||
: "M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z";
|
|
||||||
|
|
||||||
const maskBlackPath = !oldIcon
|
|
||||||
? "M23.27 4.73 19.27 .73 -.27 20.27 3.73 24.27Z"
|
|
||||||
: "M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z";
|
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
return (
|
return (
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24">
|
<svg width="20" height="20" viewBox="0 0 24 24">
|
||||||
<path
|
{showCurrentGame ? (
|
||||||
fill={!showCurrentGame && !oldIcon ? "var(--status-danger)" : "currentColor"}
|
<path fill="currentColor" d={controllerIcon} />
|
||||||
mask={!showCurrentGame ? "url(#gameActivityMask)" : void 0}
|
) : (
|
||||||
d="M3.06 20.4q-1.53 0-2.37-1.065T.06 16.74l1.26-9q.27-1.8 1.605-2.97T6.06 3.6h11.88q1.8 0 3.135 1.17t1.605 2.97l1.26 9q.21 1.53-.63 2.595T20.94 20.4q-.63 0-1.17-.225T18.78 19.5l-2.7-2.7H7.92l-2.7 2.7q-.45.45-.99.675t-1.17.225Zm14.94-7.2q.51 0 .855-.345T19.2 12q0-.51-.345-.855T18 10.8q-.51 0-.855.345T16.8 12q0 .51.345 .855T18 13.2Zm-2.4-3.6q.51 0 .855-.345T16.8 8.4q0-.51-.345-.855T15.6 7.2q-.51 0-.855.345T14.4 8.4q0 .51.345 .855T15.6 9.6ZM6.9 13.2h1.8v-2.1h2.1v-1.8h-2.1v-2.1h-1.8v2.1h-2.1v1.8h2.1v2.1Z"
|
<>
|
||||||
/>
|
|
||||||
{!showCurrentGame && <>
|
|
||||||
<path fill="var(--status-danger)" d={redLinePath} />
|
|
||||||
<mask id="gameActivityMask" >
|
<mask id="gameActivityMask" >
|
||||||
<rect fill="white" x="0" y="0" width="24" height="24" />
|
<rect fill="white" x="0" y="0" width="24" height="24" />
|
||||||
<path fill="black" d={maskBlackPath} />
|
<path fill="black" d="M23.27 4.73 19.27 .73 -.27 20.27 3.73 24.27Z" />
|
||||||
</mask>
|
</mask>
|
||||||
</>}
|
<path fill="var(--status-danger)" mask="url(#gameActivityMask)" d={controllerIcon} />
|
||||||
|
<path fill="var(--status-danger)" d="M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4Z" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -73,19 +63,10 @@ function GameActivityToggleButton() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
oldIcon: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Use the old icon style before Discord icon redesign",
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "GameActivityToggle",
|
name: "GameActivityToggle",
|
||||||
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||||
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -123,13 +123,14 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
||||||
waitFor(() => instance.state.readyState === "READY", () => {
|
waitFor(() => instance.state.readyState === "READY", () => {
|
||||||
const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement;
|
const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement;
|
||||||
element.current = elem;
|
element.current = elem;
|
||||||
elem.querySelector("img,video")?.setAttribute("draggable", "false");
|
elem.firstElementChild!.setAttribute("draggable", "false");
|
||||||
if (instance.props.animated) {
|
if (instance.props.animated) {
|
||||||
originalVideoElementRef.current = elem!.querySelector("video")!;
|
originalVideoElementRef.current = elem!.querySelector("video")!;
|
||||||
originalVideoElementRef.current.addEventListener("timeupdate", syncVideos);
|
originalVideoElementRef.current.addEventListener("timeupdate", syncVideos);
|
||||||
}
|
|
||||||
|
|
||||||
setReady(true);
|
setReady(true);
|
||||||
|
} else {
|
||||||
|
setReady(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
document.addEventListener("keyup", onKeyUp);
|
document.addEventListener("keyup", onKeyUp);
|
||||||
|
@ -154,9 +155,7 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
||||||
|
|
||||||
if (!ready) return null;
|
if (!ready) return null;
|
||||||
|
|
||||||
const box = element.current?.getBoundingClientRect();
|
const box = element.current!.getBoundingClientRect();
|
||||||
|
|
||||||
if (!box) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -171,7 +171,7 @@ export default definePlugin({
|
||||||
find: "handleImageLoad=",
|
find: "handleImageLoad=",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /placeholderVersion:\i,/,
|
match: /showThumbhashPlaceholder:\i,/,
|
||||||
replace: "...$self.makeProps(this),$&"
|
replace: "...$self.makeProps(this),$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,13 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
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 { getStegCloak } from "@utils/dependencies";
|
import { getStegCloak } from "@utils/dependencies";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
import { Button, ButtonLooks, ButtonWrapperClasses, ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
import { buildDecModal } from "./components/DecryptionModal";
|
import { buildDecModal } from "./components/DecryptionModal";
|
||||||
|
@ -65,31 +64,54 @@ function Indicator() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
function ChatBarIcon(chatBoxProps: {
|
||||||
if (!isMainChat) return null;
|
type: {
|
||||||
|
analyticsName: string;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBarButton
|
<Tooltip text="Encrypt Message">
|
||||||
tooltip="Encrypt Message"
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
// size="" = Button.Sizes.NONE
|
||||||
|
/*
|
||||||
|
many themes set "> button" to display: none, as the gift button is
|
||||||
|
the only directly descending button (all the other elements are divs.)
|
||||||
|
Thus, wrap in a div here to avoid getting hidden by that.
|
||||||
|
flex is for some reason necessary as otherwise the button goes flying off
|
||||||
|
*/
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Button
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
aria-label="Encrypt Message"
|
||||||
|
size=""
|
||||||
|
look={ButtonLooks.BLANK}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
innerClassName={ButtonWrapperClasses.button}
|
||||||
onClick={() => buildEncModal()}
|
onClick={() => buildEncModal()}
|
||||||
|
style={{ padding: "0 2px", scale: "0.9" }}
|
||||||
buttonProps={{
|
|
||||||
"aria-haspopup": "dialog",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden
|
aria-hidden
|
||||||
role="img"
|
role="img"
|
||||||
width="24"
|
width="32"
|
||||||
height="24"
|
height="32"
|
||||||
viewBox={"0 0 64 64"}
|
viewBox={"0 0 64 64"}
|
||||||
style={{ scale: "1.39", translate: "0 -1px" }}
|
style={{ scale: "1.1" }}
|
||||||
>
|
>
|
||||||
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
|
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
|
||||||
</svg>
|
</svg>
|
||||||
</ChatBarButton>
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Tooltip >
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
savedPasswords: {
|
savedPasswords: {
|
||||||
|
@ -103,7 +125,7 @@ export default definePlugin({
|
||||||
name: "InvisibleChat",
|
name: "InvisibleChat",
|
||||||
description: "Encrypt your Messages in a non-suspicious way!",
|
description: "Encrypt your Messages in a non-suspicious way!",
|
||||||
authors: [Devs.SammCheese],
|
authors: [Devs.SammCheese],
|
||||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
|
dependencies: ["MessagePopoverAPI"],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// Indicator
|
// Indicator
|
||||||
|
@ -113,6 +135,13 @@ export default definePlugin({
|
||||||
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: "ChannelTextAreaButtons",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
EMBED_API_URL: "https://embed.sammcheese.net",
|
EMBED_API_URL: "https://embed.sammcheese.net",
|
||||||
|
@ -122,7 +151,10 @@ export default definePlugin({
|
||||||
),
|
),
|
||||||
settings,
|
settings,
|
||||||
async start() {
|
async start() {
|
||||||
addButton("InvisibleChat", message => {
|
const { default: StegCloak } = await getStegCloak();
|
||||||
|
steggo = new StegCloak(true, false);
|
||||||
|
|
||||||
|
addButton("invDecrypt", message => {
|
||||||
return this.INV_REGEX.test(message?.content)
|
return this.INV_REGEX.test(message?.content)
|
||||||
? {
|
? {
|
||||||
label: "Decrypt Message",
|
label: "Decrypt Message",
|
||||||
|
@ -138,16 +170,10 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
});
|
});
|
||||||
|
|
||||||
addChatBarButton("InvisibleChat", ChatBarIcon);
|
|
||||||
|
|
||||||
const { default: StegCloak } = await getStegCloak();
|
|
||||||
steggo = new StegCloak(true, false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeButton("InvisibleChat");
|
removeButton("invDecrypt");
|
||||||
removeButton("InvisibleChat");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Gets the Embed of a Link
|
// Gets the Embed of a Link
|
||||||
|
@ -190,6 +216,7 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
chatBarIcon: ErrorBoundary.wrap(ChatBarIcon, { noop: true }),
|
||||||
popOverIcon: () => <PopOverIcon />,
|
popOverIcon: () => <PopOverIcon />,
|
||||||
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
|
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,17 +29,15 @@ import {
|
||||||
ChannelStore,
|
ChannelStore,
|
||||||
FluxDispatcher,
|
FluxDispatcher,
|
||||||
GuildStore,
|
GuildStore,
|
||||||
IconUtils,
|
|
||||||
MessageStore,
|
MessageStore,
|
||||||
Parser,
|
Parser,
|
||||||
PermissionsBits,
|
|
||||||
PermissionStore,
|
PermissionStore,
|
||||||
RestAPI,
|
RestAPI,
|
||||||
Text,
|
Text,
|
||||||
TextAndImagesSettingsStores,
|
TextAndImagesSettingsStores,
|
||||||
UserStore
|
UserStore
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Guild, Message } from "discord-types/general";
|
||||||
|
|
||||||
const messageCache = new Map<string, {
|
const messageCache = new Map<string, {
|
||||||
message?: Message;
|
message?: Message;
|
||||||
|
@ -51,9 +49,8 @@ const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageCo
|
||||||
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
|
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
|
||||||
|
|
||||||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||||
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
|
||||||
|
|
||||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||||
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
||||||
|
|
||||||
interface Attachment {
|
interface Attachment {
|
||||||
|
@ -66,6 +63,7 @@ interface Attachment {
|
||||||
interface MessageEmbedProps {
|
interface MessageEmbedProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
|
guildID: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageFetchQueue = new Queue();
|
const messageFetchQueue = new Queue();
|
||||||
|
@ -228,19 +226,19 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
|
|
||||||
let match = null as RegExpMatchArray | null;
|
let match = null as RegExpMatchArray | null;
|
||||||
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
||||||
const [_, channelID, messageID] = match;
|
const [_, guildID, channelID, messageID] = match;
|
||||||
if (embeddedBy.includes(messageID)) {
|
if (embeddedBy.includes(messageID)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkedChannel = ChannelStore.getChannel(channelID);
|
const linkedChannel = ChannelStore.getChannel(channelID);
|
||||||
if (!linkedChannel || (!linkedChannel.isPrivate() && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, linkedChannel))) {
|
if (!linkedChannel || (guildID !== "@me" && !PermissionStore.can(1024n /* view channel */, linkedChannel))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { listMode, idList } = settings.store;
|
const { listMode, idList } = settings.store;
|
||||||
|
|
||||||
const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id));
|
const isListed = [guildID, channelID, message.author.id].some(id => id && idList.includes(id));
|
||||||
|
|
||||||
if (listMode === "blacklist" && isListed) continue;
|
if (listMode === "blacklist" && isListed) continue;
|
||||||
if (listMode === "whitelist" && !isListed) continue;
|
if (listMode === "whitelist" && !isListed) continue;
|
||||||
|
@ -267,7 +265,8 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
|
|
||||||
const messageProps: MessageEmbedProps = {
|
const messageProps: MessageEmbedProps = {
|
||||||
message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]),
|
message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]),
|
||||||
channel: linkedChannel
|
channel: linkedChannel,
|
||||||
|
guildID
|
||||||
};
|
};
|
||||||
|
|
||||||
const type = settings.store.automodEmbeds;
|
const type = settings.store.automodEmbeds;
|
||||||
|
@ -281,28 +280,28 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
return accessories.length ? <>{accessories}</> : null;
|
return accessories.length ? <>{accessories}</> : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChannelLabelAndIconUrl(channel: Channel) {
|
function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbedProps): JSX.Element | null {
|
||||||
if (channel.isDM()) return ["Direct Message", IconUtils.getUserAvatarURL(UserStore.getUser(channel.recipients[0]))];
|
const isDM = guildID === "@me";
|
||||||
if (channel.isGroupDM()) return ["Group DM", IconUtils.getChannelIconURL(channel)];
|
|
||||||
return ["Server", IconUtils.getGuildIconURL(GuildStore.getGuild(channel.guild_id))];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
|
const guild = !isDM && GuildStore.getGuild(channel.guild_id);
|
||||||
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
||||||
|
|
||||||
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
|
||||||
|
|
||||||
return (
|
return <Embed
|
||||||
<Embed
|
|
||||||
embed={{
|
embed={{
|
||||||
rawDescription: "",
|
rawDescription: "",
|
||||||
color: "var(--background-secondary)",
|
color: "var(--background-secondary)",
|
||||||
author: {
|
author: {
|
||||||
name: <Text variant="text-xs/medium" tag="span">
|
name: <Text variant="text-xs/medium" tag="span">
|
||||||
<span>{channelLabel} - </span>
|
<span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span>
|
||||||
{Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)}
|
{isDM
|
||||||
|
? Parser.parse(`<@${dmReceiver.id}>`)
|
||||||
|
: Parser.parse(`<#${channel.id}>`)
|
||||||
|
}
|
||||||
</Text>,
|
</Text>,
|
||||||
iconProxyURL: iconUrl
|
iconProxyURL: guild
|
||||||
|
? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png`
|
||||||
|
: `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}`
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
renderDescription={() => (
|
renderDescription={() => (
|
||||||
|
@ -315,30 +314,25 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
const { message, channel } = props;
|
const { message, channel, guildID } = props;
|
||||||
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
||||||
|
const isDM = guildID === "@me";
|
||||||
const images = getImages(message);
|
const images = getImages(message);
|
||||||
const { parse } = Parser;
|
const { parse } = Parser;
|
||||||
|
|
||||||
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
|
||||||
|
|
||||||
return <AutoModEmbed
|
return <AutoModEmbed
|
||||||
channel={channel}
|
channel={channel}
|
||||||
childrenAccessories={
|
childrenAccessories={
|
||||||
<Text color="text-muted" variant="text-xs/medium" tag="span" className={`${EmbedClasses.embedAuthor} ${EmbedClasses.embedMargin}`}>
|
<Text color="text-muted" variant="text-xs/medium" tag="span">
|
||||||
{iconUrl && <img src={iconUrl} className={EmbedClasses.embedAuthorIcon} alt="" />}
|
{isDM
|
||||||
<span>
|
? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
|
||||||
<span>{channelLabel} - </span>
|
: parse(`<#${channel.id}>`)
|
||||||
{channel.isDM()
|
|
||||||
? Parser.parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
|
|
||||||
: Parser.parse(`<#${channel.id}>`)
|
|
||||||
}
|
}
|
||||||
</span>
|
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
||||||
import textStyle from "./deleteStyleText.css?managed";
|
import textStyle from "./deleteStyleText.css?managed";
|
||||||
|
@ -122,7 +122,7 @@ export default definePlugin({
|
||||||
|
|
||||||
makeEdit(newMessage: any, oldMessage: any): any {
|
makeEdit(newMessage: any, oldMessage: any): any {
|
||||||
return {
|
return {
|
||||||
timestamp: new Date(newMessage.edited_timestamp),
|
timestamp: moment?.call(newMessage.edited_timestamp),
|
||||||
content: oldMessage.content
|
content: oldMessage.content
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -328,7 +328,6 @@ export default definePlugin({
|
||||||
// Attachment renderer
|
// Attachment renderer
|
||||||
// Module 96063
|
// Module 96063
|
||||||
find: ".removeAttachmentHoverButton",
|
find: ".removeAttachmentHoverButton",
|
||||||
group: true,
|
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(className:\i,attachment:\i),/,
|
match: /(className:\i,attachment:\i),/,
|
||||||
|
|
|
@ -198,7 +198,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
// make the tag show the right text
|
// make the tag show the right text
|
||||||
{
|
{
|
||||||
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.BOT_TAG_BOT/,
|
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/,
|
||||||
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
|
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
|
||||||
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
|
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,10 +20,11 @@ import { Devs } from "@utils/constants";
|
||||||
import { isNonNullish } from "@utils/guards";
|
import { isNonNullish } from "@utils/guards";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
||||||
import { Channel, User } from "discord-types/general";
|
import { Channel, User } from "discord-types/general";
|
||||||
|
|
||||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||||
|
const AvatarUtils = findByPropsLazy("getChannelIconURL");
|
||||||
const UserUtils = findByPropsLazy("getGlobalName");
|
const UserUtils = findByPropsLazy("getGlobalName");
|
||||||
|
|
||||||
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
|
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
|
||||||
|
@ -70,7 +71,7 @@ export default definePlugin({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={IconUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
src={AvatarUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
||||||
size="SIZE_40"
|
size="SIZE_40"
|
||||||
className={ProfileListClasses.listAvatar}
|
className={ProfileListClasses.listAvatar}
|
||||||
>
|
>
|
||||||
|
|
|
@ -27,8 +27,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".nsfwAllowed=null",
|
find: ".nsfwAllowed=null",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.nsfwAllowed=)null!==.+?(?=[,;])/,
|
match: /(\w+)\.nsfwAllowed=/,
|
||||||
replace: "!0",
|
replace: "$1.nsfwAllowed=true;",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -104,7 +104,6 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
|
||||||
guildMember.nick || UserStore.getUser(guildMember.userId).username
|
guildMember.nick || UserStore.getUser(guildMember.userId).username
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onDropDownClick={state => settings.store.defaultPermissionsDropdownState = !state}
|
|
||||||
defaultState={settings.store.defaultPermissionsDropdownState}
|
defaultState={settings.store.defaultPermissionsDropdownState}
|
||||||
buttons={[
|
buttons={[
|
||||||
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
|
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
|
||||||
|
|
|
@ -126,9 +126,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
|
||||||
|
|
||||||
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
|
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
|
||||||
return (children, props) => () => {
|
return (children, props) => () => {
|
||||||
if (!props) return;
|
if (!props || (type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild)) return children;
|
||||||
if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild)))
|
|
||||||
return children;
|
|
||||||
|
|
||||||
const group = findGroupChildrenByChildId(childId, children);
|
const group = findGroupChildrenByChildId(childId, children);
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ const settings = definePluginSettings({
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PictureInPicture",
|
name: "PictureInPicture",
|
||||||
description: "Adds picture in picture to videos (next to the Download button)",
|
description: "Adds picture in picture to videos (next to the Download button)",
|
||||||
authors: [Devs.Nobody],
|
authors: [Devs.Lumap],
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,14 +16,22 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { generateId, sendBotMessage } from "@api/Commands";
|
import { generateId, sendBotMessage } from "@api/Commands";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { StartAt } from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
|
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||||
import { MessageAttachment } from "discord-types/general";
|
import { MessageAttachment } from "discord-types/general";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: {
|
||||||
|
analyticsName: string;
|
||||||
|
isEmpty: boolean;
|
||||||
|
attachments: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const UploadStore = findByPropsLazy("getUploads");
|
const UploadStore = findByPropsLazy("getUploads");
|
||||||
|
|
||||||
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
||||||
|
@ -73,11 +81,13 @@ const getAttachments = async (channelId: string) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
export function PreviewButton(chatBoxProps: Props) {
|
||||||
|
const { isEmpty, attachments } = chatBoxProps.type;
|
||||||
|
|
||||||
const channelId = SelectedChannelStore.getChannelId();
|
const channelId = SelectedChannelStore.getChannelId();
|
||||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||||
|
|
||||||
if (!isMainChat) return null;
|
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||||
|
|
||||||
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
|
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
|
||||||
const hasContent = !isEmpty && draft?.length > 0;
|
const hasContent = !isEmpty && draft?.length > 0;
|
||||||
|
@ -85,8 +95,10 @@ const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments
|
||||||
if (!hasContent && !hasAttachments) return null;
|
if (!hasContent && !hasAttachments) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBarButton
|
<Tooltip text="Preview Message">
|
||||||
tooltip="Preview Message"
|
{tooltipProps => (
|
||||||
|
<Button
|
||||||
|
{...tooltipProps}
|
||||||
onClick={async () =>
|
onClick={async () =>
|
||||||
sendBotMessage(
|
sendBotMessage(
|
||||||
channelId,
|
channelId,
|
||||||
|
@ -96,36 +108,34 @@ const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments
|
||||||
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
buttonProps={{
|
size=""
|
||||||
style: {
|
look={ButtonLooks.BLANK}
|
||||||
translate: "0 2px"
|
innerClassName={ButtonWrapperClasses.button}
|
||||||
}
|
style={{ padding: "0 2px", height: "100%" }}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<svg
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
fill="currentColor"
|
<img width={24} height={24} src="https://discord.com/assets/4c5a77a89716352686f590a6f014770c.svg" />
|
||||||
fillRule="evenodd"
|
</div>
|
||||||
width="24"
|
</Button>
|
||||||
height="24"
|
)}
|
||||||
viewBox="0 0 24 24"
|
</Tooltip>
|
||||||
style={{ scale: "1.096", translate: "0 -1px" }}
|
|
||||||
>
|
|
||||||
<path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
|
|
||||||
</svg>
|
|
||||||
</ChatBarButton>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PreviewMessage",
|
name: "PreviewMessage",
|
||||||
description: "Lets you preview your message before sending it.",
|
description: "Lets you preview your message before sending it.",
|
||||||
authors: [Devs.Aria],
|
authors: [Devs.Aria],
|
||||||
dependencies: ["ChatInputButtonAPI"],
|
patches: [
|
||||||
// start early to ensure we're the first plugin to add our button
|
{
|
||||||
// This makes the popping in less awkward
|
find: "ChannelTextAreaButtons",
|
||||||
startAt: StartAt.Init,
|
replacement: {
|
||||||
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
start: () => addChatBarButton("previewMessage", PreviewButton),
|
chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
||||||
stop: () => removeChatBarButton("previewMessage"),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,13 +25,12 @@ function onClick() {
|
||||||
const channels: Array<any> = [];
|
const channels: Array<any> = [];
|
||||||
|
|
||||||
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
||||||
GuildChannelStore.getChannels(guild.id).SELECTABLE
|
GuildChannelStore.getChannels(guild.id).SELECTABLE.forEach((c: { channel: { id: string; }; }) => {
|
||||||
.concat(GuildChannelStore.getChannels(guild.id).VOCAL)
|
|
||||||
.forEach((c: { channel: { id: string; }; }) => {
|
|
||||||
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
||||||
|
|
||||||
channels.push({
|
channels.push({
|
||||||
channelId: c.channel.id,
|
channelId: c.channel.id,
|
||||||
|
// messageId: c.channel?.lastMessageId,
|
||||||
messageId: ReadStateStore.lastMessageId(c.channel.id),
|
messageId: ReadStateStore.lastMessageId(c.channel.id),
|
||||||
readStateType: 0
|
readStateType: 0
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,8 +36,17 @@ function search(src: string, engine: string) {
|
||||||
open(engine + encodeURIComponent(src), "_blank");
|
open(engine + encodeURIComponent(src), "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSearchItem(src: string) {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
return (
|
if (!props) return;
|
||||||
|
const { reverseImageSearchType, itemHref, itemSrc } = props;
|
||||||
|
|
||||||
|
if (!reverseImageSearchType || reverseImageSearchType !== "img") return;
|
||||||
|
|
||||||
|
const src = itemHref ?? itemSrc;
|
||||||
|
|
||||||
|
const group = findGroupChildrenByChildId("copy-link", children);
|
||||||
|
if (group) {
|
||||||
|
group.push((
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
label="Search Image"
|
label="Search Image"
|
||||||
key="search-image"
|
key="search-image"
|
||||||
|
@ -81,23 +90,8 @@ function makeSearchItem(src: string) {
|
||||||
action={() => Object.values(Engines).forEach(e => search(src, e))}
|
action={() => Object.values(Engines).forEach(e => search(src, e))}
|
||||||
/>
|
/>
|
||||||
</Menu.MenuItem>
|
</Menu.MenuItem>
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
|
||||||
if (props?.reverseImageSearchType !== "img") return;
|
|
||||||
|
|
||||||
const src = props.itemHref ?? props.itemSrc;
|
|
||||||
|
|
||||||
const group = findGroupChildrenByChildId("copy-link", children);
|
|
||||||
group?.push(makeSearchItem(src));
|
|
||||||
};
|
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
|
||||||
if (!props?.src) return;
|
|
||||||
|
|
||||||
const group = findGroupChildrenByChildId("copy-native-link", children) ?? children;
|
|
||||||
group.push(makeSearchItem(props.src));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -117,12 +111,10 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addContextMenuPatch("message", messageContextMenuPatch);
|
addContextMenuPatch("message", imageContextMenuPatch);
|
||||||
addContextMenuPatch("image-context", imageContextMenuPatch);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeContextMenuPatch("message", messageContextMenuPatch);
|
removeContextMenuPatch("message", imageContextMenuPatch);
|
||||||
removeContextMenuPatch("image-context", imageContextMenuPatch);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { openModal } from "@utils/modal";
|
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { showToast, Toasts, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { ReviewDBAuth } from "./entities";
|
|
||||||
|
|
||||||
const DATA_STORE_KEY = "rdb-auth";
|
|
||||||
|
|
||||||
const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
|
|
||||||
|
|
||||||
export let Auth: ReviewDBAuth = {};
|
|
||||||
|
|
||||||
export async function initAuth() {
|
|
||||||
Auth = await getAuth() ?? {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAuth(): Promise<ReviewDBAuth | undefined> {
|
|
||||||
const auth = await DataStore.get(DATA_STORE_KEY);
|
|
||||||
return auth?.[UserStore.getCurrentUser()?.id];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getToken() {
|
|
||||||
const auth = await getAuth();
|
|
||||||
return auth?.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateAuth(newAuth: ReviewDBAuth) {
|
|
||||||
return DataStore.update(DATA_STORE_KEY, auth => {
|
|
||||||
auth ??= {};
|
|
||||||
Auth = auth[UserStore.getCurrentUser().id] ??= {};
|
|
||||||
|
|
||||||
if (newAuth.token) Auth.token = newAuth.token;
|
|
||||||
if (newAuth.user) Auth.user = newAuth.user;
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function authorize(callback?: any) {
|
|
||||||
openModal(props =>
|
|
||||||
<OAuth2AuthorizeModal
|
|
||||||
{...props}
|
|
||||||
scopes={["identify"]}
|
|
||||||
responseType="code"
|
|
||||||
redirectUri="https://manti.vendicated.dev/api/reviewdb/auth"
|
|
||||||
permissions={0n}
|
|
||||||
clientId="915703782174752809"
|
|
||||||
cancelCompletesFlow={false}
|
|
||||||
callback={async (response: any) => {
|
|
||||||
try {
|
|
||||||
const url = new URL(response.location);
|
|
||||||
url.searchParams.append("clientMod", "vencord");
|
|
||||||
const res = await fetch(url, {
|
|
||||||
headers: new Headers({ Accept: "application/json" })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const { message } = await res.json();
|
|
||||||
showToast(message || "An error occured while authorizing", Toasts.Type.FAILURE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { token } = await res.json();
|
|
||||||
updateAuth({ token });
|
|
||||||
showToast("Successfully logged in!", Toasts.Type.SUCCESS);
|
|
||||||
callback?.();
|
|
||||||
} catch (e) {
|
|
||||||
new Logger("ReviewDB").error("Failed to authorize", e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
|
||||||
import { useAwaiter } from "@utils/react";
|
|
||||||
import { Forms, Tooltip, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Auth } from "../auth";
|
|
||||||
import { ReviewDBUser } from "../entities";
|
|
||||||
import { fetchBlocks, unblockUser } from "../reviewDbApi";
|
|
||||||
import { cl } from "../utils";
|
|
||||||
|
|
||||||
function UnblockButton(props: { onClick?(): void; }) {
|
|
||||||
return (
|
|
||||||
<Tooltip text="Unblock user">
|
|
||||||
{tooltipProps => (
|
|
||||||
<div
|
|
||||||
{...tooltipProps}
|
|
||||||
role="button"
|
|
||||||
onClick={props.onClick}
|
|
||||||
className={cl("block-modal-unblock")}
|
|
||||||
>
|
|
||||||
<svg height="20" viewBox="0 -960 960 960" width="20" fill="var(--status-danger)">
|
|
||||||
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q54 0 104-17.5t92-50.5L228-676q-33 42-50.5 92T160-480q0 134 93 227t227 93Zm252-124q33-42 50.5-92T800-480q0-134-93-227t-227-93q-54 0-104 17.5T284-732l448 448Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BlockedUser({ user, isBusy, setIsBusy }: { user: ReviewDBUser; isBusy: boolean; setIsBusy(v: boolean): void; }) {
|
|
||||||
const [gone, setGone] = useState(false);
|
|
||||||
if (gone) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cl("block-modal-row")}>
|
|
||||||
<img src={user.profilePhoto} alt="" />
|
|
||||||
<Forms.FormText className={cl("block-modal-username")}>{user.username}</Forms.FormText>
|
|
||||||
<UnblockButton
|
|
||||||
onClick={isBusy ? undefined : async () => {
|
|
||||||
setIsBusy(true);
|
|
||||||
try {
|
|
||||||
await unblockUser(user.discordID);
|
|
||||||
setGone(true);
|
|
||||||
} finally {
|
|
||||||
setIsBusy(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Modal() {
|
|
||||||
const [isBusy, setIsBusy] = useState(false);
|
|
||||||
const [blocks, error, pending] = useAwaiter(fetchBlocks, {
|
|
||||||
onError: e => new Logger("ReviewDB").error("Failed to fetch blocks", e),
|
|
||||||
fallbackValue: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pending)
|
|
||||||
return null;
|
|
||||||
if (error)
|
|
||||||
return <Forms.FormText>Failed to fetch blocks: ${String(error)}</Forms.FormText>;
|
|
||||||
if (!blocks.length)
|
|
||||||
return <Forms.FormText>No blocked users.</Forms.FormText>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{blocks.map(b => (
|
|
||||||
<BlockedUser
|
|
||||||
key={b.discordID}
|
|
||||||
user={b}
|
|
||||||
isBusy={isBusy}
|
|
||||||
setIsBusy={setIsBusy}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openBlockModal() {
|
|
||||||
openModal(modalProps => (
|
|
||||||
<ModalRoot {...modalProps}>
|
|
||||||
<ModalHeader className={cl("block-modal-header")}>
|
|
||||||
<Forms.FormTitle style={{ margin: 0 }}>Blocked Users</Forms.FormTitle>
|
|
||||||
<ModalCloseButton onClick={modalProps.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalContent className={cl("block-modal")}>
|
|
||||||
{Auth.token ? <Modal /> : <Forms.FormText>You are not logged into ReviewDB!</Forms.FormText>}
|
|
||||||
</ModalContent>
|
|
||||||
</ModalRoot>
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -1,85 +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 { DeleteIcon } from "@components/Icons";
|
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { Tooltip } from "@webpack/common";
|
|
||||||
|
|
||||||
const iconClasses = findByPropsLazy("button", "wrapper", "disabled", "separator");
|
|
||||||
|
|
||||||
export function DeleteButton({ onClick }: { onClick(): void; }) {
|
|
||||||
return (
|
|
||||||
<Tooltip text="Delete Review">
|
|
||||||
{props => (
|
|
||||||
<div
|
|
||||||
{...props}
|
|
||||||
className={classes(iconClasses.button, iconClasses.dangerous)}
|
|
||||||
onClick={onClick}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<DeleteIcon width="20" height="20" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ReportButton({ onClick }: { onClick(): void; }) {
|
|
||||||
return (
|
|
||||||
<Tooltip text="Report Review">
|
|
||||||
{props => (
|
|
||||||
<div
|
|
||||||
{...props}
|
|
||||||
className={iconClasses.button}
|
|
||||||
onClick={onClick}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24">
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M20,6.002H14V3.002C14,2.45 13.553,2.002 13,2.002H4C3.447,2.002 3,2.45 3,3.002V22.002H5V14.002H10.586L8.293,16.295C8.007,16.581 7.922,17.011 8.076,17.385C8.23,17.759 8.596,18.002 9,18.002H20C20.553,18.002 21,17.554 21,17.002V7.002C21,6.45 20.553,6.002 20,6.002Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BlockButton({ onClick, isBlocked }: { onClick(): void; isBlocked: boolean; }) {
|
|
||||||
return (
|
|
||||||
<Tooltip text={`${isBlocked ? "Unblock" : "Block"} user`}>
|
|
||||||
{props => (
|
|
||||||
<div
|
|
||||||
{...props}
|
|
||||||
className={iconClasses.button}
|
|
||||||
onClick={onClick}
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<svg height="20" viewBox="0 -960 960 960" width="20" fill="currentColor">
|
|
||||||
{isBlocked
|
|
||||||
? <path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
|
|
||||||
: <path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q54 0 104-17.5t92-50.5L228-676q-33 42-50.5 92T160-480q0 134 93 227t227 93Zm252-124q33-42 50.5-92T800-480q0-134-93-227t-227-93q-54 0-104 17.5T284-732l448 448Z" />
|
|
||||||
}
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,50 +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 { MaskedLink, React, Tooltip } from "@webpack/common";
|
|
||||||
import { HTMLAttributes } from "react";
|
|
||||||
|
|
||||||
import { Badge } from "../entities";
|
|
||||||
import { cl } from "../utils";
|
|
||||||
|
|
||||||
export default function ReviewBadge(badge: Badge & { onClick?(): void; }) {
|
|
||||||
const Wrapper = badge.redirectURL
|
|
||||||
? MaskedLink
|
|
||||||
: (props: HTMLAttributes<HTMLDivElement>) => (
|
|
||||||
<span {...props} role="button">{props.children}</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
text={badge.name}>
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<Wrapper className={cl("blocked-badge")} href={badge.redirectURL!} onClick={badge.onClick}>
|
|
||||||
<img
|
|
||||||
className={cl("badge")}
|
|
||||||
width="22px"
|
|
||||||
height="22px"
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
src={badge.icon}
|
|
||||||
alt={badge.description}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,191 +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 { openUserProfile } from "@utils/discord";
|
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import { LazyComponent } from "@utils/react";
|
|
||||||
import { filters, findBulk } from "@webpack";
|
|
||||||
import { Alerts, Parser, Timestamp, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Auth, getToken } from "../auth";
|
|
||||||
import { Review, ReviewType } from "../entities";
|
|
||||||
import { blockUser, deleteReview, reportReview, unblockUser } from "../reviewDbApi";
|
|
||||||
import { settings } from "../settings";
|
|
||||||
import { canBlockReviewAuthor, canDeleteReview, canReportReview, cl, showToast } from "../utils";
|
|
||||||
import { openBlockModal } from "./BlockedUserModal";
|
|
||||||
import { BlockButton, DeleteButton, ReportButton } from "./MessageButton";
|
|
||||||
import ReviewBadge from "./ReviewBadge";
|
|
||||||
|
|
||||||
export default LazyComponent(() => {
|
|
||||||
// this is terrible, blame mantika
|
|
||||||
const p = filters.byProps;
|
|
||||||
const [
|
|
||||||
{ cozyMessage, buttons, message, buttonsInner, groupStart },
|
|
||||||
{ container, isHeader },
|
|
||||||
{ avatar, clickable, username, wrapper, cozy },
|
|
||||||
buttonClasses,
|
|
||||||
botTag
|
|
||||||
] = findBulk(
|
|
||||||
p("cozyMessage"),
|
|
||||||
p("container", "isHeader"),
|
|
||||||
p("avatar", "zalgo"),
|
|
||||||
p("button", "wrapper", "selected"),
|
|
||||||
p("botTag", "botTagRegular")
|
|
||||||
);
|
|
||||||
|
|
||||||
const dateFormat = new Intl.DateTimeFormat();
|
|
||||||
|
|
||||||
return function ReviewComponent({ review, refetch, profileId }: { review: Review; refetch(): void; profileId: string; }) {
|
|
||||||
const [showAll, setShowAll] = useState(false);
|
|
||||||
|
|
||||||
function openModal() {
|
|
||||||
openUserProfile(review.sender.discordID);
|
|
||||||
}
|
|
||||||
|
|
||||||
function delReview() {
|
|
||||||
Alerts.show({
|
|
||||||
title: "Are you sure?",
|
|
||||||
body: "Do you really want to delete this review?",
|
|
||||||
confirmText: "Delete",
|
|
||||||
cancelText: "Nevermind",
|
|
||||||
onConfirm: async () => {
|
|
||||||
if (!(await getToken())) {
|
|
||||||
return showToast("You must be logged in to delete reviews.");
|
|
||||||
} else {
|
|
||||||
deleteReview(review.id).then(res => {
|
|
||||||
if (res) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportRev() {
|
|
||||||
Alerts.show({
|
|
||||||
title: "Are you sure?",
|
|
||||||
body: "Do you really you want to report this review?",
|
|
||||||
confirmText: "Report",
|
|
||||||
cancelText: "Nevermind",
|
|
||||||
// confirmColor: "red", this just adds a class name and breaks the submit button guh
|
|
||||||
onConfirm: async () => {
|
|
||||||
if (!(await getToken())) {
|
|
||||||
return showToast("You must be logged in to report reviews.");
|
|
||||||
} else {
|
|
||||||
reportReview(review.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAuthorBlocked = Auth?.user?.blockedUsers?.includes(review.sender.discordID) ?? false;
|
|
||||||
|
|
||||||
function blockReviewSender() {
|
|
||||||
if (isAuthorBlocked)
|
|
||||||
return unblockUser(review.sender.discordID);
|
|
||||||
|
|
||||||
Alerts.show({
|
|
||||||
title: "Are you sure?",
|
|
||||||
body: "Do you really you want to block this user? They will be unable to leave further reviews on your profile. You can unblock users in the plugin settings.",
|
|
||||||
confirmText: "Block",
|
|
||||||
cancelText: "Nevermind",
|
|
||||||
// confirmColor: "red", this just adds a class name and breaks the submit button guh
|
|
||||||
onConfirm: async () => {
|
|
||||||
if (!(await getToken())) {
|
|
||||||
return showToast("You must be logged in to block users.");
|
|
||||||
} else {
|
|
||||||
blockUser(review.sender.discordID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes(cl("review"), cozyMessage, wrapper, message, groupStart, cozy)} style={
|
|
||||||
{
|
|
||||||
marginLeft: "0px",
|
|
||||||
paddingLeft: "52px", // wth is this
|
|
||||||
// nobody knows anymore
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
|
|
||||||
<img
|
|
||||||
className={classes(avatar, clickable)}
|
|
||||||
onClick={openModal}
|
|
||||||
src={review.sender.profilePhoto || "/assets/1f0bfc0865d324c2587920a7d80c609b.png?size=128"}
|
|
||||||
style={{ left: "0px", zIndex: 0 }}
|
|
||||||
/>
|
|
||||||
<div style={{ display: "inline-flex", justifyContent: "center", alignItems: "center" }}>
|
|
||||||
<span
|
|
||||||
className={classes(clickable, username)}
|
|
||||||
style={{ color: "var(--channels-default)", fontSize: "14px" }}
|
|
||||||
onClick={() => openModal()}
|
|
||||||
>
|
|
||||||
{review.sender.username}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{review.type === ReviewType.System && (
|
|
||||||
<span
|
|
||||||
className={classes(botTag.botTagVerified, botTag.botTagRegular, botTag.botTag, botTag.px, botTag.rem)}
|
|
||||||
style={{ marginLeft: "4px" }}>
|
|
||||||
<span className={botTag.botText}>
|
|
||||||
System
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isAuthorBlocked && (
|
|
||||||
<ReviewBadge
|
|
||||||
name="You have blocked this user"
|
|
||||||
description="You have blocked this user"
|
|
||||||
icon="/assets/aaee57e0090991557b66.svg"
|
|
||||||
type={0}
|
|
||||||
onClick={() => openBlockModal()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{review.sender.badges.map(badge => <ReviewBadge {...badge} />)}
|
|
||||||
|
|
||||||
{
|
|
||||||
!settings.store.hideTimestamps && review.type !== ReviewType.System && (
|
|
||||||
<Timestamp timestamp={new Date(review.timestamp * 1000)} >
|
|
||||||
{dateFormat.format(review.timestamp * 1000)}
|
|
||||||
</Timestamp>)
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={cl("review-comment")}>
|
|
||||||
{(review.comment.length > 200 && !showAll)
|
|
||||||
? [Parser.parseGuildEventDescription(review.comment.substring(0, 200)), "...", <br />, (<a onClick={() => setShowAll(true)}>Read more</a>)]
|
|
||||||
: Parser.parseGuildEventDescription(review.comment)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{review.id !== 0 && (
|
|
||||||
<div className={classes(container, isHeader, buttons)} style={{
|
|
||||||
padding: "0px",
|
|
||||||
}}>
|
|
||||||
<div className={classes(buttonClasses.wrapper, buttonsInner)} >
|
|
||||||
{canReportReview(review) && <ReportButton onClick={reportRev} />}
|
|
||||||
{canBlockReviewAuthor(profileId, review) && <BlockButton isBlocked={isAuthorBlocked} onClick={blockReviewSender} />}
|
|
||||||
{canDeleteReview(profileId, review) && <DeleteButton onClick={delReview} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
|
||||||
import { useForceUpdater } from "@utils/react";
|
|
||||||
import { Paginator, Text, useRef, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Auth } from "../auth";
|
|
||||||
import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
|
|
||||||
import { cl } from "../utils";
|
|
||||||
import ReviewComponent from "./ReviewComponent";
|
|
||||||
import ReviewsView, { ReviewsInputComponent } from "./ReviewsView";
|
|
||||||
|
|
||||||
function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: string; name: string; }) {
|
|
||||||
const [data, setData] = useState<Response>();
|
|
||||||
const [signal, refetch] = useForceUpdater(true);
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const reviewCount = data?.reviewCount;
|
|
||||||
const ownReview = data?.reviews.find(r => r.sender.discordID === Auth.user?.discordID);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
|
||||||
<ModalHeader>
|
|
||||||
<Text variant="heading-lg/semibold" className={cl("modal-header")}>
|
|
||||||
{name}'s Reviews
|
|
||||||
{!!reviewCount && <span> ({reviewCount} Reviews)</span>}
|
|
||||||
</Text>
|
|
||||||
<ModalCloseButton onClick={modalProps.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalContent scrollerRef={ref}>
|
|
||||||
<div className={cl("modal-reviews")}>
|
|
||||||
<ReviewsView
|
|
||||||
discordId={discordId}
|
|
||||||
name={name}
|
|
||||||
page={page}
|
|
||||||
refetchSignal={signal}
|
|
||||||
onFetchReviews={setData}
|
|
||||||
scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })}
|
|
||||||
hideOwnReview
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ModalContent>
|
|
||||||
|
|
||||||
<ModalFooter className={cl("modal-footer")}>
|
|
||||||
<div>
|
|
||||||
{ownReview && (
|
|
||||||
<ReviewComponent
|
|
||||||
refetch={refetch}
|
|
||||||
review={ownReview}
|
|
||||||
profileId={discordId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ReviewsInputComponent
|
|
||||||
isAuthor={ownReview != null}
|
|
||||||
discordId={discordId}
|
|
||||||
name={name}
|
|
||||||
refetch={refetch}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!!reviewCount && (
|
|
||||||
<Paginator
|
|
||||||
currentPage={page}
|
|
||||||
maxVisiblePages={5}
|
|
||||||
pageSize={REVIEWS_PER_PAGE}
|
|
||||||
totalCount={reviewCount}
|
|
||||||
onPageChange={setPage}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalRoot>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openReviewsModal(discordId: string, name: string) {
|
|
||||||
openModal(props => (
|
|
||||||
<Modal
|
|
||||||
modalProps={props}
|
|
||||||
discordId={discordId}
|
|
||||||
name={name}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -1,197 +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 { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react";
|
|
||||||
import { find, findByPropsLazy } from "@webpack";
|
|
||||||
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Auth, authorize } from "../auth";
|
|
||||||
import { Review } from "../entities";
|
|
||||||
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
|
|
||||||
import { settings } from "../settings";
|
|
||||||
import { cl, showToast } from "../utils";
|
|
||||||
import ReviewComponent from "./ReviewComponent";
|
|
||||||
|
|
||||||
|
|
||||||
const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
|
|
||||||
const { ChatInputTypes } = findByPropsLazy("ChatInputTypes");
|
|
||||||
|
|
||||||
const InputComponent = LazyComponent(() => find(m => m.default?.type?.render?.toString().includes("default.CHANNEL_TEXT_AREA")).default);
|
|
||||||
|
|
||||||
interface UserProps {
|
|
||||||
discordId: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props extends UserProps {
|
|
||||||
onFetchReviews(data: Response): void;
|
|
||||||
refetchSignal?: unknown;
|
|
||||||
showInput?: boolean;
|
|
||||||
page?: number;
|
|
||||||
scrollToTop?(): void;
|
|
||||||
hideOwnReview?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ReviewsView({
|
|
||||||
discordId,
|
|
||||||
name,
|
|
||||||
onFetchReviews,
|
|
||||||
refetchSignal,
|
|
||||||
scrollToTop,
|
|
||||||
page = 1,
|
|
||||||
showInput = false,
|
|
||||||
hideOwnReview = false,
|
|
||||||
}: Props) {
|
|
||||||
const [signal, refetch] = useForceUpdater(true);
|
|
||||||
|
|
||||||
const [reviewData] = useAwaiter(() => getReviews(discordId, (page - 1) * REVIEWS_PER_PAGE), {
|
|
||||||
fallbackValue: null,
|
|
||||||
deps: [refetchSignal, signal, page],
|
|
||||||
onSuccess: data => {
|
|
||||||
if (settings.store.hideBlockedUsers)
|
|
||||||
data!.reviews = data!.reviews?.filter(r => !RelationshipStore.isBlocked(r.sender.discordID));
|
|
||||||
|
|
||||||
scrollToTop?.();
|
|
||||||
onFetchReviews(data!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!reviewData) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ReviewList
|
|
||||||
refetch={refetch}
|
|
||||||
reviews={reviewData!.reviews}
|
|
||||||
hideOwnReview={hideOwnReview}
|
|
||||||
profileId={discordId}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{showInput && (
|
|
||||||
<ReviewsInputComponent
|
|
||||||
name={name}
|
|
||||||
discordId={discordId}
|
|
||||||
refetch={refetch}
|
|
||||||
isAuthor={reviewData!.reviews?.some(r => r.sender.discordID === UserStore.getCurrentUser().id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) {
|
|
||||||
const myId = UserStore.getCurrentUser().id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cl("view")}>
|
|
||||||
{reviews?.map(review =>
|
|
||||||
(review.sender.discordID !== myId || !hideOwnReview) &&
|
|
||||||
<ReviewComponent
|
|
||||||
key={review.id}
|
|
||||||
review={review}
|
|
||||||
refetch={refetch}
|
|
||||||
profileId={profileId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{reviews?.length === 0 && (
|
|
||||||
<Forms.FormText className={cl("placeholder")}>
|
|
||||||
Looks like nobody reviewed this user yet. You could be the first!
|
|
||||||
</Forms.FormText>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) {
|
|
||||||
const { token } = Auth;
|
|
||||||
const editorRef = useRef<any>(null);
|
|
||||||
const inputType = ChatInputTypes.FORM;
|
|
||||||
inputType.disableAutoFocus = true;
|
|
||||||
|
|
||||||
const channel = {
|
|
||||||
flags_: 256,
|
|
||||||
guild_id_: null,
|
|
||||||
id: "0",
|
|
||||||
getGuildId: () => null,
|
|
||||||
isPrivate: () => true,
|
|
||||||
isActiveThread: () => false,
|
|
||||||
isArchivedLockedThread: () => false,
|
|
||||||
isDM: () => true,
|
|
||||||
roles: { "0": { permissions: 0n } },
|
|
||||||
getRecipientId: () => "0",
|
|
||||||
hasFlag: () => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div onClick={() => {
|
|
||||||
if (!token) {
|
|
||||||
showToast("Opening authorization window...");
|
|
||||||
authorize();
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<InputComponent
|
|
||||||
className={cl("input")}
|
|
||||||
channel={channel}
|
|
||||||
placeholder={
|
|
||||||
!token
|
|
||||||
? "You need to authorize to review users!"
|
|
||||||
: isAuthor
|
|
||||||
? `Update review for @${name}`
|
|
||||||
: `Review @${name}`
|
|
||||||
}
|
|
||||||
type={inputType}
|
|
||||||
disableThemedBackground={true}
|
|
||||||
setEditorRef={ref => editorRef.current = ref}
|
|
||||||
textValue=""
|
|
||||||
onSubmit={
|
|
||||||
async res => {
|
|
||||||
const response = await addReview({
|
|
||||||
userid: discordId,
|
|
||||||
comment: res.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
refetch();
|
|
||||||
|
|
||||||
const slateEditor = editorRef.current.ref.current.getSlateEditor();
|
|
||||||
|
|
||||||
// clear editor
|
|
||||||
Transforms.delete(slateEditor, {
|
|
||||||
at: {
|
|
||||||
anchor: Editor.start(slateEditor, []),
|
|
||||||
focus: Editor.end(slateEditor, []),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// even tho we need to return this, it doesnt do anything
|
|
||||||
return {
|
|
||||||
shouldClear: false,
|
|
||||||
shouldRefocus: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const enum UserType {
|
|
||||||
Banned = -1,
|
|
||||||
Normal = 0,
|
|
||||||
Admin = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum ReviewType {
|
|
||||||
User = 0,
|
|
||||||
Server = 1,
|
|
||||||
Support = 2,
|
|
||||||
System = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum NotificationType {
|
|
||||||
Info = 0,
|
|
||||||
Ban = 1,
|
|
||||||
Unban = 2,
|
|
||||||
Warning = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReviewDBAuth {
|
|
||||||
token?: string;
|
|
||||||
user?: ReviewDBCurrentUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Badge {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
icon: string;
|
|
||||||
redirectURL?: string;
|
|
||||||
type: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BanInfo {
|
|
||||||
id: string;
|
|
||||||
discordID: string;
|
|
||||||
reviewID: number;
|
|
||||||
reviewContent: string;
|
|
||||||
banEndDate: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Notification {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
content: string;
|
|
||||||
type: NotificationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReviewDBUser {
|
|
||||||
ID: number;
|
|
||||||
discordID: string;
|
|
||||||
username: string;
|
|
||||||
type: UserType;
|
|
||||||
profilePhoto: string;
|
|
||||||
badges: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReviewDBCurrentUser extends ReviewDBUser {
|
|
||||||
warningCount: number;
|
|
||||||
clientMod: string;
|
|
||||||
banInfo: BanInfo | null;
|
|
||||||
notification: Notification | null;
|
|
||||||
lastReviewID: number;
|
|
||||||
blockedUsers?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReviewAuthor {
|
|
||||||
id: number,
|
|
||||||
discordID: string,
|
|
||||||
username: string,
|
|
||||||
profilePhoto: string,
|
|
||||||
badges: Badge[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Review {
|
|
||||||
comment: string,
|
|
||||||
id: number,
|
|
||||||
star: number,
|
|
||||||
sender: ReviewAuthor,
|
|
||||||
timestamp: number;
|
|
||||||
type?: ReviewType;
|
|
||||||
}
|
|
|
@ -1,158 +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 "./style.css";
|
|
||||||
|
|
||||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import ExpandableHeader from "@components/ExpandableHeader";
|
|
||||||
import { OpenExternalIcon } from "@components/Icons";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { Alerts, Menu, Parser, useState } from "@webpack/common";
|
|
||||||
import { Guild, User } from "discord-types/general";
|
|
||||||
|
|
||||||
import { Auth, initAuth, updateAuth } from "./auth";
|
|
||||||
import { openReviewsModal } from "./components/ReviewModal";
|
|
||||||
import ReviewsView from "./components/ReviewsView";
|
|
||||||
import { NotificationType } from "./entities";
|
|
||||||
import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
|
|
||||||
import { settings } from "./settings";
|
|
||||||
import { showToast } from "./utils";
|
|
||||||
|
|
||||||
const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => {
|
|
||||||
children.push(
|
|
||||||
<Menu.MenuItem
|
|
||||||
label="View Reviews"
|
|
||||||
id="vc-rdb-server-reviews"
|
|
||||||
icon={OpenExternalIcon}
|
|
||||||
action={() => openReviewsModal(props.guild.id, props.guild.name)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "ReviewDB",
|
|
||||||
description: "Review other users (Adds a new settings to profiles)",
|
|
||||||
authors: [Devs.mantikafasi, Devs.Ven],
|
|
||||||
|
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "showBorder:null",
|
|
||||||
replacement: {
|
|
||||||
match: /user:(\i),setNote:\i,canDM.+?\}\)/,
|
|
||||||
replace: "$&,$self.getReviewsComponent($1)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
flux: {
|
|
||||||
CONNECTION_OPEN: initAuth,
|
|
||||||
},
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
|
||||||
|
|
||||||
const s = settings.store;
|
|
||||||
const { lastReviewId, notifyReviews } = s;
|
|
||||||
|
|
||||||
const legacy = s as any as { token?: string; };
|
|
||||||
if (legacy.token) {
|
|
||||||
await updateAuth({ token: legacy.token });
|
|
||||||
legacy.token = undefined;
|
|
||||||
new Logger("ReviewDB").info("Migrated legacy settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
await initAuth();
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
|
||||||
if (!Auth.token) return;
|
|
||||||
|
|
||||||
const user = await getCurrentUserInfo(Auth.token);
|
|
||||||
updateAuth({ user });
|
|
||||||
|
|
||||||
if (notifyReviews) {
|
|
||||||
if (lastReviewId && lastReviewId < user.lastReviewID) {
|
|
||||||
s.lastReviewId = user.lastReviewID;
|
|
||||||
if (user.lastReviewID !== 0)
|
|
||||||
showToast("You have new reviews on your profile!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.notification) {
|
|
||||||
const props = user.notification.type === NotificationType.Ban ? {
|
|
||||||
cancelText: "Appeal",
|
|
||||||
confirmText: "Ok",
|
|
||||||
onCancel: async () =>
|
|
||||||
VencordNative.native.openExternal(
|
|
||||||
"https://reviewdb.mantikafasi.dev/api/redirect?"
|
|
||||||
+ new URLSearchParams({
|
|
||||||
token: Auth.token!,
|
|
||||||
page: "dashboard/appeal"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} : {};
|
|
||||||
|
|
||||||
Alerts.show({
|
|
||||||
title: user.notification.title,
|
|
||||||
body: (
|
|
||||||
Parser.parse(
|
|
||||||
user.notification.content,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
),
|
|
||||||
...props
|
|
||||||
});
|
|
||||||
|
|
||||||
readNotification(user.notification.id);
|
|
||||||
}
|
|
||||||
}, 4000);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
|
||||||
},
|
|
||||||
|
|
||||||
getReviewsComponent: ErrorBoundary.wrap((user: User) => {
|
|
||||||
const [reviewCount, setReviewCount] = useState<number>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ExpandableHeader
|
|
||||||
headerText="User Reviews"
|
|
||||||
onMoreClick={() => openReviewsModal(user.id, user.username)}
|
|
||||||
moreTooltipText={
|
|
||||||
reviewCount && reviewCount > 50
|
|
||||||
? `View all ${reviewCount} reviews`
|
|
||||||
: "Open Review Modal"
|
|
||||||
}
|
|
||||||
onDropDownClick={state => settings.store.reviewsDropdownState = !state}
|
|
||||||
defaultState={settings.store.reviewsDropdownState}
|
|
||||||
>
|
|
||||||
<ReviewsView
|
|
||||||
discordId={user.id}
|
|
||||||
name={user.username}
|
|
||||||
onFetchReviews={r => setReviewCount(r.reviewCount)}
|
|
||||||
showInput
|
|
||||||
/>
|
|
||||||
</ExpandableHeader>
|
|
||||||
);
|
|
||||||
}, { message: "Failed to render Reviews" })
|
|
||||||
});
|
|
|
@ -1,202 +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 { Toasts } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Auth, authorize, getToken, updateAuth } from "./auth";
|
|
||||||
import { Review, ReviewDBCurrentUser, ReviewDBUser, ReviewType } from "./entities";
|
|
||||||
import { settings } from "./settings";
|
|
||||||
import { showToast } from "./utils";
|
|
||||||
|
|
||||||
const API_URL = "https://manti.vendicated.dev/api/reviewdb";
|
|
||||||
|
|
||||||
export const REVIEWS_PER_PAGE = 50;
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
message: string;
|
|
||||||
reviews: Review[];
|
|
||||||
updated: boolean;
|
|
||||||
hasNextPage: boolean;
|
|
||||||
reviewCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WarningFlag = 0b00000010;
|
|
||||||
|
|
||||||
async function rdbRequest(path: string, options: RequestInit = {}) {
|
|
||||||
return fetch(API_URL + path, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
...options.headers,
|
|
||||||
Authorization: await getToken() || "",
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getReviews(id: string, offset = 0): Promise<Response> {
|
|
||||||
let flags = 0;
|
|
||||||
if (!settings.store.showWarning) flags |= WarningFlag;
|
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
flags: String(flags),
|
|
||||||
offset: String(offset)
|
|
||||||
});
|
|
||||||
const req = await fetch(`${API_URL}/users/${id}/reviews?${params}`);
|
|
||||||
|
|
||||||
const res = (req.ok)
|
|
||||||
? await req.json() as Response
|
|
||||||
: {
|
|
||||||
message: req.status === 429 ? "You are sending requests too fast. Wait a few seconds and try again." : "An Error occured while fetching reviews. Please try again later.",
|
|
||||||
reviews: [],
|
|
||||||
updated: false,
|
|
||||||
hasNextPage: false,
|
|
||||||
reviewCount: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!req.ok) {
|
|
||||||
showToast(res.message, Toasts.Type.FAILURE);
|
|
||||||
return {
|
|
||||||
...res,
|
|
||||||
reviews: [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
comment: res.message,
|
|
||||||
star: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
type: ReviewType.System,
|
|
||||||
sender: {
|
|
||||||
id: 0,
|
|
||||||
username: "ReviewDB",
|
|
||||||
profilePhoto: "https://cdn.discordapp.com/avatars/1134864775000629298/3f87ad315b32ee464d84f1270c8d1b37.png?size=256&format=webp&quality=lossless",
|
|
||||||
discordID: "1134864775000629298",
|
|
||||||
badges: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addReview(review: any): Promise<Response | null> {
|
|
||||||
|
|
||||||
const token = await getToken();
|
|
||||||
if (!token) {
|
|
||||||
showToast("Please authorize to add a review.");
|
|
||||||
authorize();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await rdbRequest(`/users/${review.userid}/reviews`, {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify(review),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
}).then(async r => {
|
|
||||||
const data = await r.json() as Response;
|
|
||||||
showToast(data.message);
|
|
||||||
return r.ok ? data : null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteReview(id: number): Promise<Response | null> {
|
|
||||||
return await rdbRequest(`/users/${id}/reviews`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "application/json",
|
|
||||||
}),
|
|
||||||
body: JSON.stringify({
|
|
||||||
reviewid: id
|
|
||||||
})
|
|
||||||
}).then(async r => {
|
|
||||||
const data = await r.json() as Response;
|
|
||||||
showToast(data.message);
|
|
||||||
return r.ok ? data : null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function reportReview(id: number) {
|
|
||||||
const res = await rdbRequest("/reports", {
|
|
||||||
method: "PUT",
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "application/json",
|
|
||||||
}),
|
|
||||||
body: JSON.stringify({
|
|
||||||
reviewid: id,
|
|
||||||
})
|
|
||||||
}).then(r => r.json()) as Response;
|
|
||||||
|
|
||||||
showToast(res.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function patchBlock(action: "block" | "unblock", userId: string) {
|
|
||||||
const res = await rdbRequest("/blocks", {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "application/json",
|
|
||||||
}),
|
|
||||||
body: JSON.stringify({
|
|
||||||
action: action,
|
|
||||||
discordId: userId
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
showToast(`Failed to ${action} user`, Toasts.Type.FAILURE);
|
|
||||||
} else {
|
|
||||||
showToast(`Successfully ${action}ed user`, Toasts.Type.SUCCESS);
|
|
||||||
|
|
||||||
if (Auth?.user?.blockedUsers) {
|
|
||||||
const newBlockedUsers = action === "block"
|
|
||||||
? [...Auth.user.blockedUsers, userId]
|
|
||||||
: Auth.user.blockedUsers.filter(id => id !== userId);
|
|
||||||
updateAuth({ user: { ...Auth.user, blockedUsers: newBlockedUsers } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const blockUser = (userId: string) => patchBlock("block", userId);
|
|
||||||
export const unblockUser = (userId: string) => patchBlock("unblock", userId);
|
|
||||||
|
|
||||||
export async function fetchBlocks(): Promise<ReviewDBUser[]> {
|
|
||||||
const res = await rdbRequest("/blocks", {
|
|
||||||
method: "GET",
|
|
||||||
headers: new Headers({
|
|
||||||
Accept: "application/json",
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);
|
|
||||||
return res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentUserInfo(token: string): Promise<ReviewDBCurrentUser> {
|
|
||||||
return rdbRequest("/users", {
|
|
||||||
method: "POST",
|
|
||||||
}).then(r => r.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readNotification(id: number) {
|
|
||||||
return rdbRequest(`/notifications?id=${id}`, {
|
|
||||||
method: "PATCH"
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { OptionType } from "@utils/types";
|
|
||||||
import { Button } from "@webpack/common";
|
|
||||||
|
|
||||||
import { authorize, getToken } from "./auth";
|
|
||||||
import { openBlockModal } from "./components/BlockedUserModal";
|
|
||||||
import { cl } from "./utils";
|
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
|
||||||
authorize: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "Authorize with ReviewDB",
|
|
||||||
component: () => (
|
|
||||||
<Button onClick={() => authorize()}>
|
|
||||||
Authorize with ReviewDB
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
notifyReviews: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Notify about new reviews on startup",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
showWarning: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Display warning to be respectful at the top of the reviews list",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
hideTimestamps: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Hide timestamps on reviews",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hideBlockedUsers: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Hide reviews from blocked users",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
buttons: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "ReviewDB buttons",
|
|
||||||
component: () => (
|
|
||||||
<div className={cl("button-grid")} >
|
|
||||||
<Button onClick={openBlockModal}>Manage Blocked Users</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
color={Button.Colors.GREEN}
|
|
||||||
onClick={() => {
|
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/mantikafasi");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Support ReviewDB development
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button onClick={async () => {
|
|
||||||
let url = "https://reviewdb.mantikafasi.dev/";
|
|
||||||
const token = await getToken();
|
|
||||||
if (token)
|
|
||||||
url += "/api/redirect?token=" + encodeURIComponent(token);
|
|
||||||
|
|
||||||
VencordNative.native.openExternal(url);
|
|
||||||
}}>
|
|
||||||
ReviewDB website
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
|
|
||||||
<Button onClick={() => {
|
|
||||||
VencordNative.native.openExternal("https://discord.gg/eWPBSbvznt");
|
|
||||||
}}>
|
|
||||||
ReviewDB Support Server
|
|
||||||
</Button>
|
|
||||||
</div >
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}).withPrivateSettings<{
|
|
||||||
lastReviewId?: number;
|
|
||||||
reviewsDropdownState?: boolean;
|
|
||||||
}>();
|
|
|
@ -1,140 +0,0 @@
|
||||||
[class|="section"]:not([class|="lastSection"]) + .vc-rdb-view {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-badge {
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-input {
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
resize: none;
|
|
||||||
overflow: hidden;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--profile-message-input-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-modal-footer > div {
|
|
||||||
width: 100%;
|
|
||||||
margin: 6px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */
|
|
||||||
.vc-rdb-input > div > div {
|
|
||||||
padding-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-placeholder {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-input * {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-modal-footer {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-modal-footer .vc-rdb-input {
|
|
||||||
margin-bottom: 0;
|
|
||||||
background: var(--input-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-modal-footer [class|="pageControlContainer"] {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-modal-header {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-modal-reviews {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-review {
|
|
||||||
padding-top: 8px !important;
|
|
||||||
padding-bottom: 8px !important;
|
|
||||||
padding-right: 32px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-review:hover {
|
|
||||||
background: var(--background-message-hover) !important;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-review-comment img {
|
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-review-comment {
|
|
||||||
overflow-y: hidden;
|
|
||||||
margin-top: 1px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: var(--text-normal);
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-blocked-badge {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal {
|
|
||||||
padding: 1em;
|
|
||||||
display: grid;
|
|
||||||
gap: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-button-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-disable-next-line media-feature-range-notation */
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.vc-rdb-button-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal-row {
|
|
||||||
display: flex;
|
|
||||||
height: 2em;
|
|
||||||
gap: 0.5em;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal-row img {
|
|
||||||
border-radius: 50%;
|
|
||||||
height: 2em;
|
|
||||||
width: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal img::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--background-modifier-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal-username {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rdb-block-modal-unblock {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,54 +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 { classNameFactory } from "@api/Styles";
|
|
||||||
import { Toasts, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Auth } from "./auth";
|
|
||||||
import { Review, UserType } from "./entities";
|
|
||||||
|
|
||||||
export const cl = classNameFactory("vc-rdb-");
|
|
||||||
|
|
||||||
export function canDeleteReview(profileId: string, review: Review) {
|
|
||||||
const myId = UserStore.getCurrentUser().id;
|
|
||||||
return (
|
|
||||||
myId === profileId
|
|
||||||
|| review.sender.discordID === myId
|
|
||||||
|| Auth.user?.type === UserType.Admin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canBlockReviewAuthor(profileId: string, review: Review) {
|
|
||||||
const myId = UserStore.getCurrentUser().id;
|
|
||||||
return profileId === myId && review.sender.discordID !== myId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canReportReview(review: Review) {
|
|
||||||
return review.sender.discordID !== UserStore.getCurrentUser().id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showToast(message: string, type = Toasts.Type.MESSAGE) {
|
|
||||||
Toasts.show({
|
|
||||||
id: Toasts.genId(),
|
|
||||||
message,
|
|
||||||
type,
|
|
||||||
options: {
|
|
||||||
position: Toasts.Position.BOTTOM, // NOBODY LIKES TOASTS AT THE TOP
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -72,6 +72,10 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: 'tutorialId:"whos-online',
|
find: 'tutorialId:"whos-online',
|
||||||
replacement: [
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /\i.roleIcon,\.\.\.\i/,
|
||||||
|
replace: "$&,color:$self.roleGroupColor(arguments[0])"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
match: /null,\i," — ",\i\]/,
|
match: /null,\i," — ",\i\]/,
|
||||||
replace: "null,$self.roleGroupColor(arguments[0])]"
|
replace: "null,$self.roleGroupColor(arguments[0])]"
|
||||||
|
@ -79,16 +83,6 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.memberList,
|
predicate: () => settings.store.memberList,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: ".Messages.THREAD_BROWSER_PRIVATE",
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /children:\[\i," — ",\i\]/,
|
|
||||||
replace: "children:[$self.roleGroupColor(arguments[0])]"
|
|
||||||
},
|
|
||||||
],
|
|
||||||
predicate: () => settings.store.memberList,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: "renderPrioritySpeaker",
|
find: "renderPrioritySpeaker",
|
||||||
replacement: [
|
replacement: [
|
||||||
|
|
|
@ -18,24 +18,14 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord";
|
import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Button, Forms, Parser, Select, useMemo, useState } from "@webpack/common";
|
import { Button, ButtonLooks, ButtonWrapperClasses, Forms, Parser, Select, Tooltip, useMemo, useState } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
replaceMessageContents: {
|
|
||||||
description: "Replace timestamps in message contents",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseTime(time: string) {
|
function parseTime(time: string) {
|
||||||
const cleanTime = time.slice(1, -1).replace(/(\d)(AM|PM)$/i, "$1 $2");
|
const cleanTime = time.slice(1, -1).replace(/(\d)(AM|PM)$/i, "$1 $2");
|
||||||
|
@ -123,12 +113,47 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
export default definePlugin({
|
||||||
if (!isMainChat) return null;
|
name: "SendTimestamps",
|
||||||
|
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
||||||
|
authors: [Devs.Ven, Devs.Tyler],
|
||||||
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "ChannelTextAreaButtons",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.listener = addPreSendListener((_, msg) => {
|
||||||
|
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
removePreSendListener(this.listener);
|
||||||
|
},
|
||||||
|
|
||||||
|
chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) {
|
||||||
|
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBarButton
|
<Tooltip text="Insert Timestamp">
|
||||||
tooltip="Insert Timestamp"
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Button
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
aria-label="Insert Timestamp"
|
||||||
|
size=""
|
||||||
|
look={ButtonLooks.BLANK}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
innerClassName={ButtonWrapperClasses.button}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const key = openModal(props => (
|
const key = openModal(props => (
|
||||||
<PickerModal
|
<PickerModal
|
||||||
|
@ -137,45 +162,28 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}}
|
}}
|
||||||
buttonProps={{ "aria-haspopup": "dialog" }}
|
className={cl("button")}
|
||||||
>
|
>
|
||||||
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
role="img"
|
role="img"
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
style={{ scale: "1.2" }}
|
|
||||||
>
|
>
|
||||||
<g fill="none" fill-rule="evenodd">
|
<g fill="none" fill-rule="evenodd">
|
||||||
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
|
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
|
||||||
<rect width="24" height="24" />
|
<rect width="24" height="24" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</ChatBarButton>
|
</div>
|
||||||
);
|
</Button>
|
||||||
};
|
</div>
|
||||||
|
)
|
||||||
export default definePlugin({
|
|
||||||
name: "SendTimestamps",
|
|
||||||
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
|
||||||
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
|
||||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
|
||||||
|
|
||||||
settings,
|
|
||||||
|
|
||||||
start() {
|
|
||||||
addChatBarButton("SendTimestamps", ChatBarIcon);
|
|
||||||
this.listener = addPreSendListener((_, msg) => {
|
|
||||||
if (settings.store.replaceMessageContents) {
|
|
||||||
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
|
||||||
}
|
}
|
||||||
});
|
</Tooltip >
|
||||||
},
|
);
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeChatBarButton("SendTimestamps");
|
|
||||||
removePreSendListener(this.listener);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
settingsAboutComponent() {
|
settingsAboutComponent() {
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-st-button {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.vc-st-button svg {
|
.vc-st-button svg {
|
||||||
transform: scale(1.1) translateY(1px);
|
transform: scale(1.1) translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ import { classes } from "@utils/misc";
|
||||||
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByPropsLazy, findExportedComponentLazy } from "@webpack";
|
import { findByPropsLazy, findExportedComponentLazy } from "@webpack";
|
||||||
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
|
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
|
||||||
import { Guild, User } from "discord-types/general";
|
import { Guild, User } from "discord-types/general";
|
||||||
|
|
||||||
|
const IconUtils = findByPropsLazy("getGuildBannerURL");
|
||||||
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
|
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
|
||||||
const FriendRow = findExportedComponentLazy("FriendRow");
|
const FriendRow = findExportedComponentLazy("FriendRow");
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ const fetched = {
|
||||||
|
|
||||||
function renderTimestamp(timestamp: number) {
|
function renderTimestamp(timestamp: number) {
|
||||||
return (
|
return (
|
||||||
<Timestamp timestamp={new Date(timestamp)} />
|
<Timestamp timestamp={moment(timestamp)} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +65,10 @@ function GuildProfileModal({ guild }: GuildProps) {
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
||||||
|
|
||||||
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL(guild, true)!.replace(/\?size=\d+$/, "?size=1024");
|
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({
|
||||||
|
id: guild.id,
|
||||||
|
banner: guild.banner
|
||||||
|
}, true).replace(/\?size=\d+$/, "?size=1024");
|
||||||
|
|
||||||
const iconUrl = guild.icon && IconUtils.getGuildIconURL({
|
const iconUrl = guild.icon && IconUtils.getGuildIconURL({
|
||||||
id: guild.id,
|
id: guild.id,
|
||||||
|
@ -85,7 +89,7 @@ function GuildProfileModal({ guild }: GuildProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={cl("header")}>
|
<div className={cl("header")}>
|
||||||
{iconUrl
|
{guild.icon
|
||||||
? <img
|
? <img
|
||||||
src={iconUrl}
|
src={iconUrl}
|
||||||
alt=""
|
alt=""
|
||||||
|
@ -146,7 +150,7 @@ function Owner(guildId: string, owner: User) {
|
||||||
avatar: guildAvatar,
|
avatar: guildAvatar,
|
||||||
guildId,
|
guildId,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
})
|
}, true)
|
||||||
: IconUtils.getUserAvatarURL(owner, true);
|
: IconUtils.getUserAvatarURL(owner, true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { VerifiedIcon } from "./VerifiedIcon";
|
||||||
|
|
||||||
const Section = findComponentByCodeLazy(".lastSection", "children:");
|
const Section = findComponentByCodeLazy(".lastSection", "children:");
|
||||||
const ThemeStore = findStoreLazy("ThemeStore");
|
const ThemeStore = findStoreLazy("ThemeStore");
|
||||||
const platformHooks: { useLegacyPlatformType(platform: string): string; } = findByPropsLazy("useLegacyPlatformType");
|
|
||||||
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
|
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
|
||||||
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
|
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
|
||||||
|
|
||||||
|
@ -112,7 +111,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
|
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
|
||||||
const platform = platforms.get(platformHooks.useLegacyPlatformType(connection.type));
|
const platform = platforms.get(connection.type);
|
||||||
const url = platform.getPlatformUserUrl?.(connection);
|
const url = platform.getPlatformUserUrl?.(connection);
|
||||||
|
|
||||||
const img = (
|
const img = (
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { formatDuration } from "@utils/text";
|
import { formatDuration } from "@utils/text";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
|
||||||
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
|
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
import type { Channel } from "discord-types/general";
|
import type { Channel } from "discord-types/general";
|
||||||
|
|
||||||
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
|
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
|
||||||
|
@ -120,7 +120,7 @@ const VideoQualityModesToNames = {
|
||||||
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
||||||
|
|
||||||
function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
const { defaultAllowedUsersAndRolesDropdownState } = settings.use(["defaultAllowedUsersAndRolesDropdownState"]);
|
const [viewAllowedUsersAndRoles, setViewAllowedUsersAndRoles] = useState(settings.store.defaultAllowedUsersAndRolesDropdownState);
|
||||||
const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]);
|
const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -216,12 +216,12 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
{lastMessageId &&
|
{lastMessageId &&
|
||||||
<Text variant="text-md/normal">
|
<Text variant="text-md/normal">
|
||||||
Last {channel.isForumChannel() ? "post" : "message"} created:
|
Last {channel.isForumChannel() ? "post" : "message"} created:
|
||||||
<Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
<Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
{lastPinTimestamp &&
|
{lastPinTimestamp &&
|
||||||
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text>
|
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text>
|
||||||
}
|
}
|
||||||
{(rateLimitPerUser ?? 0) > 0 &&
|
{(rateLimitPerUser ?? 0) > 0 &&
|
||||||
<Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text>
|
<Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text>
|
||||||
|
@ -301,19 +301,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Text variant="text-lg/bold">Allowed users and roles:</Text>
|
<Text variant="text-lg/bold">Allowed users and roles:</Text>
|
||||||
<Tooltip text={defaultAllowedUsersAndRolesDropdownState ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
|
<Tooltip text={viewAllowedUsersAndRoles ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<button
|
<button
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn"
|
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn"
|
||||||
onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState}
|
onClick={() => setViewAllowedUsersAndRoles(v => !v)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
transform={defaultAllowedUsersAndRolesDropdownState ? "scale(1 -1)" : "scale(1 1)"}
|
transform={viewAllowedUsersAndRoles ? "scale(1 -1)" : "scale(1 1)"}
|
||||||
>
|
>
|
||||||
<path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
<path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -321,7 +321,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{defaultAllowedUsersAndRolesDropdownState && <ChannelBeginHeader channel={channel} />}
|
{viewAllowedUsersAndRoles && <ChannelBeginHeader channel={channel} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,7 +29,7 @@ import type { Channel, Role } from "discord-types/general";
|
||||||
|
|
||||||
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
||||||
|
|
||||||
const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon");
|
const ChannelListClasses = findByPropsLazy("channelEmoji", "unread", "icon");
|
||||||
|
|
||||||
const enum ShowMode {
|
const enum ShowMode {
|
||||||
LockIcon,
|
LockIcon,
|
||||||
|
@ -162,7 +162,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Add the hidden eye icon if the channel is hidden
|
// Add the hidden eye icon if the channel is hidden
|
||||||
{
|
{
|
||||||
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
|
match: /\i\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||||
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
|
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
|
||||||
},
|
},
|
||||||
// Make voice channels also appear as muted if they are muted
|
// Make voice channels also appear as muted if they are muted
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { React, useEffect, useState } from "@webpack/common";
|
import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
let lastState = false;
|
let lastState = false;
|
||||||
|
|
||||||
|
@ -41,15 +41,19 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
function SilentMessageToggle(chatBoxProps: {
|
||||||
const [enabled, setEnabled] = useState(lastState);
|
type: {
|
||||||
|
analyticsName: string;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const [enabled, setEnabled] = React.useState(lastState);
|
||||||
|
|
||||||
function setEnabledValue(value: boolean) {
|
function setEnabledValue(value: boolean) {
|
||||||
if (settings.store.persistState) lastState = value;
|
if (settings.store.persistState) lastState = value;
|
||||||
setEnabled(value);
|
setEnabled(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
React.useEffect(() => {
|
||||||
const listener: SendListener = (_, message) => {
|
const listener: SendListener = (_, message) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (settings.store.autoDisable) setEnabledValue(false);
|
if (settings.store.autoDisable) setEnabledValue(false);
|
||||||
|
@ -61,19 +65,22 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||||
return () => void removePreSendListener(listener);
|
return () => void removePreSendListener(listener);
|
||||||
}, [enabled]);
|
}, [enabled]);
|
||||||
|
|
||||||
if (!isMainChat) return null;
|
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBarButton
|
<Tooltip text={enabled ? "Disable Silent Message" : "Enable Silent Message"}>
|
||||||
tooltip={enabled ? "Disable Silent Message" : "Enable Silent Message"}
|
{tooltipProps => (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Button
|
||||||
|
{...tooltipProps}
|
||||||
onClick={() => setEnabledValue(!enabled)}
|
onClick={() => setEnabledValue(!enabled)}
|
||||||
|
size=""
|
||||||
|
look={ButtonLooks.BLANK}
|
||||||
|
innerClassName={ButtonWrapperClasses.button}
|
||||||
|
style={{ padding: "0 6px" }}
|
||||||
>
|
>
|
||||||
<svg
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
width="24"
|
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
style={{ scale: "1.2" }}
|
|
||||||
>
|
|
||||||
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||||
{!enabled && <>
|
{!enabled && <>
|
||||||
<mask id="_">
|
<mask id="_">
|
||||||
|
@ -83,17 +90,30 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||||
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
||||||
</>}
|
</>}
|
||||||
</svg>
|
</svg>
|
||||||
</ChatBarButton>
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SilentMessageToggle",
|
name: "SilentMessageToggle",
|
||||||
authors: [Devs.Nuckyz, Devs.CatNoir],
|
authors: [Devs.Nuckyz, Devs.CatNoir],
|
||||||
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
||||||
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
dependencies: ["MessageEventsAPI"],
|
||||||
settings,
|
|
||||||
|
|
||||||
start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle),
|
settings,
|
||||||
stop: () => removeChatBarButton("SilentMessageToggle")
|
patches: [
|
||||||
|
{
|
||||||
|
find: "ChannelTextAreaButtons",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
chatBarIcon: ErrorBoundary.wrap(SilentMessageToggle, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { FluxDispatcher, React } from "@webpack/common";
|
import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, React, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showIcon: {
|
showIcon: {
|
||||||
|
@ -37,32 +37,45 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
function SilentTypingToggle(chatBoxProps: {
|
||||||
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
type: {
|
||||||
|
analyticsName: string;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { isEnabled } = settings.use(["isEnabled"]);
|
||||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||||
|
|
||||||
if (!isMainChat || !showIcon) return null;
|
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBarButton
|
<Tooltip text={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}>
|
||||||
tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}
|
{(tooltipProps: any) => (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Button
|
||||||
|
{...tooltipProps}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
|
size=""
|
||||||
|
look={ButtonLooks.BLANK}
|
||||||
|
innerClassName={ButtonWrapperClasses.button}
|
||||||
|
style={{ padding: "0 6px" }}
|
||||||
>
|
>
|
||||||
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||||
<path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
|
<path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
|
||||||
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
|
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
|
||||||
</svg>
|
</svg>
|
||||||
</ChatBarButton>
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SilentTyping",
|
name: "SilentTyping",
|
||||||
authors: [Devs.Ven, Devs.Rini],
|
authors: [Devs.Ven, Devs.Rini],
|
||||||
description: "Hide that you are typing",
|
description: "Hide that you are typing",
|
||||||
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
|
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||||
|
@ -71,8 +84,17 @@ export default definePlugin({
|
||||||
replace: "startTyping:$self.startTyping,stop"
|
replace: "startTyping:$self.startTyping,stop"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: "ChannelTextAreaButtons",
|
||||||
|
predicate: () => settings.store.showIcon,
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
dependencies: ["CommandsAPI"],
|
||||||
|
settings,
|
||||||
commands: [{
|
commands: [{
|
||||||
name: "silenttype",
|
name: "silenttype",
|
||||||
description: "Toggle whether you're hiding that you're typing or not.",
|
description: "Toggle whether you're hiding that you're typing or not.",
|
||||||
|
@ -98,6 +120,5 @@ export default definePlugin({
|
||||||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||||
},
|
},
|
||||||
|
|
||||||
start: () => addChatBarButton("SilentTyping", SilentTypingToggle),
|
chatBarIcon: ErrorBoundary.wrap(SilentTypingToggle, { noop: true }),
|
||||||
stop: () => removeChatBarButton("SilentTyping"),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -213,7 +213,7 @@ function applyRules(content: string): string {
|
||||||
|
|
||||||
if (stringRules) {
|
if (stringRules) {
|
||||||
for (const rule of stringRules) {
|
for (const rule of stringRules) {
|
||||||
if (!rule.find) continue;
|
if (!rule.find || !rule.replace) continue;
|
||||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||||
|
|
||||||
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
||||||
|
@ -222,7 +222,7 @@ function applyRules(content: string): string {
|
||||||
|
|
||||||
if (regexRules) {
|
if (regexRules) {
|
||||||
for (const rule of regexRules) {
|
for (const rule of regexRules) {
|
||||||
if (!rule.find) continue;
|
if (!rule.find || !rule.replace) continue;
|
||||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,11 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import { Alerts, Forms } from "@webpack/common";
|
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import { TranslateModal } from "./TranslateModal";
|
import { TranslateModal } from "./TranslateModal";
|
||||||
|
@ -39,36 +37,26 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
export function TranslateChatBarIcon({ slateProps }: { slateProps: { type: { analyticsName: string; }; }; }) {
|
||||||
const { autoTranslate } = settings.use(["autoTranslate"]);
|
const { autoTranslate } = settings.use(["autoTranslate"]);
|
||||||
|
|
||||||
if (!isMainChat) return null;
|
if (slateProps.type.analyticsName !== "normal")
|
||||||
|
return null;
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => settings.store.autoTranslate = !autoTranslate;
|
||||||
const newState = !autoTranslate;
|
|
||||||
settings.store.autoTranslate = newState;
|
|
||||||
if (newState && settings.store.showAutoTranslateAlert !== false)
|
|
||||||
Alerts.show({
|
|
||||||
title: "Vencord Auto-Translate Enabled",
|
|
||||||
body: <>
|
|
||||||
<Forms.FormText>
|
|
||||||
You just enabled auto translate (by right clicking the Translate icon). Any message you send will automatically be translated before being sent.
|
|
||||||
</Forms.FormText>
|
|
||||||
<Forms.FormText className={Margins.top16}>
|
|
||||||
If this was an accident, disable it again, or it will change your message content before sending.
|
|
||||||
</Forms.FormText>
|
|
||||||
</>,
|
|
||||||
cancelText: "Disable Auto-Translate",
|
|
||||||
confirmText: "Got it",
|
|
||||||
secondaryConfirmText: "Don't show again",
|
|
||||||
onConfirmSecondary: () => settings.store.showAutoTranslateAlert = false,
|
|
||||||
onCancel: () => settings.store.autoTranslate = false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBarButton
|
<Tooltip text="Open Translate Modal">
|
||||||
tooltip="Open Translate Modal"
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Button
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
aria-label="Open Translate Modal"
|
||||||
|
size=""
|
||||||
|
look={ButtonLooks.BLANK}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
innerClassName={ButtonWrapperClasses.button}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
if (e.shiftKey) return toggle();
|
if (e.shiftKey) return toggle();
|
||||||
|
|
||||||
|
@ -77,11 +65,14 @@ export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
));
|
));
|
||||||
}}
|
}}
|
||||||
onContextMenu={() => toggle()}
|
onContextMenu={() => toggle()}
|
||||||
buttonProps={{
|
style={{ padding: "0 4px" }}
|
||||||
"aria-haspopup": "dialog"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate, "chat-button": true })} />
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
</ChatBarButton>
|
<TranslateIcon className={cl({ "auto-translate": autoTranslate })} />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore, Menu } from "@webpack/common";
|
import { ChannelStore, Menu } from "@webpack/common";
|
||||||
|
@ -55,16 +55,25 @@ export default definePlugin({
|
||||||
name: "Translate",
|
name: "Translate",
|
||||||
description: "Translate messages with Google Translate",
|
description: "Translate messages with Google Translate",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI"],
|
||||||
settings,
|
settings,
|
||||||
// not used, just here in case some other plugin wants it or w/e
|
// not used, just here in case some other plugin wants it or w/e
|
||||||
translate,
|
translate,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "ChannelTextAreaButtons",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||||
|
|
||||||
addContextMenuPatch("message", messageCtxPatch);
|
addContextMenuPatch("message", messageCtxPatch);
|
||||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
|
||||||
|
|
||||||
addButton("vc-translate", message => {
|
addButton("vc-translate", message => {
|
||||||
if (!message.content) return null;
|
if (!message.content) return null;
|
||||||
|
@ -92,8 +101,13 @@ export default definePlugin({
|
||||||
stop() {
|
stop() {
|
||||||
removePreSendListener(this.preSend);
|
removePreSendListener(this.preSend);
|
||||||
removeContextMenuPatch("message", messageCtxPatch);
|
removeContextMenuPatch("message", messageCtxPatch);
|
||||||
removeChatBarButton("vc-translate");
|
|
||||||
removeButton("vc-translate");
|
removeButton("vc-translate");
|
||||||
removeAccessory("vc-translation");
|
removeAccessory("vc-translation");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
chatBarIcon: (slateProps: any) => (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<TranslateChatBarIcon slateProps={slateProps} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,6 +49,4 @@ export const settings = definePluginSettings({
|
||||||
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
}).withPrivateSettings<{
|
});
|
||||||
showAutoTranslateAlert: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
|
@ -35,7 +35,3 @@
|
||||||
.vc-trans-auto-translate {
|
.vc-trans-auto-translate {
|
||||||
color: var(--green-360);
|
color: var(--green-360);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-trans-chat-button {
|
|
||||||
scale: 1.085;
|
|
||||||
}
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "UNREAD_IMPORTANT:",
|
find: "UNREAD_IMPORTANT:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
|
match: /channel:(\i).{0,100}?channelEmoji,.{0,250}?\.children.{0,50}?:null/,
|
||||||
replace: "$&,$self.TypingIndicator($1.id)"
|
replace: "$&,$self.TypingIndicator($1.id)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Urban Dictionary
|
|
||||||
|
|
||||||
Use /urban slash command to search for a definition for a word on [Urban Dictionary](https://www.urbandictionary.com/).
|
|
||||||
|
|
||||||
## Preview
|
|
||||||
|
|
||||||
![preview](https://i.imgur.com/1zwzj38.png)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
- Enable this plugin
|
|
||||||
- Set plugin settings as desired
|
|
||||||
- Type /urban and start getting definitions right into your Discord client.
|
|
|
@ -18,24 +18,14 @@
|
||||||
|
|
||||||
import { ApplicationCommandOptionType, sendBotMessage } from "@api/Commands";
|
import { ApplicationCommandOptionType, sendBotMessage } from "@api/Commands";
|
||||||
import { ApplicationCommandInputType } from "@api/Commands/types";
|
import { ApplicationCommandInputType } from "@api/Commands/types";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
resultsAmount: {
|
|
||||||
type: OptionType.NUMBER,
|
|
||||||
description: "The amount of results you want to get (more gives better results, but is slower)",
|
|
||||||
default: 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "UrbanDictionary",
|
name: "UrbanDictionary",
|
||||||
description: "Search for a word on Urban Dictionary via /urban slash command",
|
description: "Search for a word on Urban Dictionary via /urban slash command",
|
||||||
authors: [Devs.jewdev],
|
authors: [Devs.jewdev],
|
||||||
dependencies: ["CommandsAPI"],
|
dependencies: ["CommandsAPI"],
|
||||||
settings,
|
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
name: "urban",
|
name: "urban",
|
||||||
|
@ -51,16 +41,12 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
execute: async (args, ctx) => {
|
execute: async (args, ctx) => {
|
||||||
try {
|
try {
|
||||||
const query: string = encodeURIComponent(args[0].value);
|
const query = encodeURIComponent(args[0].value);
|
||||||
const { list } = await fetch(`https://api.urbandictionary.com/v0/define?term=${query}&per_page=${settings.store.resultsAmount}`).then(response => response.json());
|
const { list: [definition] } = await (await fetch(`https://api.urbandictionary.com/v0/define?term=${query}`)).json();
|
||||||
|
|
||||||
if (!list.length)
|
if (!definition)
|
||||||
return void sendBotMessage(ctx.channel.id, { content: "No results found." });
|
return void sendBotMessage(ctx.channel.id, { content: "No results found." });
|
||||||
|
|
||||||
const definition = list.reduce((prev, curr) => {
|
|
||||||
return prev.thumbs_up > curr.thumbs_up ? prev : curr;
|
|
||||||
});
|
|
||||||
|
|
||||||
const linkify = (text: string) => text
|
const linkify = (text: string) => text
|
||||||
.replaceAll("\r\n", "\n")
|
.replaceAll("\r\n", "\n")
|
||||||
.replace(/([*>_`~\\])/gsi, "\\$1")
|
.replace(/([*>_`~\\])/gsi, "\\$1")
|
||||||
|
|
|
@ -96,7 +96,7 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// above message box
|
// above message box
|
||||||
{
|
{
|
||||||
find: ".popularApplicationCommandIds,",
|
find: ".lastEditedByContainer",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/,
|
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/,
|
||||||
replace: "$self.patchPopout(arguments[0]),$&",
|
replace: "$self.patchPopout(arguments[0]),$&",
|
||||||
|
|
|
@ -33,7 +33,7 @@ function VencordPopout(onClose: () => void) {
|
||||||
const pluginEntries = [] as ReactNode[];
|
const pluginEntries = [] as ReactNode[];
|
||||||
|
|
||||||
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
||||||
if (plugin.toolboxActions && Vencord.Plugins.isPluginEnabled(plugin.name)) {
|
if (plugin.toolboxActions) {
|
||||||
pluginEntries.push(
|
pluginEntries.push(
|
||||||
<Menu.MenuGroup
|
<Menu.MenuGroup
|
||||||
label={plugin.name}
|
label={plugin.name}
|
||||||
|
|
|
@ -22,9 +22,11 @@ import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { openImageModal } from "@utils/discord";
|
import { openImageModal } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { GuildMemberStore, IconUtils, Menu } from "@webpack/common";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
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 BannerStore = findByPropsLazy("getGuildBannerURL");
|
||||||
|
|
||||||
interface UserContextProps {
|
interface UserContextProps {
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
|
@ -89,19 +91,19 @@ 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(IconUtils.getUserAvatarURL(user, true))}
|
action={() => openImage(BannerStore.getUserAvatarURL(user, true))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
{memberAvatar && (
|
{memberAvatar && (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="view-server-avatar"
|
id="view-server-avatar"
|
||||||
label="View Server Avatar"
|
label="View Server Avatar"
|
||||||
action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({
|
action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
avatar: memberAvatar,
|
avatar: memberAvatar,
|
||||||
guildId: guildId!,
|
guildId,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
}))}
|
}, true))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -122,11 +124,11 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
||||||
id="view-icon"
|
id="view-icon"
|
||||||
label="View Icon"
|
label="View Icon"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(IconUtils.getGuildIconURL({
|
openImage(BannerStore.getGuildIconURL({
|
||||||
id,
|
id,
|
||||||
icon,
|
icon,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
})!)
|
}))
|
||||||
}
|
}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
@ -136,7 +138,10 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
||||||
id="view-banner"
|
id="view-banner"
|
||||||
label="View Banner"
|
label="View Banner"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(IconUtils.getGuildBannerURL(guild, true)!)
|
openImage(BannerStore.getGuildBannerURL({
|
||||||
|
id,
|
||||||
|
banner,
|
||||||
|
}, true))
|
||||||
}
|
}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -27,7 +27,7 @@ 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, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, ChannelStore, Forms, i18n, Menu, Text } from "@webpack/common";
|
import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,26 +117,22 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function MakeContextCallback(name: "Guild" | "User" | "Channel") {
|
function MakeContextCallback(name: string) {
|
||||||
const callback: NavContextMenuPatchCallback = (children, props) => () => {
|
const callback: NavContextMenuPatchCallback = (children, props) => () => {
|
||||||
const value = props[name.toLowerCase()];
|
if ((name === "Guild" && !props.guild) || (name === "User" && !props.user)) return;
|
||||||
if (!value) return;
|
|
||||||
if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings
|
|
||||||
|
|
||||||
const lastChild = children.at(-1);
|
const lastChild = children.at(-1);
|
||||||
if (lastChild?.key === "developer-actions") {
|
if (lastChild?.key === "developer-actions") {
|
||||||
const p = lastChild.props;
|
const p = lastChild.props;
|
||||||
if (!Array.isArray(p.children))
|
if (!Array.isArray(p.children))
|
||||||
p.children = [p.children];
|
p.children = [p.children];
|
||||||
|
({ children } = p);
|
||||||
children = p.children;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
children.splice(-1, 0,
|
children.splice(-1, 0,
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id={`vc-view-${name.toLowerCase()}-raw`}
|
id={`vc-view-${name.toLowerCase()}-raw`}
|
||||||
label="View Raw"
|
label="View Raw"
|
||||||
action={() => openViewRawModal(JSON.stringify(value, null, 4), name)}
|
action={() => openViewRawModal(JSON.stringify(props[name.toLowerCase()], null, 4), name)}
|
||||||
icon={CopyIcon}
|
icon={CopyIcon}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -46,28 +46,6 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const MEDIA_PROXY_URL = "https://media.discordapp.net";
|
|
||||||
const CDN_URL = "cdn.discordapp.com";
|
|
||||||
|
|
||||||
function fixImageUrl(urlString: string) {
|
|
||||||
const url = new URL(urlString);
|
|
||||||
if (url.host === CDN_URL) return urlString;
|
|
||||||
|
|
||||||
url.searchParams.delete("width");
|
|
||||||
url.searchParams.delete("height");
|
|
||||||
|
|
||||||
if (url.origin === MEDIA_PROXY_URL) {
|
|
||||||
url.host = CDN_URL;
|
|
||||||
url.searchParams.delete("size");
|
|
||||||
url.searchParams.delete("quality");
|
|
||||||
url.searchParams.delete("format");
|
|
||||||
} else {
|
|
||||||
url.searchParams.set("quality", "lossless");
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "WebContextMenus",
|
name: "WebContextMenus",
|
||||||
description: "Re-adds context menus missing in the web version of Discord: Links & Images (Copy/Open Link/Image), Text Area (Copy, Cut, Paste, SpellCheck)",
|
description: "Re-adds context menus missing in the web version of Discord: Links & Images (Copy/Open Link/Image), Text Area (Copy, Cut, Paste, SpellCheck)",
|
||||||
|
@ -191,53 +169,32 @@ export default definePlugin({
|
||||||
match: /let\{text:\i=""/,
|
match: /let\{text:\i=""/,
|
||||||
replace: "return [null,null];$&"
|
replace: "return [null,null];$&"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Add back "Show My Camera" context menu
|
|
||||||
{
|
|
||||||
find: '.default("MediaEngineWebRTC");',
|
|
||||||
replacement: {
|
|
||||||
match: /supports\(\i\)\{switch\(\i\)\{case (\i).Features/,
|
|
||||||
replace: "$&.DISABLE_VIDEO:return true;case $1.Features"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
async copyImage(url: string) {
|
async copyImage(url: string) {
|
||||||
url = fixImageUrl(url);
|
// Clipboard only supports image/png, jpeg and similar won't work. Thus, we need to convert it to png
|
||||||
|
// via canvas first
|
||||||
let imageData = await fetch(url).then(r => r.blob());
|
const img = new Image();
|
||||||
if (imageData.type !== "image/png") {
|
img.onload = () => {
|
||||||
const bitmap = await createImageBitmap(imageData);
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = bitmap.width;
|
canvas.width = img.naturalWidth;
|
||||||
canvas.height = bitmap.height;
|
canvas.height = img.naturalHeight;
|
||||||
canvas.getContext("2d")!.drawImage(bitmap, 0, 0);
|
canvas.getContext("2d")!.drawImage(img, 0, 0);
|
||||||
|
|
||||||
await new Promise<void>(done => {
|
|
||||||
canvas.toBlob(data => {
|
canvas.toBlob(data => {
|
||||||
imageData = data!;
|
|
||||||
done();
|
|
||||||
}, "image/png");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_VESKTOP && VesktopNative.clipboard) {
|
|
||||||
VesktopNative.clipboard.copyImage(await imageData.arrayBuffer(), url);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
navigator.clipboard.write([
|
navigator.clipboard.write([
|
||||||
new ClipboardItem({
|
new ClipboardItem({
|
||||||
"image/png": imageData
|
"image/png": data!
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}, "image/png");
|
||||||
|
};
|
||||||
|
img.crossOrigin = "anonymous";
|
||||||
|
img.src = url;
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveImage(url: string) {
|
async saveImage(url: string) {
|
||||||
url = fixImageUrl(url);
|
|
||||||
|
|
||||||
const data = await fetchImage(url);
|
const data = await fetchImage(url);
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,6 @@ export interface Dev {
|
||||||
* If you are fine with attribution but don't want the badge, add badge: false
|
* If you are fine with attribution but don't want the badge, add badge: false
|
||||||
*/
|
*/
|
||||||
export const Devs = /* #__PURE__*/ Object.freeze({
|
export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
Nobody: {
|
|
||||||
name: "Nobody",
|
|
||||||
id: 0n,
|
|
||||||
},
|
|
||||||
Ven: {
|
Ven: {
|
||||||
name: "Vendicated",
|
name: "Vendicated",
|
||||||
id: 343383572805058560n
|
id: 343383572805058560n
|
||||||
|
@ -363,6 +359,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "bb010g",
|
name: "bb010g",
|
||||||
id: 72791153467990016n,
|
id: 72791153467990016n,
|
||||||
},
|
},
|
||||||
|
Lumap: {
|
||||||
|
name: "lumap",
|
||||||
|
id: 635383782576357407n
|
||||||
|
},
|
||||||
Dolfies: {
|
Dolfies: {
|
||||||
name: "Dolfies",
|
name: "Dolfies",
|
||||||
id: 852892297661906993n,
|
id: 852892297661906993n,
|
||||||
|
@ -399,18 +399,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "maisy",
|
name: "maisy",
|
||||||
id: 257109471589957632n,
|
id: 257109471589957632n,
|
||||||
},
|
},
|
||||||
Grzesiek11: {
|
|
||||||
name: "Grzesiek11",
|
|
||||||
id: 368475654662127616n,
|
|
||||||
},
|
|
||||||
Samwich: {
|
|
||||||
name: "Samwich",
|
|
||||||
id: 976176454511509554n,
|
|
||||||
},
|
|
||||||
coolelectronics: {
|
|
||||||
name: "coolelectronics",
|
|
||||||
id: 696392247205298207n,
|
|
||||||
}
|
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
3
src/webpack/common/types/components.d.ts
vendored
3
src/webpack/common/types/components.d.ts
vendored
|
@ -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 type { Moment } from "moment";
|
||||||
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
||||||
|
@ -153,7 +154,7 @@ export type Switch = ComponentType<PropsWithChildren<{
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
export type Timestamp = ComponentType<PropsWithChildren<{
|
export type Timestamp = ComponentType<PropsWithChildren<{
|
||||||
timestamp: Date;
|
timestamp: Moment;
|
||||||
isEdited?: boolean;
|
isEdited?: boolean;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
45
src/webpack/common/types/utils.d.ts
vendored
45
src/webpack/common/types/utils.d.ts
vendored
|
@ -16,7 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Guild, GuildMember } from "discord-types/general";
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
import type { FluxEvents } from "./fluxEvents";
|
import type { FluxEvents } from "./fluxEvents";
|
||||||
|
@ -183,47 +182,3 @@ export interface NavigationRouter {
|
||||||
getLastRouteChangeSource(): any;
|
getLastRouteChangeSource(): any;
|
||||||
getLastRouteChangeSourceLocationStack(): any;
|
getLastRouteChangeSourceLocationStack(): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IconUtils {
|
|
||||||
getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string;
|
|
||||||
getDefaultAvatarURL(id: string, discriminator?: string): string;
|
|
||||||
getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
|
|
||||||
getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined;
|
|
||||||
|
|
||||||
getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null;
|
|
||||||
getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string;
|
|
||||||
getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
|
|
||||||
|
|
||||||
getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined;
|
|
||||||
getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null;
|
|
||||||
|
|
||||||
getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined;
|
|
||||||
getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string;
|
|
||||||
|
|
||||||
hasAnimatedGuildIcon(guild: Guild): boolean;
|
|
||||||
isAnimatedIconHash(hash: string): boolean;
|
|
||||||
|
|
||||||
getGuildSplashURL: any;
|
|
||||||
getGuildDiscoverySplashURL: any;
|
|
||||||
getGuildHomeHeaderURL: any;
|
|
||||||
getResourceChannelIconURL: any;
|
|
||||||
getNewMemberActionIconURL: any;
|
|
||||||
getGuildTemplateIconURL: any;
|
|
||||||
getApplicationIconURL: any;
|
|
||||||
getGameAssetURL: any;
|
|
||||||
getVideoFilterAssetURL: any;
|
|
||||||
|
|
||||||
getGuildMemberAvatarSource: any;
|
|
||||||
getUserAvatarSource: any;
|
|
||||||
getGuildSplashSource: any;
|
|
||||||
getGuildDiscoverySplashSource: any;
|
|
||||||
makeSource: any;
|
|
||||||
getGameAssetSource: any;
|
|
||||||
getGuildIconSource: any;
|
|
||||||
getGuildTemplateIconSource: any;
|
|
||||||
getGuildBannerSource: any;
|
|
||||||
getGuildHomeHeaderSource: any;
|
|
||||||
getChannelIconSource: any;
|
|
||||||
getApplicationIconSource: any;
|
|
||||||
getAnimatableSourceWithFallback: any;
|
|
||||||
}
|
|
||||||
|
|
|
@ -137,5 +137,3 @@ export const { persist: zustandPersist }: typeof import("zustand/middleware") =
|
||||||
export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||||
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
||||||
export const InviteActions = findByPropsLazy("resolveInvite");
|
export const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
|
|
||||||
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
|
||||||
|
|
Loading…
Reference in a new issue