merge: dev
This commit is contained in:
commit
791eaa06d4
54 changed files with 499 additions and 1622 deletions
20
.github/ISSUE_TEMPLATE/blank.yml
vendored
20
.github/ISSUE_TEMPLATE/blank.yml
vendored
|
@ -2,9 +2,29 @@ name: Blank Issue
|
||||||
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
|
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
|
||||||
|
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# READ THIS BEFORE OPENING AN ISSUE
|
||||||
|
|
||||||
|
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||||
|
|
||||||
|
DO NOT USE THIS FORM, unless
|
||||||
|
- you are a vencord contributor
|
||||||
|
- you were given explicit permission to use this form by a moderator in our support server
|
||||||
|
- you are filing a security related report
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: content
|
id: content
|
||||||
attributes:
|
attributes:
|
||||||
label: Content
|
label: Content
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: agreement-check
|
||||||
|
attributes:
|
||||||
|
label: Request Agreement
|
||||||
|
options:
|
||||||
|
- label: I have read the requirements for opening an issue above
|
||||||
|
required: true
|
||||||
|
|
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -4,6 +4,18 @@ labels: [bug]
|
||||||
title: "[Bug] <title>"
|
title: "[Bug] <title>"
|
||||||
|
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# READ THIS BEFORE OPENING AN ISSUE
|
||||||
|
|
||||||
|
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||||
|
|
||||||
|
DO NOT USE THIS FORM, unless
|
||||||
|
- you are a vencord contributor
|
||||||
|
- you were given explicit permission to use this form by a moderator in our support server
|
||||||
|
- you are filing a security related report
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: discord
|
id: discord
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -64,3 +76,5 @@ body:
|
||||||
options:
|
options:
|
||||||
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
||||||
required: true
|
required: true
|
||||||
|
- label: I have read the requirements for opening an issue above
|
||||||
|
required: true
|
||||||
|
|
18
.github/workflows/publish.yml
vendored
18
.github/workflows/publish.yml
vendored
|
@ -36,26 +36,10 @@ jobs:
|
||||||
|
|
||||||
- name: Publish extension
|
- name: Publish extension
|
||||||
run: |
|
run: |
|
||||||
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
|
|
||||||
EXIT_CODE=0
|
|
||||||
|
|
||||||
# Chrome
|
|
||||||
cd dist/chromium-unpacked
|
cd dist/chromium-unpacked
|
||||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
|
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
||||||
|
|
||||||
# Firefox
|
|
||||||
cd ../firefox-unpacked
|
|
||||||
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
|
|
||||||
web-ext-submit || EXIT_CODE=$?
|
|
||||||
|
|
||||||
exit $EXIT_CODE
|
|
||||||
env:
|
env:
|
||||||
# Chrome
|
|
||||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||||
|
|
||||||
# Firefox
|
|
||||||
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
|
|
||||||
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
|
||||||
|
|
24
README.md
24
README.md
|
@ -22,29 +22,7 @@ The cutest Discord client mod
|
||||||
|
|
||||||
## Installing / Uninstalling
|
## Installing / Uninstalling
|
||||||
|
|
||||||
Click the below button to install Vencord to the Discord Desktop app
|
Visit https://vencord.dev/download
|
||||||
|
|
||||||
[![Download and run the Installer](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#vencord-installer)
|
|
||||||
|
|
||||||
## Installing on Browser
|
|
||||||
|
|
||||||
[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
|
|
||||||
|
|
||||||
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Alternative Downloads</summary>
|
|
||||||
|
|
||||||
## Vencord Desktop
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up
|
|
||||||
|
|
||||||
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app
|
|
||||||
|
|
||||||
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Join our Support/Community Server
|
## Join our Support/Community Server
|
||||||
|
|
||||||
|
|
32
browser/background.js
Normal file
32
browser/background.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T[]} arr
|
||||||
|
* @param {(v: T) => boolean} predicate
|
||||||
|
*/
|
||||||
|
function removeFirst(arr, predicate) {
|
||||||
|
const idx = arr.findIndex(predicate);
|
||||||
|
if (idx !== -1) arr.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.webRequest.onHeadersReceived.addListener(
|
||||||
|
({ responseHeaders, type, url }) => {
|
||||||
|
if (!responseHeaders) return;
|
||||||
|
|
||||||
|
if (type === "main_frame") {
|
||||||
|
// In main frame requests, the CSP needs to be removed to enable fetching of custom css
|
||||||
|
// as desired by the user
|
||||||
|
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy");
|
||||||
|
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) {
|
||||||
|
// Most users will load css from GitHub, but GitHub doesn't set the correct content type,
|
||||||
|
// so we fix it here
|
||||||
|
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type");
|
||||||
|
responseHeaders.push({
|
||||||
|
name: "Content-Type",
|
||||||
|
value: "text/css"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { responseHeaders };
|
||||||
|
},
|
||||||
|
{ urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] },
|
||||||
|
["blocking", "responseHeaders"]
|
||||||
|
);
|
|
@ -26,7 +26,11 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"web_accessible_resources": ["dist/*", "third-party/*"],
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
|
||||||
|
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.5.2",
|
"version": "1.5.6",
|
||||||
"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": {
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
"build": {
|
"build": {
|
||||||
"overwriteDest": true
|
"overwriteDest": true
|
||||||
},
|
},
|
||||||
"sourceDir": "./dist/extension-v2-unpacked"
|
"sourceDir": "./dist/firefox-unpacked"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18",
|
||||||
|
|
|
@ -145,11 +145,11 @@ async function loadDir(dir, basePath = "") {
|
||||||
/**
|
/**
|
||||||
* @type {(target: string, files: string[]) => Promise<void>}
|
* @type {(target: string, files: string[]) => Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function buildExtension(target, files, noMonaco = false) {
|
async function buildExtension(target, files) {
|
||||||
const entries = {
|
const entries = {
|
||||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||||
...(noMonaco ? {} : await loadDir("dist/monaco")),
|
...await loadDir("dist/monaco"),
|
||||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
||||||
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
||||||
))),
|
))),
|
||||||
|
@ -195,8 +195,11 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
appendCssRuntime,
|
appendCssRuntime,
|
||||||
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
||||||
buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true),
|
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip");
|
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
|
||||||
console.info("Packed Chromium Extension written to dist/extension.zip");
|
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||||
|
|
||||||
|
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||||
|
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { Channel, User } from "discord-types/general/index.js";
|
||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
activities: any[];
|
activities: any[];
|
||||||
canUseAvatarDecorations: boolean;
|
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
/**
|
/**
|
||||||
* Only for DM members
|
* Only for DM members
|
||||||
|
@ -52,9 +51,9 @@ export function removeDecorator(identifier: string) {
|
||||||
decorators.delete(identifier);
|
decorators.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return [...decorators.values()].map(decoratorObj => {
|
return Array.from(decorators.values(), decoratorObj => {
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
const { decorator, onlyIn } = decoratorObj;
|
||||||
// this can most likely be done cleaner
|
// this can most likely be done cleaner
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
||||||
|
|
|
@ -253,7 +253,8 @@ type ResolvePropDeep<T, P> = P extends "" ? T :
|
||||||
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void, exact?: boolean): void;
|
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void, exact?: boolean): void;
|
||||||
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void, exact?: boolean): void;
|
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void, exact?: boolean): void;
|
||||||
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void, exact = true) {
|
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void, exact = true) {
|
||||||
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
if (path)
|
||||||
|
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
||||||
(onUpdate as SubscriptionCallback)._exact = exact;
|
(onUpdate as SubscriptionCallback)._exact = exact;
|
||||||
subscriptions.add(onUpdate);
|
subscriptions.add(onUpdate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,11 @@ import "./themesStyles.css";
|
||||||
|
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { CogWheel, DeleteIcon } from "@components/Icons";
|
import { CogWheel, DeleteIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
||||||
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared";
|
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared";
|
||||||
import { IsFirefox } from "@utils/constants";
|
|
||||||
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 { openModal } from "@utils/modal";
|
||||||
|
@ -353,14 +351,12 @@ function ThemesTab() {
|
||||||
>
|
>
|
||||||
Load missing Themes
|
Load missing Themes
|
||||||
</Button>
|
</Button>
|
||||||
{!IsFirefox && (
|
<Button
|
||||||
<Button
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
size={Button.Sizes.SMALL}
|
||||||
size={Button.Sizes.SMALL}
|
>
|
||||||
>
|
Edit QuickCSS
|
||||||
Edit QuickCSS
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
@ -435,15 +431,6 @@ function ThemesTab() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsTab title="Themes">
|
<SettingsTab title="Themes">
|
||||||
{IsFirefox && (
|
|
||||||
<ErrorCard>
|
|
||||||
<Forms.FormTitle tag="h5">Warning</Forms.FormTitle>
|
|
||||||
<Forms.FormText>
|
|
||||||
You are using Firefox. Expect the vast majority of themes to not work.
|
|
||||||
If this is a problem, use a chromium browser or Discord Desktop / Vesktop.
|
|
||||||
</Forms.FormText>
|
|
||||||
</ErrorCard>
|
|
||||||
)}
|
|
||||||
<TabBar
|
<TabBar
|
||||||
type="top"
|
type="top"
|
||||||
look="brand"
|
look="brand"
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { IsFirefox } from "@utils/constants";
|
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
|
@ -110,14 +109,12 @@ function VencordSettings() {
|
||||||
Restart Client
|
Restart Client
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!IsFirefox && (
|
<Button
|
||||||
<Button
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
size={Button.Sizes.SMALL}
|
||||||
size={Button.Sizes.SMALL}
|
disabled={settingsDir === "Loading..."}>
|
||||||
disabled={settingsDir === "Loading..."}>
|
Open QuickCSS File
|
||||||
Open QuickCSS File
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!IS_WEB && (
|
{!IS_WEB && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => showItemInFolder(settingsDir)}
|
onClick={() => showItemInFolder(settingsDir)}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// obtain BUILT_IN_COMMANDS instance
|
// obtain BUILT_IN_COMMANDS instance
|
||||||
{
|
{
|
||||||
find: '"giphy","tenor"',
|
find: ',"tenor"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Matches BUILT_IN_COMMANDS. This is not exported so this is
|
// Matches BUILT_IN_COMMANDS. This is not exported so this is
|
||||||
|
@ -34,7 +34,7 @@ export default definePlugin({
|
||||||
// patch simpler
|
// patch simpler
|
||||||
|
|
||||||
// textCommands = builtInCommands.filter(...)
|
// textCommands = builtInCommands.filter(...)
|
||||||
match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/,
|
match: /(?<=\w=)(\w)(\.filter\(.{0,60}tenor)/,
|
||||||
replace: "Vencord.Api.Commands._init($1)$2",
|
replace: "Vencord.Api.Commands._init($1)$2",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -22,21 +22,28 @@ import definePlugin from "@utils/types";
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberListDecoratorsAPI",
|
name: "MemberListDecoratorsAPI",
|
||||||
description: "API to add decorators to member list (both in servers and DMs)",
|
description: "API to add decorators to member list (both in servers and DMs)",
|
||||||
authors: [Devs.TheSun],
|
authors: [Devs.TheSun, Devs.Ven],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "lostPermissionTooltipText,",
|
find: "lostPermissionTooltipText,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /Fragment,{children:\[(.{30,80})\]/,
|
match: /decorators:.{0,100}?children:\[(?<=(\i)\.lostPermissionTooltipText.+?)/,
|
||||||
replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)"
|
replace: "$&...Vencord.Api.MemberListDecorators.__getDecorators($1),"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "PrivateChannel.renderAvatar",
|
find: "PrivateChannel.renderAvatar",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/,
|
// props are shadowed by nested props so we have to do this
|
||||||
replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)"
|
{
|
||||||
}
|
match: /\i=(\i)\.applicationStream,/,
|
||||||
|
replace: "$&vencordProps=$1,"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
||||||
|
replace: "decorators:[...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)), $1?$2:null]"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,8 +39,9 @@ export default definePlugin({
|
||||||
addContextMenuPatch("user-settings-cog", children => () => {
|
addContextMenuPatch("user-settings-cog", children => () => {
|
||||||
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
||||||
section?.forEach(c => {
|
section?.forEach(c => {
|
||||||
if (c?.props?.id?.startsWith("Vencord")) {
|
const id = c?.props?.id;
|
||||||
c.props.action = () => SettingsRouter.open(c.props.id);
|
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
|
||||||
|
c.props.action = () => SettingsRouter.open(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore } from "@api/index";
|
||||||
import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -30,7 +30,6 @@ import plugins from "~plugins";
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
|
|
||||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
||||||
const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss";
|
|
||||||
|
|
||||||
const AllowedChannelIds = [
|
const AllowedChannelIds = [
|
||||||
SUPPORT_CHANNEL_ID,
|
SUPPORT_CHANNEL_ID,
|
||||||
|
@ -116,22 +115,6 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "
|
||||||
onConfirm: rememberDismiss
|
onConfirm: rememberDismiss
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsFirefox) {
|
|
||||||
const rememberDismiss = () => DataStore.set(FIREFOX_DISMISS_KEY, true);
|
|
||||||
|
|
||||||
Alerts.show({
|
|
||||||
title: "Hold on!",
|
|
||||||
body: <div>
|
|
||||||
<Forms.FormText>You are using Firefox.</Forms.FormText>
|
|
||||||
<Forms.FormText>Due to Firefox's stupid extension guidelines, most themes and many plugins will not function correctly.</Forms.FormText>
|
|
||||||
<Forms.FormText>Do not report bugs. Do not ask for help with broken plugins.</Forms.FormText>
|
|
||||||
<Forms.FormText>Instead, use a chromium browser, Discord Desktop, or Vesktop.</Forms.FormText>
|
|
||||||
</div>,
|
|
||||||
onCancel: rememberDismiss,
|
|
||||||
onConfirm: rememberDismiss
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,6 +58,26 @@ export default definePlugin({
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|
||||||
|
async handleEvent(e: MessageEvent<any>) {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
|
||||||
|
const { activity } = data;
|
||||||
|
const assets = activity?.assets;
|
||||||
|
|
||||||
|
if (assets?.large_image) assets.large_image = await lookupAsset(activity.application_id, assets.large_image);
|
||||||
|
if (assets?.small_image) assets.small_image = await lookupAsset(activity.application_id, assets.small_image);
|
||||||
|
|
||||||
|
if (activity) {
|
||||||
|
const appId = activity.application_id;
|
||||||
|
apps[appId] ||= await lookupApp(appId);
|
||||||
|
|
||||||
|
const app = apps[appId];
|
||||||
|
activity.name ||= app.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
|
||||||
|
},
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
|
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
|
||||||
if ("armcord" in window) return;
|
if ("armcord" in window) return;
|
||||||
|
@ -65,22 +85,7 @@ export default definePlugin({
|
||||||
if (ws) ws.close();
|
if (ws) ws.close();
|
||||||
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
||||||
|
|
||||||
ws.onmessage = async e => { // on message, set status to data
|
ws.onmessage = this.handleEvent;
|
||||||
const data = JSON.parse(e.data);
|
|
||||||
|
|
||||||
if (data.activity?.assets?.large_image) data.activity.assets.large_image = await lookupAsset(data.activity.application_id, data.activity.assets.large_image);
|
|
||||||
if (data.activity?.assets?.small_image) data.activity.assets.small_image = await lookupAsset(data.activity.application_id, data.activity.assets.small_image);
|
|
||||||
|
|
||||||
if (data.activity) {
|
|
||||||
const appId = data.activity.application_id;
|
|
||||||
apps[appId] ||= await lookupApp(appId);
|
|
||||||
|
|
||||||
const app = apps[appId];
|
|
||||||
data.activity.name ||= app.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
|
|
||||||
};
|
|
||||||
|
|
||||||
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s
|
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s
|
||||||
if (!connectionSuccessful) {
|
if (!connectionSuccessful) {
|
||||||
|
|
|
@ -48,6 +48,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".ADD_ROLE_A11Y_LABEL",
|
find: ".ADD_ROLE_A11Y_LABEL",
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"dot"===\i/,
|
match: /"dot"===\i/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
|
@ -56,6 +57,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".roleVerifiedIcon",
|
find: ".roleVerifiedIcon",
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"dot"===\i/,
|
match: /"dot"===\i/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
|
|
|
@ -33,12 +33,6 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: false,
|
default: false,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
},
|
|
||||||
forceStagingBanner: {
|
|
||||||
description: "Whether to force Staging banner under user area.",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,12 +77,13 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Fix search history being disabled / broken with isStaff
|
||||||
{
|
{
|
||||||
find: ".Messages.DEV_NOTICE_STAGING",
|
find: 'get("disable_new_search")',
|
||||||
predicate: () => settings.store.forceStagingBanner,
|
predicate: () => settings.store.enableIsStaff,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/,
|
match: /(?<=showNewSearch"\);return)\s?!/,
|
||||||
replace: "true"
|
replace: "!1&&!"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -222,8 +222,7 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.enableStreamQualityBypass,
|
predicate: () => settings.store.enableStreamQualityBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
"canUseHighVideoUploadQuality",
|
"canUseHighVideoUploadQuality",
|
||||||
// TODO: Remove the last two when they get removed from stable
|
"canStreamQuality",
|
||||||
"(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
|
|
||||||
].map(func => {
|
].map(func => {
|
||||||
return {
|
return {
|
||||||
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
|
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
|
||||||
|
|
|
@ -27,12 +27,12 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// This is the logic where it decides whether to render the owner crown or not
|
// This is the logic where it decides whether to render the owner crown or not
|
||||||
find: ".renderOwner=",
|
find: ".MULTIPLE_AVATAR",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /isOwner;return null!=(\w+)?&&/g,
|
match: /(\i)=(\i)\.isOwner,/,
|
||||||
replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&"
|
replace: "$1=$self.isGuildOwner($2),"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isGuildOwner(props) {
|
isGuildOwner(props) {
|
||||||
// Check if channel is a Group DM, if so return false
|
// Check if channel is a Group DM, if so return false
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a modification for Discord's desktop app
|
* Vencord, a Discord client mod
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
*
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
* 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 * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { getSettingStoreLazy } from "@api/SettingsStore";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { useForceUpdater } from "@utils/react";
|
import { useForceUpdater } from "@utils/react";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { Tooltip } from "webpack/common";
|
import { Tooltip } from "webpack/common";
|
||||||
|
|
||||||
const enum ActivitiesTypes {
|
const enum ActivitiesTypes {
|
||||||
|
@ -31,203 +21,154 @@ const enum ActivitiesTypes {
|
||||||
|
|
||||||
interface IgnoredActivity {
|
interface IgnoredActivity {
|
||||||
id: string;
|
id: string;
|
||||||
|
name: string;
|
||||||
type: ActivitiesTypes;
|
type: ActivitiesTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn");
|
|
||||||
const TryItOutClasses = findByPropsLazy("tryItOutBadge", "tryItOutBadgeIcon");
|
|
||||||
const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight");
|
|
||||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||||
|
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
|
||||||
|
|
||||||
function ToggleIconOff() {
|
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
className={RegisteredGamesClasses.overlayToggleIconOff}
|
|
||||||
height="24"
|
|
||||||
width="24"
|
|
||||||
viewBox="0 2.2 32 26"
|
|
||||||
aria-hidden={true}
|
|
||||||
role="img"
|
|
||||||
>
|
|
||||||
<g
|
|
||||||
fill="none"
|
|
||||||
fillRule="evenodd"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
className={RegisteredGamesClasses.fill}
|
|
||||||
fill="currentColor"
|
|
||||||
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
className={RegisteredGamesClasses.fill}
|
|
||||||
x="3"
|
|
||||||
y="26"
|
|
||||||
width="26"
|
|
||||||
height="2"
|
|
||||||
transform="rotate(-45 2 20)"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ToggleIconOn({ forceWhite }: { forceWhite?: boolean; }) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
className={RegisteredGamesClasses.overlayToggleIconOn}
|
|
||||||
height="24"
|
|
||||||
width="24"
|
|
||||||
viewBox="0 2.2 32 26"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
className={forceWhite ? "" : RegisteredGamesClasses.fill}
|
|
||||||
fill={forceWhite ? "var(--white-500)" : ""}
|
|
||||||
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ToggleActivityComponent({ activity, forceWhite, forceLeftMargin }: { activity: IgnoredActivity; forceWhite?: boolean; forceLeftMargin?: boolean; }) {
|
|
||||||
const forceUpdate = useForceUpdater();
|
const forceUpdate = useForceUpdater();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text="Toggle activity">
|
<Tooltip text={tooltipText}>
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{tooltipProps => (
|
||||||
<div
|
<button
|
||||||
onMouseLeave={onMouseLeave}
|
{...tooltipProps}
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
className={RegisteredGamesClasses.overlayToggleIcon}
|
|
||||||
role="button"
|
|
||||||
aria-label="Toggle activity"
|
|
||||||
tabIndex={0}
|
|
||||||
style={forceLeftMargin ? { marginLeft: "2px" } : undefined}
|
|
||||||
onClick={e => handleActivityToggle(e, activity, forceUpdate)}
|
onClick={e => handleActivityToggle(e, activity, forceUpdate)}
|
||||||
|
style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
|
||||||
>
|
>
|
||||||
{
|
<svg
|
||||||
ignoredActivitiesCache.has(activity.id)
|
width="24"
|
||||||
? <ToggleIconOff />
|
height="24"
|
||||||
: <ToggleIconOn forceWhite={forceWhite} />
|
viewBox="0 -960 960 960"
|
||||||
}
|
>
|
||||||
</div>
|
<path fill={fill} d={path} />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) {
|
const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
|
||||||
return (
|
const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
|
||||||
<div
|
|
||||||
className={`${TryItOutClasses.tryItOutBadge} ${BaseShapeRoundClasses.baseShapeRound}`}
|
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
style={{ padding: "0px 2px", height: 28 }}
|
if (getIgnoredActivities().some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||||
>
|
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||||
<ToggleActivityComponent activity={activity} forceWhite={true} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) {
|
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity, forceUpdateButton: () => void) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id);
|
|
||||||
else ignoredActivitiesCache.set(activity.id, activity);
|
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
||||||
forceUpdateComponent();
|
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
||||||
saveCacheToDatastore();
|
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
||||||
|
|
||||||
|
// Trigger activities recalculation
|
||||||
|
ShowCurrentGame?.updateSetting(old => old);
|
||||||
|
forceUpdateButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveCacheToDatastore() {
|
const settings = definePluginSettings({}).withPrivateSettings<{
|
||||||
await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache);
|
ignoredActivities: IgnoredActivity[];
|
||||||
}
|
}>();
|
||||||
|
|
||||||
let ignoredActivitiesCache = new Map<IgnoredActivity["id"], IgnoredActivity>();
|
function getIgnoredActivities() {
|
||||||
|
return settings.store.ignoredActivities ??= [];
|
||||||
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "IgnoreActivities",
|
name: "IgnoreActivities",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.",
|
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.",
|
||||||
|
|
||||||
|
dependencies: ["SettingsStoreAPI"],
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
find: '.displayName="LocalActivityStore"',
|
||||||
replacement: {
|
|
||||||
match: /!(\i)(\)return null;var \i=(\i)\.overlay.+?children:)(\[.{0,70}overlayStatusText.+?\])(?=}\)}\(\))/,
|
|
||||||
replace: (_, platformCheck, restWithoutPlatformCheck, props, children) => "false"
|
|
||||||
+ `${restWithoutPlatformCheck}`
|
|
||||||
+ `(${platformCheck}?${children}:[])`
|
|
||||||
+ `.concat(Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(${props}))`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: ".overlayBadge",
|
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i)\.name.+?null/,
|
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
|
||||||
replace: (m, props) => `[${m},$self.renderToggleActivityButton(${props})]`
|
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i\.application)\.name.+?null/,
|
|
||||||
replace: (m, props) => `${m},$self.renderToggleActivityButton(${props})`
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: '.displayName="LocalActivityStore"',
|
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
|
match: /\(\)\.removeGame.+?null(?<=(\i)\?\i=\i\.\i\.Messages\.SETTINGS_GAMES_NOW_PLAYING_STATE.+?=(\i)\.overlay.+?)/,
|
||||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
replace: (m, nowPlaying, props) => `${m},$self.renderToggleGameActivityButton(${props},${nowPlaying})`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.EMBEDDED_ACTIVITIES_HAVE_PLAYED_ONE_KNOWN",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
||||||
|
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<=\(\)\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
|
||||||
|
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
const ignoredActivitiesData = await DataStore.get<string[] | Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities") ?? new Map<IgnoredActivity["id"], IgnoredActivity>();
|
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
|
||||||
/** Migrate old data */
|
|
||||||
if (Array.isArray(ignoredActivitiesData)) {
|
|
||||||
for (const id of ignoredActivitiesData) {
|
|
||||||
ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game });
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveCacheToDatastore();
|
if (oldIgnoredActivitiesData != null) {
|
||||||
} else ignoredActivitiesCache = ignoredActivitiesData;
|
settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
|
||||||
|
.map(activity => ({ ...activity, name: "Unknown Name" }));
|
||||||
|
|
||||||
if (ignoredActivitiesCache.size !== 0) {
|
DataStore.del("IgnoreActivities_ignoredActivities");
|
||||||
const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen();
|
}
|
||||||
|
|
||||||
for (const ignoredActivity of ignoredActivitiesCache.values()) {
|
if (getIgnoredActivities().length !== 0) {
|
||||||
|
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||||
|
|
||||||
|
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
||||||
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||||
|
|
||||||
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||||
/** Custom added game which no longer exists */
|
getIgnoredActivities().splice(index, 1);
|
||||||
ignoredActivitiesCache.delete(ignoredActivity.id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveCacheToDatastore();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderToggleGameActivityButton(props: { id?: string; exePath: string; }) {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary noop>
|
|
||||||
<ToggleActivityComponent activity={{ id: props.id ?? props.exePath, type: ActivitiesTypes.Game }} forceLeftMargin={true} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderToggleActivityButton(props: { id: string; }) {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary noop>
|
|
||||||
<ToggleActivityComponentWithBackground activity={{ id: props.id, type: ActivitiesTypes.Embedded }} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
|
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
|
||||||
if (props.type === 0) {
|
if (props.type === 0 || props.type === 3) {
|
||||||
if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id);
|
if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id);
|
||||||
else {
|
else {
|
||||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
if (exePath) return !ignoredActivitiesCache.has(exePath);
|
if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<div style={{ marginLeft: 12, zIndex: 0 }}>
|
||||||
|
{ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)}
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderToggleActivityButton(props: { id: string; name: string; }) {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
{ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })}
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -165,7 +165,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"renderLinkComponent","maxWidth"',
|
find: '"renderLinkComponent","maxWidth"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(return\(.{1,100}\(\)\.wrapper.{1,100})(src)/,
|
match: /(return\(.{1,100}\(\)\.wrapper.{1,200})(src)/,
|
||||||
replace: `$1id: '${ELEMENT_ID}',$2`
|
replace: `$1id: '${ELEMENT_ID}',$2`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -174,8 +174,8 @@ export default definePlugin({
|
||||||
find: "handleImageLoad=",
|
find: "handleImageLoad=",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(render=function\(\){.{1,500}limitResponsiveWidth.{1,600})onMouseEnter:/,
|
match: /showThumbhashPlaceholder:/,
|
||||||
replace: "$1...$self.makeProps(this),onMouseEnter:"
|
replace: "...$self.makeProps(this),$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -189,7 +189,6 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
find: ".carouselModal,",
|
find: ".carouselModal,",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
|
@ -53,8 +53,6 @@ interface TagSettings {
|
||||||
[k: string]: TagSetting;
|
[k: string]: TagSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CLYDE_ID = "1081004946872352958";
|
|
||||||
|
|
||||||
// PermissionStore.computePermissions is not the same function and doesn't work here
|
// PermissionStore.computePermissions is not the same function and doesn't work here
|
||||||
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
||||||
computePermissions({ ...args }): bigint;
|
computePermissions({ ...args }): bigint;
|
||||||
|
@ -215,7 +213,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// add HTML data attributes (for easier theming)
|
// add HTML data attributes (for easier theming)
|
||||||
{
|
{
|
||||||
match: /children:\[(?=\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
|
match: /children:\[(?=\i\?null:\i,\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
|
||||||
replace: "'data-tag':$1.toLowerCase(),children:["
|
replace: "'data-tag':$1.toLowerCase(),children:["
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -230,10 +228,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// in the member list
|
// in the member list
|
||||||
{
|
{
|
||||||
find: ".renderBot=function(){",
|
find: ".Messages.GUILD_OWNER,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/,
|
match: /(?<type>\i)=\(null==.{0,50}\.BOT,null!=(?<user>\i)&&\i\.bot/,
|
||||||
replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type"
|
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }), typeof $<type> === 'number'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// pass channel id down props to be used in profiles
|
// pass channel id down props to be used in profiles
|
||||||
|
@ -253,7 +251,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// in profiles
|
// in profiles
|
||||||
{
|
{
|
||||||
find: ",botType:",
|
find: "showStreamerModeTooltip:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,botType:(\i\((\i)\)),/g,
|
match: /,botType:(\i\((\i)\)),/g,
|
||||||
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
|
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
|
||||||
|
@ -341,15 +339,17 @@ export default definePlugin({
|
||||||
message, user, channelId, origType, location, channel
|
message, user, channelId, origType, location, channel
|
||||||
}: {
|
}: {
|
||||||
message?: Message,
|
message?: Message,
|
||||||
user: User,
|
user: User & { isClyde(): boolean; },
|
||||||
channel?: Channel & { isForumPost(): boolean; },
|
channel?: Channel & { isForumPost(): boolean; },
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
origType?: number;
|
origType?: number;
|
||||||
location: "chat" | "not-chat";
|
location: "chat" | "not-chat";
|
||||||
}): number | null {
|
}): number | null {
|
||||||
|
if (!user)
|
||||||
|
return null;
|
||||||
if (location === "chat" && user.id === "1")
|
if (location === "chat" && user.id === "1")
|
||||||
return Tag.Types.OFFICIAL;
|
return Tag.Types.OFFICIAL;
|
||||||
if (user.id === CLYDE_ID)
|
if (user.isClyde())
|
||||||
return Tag.Types.AI;
|
return Tag.Types.AI;
|
||||||
|
|
||||||
let type = typeof origType === "number" ? origType : null;
|
let type = typeof origType === "number" ? origType : null;
|
||||||
|
|
41
src/plugins/noMosaic/index.ts
Normal file
41
src/plugins/noMosaic/index.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import style from "./styles.css?managed";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NoMosaic",
|
||||||
|
authors: [Devs.AutumnVN],
|
||||||
|
description: "Removes Discord new image mosaic",
|
||||||
|
tags: ["image", "mosaic", "media"],
|
||||||
|
patches: [{
|
||||||
|
find: "Media Mosaic",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
||||||
|
replace: 'mediaLayoutType:"RESPONSIVE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /\i===\i\.\i\.MOSAIC/,
|
||||||
|
replace: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
||||||
|
replace: '"INVALID"',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
start() {
|
||||||
|
enableStyle(style);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
disableStyle(style);
|
||||||
|
}
|
||||||
|
});
|
3
src/plugins/noMosaic/styles.css
Normal file
3
src/plugins/noMosaic/styles.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[class^="nonMediaAttachmentsContainer-"] [class*="messageAttachment-"] {
|
||||||
|
position: relative;
|
||||||
|
}
|
7
src/plugins/onePingPerDM/README.md
Normal file
7
src/plugins/onePingPerDM/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# OnePingPerDM
|
||||||
|
If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
- Prevents ping audio spam in DMs
|
||||||
|
- Be able to distinguish more than one ping as multiple users
|
||||||
|
- Be less annoyed while gaming
|
39
src/plugins/onePingPerDM/index.ts
Normal file
39
src/plugins/onePingPerDM/index.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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";
|
||||||
|
import { ChannelStore, ReadStateStore } from "@webpack/common";
|
||||||
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
const enum ChannelType {
|
||||||
|
DM = 1,
|
||||||
|
GROUP_DM = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "OnePingPerDM",
|
||||||
|
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
|
||||||
|
authors: [Devs.ProffDea],
|
||||||
|
patches: [{
|
||||||
|
find: ".getDesktopType()===",
|
||||||
|
replacement: [{
|
||||||
|
match: /if\((\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\){/,
|
||||||
|
replace: "if($1){if(!$self.isPrivateChannelRead(arguments[0]?.message))return;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /sound:(\i\?\i:void 0,volume:\i,onClick:)/,
|
||||||
|
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
isPrivateChannelRead(message: Message) {
|
||||||
|
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||||
|
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
|
||||||
|
},
|
||||||
|
});
|
|
@ -19,10 +19,7 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
import { GenericStore } from "@webpack/common";
|
|
||||||
|
|
||||||
const PoggerModeSettingsStore: GenericStore = findStoreLazy("PoggermodeSettingsStore");
|
|
||||||
|
|
||||||
const enum Intensity {
|
const enum Intensity {
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -61,9 +58,12 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
|
|
||||||
function setPoggerState(state: boolean) {
|
function setPoggerState(state: boolean) {
|
||||||
Object.assign(PoggerModeSettingsStore.__getLocalVars().state, {
|
FluxDispatcher.dispatch({
|
||||||
enabled: state,
|
type: "POGGERMODE_SETTINGS_UPDATE",
|
||||||
settingsVisible: state
|
settings: {
|
||||||
|
enabled: state,
|
||||||
|
settingsVisible: state
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,5 +101,8 @@ function setSettings(intensity: Intensity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(PoggerModeSettingsStore.__getLocalVars().state, state);
|
FluxDispatcher.dispatch({
|
||||||
|
type: "POGGERMODE_SETTINGS_UPDATE",
|
||||||
|
settings: state
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
9
src/plugins/permissionFreeWill/README.md
Normal file
9
src/plugins/permissionFreeWill/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# PermissionFreeWill
|
||||||
|
|
||||||
|
Removes the client-side restrictions that prevent editing channel permissions, such as permission lockouts ("Pretty sure
|
||||||
|
you don't want to do this") and onboarding requirements ("Making this change will make your server incompatible [...]")
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
This plugin will let you create permissions in servers that **WILL** lock you out of channels until an administrator
|
||||||
|
can resolve it for you. Please be careful with the overwrites you are making and check carefully.
|
56
src/plugins/permissionFreeWill/index.ts
Normal file
56
src/plugins/permissionFreeWill/index.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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 { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
lockout: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
description: 'Bypass the permission lockout prevention ("Pretty sure you don\'t want to do this")',
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
onboarding: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
description: 'Bypass the onboarding requirements ("Making this change will make your server incompatible [...]")',
|
||||||
|
restartNeeded: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "PermissionFreeWill",
|
||||||
|
description: "Disables the client-side restrictions for channel permission management.",
|
||||||
|
authors: [Devs.lewisakura],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
// Permission lockout, just set the check to true
|
||||||
|
{
|
||||||
|
find: "Messages.SELF_DENY_PERMISSION_BODY",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
|
||||||
|
replace: "$&true||"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
predicate: () => settings.store.lockout
|
||||||
|
},
|
||||||
|
// Onboarding, same thing but we need to prevent the check
|
||||||
|
{
|
||||||
|
find: "Messages.ONBOARDING_CHANNEL_THRESHOLD_WARNING",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /case 1:if\((?=!\i\.sent.{20,30}Messages\.CANNOT_CHANGE_CHANNEL_PERMS)/,
|
||||||
|
replace: "$&false&&"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
predicate: () => settings.store.onboarding
|
||||||
|
}
|
||||||
|
],
|
||||||
|
settings
|
||||||
|
});
|
|
@ -4,6 +4,8 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
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";
|
||||||
|
@ -41,6 +43,7 @@ export default definePlugin({
|
||||||
{tooltipProps => (
|
{tooltipProps => (
|
||||||
<div
|
<div
|
||||||
{...tooltipProps}
|
{...tooltipProps}
|
||||||
|
className="vc-pip-button"
|
||||||
role="button"
|
role="button"
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
@ -71,7 +74,7 @@ export default definePlugin({
|
||||||
>
|
>
|
||||||
<svg width="24px" height="24px" viewBox="0 0 24 24">
|
<svg width="24px" height="24px" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
fill="var(--interactive-normal)"
|
fill="currentColor"
|
||||||
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
|
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
8
src/plugins/pictureInPicture/styles.css
Normal file
8
src/plugins/pictureInPicture/styles.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.vc-pip-button {
|
||||||
|
color: var(--interactive-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-pip-button:hover {
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
color: var(--interactive-hover);
|
||||||
|
}
|
|
@ -68,8 +68,7 @@ export default definePlugin({
|
||||||
find: ".USER_PROFILE_ACTIVITY",
|
find: ".USER_PROFILE_ACTIVITY",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
/* FIXME: old name is getGlobalName, new name is getName. Remove optional Global once stable has also migrated */
|
match: /\.getName\(\i\);(?<=displayProfile.{0,200})/,
|
||||||
match: /\.get(?:Global)?Name\(\i\);(?<=displayProfile.{0,200})/,
|
|
||||||
replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;"
|
replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;"
|
||||||
},
|
},
|
||||||
PRONOUN_TOOLTIP_PATCH
|
PRONOUN_TOOLTIP_PATCH
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { 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}
|
|
||||||
>
|
|
||||||
<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}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,46 +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 { MaskedLinkStore, Tooltip } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Badge } from "../entities";
|
|
||||||
import { cl } from "../utils";
|
|
||||||
|
|
||||||
export default function ReviewBadge(badge: Badge) {
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
text={badge.name}>
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<img
|
|
||||||
className={cl("badge")}
|
|
||||||
width="22px"
|
|
||||||
height="22px"
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
src={badge.icon}
|
|
||||||
alt={badge.description}
|
|
||||||
onClick={() =>
|
|
||||||
MaskedLinkStore.openUntrustedLink({
|
|
||||||
href: badge.redirectURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,147 +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, moment, Parser, Timestamp, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Review, ReviewType } from "../entities";
|
|
||||||
import { deleteReview, reportReview } from "../reviewDbApi";
|
|
||||||
import { settings } from "../settings";
|
|
||||||
import { canDeleteReview, cl, showToast } from "../utils";
|
|
||||||
import { DeleteButton, ReportButton } from "./MessageButton";
|
|
||||||
import ReviewBadge from "./ReviewBadge";
|
|
||||||
|
|
||||||
export default LazyComponent(() => {
|
|
||||||
// this is terrible, blame ven
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
const dateFormat = new Intl.DateTimeFormat();
|
|
||||||
|
|
||||||
return function ReviewComponent({ review, refetch }: { review: Review; refetch(): void; }) {
|
|
||||||
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: () => {
|
|
||||||
deleteReview(review.id).then(res => {
|
|
||||||
if (res.success) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
showToast(res.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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: () => reportReview(review.id)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes(cozyMessage, wrapper, message, groupStart, cozy, cl("review"))} style={
|
|
||||||
{
|
|
||||||
marginLeft: "0px",
|
|
||||||
paddingLeft: "52px", // wth is this
|
|
||||||
paddingRight: "16px"
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
|
|
||||||
<img
|
|
||||||
className={classes(avatar, clickable)}
|
|
||||||
onClick={openModal}
|
|
||||||
src={review.sender.profilePhoto || "/assets/1f0bfc0865d324c2587920a7d80c609b.png?size=128"}
|
|
||||||
style={{ left: "0px" }}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
{review.sender.badges.map(badge => <ReviewBadge {...badge} />)}
|
|
||||||
|
|
||||||
{
|
|
||||||
!settings.store.hideTimestamps && review.type !== ReviewType.System && (
|
|
||||||
<Timestamp timestamp={moment(review.timestamp * 1000)} >
|
|
||||||
{dateFormat.format(review.timestamp * 1000)}
|
|
||||||
</Timestamp>)
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={cl("review-comment")}>
|
|
||||||
{Parser.parseGuildEventDescription(review.comment)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{review.id !== 0 && (
|
|
||||||
<div className={classes(container, isHeader, buttons)} style={{
|
|
||||||
padding: "0px",
|
|
||||||
}}>
|
|
||||||
<div className={classes(buttonClasses.wrapper, buttonsInner)} >
|
|
||||||
<ReportButton onClick={reportRev} />
|
|
||||||
|
|
||||||
{canDeleteReview(review, UserStore.getCurrentUser().id) && (
|
|
||||||
<DeleteButton onClick={delReview} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -1,104 +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 { Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
|
|
||||||
import { settings } from "../settings";
|
|
||||||
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 === settings.store.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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<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 { Review } from "../entities";
|
|
||||||
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
|
|
||||||
import { settings } from "../settings";
|
|
||||||
import { authorize, cl, showToast } from "../utils";
|
|
||||||
import ReviewComponent from "./ReviewComponent";
|
|
||||||
|
|
||||||
|
|
||||||
const Editor = findByPropsLazy("start", "end", "addMark");
|
|
||||||
const Transform = findByPropsLazy("unwrapNodes");
|
|
||||||
const InputTypes = findByPropsLazy("VOICE_CHANNEL_STATUS", "SIDEBAR");
|
|
||||||
|
|
||||||
const InputComponent = LazyComponent(() => find(m => m?.type?.render?.toString().includes("CHANNEL_TEXT_AREA).AnalyticsLocationProvider")));
|
|
||||||
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{showInput && (
|
|
||||||
<ReviewsInputComponent
|
|
||||||
name={name}
|
|
||||||
discordId={discordId}
|
|
||||||
refetch={refetch}
|
|
||||||
isAuthor={reviewData!.reviews?.some(r => r.sender.discordID === UserStore.getCurrentUser().id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReviewList({ refetch, reviews, hideOwnReview }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; }) {
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{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 } = settings.store;
|
|
||||||
const editorRef = useRef<any>(null);
|
|
||||||
const inputType = InputTypes.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?.success) {
|
|
||||||
refetch();
|
|
||||||
|
|
||||||
const slateEditor = editorRef.current.ref.current.getSlateEditor();
|
|
||||||
|
|
||||||
// clear editor
|
|
||||||
Transform.delete(slateEditor, {
|
|
||||||
at: {
|
|
||||||
anchor: Editor.start(slateEditor, []),
|
|
||||||
focus: Editor.end(slateEditor, []),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (response?.message) {
|
|
||||||
showToast(response.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// even tho we need to return this, it doesnt do anything
|
|
||||||
return {
|
|
||||||
shouldClear: false,
|
|
||||||
shouldRefocus: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,91 +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 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;
|
|
||||||
profilePhoto: string;
|
|
||||||
clientMod: string;
|
|
||||||
warningCount: number;
|
|
||||||
badges: any[];
|
|
||||||
banInfo: BanInfo | null;
|
|
||||||
notification: Notification | null;
|
|
||||||
lastReviewID: number;
|
|
||||||
type: UserType;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,140 +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 definePlugin from "@utils/types";
|
|
||||||
import { Alerts, Menu, Parser, useState } from "@webpack/common";
|
|
||||||
import { Guild, User } from "discord-types/general";
|
|
||||||
|
|
||||||
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: "disableBorderColor:!0",
|
|
||||||
replacement: {
|
|
||||||
match: /\(.{0,10}\{user:(.),setNote:.,canDM:.,.+?\}\)/,
|
|
||||||
replace: "$&,$self.getReviewsComponent($1)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
const s = settings.store;
|
|
||||||
const { token, lastReviewId, notifyReviews } = s;
|
|
||||||
|
|
||||||
if (!notifyReviews || !token) return;
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
|
||||||
const user = await getCurrentUserInfo(token);
|
|
||||||
if (lastReviewId && lastReviewId < user.lastReviewID) {
|
|
||||||
s.lastReviewId = user.lastReviewID;
|
|
||||||
if (user.lastReviewID !== 0)
|
|
||||||
showToast("You have new reviews on your profile!");
|
|
||||||
}
|
|
||||||
|
|
||||||
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
|
|
||||||
|
|
||||||
if (user.notification) {
|
|
||||||
const props = user.notification.type === NotificationType.Ban ? {
|
|
||||||
cancelText: "Appeal",
|
|
||||||
confirmText: "Ok",
|
|
||||||
onCancel: () =>
|
|
||||||
VencordNative.native.openExternal(
|
|
||||||
"https://reviewdb.mantikafasi.dev/api/redirect?"
|
|
||||||
+ new URLSearchParams({
|
|
||||||
token: settings.store.token!,
|
|
||||||
page: "dashboard/appeal"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} : {};
|
|
||||||
|
|
||||||
Alerts.show({
|
|
||||||
title: user.notification.title,
|
|
||||||
body: (
|
|
||||||
Parser.parse(
|
|
||||||
user.notification.content,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
),
|
|
||||||
...props
|
|
||||||
});
|
|
||||||
|
|
||||||
readNotification(user.notification.id);
|
|
||||||
}
|
|
||||||
s.user = user;
|
|
||||||
}, 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,151 +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 { Review, ReviewDBUser } from "./entities";
|
|
||||||
import { settings } from "./settings";
|
|
||||||
import { authorize, showToast } from "./utils";
|
|
||||||
|
|
||||||
const API_URL = "https://manti.vendicated.dev";
|
|
||||||
|
|
||||||
export const REVIEWS_PER_PAGE = 50;
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
success: boolean,
|
|
||||||
message: string;
|
|
||||||
reviews: Review[];
|
|
||||||
updated: boolean;
|
|
||||||
hasNextPage: boolean;
|
|
||||||
reviewCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WarningFlag = 0b00000010;
|
|
||||||
|
|
||||||
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}/api/reviewdb/users/${id}/reviews?${params}`);
|
|
||||||
|
|
||||||
const res = (req.status === 200)
|
|
||||||
? await req.json() as Response
|
|
||||||
: {
|
|
||||||
success: false,
|
|
||||||
message: "An Error occured while fetching reviews. Please try again later.",
|
|
||||||
reviews: [],
|
|
||||||
updated: false,
|
|
||||||
hasNextPage: false,
|
|
||||||
reviewCount: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!res.success) {
|
|
||||||
showToast(res.message);
|
|
||||||
return {
|
|
||||||
...res,
|
|
||||||
reviews: [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
comment: "An Error occured while fetching reviews. Please try again later.",
|
|
||||||
star: 0,
|
|
||||||
timestamp: 0,
|
|
||||||
sender: {
|
|
||||||
id: 0,
|
|
||||||
username: "Error",
|
|
||||||
profilePhoto: "https://cdn.discordapp.com/attachments/1045394533384462377/1084900598035513447/646808599204593683.png?size=128",
|
|
||||||
discordID: "0",
|
|
||||||
badges: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addReview(review: any): Promise<Response | null> {
|
|
||||||
review.token = settings.store.token;
|
|
||||||
|
|
||||||
if (!review.token) {
|
|
||||||
showToast("Please authorize to add a review.");
|
|
||||||
authorize();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(API_URL + `/api/reviewdb/users/${review.userid}/reviews`, {
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify(review),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(res => {
|
|
||||||
showToast(res.message);
|
|
||||||
return res ?? null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteReview(id: number): Promise<Response> {
|
|
||||||
return fetch(API_URL + `/api/reviewdb/users/${id}/reviews`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "application/json",
|
|
||||||
}),
|
|
||||||
body: JSON.stringify({
|
|
||||||
token: settings.store.token,
|
|
||||||
reviewid: id
|
|
||||||
})
|
|
||||||
}).then(r => r.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function reportReview(id: number) {
|
|
||||||
const res = await fetch(API_URL + "/api/reviewdb/reports", {
|
|
||||||
method: "PUT",
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "application/json",
|
|
||||||
}),
|
|
||||||
body: JSON.stringify({
|
|
||||||
reviewid: id,
|
|
||||||
token: settings.store.token
|
|
||||||
})
|
|
||||||
}).then(r => r.json()) as Response;
|
|
||||||
|
|
||||||
showToast(res.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentUserInfo(token: string): Promise<ReviewDBUser> {
|
|
||||||
return fetch(API_URL + "/api/reviewdb/users", {
|
|
||||||
body: JSON.stringify({ token }),
|
|
||||||
method: "POST",
|
|
||||||
}).then(r => r.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readNotification(id: number) {
|
|
||||||
return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Authorization": settings.store.token || "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,87 +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 { ReviewDBUser } from "./entities";
|
|
||||||
import { authorize } 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,
|
|
||||||
},
|
|
||||||
website: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "ReviewDB website",
|
|
||||||
component: () => (
|
|
||||||
<Button onClick={() => {
|
|
||||||
let url = "https://reviewdb.mantikafasi.dev/";
|
|
||||||
if (settings.store.token)
|
|
||||||
url += "/api/redirect?token=" + encodeURIComponent(settings.store.token);
|
|
||||||
|
|
||||||
VencordNative.native.openExternal(url);
|
|
||||||
}}>
|
|
||||||
ReviewDB website
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
supportServer: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "ReviewDB Support Server",
|
|
||||||
component: () => (
|
|
||||||
<Button onClick={() => {
|
|
||||||
VencordNative.native.openExternal("https://discord.gg/eWPBSbvznt");
|
|
||||||
}}>
|
|
||||||
ReviewDB Support Server
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}).withPrivateSettings<{
|
|
||||||
token?: string;
|
|
||||||
user?: ReviewDBUser;
|
|
||||||
lastReviewId?: number;
|
|
||||||
reviewsDropdownState?: boolean;
|
|
||||||
}>();
|
|
|
@ -1,76 +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 {
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 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;
|
|
||||||
}
|
|
|
@ -1,81 +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 { Logger } from "@utils/Logger";
|
|
||||||
import { openModal } from "@utils/modal";
|
|
||||||
import { findByProps } from "@webpack";
|
|
||||||
import { React, Toasts } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Review, UserType } from "./entities";
|
|
||||||
import { settings } from "./settings";
|
|
||||||
|
|
||||||
export const cl = classNameFactory("vc-rdb-");
|
|
||||||
|
|
||||||
export function authorize(callback?: any) {
|
|
||||||
const { OAuth2AuthorizeModal } = findByProps("OAuth2AuthorizeModal");
|
|
||||||
|
|
||||||
openModal((props: any) =>
|
|
||||||
<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" })
|
|
||||||
});
|
|
||||||
const { token, success } = await res.json();
|
|
||||||
if (success) {
|
|
||||||
settings.store.token = token;
|
|
||||||
showToast("Successfully logged in!");
|
|
||||||
callback?.();
|
|
||||||
} else if (res.status === 1) {
|
|
||||||
showToast("An Error occurred while logging in.");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
new Logger("ReviewDB").error("Failed to authorize", e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showToast(text: string) {
|
|
||||||
Toasts.show({
|
|
||||||
type: Toasts.Type.MESSAGE,
|
|
||||||
message: text,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canDeleteReview(review: Review, userId: string) {
|
|
||||||
return (
|
|
||||||
review.sender.discordID === userId
|
|
||||||
|| settings.store.user?.type === UserType.Admin
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -44,7 +44,7 @@ const settings = definePluginSettings({
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "RoleColorEverywhere",
|
name: "RoleColorEverywhere",
|
||||||
authors: [Devs.KingFish, Devs.lewisakura],
|
authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN],
|
||||||
description: "Adds the top role color anywhere possible",
|
description: "Adds the top role color anywhere possible",
|
||||||
patches: [
|
patches: [
|
||||||
// Chat Mentions
|
// Chat Mentions
|
||||||
|
@ -78,6 +78,10 @@ export default definePlugin({
|
||||||
match: /(memo\(\(function\((\i)\).{300,500}CHANNEL_MEMBERS_A11Y_LABEL.{100,200}roleIcon.{5,20}null,).," \u2014 ",.\]/,
|
match: /(memo\(\(function\((\i)\).{300,500}CHANNEL_MEMBERS_A11Y_LABEL.{100,200}roleIcon.{5,20}null,).," \u2014 ",.\]/,
|
||||||
replace: "$1$self.roleGroupColor($2)]"
|
replace: "$1$self.roleGroupColor($2)]"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
match: /children:\[.," \u2014 ",.\]/,
|
||||||
|
replace: "children:[$self.roleGroupColor(arguments[0])]"
|
||||||
|
},
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.memberList,
|
predicate: () => settings.store.memberList,
|
||||||
},
|
},
|
||||||
|
@ -105,7 +109,7 @@ export default definePlugin({
|
||||||
return colorString && parseInt(colorString.slice(1), 16);
|
return colorString && parseInt(colorString.slice(1), 16);
|
||||||
},
|
},
|
||||||
|
|
||||||
roleGroupColor({ id, count, title, guildId }: { id: string; count: number; title: string; guildId: string; }) {
|
roleGroupColor({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) {
|
||||||
const guild = GuildStore.getGuild(guildId);
|
const guild = GuildStore.getGuild(guildId);
|
||||||
const role = guild?.roles[id];
|
const role = guild?.roles[id];
|
||||||
|
|
||||||
|
@ -113,7 +117,7 @@ export default definePlugin({
|
||||||
color: role?.colorString,
|
color: role?.colorString,
|
||||||
fontWeight: "unset",
|
fontWeight: "unset",
|
||||||
letterSpacing: ".05em"
|
letterSpacing: ".05em"
|
||||||
}}>{title} — {count}</span>;
|
}}>{title ?? label} — {count}</span>;
|
||||||
},
|
},
|
||||||
|
|
||||||
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
|
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
|
||||||
|
|
|
@ -124,7 +124,7 @@ export default definePlugin({
|
||||||
find: ".activeCommandOption",
|
find: ".activeCommandOption",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}",
|
replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -139,7 +139,9 @@ export default definePlugin({
|
||||||
removePreSendListener(this.listener);
|
removePreSendListener(this.listener);
|
||||||
},
|
},
|
||||||
|
|
||||||
chatBarIcon() {
|
chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) {
|
||||||
|
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text="Insert Timestamp">
|
<Tooltip text="Insert Timestamp">
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
|
|
@ -170,7 +170,7 @@ function ServerInfoTab({ guild }: GuildProps) {
|
||||||
const Fields = {
|
const Fields = {
|
||||||
"Server Owner": owner ? Owner(guild.id, owner) : "Loading...",
|
"Server Owner": owner ? Owner(guild.id, owner) : "Loading...",
|
||||||
"Created At": renderTimestamp(SnowflakeUtils.extractTimestamp(guild.id)),
|
"Created At": renderTimestamp(SnowflakeUtils.extractTimestamp(guild.id)),
|
||||||
"Joined At": renderTimestamp(guild.joinedAt.getTime()),
|
"Joined At": guild.joinedAt ? renderTimestamp(guild.joinedAt.getTime()) : "-", // Not available in lurked guild
|
||||||
"Vanity Link": guild.vanityURLCode ? (<a>{`discord.gg/${guild.vanityURLCode}`}</a>) : "-", // Making the anchor href valid would cause Discord to reload
|
"Vanity Link": guild.vanityURLCode ? (<a>{`discord.gg/${guild.vanityURLCode}`}</a>) : "-", // Making the anchor href valid would cause Discord to reload
|
||||||
"Preferred Locale": guild.preferredLocale || "-",
|
"Preferred Locale": guild.preferredLocale || "-",
|
||||||
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
|
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild;
|
||||||
group?.push(
|
group?.push(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-server-profile"
|
id="vc-server-profile"
|
||||||
label="Server Profile"
|
label="Server Info"
|
||||||
action={() => openGuildProfileModal(guild)}
|
action={() => openGuildProfileModal(guild)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -70,10 +70,11 @@ function clean(str: string) {
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatText(str: string, user: string, channel: string) {
|
function formatText(str: string, user: string, channel: string, displayName: string) {
|
||||||
return str
|
return str
|
||||||
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
||||||
.replaceAll("{{CHANNEL}}", clean(channel) || "channel");
|
.replaceAll("{{CHANNEL}}", clean(channel) || "channel")
|
||||||
|
.replaceAll("{{DISPLAY_NAME}}", clean(displayName) || (displayName ? "Someone" : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -143,8 +144,9 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId,
|
||||||
|
|
||||||
function playSample(tempSettings: any, type: string) {
|
function playSample(tempSettings: any, type: string) {
|
||||||
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings);
|
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings);
|
||||||
|
const currentUser = UserStore.getCurrentUser();
|
||||||
|
|
||||||
speak(formatText(settings[type + "Message"], UserStore.getCurrentUser().username, "general"), settings);
|
speak(formatText(settings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username), settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -172,9 +174,10 @@ export default definePlugin({
|
||||||
|
|
||||||
const template = Settings.plugins.VcNarrator[type + "Message"];
|
const template = Settings.plugins.VcNarrator[type + "Message"];
|
||||||
const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username;
|
const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username;
|
||||||
|
const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user);
|
||||||
const channel = ChannelStore.getChannel(id).name;
|
const channel = ChannelStore.getChannel(id).name;
|
||||||
|
|
||||||
speak(formatText(template, user, channel));
|
speak(formatText(template, user, channel, displayName));
|
||||||
|
|
||||||
// updateStatuses(type, state, isMe);
|
// updateStatuses(type, state, isMe);
|
||||||
}
|
}
|
||||||
|
@ -186,7 +189,7 @@ export default definePlugin({
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
const event = s.mute || s.selfMute ? "unmute" : "mute";
|
const event = s.mute || s.selfMute ? "unmute" : "mute";
|
||||||
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name));
|
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, ""));
|
||||||
},
|
},
|
||||||
|
|
||||||
AUDIO_TOGGLE_SELF_DEAF() {
|
AUDIO_TOGGLE_SELF_DEAF() {
|
||||||
|
@ -195,7 +198,7 @@ export default definePlugin({
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
|
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
|
||||||
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name));
|
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, ""));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -312,8 +315,8 @@ export default definePlugin({
|
||||||
You can customise the spoken messages below. You can disable specific messages by setting them to nothing
|
You can customise the spoken messages below. You can disable specific messages by setting them to nothing
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
The special placeholders <code>{"{{USER}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "}
|
The special placeholders <code>{"{{USER}}"}</code>, <code>{"{{DISPLAY_NAME}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "}
|
||||||
will be replaced with the user's name (nothing if it's yourself) and the channel's name respectively
|
will be replaced with the user's name (nothing if it's yourself), the user's display name and the channel's name respectively
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
{hasEnglishVoices && (
|
{hasEnglishVoices && (
|
||||||
<>
|
<>
|
||||||
|
|
5
src/plugins/whoReacted/README.md
Normal file
5
src/plugins/whoReacted/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# WhoReacted
|
||||||
|
|
||||||
|
Next to each reaction, display each user's avatar. Each avatar can be clicked and will open the profile.
|
||||||
|
|
||||||
|
![](https://github.com/Vendicated/Vencord/assets/57493648/97fec9e8-396f-4f5e-916e-1ec21445113d)
|
|
@ -24,14 +24,14 @@ import { LazyComponent, useForceUpdater } from "@utils/react";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCode, findByPropsLazy } from "@webpack";
|
import { findByCode, findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common";
|
||||||
import { ReactionEmoji, User } from "discord-types/general";
|
import { CustomEmoji } from "@webpack/types";
|
||||||
|
import { Message, ReactionEmoji, User } from "discord-types/general";
|
||||||
|
|
||||||
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
||||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||||
|
|
||||||
const ReactionStore = findByPropsLazy("getReactions");
|
|
||||||
|
|
||||||
const queue = new Queue();
|
const queue = new Queue();
|
||||||
|
let reactions: Record<string, ReactionCacheEntry>;
|
||||||
|
|
||||||
function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) {
|
function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) {
|
||||||
const key = emoji.name + (emoji.id ? `:${emoji.id}` : "");
|
const key = emoji.name + (emoji.id ? `:${emoji.id}` : "");
|
||||||
|
@ -57,11 +57,9 @@ function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) {
|
||||||
|
|
||||||
function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {
|
function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {
|
||||||
const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`;
|
const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`;
|
||||||
const cache = ReactionStore.__getLocalVars().reactions[key] ??= { fetched: false, users: {} };
|
const cache = reactions[key] ??= { fetched: false, users: {} };
|
||||||
if (!cache.fetched) {
|
if (!cache.fetched) {
|
||||||
queue.unshift(() =>
|
queue.unshift(() => fetchReactions(msg, e, type));
|
||||||
fetchReactions(msg, e, type)
|
|
||||||
);
|
|
||||||
cache.fetched = true;
|
cache.fetched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +90,7 @@ function handleClickAvatar(event: React.MouseEvent<HTMLElement, MouseEvent>) {
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "WhoReacted",
|
name: "WhoReacted",
|
||||||
description: "Renders the Avatars of reactors",
|
description: "Renders the avatars of users who reacted to a message",
|
||||||
authors: [Devs.Ven, Devs.KannaDev],
|
authors: [Devs.Ven, Devs.KannaDev],
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
|
@ -101,6 +99,12 @@ export default definePlugin({
|
||||||
match: /(?<=(\i)=(\i)\.hideCount,)(.+?reactionCount.+?\}\))/,
|
match: /(?<=(\i)=(\i)\.hideCount,)(.+?reactionCount.+?\}\))/,
|
||||||
replace: (_, hideCount, props, rest) => `whoReactedProps=${props},${rest},${hideCount}?null:$self.renderUsers(whoReactedProps)`
|
replace: (_, hideCount, props, rest) => `whoReactedProps=${props},${rest},${hideCount}?null:$self.renderUsers(whoReactedProps)`
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
find: '.displayName="MessageReactionsStore";',
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=CONNECTION_OPEN:function\(\){)(\i)={}/,
|
||||||
|
replace: "$&;$self.reactions=$1"
|
||||||
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
renderUsers(props: RootObject) {
|
renderUsers(props: RootObject) {
|
||||||
|
@ -150,106 +154,25 @@ export default definePlugin({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
set reactions(value: any) {
|
||||||
|
reactions = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface ReactionCacheEntry {
|
||||||
export interface GuildMemberAvatar { }
|
fetched: boolean;
|
||||||
|
users: Record<string, User>;
|
||||||
export interface Author {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
discriminator: string;
|
|
||||||
avatar: string;
|
|
||||||
avatarDecoration?: any;
|
|
||||||
email: string;
|
|
||||||
verified: boolean;
|
|
||||||
bot: boolean;
|
|
||||||
system: boolean;
|
|
||||||
mfaEnabled: boolean;
|
|
||||||
mobile: boolean;
|
|
||||||
desktop: boolean;
|
|
||||||
premiumType: number;
|
|
||||||
flags: number;
|
|
||||||
publicFlags: number;
|
|
||||||
purchasedFlags: number;
|
|
||||||
premiumUsageFlags: number;
|
|
||||||
phone: string;
|
|
||||||
nsfwAllowed: boolean;
|
|
||||||
guildMemberAvatars: GuildMemberAvatar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Emoji {
|
interface RootObject {
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Reaction {
|
|
||||||
emoji: Emoji;
|
|
||||||
count: number;
|
|
||||||
burst_user_ids: any[];
|
|
||||||
burst_count: number;
|
|
||||||
burst_colors: any[];
|
|
||||||
burst_me: boolean;
|
|
||||||
me: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
id: string;
|
|
||||||
type: number;
|
|
||||||
channel_id: string;
|
|
||||||
author: Author;
|
|
||||||
content: string;
|
|
||||||
deleted: boolean;
|
|
||||||
editHistory: any[];
|
|
||||||
attachments: any[];
|
|
||||||
embeds: any[];
|
|
||||||
mentions: any[];
|
|
||||||
mentionRoles: any[];
|
|
||||||
mentionChannels: any[];
|
|
||||||
mentioned: boolean;
|
|
||||||
pinned: boolean;
|
|
||||||
mentionEveryone: boolean;
|
|
||||||
tts: boolean;
|
|
||||||
codedLinks: any[];
|
|
||||||
giftCodes: any[];
|
|
||||||
timestamp: string;
|
|
||||||
editedTimestamp?: any;
|
|
||||||
state: string;
|
|
||||||
nonce?: any;
|
|
||||||
blocked: boolean;
|
|
||||||
call?: any;
|
|
||||||
bot: boolean;
|
|
||||||
webhookId?: any;
|
|
||||||
reactions: Reaction[];
|
|
||||||
applicationId?: any;
|
|
||||||
application?: any;
|
|
||||||
activity?: any;
|
|
||||||
messageReference?: any;
|
|
||||||
flags: number;
|
|
||||||
isSearchHit: boolean;
|
|
||||||
stickers: any[];
|
|
||||||
stickerItems: any[];
|
|
||||||
components: any[];
|
|
||||||
loggingName?: any;
|
|
||||||
interaction?: any;
|
|
||||||
interactionData?: any;
|
|
||||||
interactionError?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Emoji {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
animated: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RootObject {
|
|
||||||
message: Message;
|
message: Message;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
isLurking: boolean;
|
isLurking: boolean;
|
||||||
isPendingMember: boolean;
|
isPendingMember: boolean;
|
||||||
useChatFontScaling: boolean;
|
useChatFontScaling: boolean;
|
||||||
emoji: Emoji;
|
emoji: CustomEmoji;
|
||||||
count: number;
|
count: number;
|
||||||
burst_user_ids: any[];
|
burst_user_ids: any[];
|
||||||
burst_count: number;
|
burst_count: number;
|
||||||
|
|
|
@ -374,7 +374,11 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
archeruwu: {
|
archeruwu: {
|
||||||
name: "archer_uwu",
|
name: "archer_uwu",
|
||||||
id: 160068695383736320n
|
id: 160068695383736320n
|
||||||
}
|
},
|
||||||
|
ProffDea: {
|
||||||
|
name: "ProffDea",
|
||||||
|
id: 609329952180928513n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
@ -385,5 +389,3 @@ export const DevsById = /* #__PURE__*/ (() =>
|
||||||
.map(([_, v]) => [v.id, v] as const)
|
.map(([_, v]) => [v.id, v] as const)
|
||||||
))
|
))
|
||||||
)() as Record<string, Dev>;
|
)() as Record<string, Dev>;
|
||||||
|
|
||||||
export const IsFirefox = IS_EXTENSION && navigator.userAgent.toLowerCase().includes("firefox");
|
|
||||||
|
|
|
@ -20,5 +20,5 @@ import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
import * as t from "./types/classes";
|
import * as t from "./types/classes";
|
||||||
|
|
||||||
export const ModalImageClasses: t.ImageModalClasses = findByPropsLazy("image", "modal");
|
export const ModalImageClasses: t.ImageModalClasses = findByPropsLazy("image", "modal", "responsiveWidthMobile");
|
||||||
export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent");
|
export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent");
|
||||||
|
|
Loading…
Reference in a new issue