Compare commits
155 commits
76b4e72917
...
a2251c9066
Author | SHA1 | Date | |
---|---|---|---|
a2251c9066 | |||
|
86e94343cc | ||
|
dd44ac1ad2 | ||
|
370b3d366d | ||
|
a67c7f841d | ||
|
2c9793202d | ||
|
a257926609 | ||
|
77659be4f0 | ||
|
37b9a62460 | ||
|
7f73e13364 | ||
|
fcf2bdda70 | ||
|
fa9da2d693 | ||
|
44b21394b3 | ||
|
27fffc8bc3 | ||
|
098da8c3fd | ||
|
9cf88d4232 | ||
|
dd61b0c999 | ||
|
5dc0d06be1 | ||
|
9de6c2d4ff | ||
|
e37f62ac0a | ||
|
56a9d79f85 | ||
|
9d78233afa | ||
|
9af2ec65ae | ||
|
584885acf5 | ||
|
18fdc33ee7 | ||
|
522fdcd15d | ||
|
af135b9245 | ||
|
4b958d17fd | ||
|
bfb48b4faf | ||
|
9dd8e72245 | ||
|
aae790f1c1 | ||
|
7f17e70697 | ||
|
b3311c6f12 | ||
|
bc09225258 | ||
|
9ce923d4d7 | ||
|
7845af0802 | ||
|
89672882b9 | ||
|
e05c630a54 | ||
|
38834ef7ac | ||
|
98d49af728 | ||
|
0afe319141 | ||
|
a9e67e2955 | ||
|
1676956f61 | ||
|
8692109bc5 | ||
|
0847f205b8 | ||
|
2f94e167c4 | ||
|
589c070773 | ||
|
8567ff6239 | ||
|
e4701769a5 | ||
|
25f101602d | ||
|
85bfa1e719 | ||
|
b48998d485 | ||
|
6d605050e1 | ||
|
07c4a097e0 | ||
|
c1de41436a | ||
|
64c6f5740f | ||
|
03523446c1 | ||
|
25dc25c707 | ||
|
8ac8048845 | ||
|
ffe6bb1c5d | ||
|
74faaa216f | ||
|
a6b8b59d5c | ||
|
9eaeb24196 | ||
|
6b28e5ad85 | ||
|
d852393cfb | ||
|
0c12500c0a | ||
|
1502024440 | ||
|
45fa4f89c6 | ||
|
96dff84ed8 | ||
|
c1b8739104 | ||
|
2183d2d29d | ||
|
722dcb033e | ||
|
53cd14844f | ||
|
38daf6ec2b | ||
|
635b80c58b | ||
|
ec9e111047 | ||
|
d54f8b5e8e | ||
|
09a922a01c | ||
|
23963f40c2 | ||
|
a41abfef31 | ||
|
ddee3e1264 | ||
|
6c4afa52a3 | ||
|
ce6081c39b | ||
|
123853e848 | ||
|
0c6445b66b | ||
|
7094345516 | ||
|
148a32096c | ||
|
ab1b002ed1 | ||
|
c7a20769f9 | ||
|
9c13befcb6 | ||
|
3fdd0edf14 | ||
|
2169090ce5 | ||
|
5e13de72c2 | ||
|
3a7c27253b | ||
|
465a87f66b | ||
|
13c59f47cb | ||
|
0d7157dd20 | ||
|
88c6e2a0e9 | ||
|
535d510d3b | ||
|
cbc23b1bdd | ||
|
aa2a57b733 | ||
|
c3ccbbfa99 | ||
|
685f44d40f | ||
|
8f0009778a | ||
|
f385dc380e | ||
|
bb900785ed | ||
|
61fac0a8f1 | ||
|
1a607639cc | ||
|
c90440f031 | ||
|
baf952512c | ||
|
fbc5038306 | ||
|
ddc39fe84d | ||
|
4f8c75372c | ||
|
024a77c577 | ||
|
1130521e4b | ||
|
3bd657611c | ||
|
b659d4e9c1 | ||
|
9c60b38acc | ||
|
c5dd50ad8f | ||
|
131e91a37c | ||
|
06b4dffa62 | ||
|
3917193e8f | ||
|
8d1561aed4 | ||
|
cf3c28e1ff | ||
|
af1aa39647 | ||
|
7de1b5dcb6 | ||
|
cd06980016 | ||
|
e69236c5f8 | ||
|
2478ffb695 | ||
|
788d22f9e9 | ||
|
b69ac1cdad | ||
|
892a79b2a7 | ||
|
9895540943 | ||
|
7e0fc1d0ea | ||
|
bb771153e3 | ||
|
65d39dcf21 | ||
|
cf5e93ee52 | ||
|
4d8e4e62ca | ||
|
cb2532d22c | ||
|
8c998b9330 | ||
|
fb22c57da1 | ||
|
554bb18e9b | ||
|
44c9675795 | ||
|
322ecc5e88 | ||
|
7ee9a8bb99 | ||
|
69b54535c3 | ||
|
2a56081bc2 | ||
|
baa7d8c078 | ||
|
940193c30b | ||
|
09b646b860 | ||
|
922e3ce6fe | ||
|
cb93c11e16 | ||
|
4beef9f73b | ||
|
4da79abb21 | ||
|
4e27722b54 |
151 changed files with 2325 additions and 1683 deletions
|
@ -51,7 +51,10 @@
|
||||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||||
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||||
"yoda": "error",
|
"yoda": "error",
|
||||||
"prefer-destructuring": ["error", { "object": true, "array": false }],
|
"prefer-destructuring": ["error", {
|
||||||
|
"VariableDeclarator": { "array": false, "object": true },
|
||||||
|
"AssignmentExpression": { "array": false, "object": false }
|
||||||
|
}],
|
||||||
"operator-assignment": ["error", "always"],
|
"operator-assignment": ["error", "always"],
|
||||||
"no-useless-computed-key": "error",
|
"no-useless-computed-key": "error",
|
||||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||||
|
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -14,7 +14,8 @@ body:
|
||||||
DO NOT USE THIS FORM, unless
|
DO NOT USE THIS FORM, unless
|
||||||
- you are a vencord contributor
|
- you are a vencord contributor
|
||||||
- you were given explicit permission to use this form by a moderator in our support server
|
- you were given explicit permission to use this form by a moderator in our support server
|
||||||
- you are filing a security related report
|
|
||||||
|
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: discord
|
id: discord
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.5.8",
|
"version": "1.6.3",
|
||||||
"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": {
|
||||||
|
|
|
@ -268,7 +268,7 @@ function runTime(token: string) {
|
||||||
const { wreq } = Vencord.Webpack;
|
const { wreq } = Vencord.Webpack;
|
||||||
|
|
||||||
console.error("[PUP_DEBUG]", "Loading all chunks...");
|
console.error("[PUP_DEBUG]", "Loading all chunks...");
|
||||||
const ids = Function("return" + wreq.u.toString().match(/\{.+\}/s)![0])();
|
const ids = Function("return" + wreq.u.toString().match(/(?<=\()\{.+?\}/s)![0])();
|
||||||
for (const id in ids) {
|
for (const id in ids) {
|
||||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
|
@ -289,7 +289,7 @@ function runTime(token: string) {
|
||||||
setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000);
|
setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000);
|
||||||
}, 1000));
|
}, 1000));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[PUP_DEBUG]", "A fatal error occured");
|
console.error("[PUP_DEBUG]", "A fatal error occurred");
|
||||||
console.error("[PUP_DEBUG]", e);
|
console.error("[PUP_DEBUG]", e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLow
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
document.head.append(Object.assign(document.createElement("style"), {
|
document.head.append(Object.assign(document.createElement("style"), {
|
||||||
id: "vencord-native-titlebar-style",
|
id: "vencord-native-titlebar-style",
|
||||||
textContent: "[class*=titleBar-]{display: none!important}"
|
textContent: "[class*=titleBar]{display: none!important}"
|
||||||
}));
|
}));
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mergeDefaults } from "@utils/misc";
|
import { mergeDefaults } from "@utils/misc";
|
||||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { SnowflakeUtils } from "@webpack/common";
|
import { SnowflakeUtils } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
|
|
||||||
import { Argument } from "./types";
|
import { Argument } from "./types";
|
||||||
|
|
||||||
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
const MessageCreator = findByPropsLazy("createBotMessage");
|
||||||
const MessageSender = findByPropsLazy("receiveMessage");
|
const MessageSender = findByPropsLazy("receiveMessage");
|
||||||
|
|
||||||
export function generateId() {
|
export function generateId() {
|
||||||
|
@ -38,7 +38,7 @@ export function generateId() {
|
||||||
* @returns {Message}
|
* @returns {Message}
|
||||||
*/
|
*/
|
||||||
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
|
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
|
||||||
const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
|
const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] });
|
||||||
|
|
||||||
MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage));
|
MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage));
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
|
||||||
* Remove a context menu patch
|
* Remove a context menu patch
|
||||||
* @param navId The navId(s) for the context menu(s) to remove the patch
|
* @param navId The navId(s) for the context menu(s) to remove the patch
|
||||||
* @param patch The patch to be removed
|
* @param patch The patch to be removed
|
||||||
* @returns Wheter the patch was sucessfully removed from the context menu(s)
|
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||||
*/
|
*/
|
||||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
||||||
|
@ -82,7 +82,7 @@ export function removeContextMenuPatch<T extends string | Array<string>>(navId:
|
||||||
/**
|
/**
|
||||||
* Remove a global context menu patch
|
* Remove a global context menu patch
|
||||||
* @param patch The patch to be removed
|
* @param patch The patch to be removed
|
||||||
* @returns Wheter the patch was sucessfully removed
|
* @returns Whether the patch was successfully removed
|
||||||
*/
|
*/
|
||||||
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
|
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
|
||||||
return globalPatches.delete(patch);
|
return globalPatches.delete(patch);
|
||||||
|
|
|
@ -1,69 +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 { proxyLazy } from "@utils/lazy";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { findModuleId, wreq } from "@webpack";
|
|
||||||
|
|
||||||
import { Settings } from "./Settings";
|
|
||||||
|
|
||||||
interface Setting<T> {
|
|
||||||
/**
|
|
||||||
* Get the setting value
|
|
||||||
*/
|
|
||||||
getSetting(): T;
|
|
||||||
/**
|
|
||||||
* Update the setting value
|
|
||||||
* @param value The new value
|
|
||||||
*/
|
|
||||||
updateSetting(value: T | ((old: T) => T)): Promise<void>;
|
|
||||||
/**
|
|
||||||
* React hook for automatically updating components when the setting is updated
|
|
||||||
*/
|
|
||||||
useSetting(): T;
|
|
||||||
settingsStoreApiGroup: string;
|
|
||||||
settingsStoreApiName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SettingsStores: Array<Setting<any>> | undefined = proxyLazy(() => {
|
|
||||||
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
|
||||||
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
|
|
||||||
|
|
||||||
const mod = wreq(modId);
|
|
||||||
if (mod == null) return;
|
|
||||||
|
|
||||||
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the store for a setting
|
|
||||||
* @param group The setting group
|
|
||||||
* @param name The name of the setting
|
|
||||||
*/
|
|
||||||
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
|
|
||||||
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
|
|
||||||
|
|
||||||
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getSettingStore but lazy
|
|
||||||
*/
|
|
||||||
export function getSettingStoreLazy<T = any>(group: string, name: string) {
|
|
||||||
return proxyLazy(() => getSettingStore<T>(group, name));
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ import * as $Notices from "./Notices";
|
||||||
import * as $Notifications from "./Notifications";
|
import * as $Notifications from "./Notifications";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
import * as $Settings from "./Settings";
|
import * as $Settings from "./Settings";
|
||||||
import * as $SettingsStore from "./SettingsStore";
|
|
||||||
import * as $Styles from "./Styles";
|
import * as $Styles from "./Styles";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,10 +90,6 @@ export const MemberListDecorators = $MemberListDecorators;
|
||||||
* An API allowing you to persist data
|
* An API allowing you to persist data
|
||||||
*/
|
*/
|
||||||
export const Settings = $Settings;
|
export const Settings = $Settings;
|
||||||
/**
|
|
||||||
* An API allowing you to read, manipulate and automatically update components based on Discord settings
|
|
||||||
*/
|
|
||||||
export const SettingsStore = $SettingsStore;
|
|
||||||
/**
|
/**
|
||||||
* An API allowing you to dynamically load styles
|
* An API allowing you to dynamically load styles
|
||||||
* a
|
* a
|
||||||
|
|
|
@ -94,7 +94,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const user of plugin.authors.slice(0, 6)) {
|
for (const user of plugin.authors.slice(0, 6)) {
|
||||||
const author = user.id
|
const author = user.id
|
||||||
? await UserUtils.fetchUser(`${user.id}`)
|
? await UserUtils.getUser(`${user.id}`)
|
||||||
.catch(() => makeDummyUser({ username: user.name }))
|
.catch(() => makeDummyUser({ username: user.name }))
|
||||||
: makeDummyUser({ username: user.name });
|
: makeDummyUser({ username: user.name });
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { openModalLazy } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ export default function PluginSettings() {
|
||||||
}
|
}
|
||||||
DataStore.set("Vencord_existingPlugins", existingTimestamps);
|
DataStore.set("Vencord_existingPlugins", existingTimestamps);
|
||||||
|
|
||||||
return window._.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type P = JSX.Element | JSX.Element[];
|
type P = JSX.Element | JSX.Element[];
|
||||||
|
|
|
@ -46,7 +46,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
||||||
if (code === "ENOENT")
|
if (code === "ENOENT")
|
||||||
var err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
var err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
||||||
else {
|
else {
|
||||||
var err = `An error occured while running \`${cmd}\`:\n`;
|
var err = `An error occurred while running \`${cmd}\`:\n`;
|
||||||
err += stderr || `Code \`${code}\`. See the console for more info`;
|
err += stderr || `Code \`${code}\`. See the console for more info`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,9 @@ async function getRepo() {
|
||||||
async function calculateGitChanges() {
|
async function calculateGitChanges() {
|
||||||
await git("fetch");
|
await git("fetch");
|
||||||
|
|
||||||
const res = await git("log", "HEAD...origin/main", "--pretty=format:%an/%h/%s");
|
const branch = await git("branch", "--show-current");
|
||||||
|
|
||||||
|
const res = await git("log", `HEAD...origin/${branch.stdout.trim()}`, "--pretty=format:%an/%h/%s");
|
||||||
|
|
||||||
const commits = res.stdout.trim();
|
const commits = res.stdout.trim();
|
||||||
return commits ? commits.split("\n").map(line => {
|
return commits ? commits.split("\n").map(line => {
|
||||||
|
|
|
@ -85,17 +85,19 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// alt: "", aria-hidden: false, src: originalSrc
|
// alt: "", aria-hidden: false, src: originalSrc
|
||||||
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/g,
|
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
|
||||||
// ...badge.props, ..., src: badge.image ?? ...
|
// ...badge.props, ..., src: badge.image ?? ...
|
||||||
replace: "...$1.props,$& $1.image??"
|
replace: "...$1.props,$& $1.image??"
|
||||||
},
|
},
|
||||||
|
// replace their component with ours if applicable
|
||||||
{
|
{
|
||||||
match: /children:function(?<=(\i)\.(?:tooltip|description),spacing:\d.+?)/g,
|
match: /(?<=text:(\i)\.description,spacing:12,)children:/,
|
||||||
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) : function"
|
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
|
||||||
},
|
},
|
||||||
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
{
|
{
|
||||||
match: /onClick:function(?=.{0,200}href:(\i)\.link)/,
|
match: /href:(\i)\.link/,
|
||||||
replace: "onClick:$1.onClick??function"
|
replace: "...($1.onClick && { onClick: $1.onClick }),$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,8 @@ export default definePlugin({
|
||||||
find: "Unexpected value for option",
|
find: "Unexpected value for option",
|
||||||
replacement: {
|
replacement: {
|
||||||
// return [2, cmd.execute(args, ctx)]
|
// return [2, cmd.execute(args, ctx)]
|
||||||
match: /,(.{1,2})\.execute\((.{1,2}),(.{1,2})\)]/,
|
match: /,(\i)\.execute\((\i),(\i)\)/,
|
||||||
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})]`
|
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Show plugin name instead of "Built-In"
|
// Show plugin name instead of "Built-In"
|
||||||
|
|
|
@ -29,8 +29,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "♫ (つ。◕‿‿◕。)つ ♪",
|
find: "♫ (つ。◕‿‿◕。)つ ♪",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function \i\((\i)\){)(?=var \i,\i=\i\.navId)/,
|
match: /let{navId:/,
|
||||||
replace: (_, props) => `Vencord.Api.ContextMenu._patchContextMenu(${props});`
|
replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,25 +25,23 @@ export default definePlugin({
|
||||||
authors: [Devs.TheSun, Devs.Ven],
|
authors: [Devs.TheSun, Devs.Ven],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "lostPermissionTooltipText,",
|
find: ".lostPermission)",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /decorators:.{0,100}?children:\[(?<=(\i)\.lostPermissionTooltipText.+?)/,
|
{
|
||||||
replace: "$&...Vencord.Api.MemberListDecorators.__getDecorators($1),"
|
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
||||||
}
|
replace: "$&vencordProps=$1,"
|
||||||
|
}, {
|
||||||
|
match: /decorators:.{0,100}?children:\[/,
|
||||||
|
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "PrivateChannel.renderAvatar",
|
find: "PrivateChannel.renderAvatar",
|
||||||
replacement: [
|
replacement: {
|
||||||
// props are shadowed by nested props so we have to do this
|
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
||||||
{
|
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]"
|
||||||
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]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,9 +27,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(.container\)?,children:)(\[[^\]]+\])(}\)\};return)/,
|
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
||||||
replace: (_, pre, accessories, post) =>
|
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
||||||
`${pre}Vencord.Api.MessageAccessories._modifyAccessories(${accessories},this.props)${post}`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -25,10 +25,10 @@ export default definePlugin({
|
||||||
authors: [Devs.TheSun],
|
authors: [Devs.TheSun],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".withMentionPrefix",
|
find: '"Message Username"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/,
|
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
||||||
replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))"
|
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -27,10 +27,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"MessageActionCreators"',
|
find: '"MessageActionCreators"',
|
||||||
replacement: {
|
replacement: {
|
||||||
// editMessage: function (...) {
|
match: /async editMessage\(.+?\)\{/,
|
||||||
match: /\beditMessage:(function\(.+?\))\{/,
|
replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
|
||||||
// editMessage: async function (...) { await handlePreEdit(...); ...
|
|
||||||
replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -38,7 +36,7 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
||||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
||||||
match: /(props\.chatInputType.+?\.then\(\()(function.+?var (\i)=\i\.\i\.parse\((\i),.+?var (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
||||||
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
||||||
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
||||||
`${rest1}async ${rest2}` +
|
`${rest1}async ${rest2}` +
|
||||||
|
@ -49,10 +47,10 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '("interactionUsernameProfile',
|
find: '("interactionUsernameProfile',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /var \i=(\i)\.id,\i=(\i)\.id;return \i\.useCallback\(\(?function\((\i)\){/,
|
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||||
replace: (m, message, channel, event) =>
|
replace: (m, message, channel, event) =>
|
||||||
// the message param is shadowed by the event param, so need to alias them
|
// the message param is shadowed by the event param, so need to alias them
|
||||||
`var _msg=${message},_chan=${channel};${m}Vencord.Api.MessageEvents._handleClick(_msg, _chan, ${event});`
|
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,12 +29,12 @@ export default definePlugin({
|
||||||
find: 'displayName="NoticeStore"',
|
find: 'displayName="NoticeStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?=;\i=null;.{0,70}getPremiumSubscription)/g,
|
match: /\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
|
||||||
replace: ";if(Vencord.Api.Notices.currentNotice)return false"
|
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/,
|
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||||
replace: (_, notice) => `if(${notice}.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);`
|
replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,15 +27,15 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "Messages.DISCODO_DISABLED",
|
find: "Messages.DISCODO_DISABLED",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||||
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.SERVERS,children",
|
find: "Messages.SERVERS,children",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/,
|
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
|
||||||
replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)"
|
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,38 +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 { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "SettingsStoreAPI",
|
|
||||||
description: "Patches Discord's SettingsStores to expose their group and name",
|
|
||||||
authors: [Devs.Nuckyz],
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: '"textAndImages","renderSpoilers"',
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:function/,
|
|
||||||
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
|
@ -26,7 +26,7 @@ export default definePlugin({
|
||||||
required: true,
|
required: true,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "TRACKING_URL:",
|
find: "AnalyticsActionHandlers.handle",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /^.+$/,
|
match: /^.+$/,
|
||||||
replace: "()=>{}",
|
replace: "()=>{}",
|
||||||
|
@ -43,20 +43,21 @@ export default definePlugin({
|
||||||
find: ".METRICS,",
|
find: ".METRICS,",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /this\._intervalId.+?12e4\)/,
|
match: /this\._intervalId=/,
|
||||||
replace: ""
|
replace: "this._intervalId=undefined&&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=increment=function\(\i\){)/,
|
match: /(increment\(\i\){)/,
|
||||||
replace: "return;"
|
replace: "$1return;"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".installedLogHooks)",
|
find: ".installedLogHooks)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\(\i\.getDebugLogging\(\)&&!\i\.installedLogHooks\)/,
|
// if getDebugLogging() returns false, the hooks don't get installed.
|
||||||
replace: "if(false)"
|
match: "getDebugLogging(){",
|
||||||
|
replace: "getDebugLogging(){return false;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -21,16 +21,30 @@ import definePlugin from "@utils/types";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "AlwaysAnimate",
|
name: "AlwaysAnimate",
|
||||||
description: "Animates anything that can be animated, besides status emojis.",
|
description: "Animates anything that can be animated",
|
||||||
authors: [Devs.FieryFlames],
|
authors: [Devs.FieryFlames],
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".canAnimate",
|
find: "canAnimate:",
|
||||||
all: true,
|
all: true,
|
||||||
|
// Some modules match the find but the replacement is returned untouched
|
||||||
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.canAnimate\b/g,
|
match: /canAnimate:.+?(?=([,}].*?\)))/g,
|
||||||
replace: ".canAnimate || true"
|
replace: (m, rest) => {
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) return "canAnimate:!0";
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Status emojis
|
||||||
|
find: ".Messages.GUILD_OWNER,",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
||||||
|
replace: "!0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -27,15 +27,15 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".displayName=\"MaskedLinkStore\"",
|
find: ".displayName=\"MaskedLinkStore\"",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.isTrustedDomain=function\(.\){return.+?};/,
|
match: /(?<=isTrustedDomain\(\i\){)return \i\(\i\)/,
|
||||||
replace: ".isTrustedDomain=function(){return true};"
|
replace: "return true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: '"7z","ade","adp"',
|
find: "isSuspiciousDownload:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /JSON\.parse\('\[.+?'\)/,
|
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
|
||||||
replace: "[]"
|
replace: "$&return null;"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,26 +20,19 @@ import { popNotice, showNotice } from "@api/Notices";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { filters, findByCodeLazy, mapMangledModuleLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
const assetManager = mapMangledModuleLazy(
|
const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL");
|
||||||
"getAssetImage: size must === [number, number] for Twitch",
|
|
||||||
{
|
|
||||||
getAsset: filters.byCode("apply("),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const lookupRpcApp = findByCodeLazy(".APPLICATION_RPC(");
|
|
||||||
|
|
||||||
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
||||||
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const apps: any = {};
|
const apps: any = {};
|
||||||
async function lookupApp(applicationId: string): Promise<string> {
|
async function lookupApp(applicationId: string): Promise<string> {
|
||||||
const socket: any = {};
|
const socket: any = {};
|
||||||
await lookupRpcApp(socket, applicationId);
|
await RpcUtils.fetchApplicationsRPC(socket, applicationId);
|
||||||
return socket.application;
|
return socket.application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
src/plugins/badge.ts
Normal file
43
src/plugins/badge.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/* eslint-disable header/header */
|
||||||
|
import { BadgePosition, ProfileBadge } from "@api/Badges";
|
||||||
|
import { Badges } from "@api/index";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
|
const SHIGGY_BADGE = "https://cdn.discordapp.com/emojis/1101838344146665502.gif?size=240&quality=lossless";
|
||||||
|
const BLOBFOXBOX_BADGE = "https://cdn.discordapp.com/emojis/1036216552736952350.webp?size=240&quality=lossless";
|
||||||
|
|
||||||
|
const ShiggyBadge: ProfileBadge = {
|
||||||
|
description: "true shiggy fan",
|
||||||
|
image: SHIGGY_BADGE,
|
||||||
|
position: BadgePosition.START,
|
||||||
|
props: {
|
||||||
|
style: { transform: "scale(0.9)" }
|
||||||
|
},
|
||||||
|
shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
|
||||||
|
link: "https://ryanccn.dev/"
|
||||||
|
};
|
||||||
|
const BlobfoxBoxBadge: ProfileBadge = {
|
||||||
|
description: "blobfox",
|
||||||
|
image: BLOBFOXBOX_BADGE,
|
||||||
|
position: BadgePosition.START,
|
||||||
|
props: {
|
||||||
|
style: { transform: "scale(0.9)" }
|
||||||
|
},
|
||||||
|
shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
|
||||||
|
link: "https://ryanccn.dev/"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Ryan's Extra Badges",
|
||||||
|
description: "shiggy",
|
||||||
|
authors: [Devs.RyanCaoDev],
|
||||||
|
dependencies: ["BadgeAPI"],
|
||||||
|
|
||||||
|
|
||||||
|
start() {
|
||||||
|
Badges.addBadge(ShiggyBadge);
|
||||||
|
Badges.addBadge(BlobfoxBoxBadge);
|
||||||
|
},
|
||||||
|
});
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "BAN_CONFIRM_TITLE.",
|
find: "BAN_CONFIRM_TITLE.",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /src:\w\(\d+\)/g,
|
match: /src:\i\("\d+"\)/g,
|
||||||
replace: "src: Vencord.Settings.plugins.BANger.source"
|
replace: "src: Vencord.Settings.plugins.BANger.source"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,56 +16,44 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Settings } from "@api/Settings";
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { i18n, React, useStateFromStores } from "@webpack/common";
|
import { find, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
|
import { useStateFromStores } from "@webpack/common";
|
||||||
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-bf-");
|
import { ExpandedGuildFolderStore, settings } from ".";
|
||||||
const classes = findByPropsLazy("sidebar", "guilds");
|
|
||||||
|
|
||||||
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
|
||||||
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
||||||
|
const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")')));
|
||||||
|
|
||||||
function Guilds(props: {
|
export default ErrorBoundary.wrap(guildsBarProps => {
|
||||||
className: string;
|
|
||||||
bfGuildFolders: any[];
|
|
||||||
}) {
|
|
||||||
// @ts-expect-error
|
|
||||||
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
|
|
||||||
|
|
||||||
// TODO: Make this better
|
|
||||||
const scrollerProps = res.props.children?.props?.children?.props?.children?.[1]?.props;
|
|
||||||
if (scrollerProps?.children) {
|
|
||||||
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
|
||||||
if (servers) scrollerProps.children = servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(() => {
|
|
||||||
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
||||||
const fullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
|
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
|
||||||
|
|
||||||
const guilds = document.querySelector(`.${classes.guilds}`);
|
|
||||||
|
|
||||||
const visible = !!expandedFolders.size;
|
|
||||||
const className = cl("folder-sidebar", { fullscreen });
|
|
||||||
|
|
||||||
const Sidebar = (
|
const Sidebar = (
|
||||||
<Guilds
|
<GuildsBar
|
||||||
className={classes.guilds}
|
{...guildsBarProps}
|
||||||
bfGuildFolders={Array.from(expandedFolders)}
|
isBetterFolders={true}
|
||||||
|
betterFoldersExpandedIds={expandedFolders}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!guilds || !Settings.plugins.BetterFolders.sidebarAnim)
|
const visible = !!expandedFolders.size;
|
||||||
|
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
|
||||||
|
|
||||||
|
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.
|
||||||
|
// Also display flex otherwise to fix scrolling
|
||||||
|
const barStyle = {
|
||||||
|
display: isFullscreen ? "none" : "flex",
|
||||||
|
} as CSSProperties;
|
||||||
|
|
||||||
|
if (!guilds || !settings.store.sidebarAnim) {
|
||||||
return visible
|
return visible
|
||||||
? <div className={className}>{Sidebar}</div>
|
? <div style={barStyle}>{Sidebar}</div>
|
||||||
: null;
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animations.Transition
|
<Animations.Transition
|
||||||
|
@ -75,11 +63,13 @@ export default ErrorBoundary.wrap(() => {
|
||||||
leave={{ width: 0 }}
|
leave={{ width: 0 }}
|
||||||
config={{ duration: 200 }}
|
config={{ duration: 200 }}
|
||||||
>
|
>
|
||||||
{(style, show) => show && (
|
{(animationStyle, show) =>
|
||||||
<Animations.animated.div style={style} className={className}>
|
show && (
|
||||||
{Sidebar}
|
<Animations.animated.div style={{ ...animationStyle, ...barStyle }}>
|
||||||
</Animations.animated.div>
|
{Sidebar}
|
||||||
)}
|
</Animations.animated.div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Animations.Transition>
|
</Animations.Transition>
|
||||||
);
|
);
|
||||||
}, { noop: true });
|
}, { noop: true });
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
.vc-bf-folder-sidebar [class*="wrapper-"] > [class*="listItem-"]:first-of-type,
|
|
||||||
.vc-bf-folder-sidebar [class*="unreadMentionsIndicator"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-bf-folder-sidebar [class*="expandedFolderBackground-"] {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-bf-folder-sidebar {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-bf-fullscreen {
|
|
||||||
width: 0 !important;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
|
@ -1,177 +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 "./betterFolders.css";
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
|
||||||
import { FluxDispatcher } from "@webpack/common";
|
|
||||||
|
|
||||||
import FolderSideBar from "./FolderSideBar";
|
|
||||||
|
|
||||||
const GuildsTree = findLazy(m => m.prototype?.convertToFolder);
|
|
||||||
const GuildFolderStore = findStoreLazy("SortedGuildStore");
|
|
||||||
const ExpandedFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
|
||||||
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
sidebar: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Display servers from folder on dedicated sidebar",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
sidebarAnim: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Animate opening the folder sidebar",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
closeAllFolders: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Close all folders when selecting a server not in a folder",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
closeAllHomeButton: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Close all folders when clicking on the home button",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
closeOthers: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Close other folders when opening a folder",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
forceOpen: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Force a folder to open when switching to a server of that folder",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "BetterFolders",
|
|
||||||
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
|
|
||||||
authors: [Devs.juby, Devs.AutumnVN],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: '("guildsnav")',
|
|
||||||
predicate: () => settings.store.sidebar,
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /(\i)\(\){return \i\(\(0,\i\.jsx\)\("div",{className:\i\(\)\.guildSeparator}\)\)}/,
|
|
||||||
replace: "$&$self.Separator=$1;"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Folder component patch
|
|
||||||
{
|
|
||||||
match: /\i\(\(function\(\i,\i,\i\){var \i=\i\.key;return.+\(\i\)},\i\)}\)\)/,
|
|
||||||
replace: "arguments[0].bfHideServers?null:$&"
|
|
||||||
},
|
|
||||||
|
|
||||||
// BEGIN Guilds component patch
|
|
||||||
{
|
|
||||||
match: /(\i)\.themeOverride,(.{15,25}\(function\(\){var \i=)(\i\.\i\.getGuildsTree\(\))/,
|
|
||||||
replace: "$1.themeOverride,bfPatch=$1.bfGuildFolders,$2bfPatch?$self.getGuildsTree(bfPatch,$3):$3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /return(\(0,\i\.jsx\))(\(\i,{)(folderNode:\i,setNodeRef:\i\.setNodeRef,draggable:!0,.+},\i\.id\));case/,
|
|
||||||
replace: "var bfHideServers=typeof bfPatch==='undefined',folder=$1$2bfHideServers,$3;return !bfHideServers&&arguments[1]?[$1($self.Separator,{}),folder]:folder;case"
|
|
||||||
},
|
|
||||||
// END
|
|
||||||
|
|
||||||
{
|
|
||||||
match: /\("guildsnav"\);return\(0,\i\.jsx\)\(.{1,6},{navigator:\i,children:\(0,\i\.jsx\)\(/,
|
|
||||||
replace: "$&$self.Guilds="
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "APPLICATION_LIBRARY,render",
|
|
||||||
predicate: () => settings.store.sidebar,
|
|
||||||
replacement: {
|
|
||||||
match: /(\(0,\i\.jsx\))\(\i\..,{className:\i\(\)\.guilds,themeOverride:\i}\)/,
|
|
||||||
replace: "$&,$1($self.FolderSideBar,{})"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: '("guildsnav")',
|
|
||||||
predicate: () => settings.store.closeAllHomeButton,
|
|
||||||
replacement: {
|
|
||||||
match: ",onClick:function(){if(!__OVERLAY__){",
|
|
||||||
replace: "$&$self.closeFolders();"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
settings,
|
|
||||||
|
|
||||||
start() {
|
|
||||||
const getGuildFolder = (id: string) => GuildFolderStore.getGuildFolders().find(f => f.guildIds.includes(id));
|
|
||||||
|
|
||||||
FluxDispatcher.subscribe("CHANNEL_SELECT", this.onSwitch = data => {
|
|
||||||
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.lastGuildId !== data.guildId) {
|
|
||||||
this.lastGuildId = data.guildId;
|
|
||||||
|
|
||||||
const guildFolder = getGuildFolder(data.guildId);
|
|
||||||
if (guildFolder?.folderId) {
|
|
||||||
if (settings.store.forceOpen && !ExpandedFolderStore.isFolderExpanded(guildFolder.folderId))
|
|
||||||
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
|
|
||||||
} else if (settings.store.closeAllFolders)
|
|
||||||
this.closeFolders();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
FluxDispatcher.subscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder = e => {
|
|
||||||
if (settings.store.closeOthers && !this.dispatching)
|
|
||||||
FluxDispatcher.wait(() => {
|
|
||||||
const expandedFolders = ExpandedFolderStore.getExpandedFolders();
|
|
||||||
if (expandedFolders.size > 1) {
|
|
||||||
this.dispatching = true;
|
|
||||||
|
|
||||||
for (const id of expandedFolders) if (id !== e.folderId)
|
|
||||||
FolderUtils.toggleGuildFolderExpand(id);
|
|
||||||
|
|
||||||
this.dispatching = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onSwitch);
|
|
||||||
FluxDispatcher.unsubscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder);
|
|
||||||
},
|
|
||||||
|
|
||||||
FolderSideBar,
|
|
||||||
|
|
||||||
getGuildsTree(folders, oldTree) {
|
|
||||||
const tree = new GuildsTree();
|
|
||||||
tree.root.children = oldTree.root.children.filter(e => folders.includes(e.id));
|
|
||||||
tree.nodes = folders.map(id => oldTree.nodes[id]);
|
|
||||||
return tree;
|
|
||||||
},
|
|
||||||
|
|
||||||
closeFolders() {
|
|
||||||
for (const id of ExpandedFolderStore.getExpandedFolders())
|
|
||||||
FolderUtils.toggleGuildFolderExpand(id);
|
|
||||||
},
|
|
||||||
});
|
|
307
src/plugins/betterFolders/index.tsx
Normal file
307
src/plugins/betterFolders/index.tsx
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { proxyLazy } from "@utils/lazy";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
|
import { FluxDispatcher, i18n } from "@webpack/common";
|
||||||
|
|
||||||
|
import FolderSideBar from "./FolderSideBar";
|
||||||
|
|
||||||
|
enum FolderIconDisplay {
|
||||||
|
Never,
|
||||||
|
Always,
|
||||||
|
MoreThanOneFolderExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree);
|
||||||
|
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
||||||
|
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
|
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||||
|
|
||||||
|
let lastGuildId = null as string | null;
|
||||||
|
let dispatchingFoldersClose = false;
|
||||||
|
|
||||||
|
function getGuildFolder(id: string) {
|
||||||
|
return SortedGuildStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeFolders() {
|
||||||
|
for (const id of ExpandedGuildFolderStore.getExpandedFolders())
|
||||||
|
FolderUtils.toggleGuildFolderExpand(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
|
sidebar: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Display servers from folder on dedicated sidebar",
|
||||||
|
restartNeeded: true,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
sidebarAnim: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Animate opening the folder sidebar",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
closeAllFolders: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Close all folders when selecting a server not in a folder",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
closeAllHomeButton: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Close all folders when clicking on the home button",
|
||||||
|
restartNeeded: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
closeOthers: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Close other folders when opening a folder",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
forceOpen: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Force a folder to open when switching to a server of that folder",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
keepIcons: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar",
|
||||||
|
restartNeeded: true,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showFolderIcon: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Show the folder icon above the folder guilds in the BetterFolders sidebar",
|
||||||
|
options: [
|
||||||
|
{ label: "Never", value: FolderIconDisplay.Never },
|
||||||
|
{ label: "Always", value: FolderIconDisplay.Always, default: true },
|
||||||
|
{ label: "When more than one folder is expanded", value: FolderIconDisplay.MoreThanOneFolderExpanded }
|
||||||
|
],
|
||||||
|
restartNeeded: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "BetterFolders",
|
||||||
|
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
|
||||||
|
authors: [Devs.juby, Devs.AutumnVN, Devs.Nuckyz],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '("guildsnav")',
|
||||||
|
predicate: () => settings.store.sidebar,
|
||||||
|
replacement: [
|
||||||
|
// Create the isBetterFolders variable in the GuildsBar component
|
||||||
|
{
|
||||||
|
match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
|
||||||
|
replace: ",isBetterFolders"
|
||||||
|
},
|
||||||
|
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
||||||
|
{
|
||||||
|
match: /(useStateFromStoresArray\).{0,25}let \i)=(\i\.\i.getGuildsTree\(\))/,
|
||||||
|
replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`
|
||||||
|
},
|
||||||
|
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||||
|
{
|
||||||
|
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
||||||
|
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
|
||||||
|
},
|
||||||
|
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||||
|
{
|
||||||
|
match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/,
|
||||||
|
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
|
||||||
|
},
|
||||||
|
// Export the isBetterFolders variable to the folders component
|
||||||
|
{
|
||||||
|
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
|
||||||
|
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This is the parent folder component
|
||||||
|
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
|
||||||
|
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
// Modify the expanded state to instead return the list of expanded folders
|
||||||
|
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
|
||||||
|
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
|
||||||
|
// Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined
|
||||||
|
match: /(?<=folderNode:(\i),expanded:)\i(?=,)/,
|
||||||
|
replace: (isExpandedOrExpandedIds, folderNote) => ""
|
||||||
|
+ `typeof ${isExpandedOrExpandedIds}==="boolean"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),`
|
||||||
|
+ `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
|
||||||
|
predicate: () => settings.store.sidebar,
|
||||||
|
replacement: [
|
||||||
|
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
|
||||||
|
|
||||||
|
// If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions
|
||||||
|
{
|
||||||
|
predicate: () => settings.store.keepIcons,
|
||||||
|
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
|
||||||
|
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
|
||||||
|
},
|
||||||
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
|
{
|
||||||
|
predicate: () => !settings.store.keepIcons,
|
||||||
|
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
|
||||||
|
replace: "!!arguments[0].isBetterFolders&&"
|
||||||
|
},
|
||||||
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
|
{
|
||||||
|
predicate: () => !settings.store.keepIcons,
|
||||||
|
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
|
||||||
|
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||||
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
|
match: /(?<=\.wrapper,children:\[)/,
|
||||||
|
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
||||||
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
|
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
||||||
|
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "APPLICATION_LIBRARY,render",
|
||||||
|
predicate: () => settings.store.sidebar,
|
||||||
|
replacement: {
|
||||||
|
// Render the Better Folders sidebar
|
||||||
|
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
|
||||||
|
replace: ",$self.FolderSideBar($1)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.DISCODO_DISABLED",
|
||||||
|
predicate: () => settings.store.closeAllHomeButton,
|
||||||
|
replacement: {
|
||||||
|
// Close all folders when clicking the home button
|
||||||
|
match: /(?<=onClick:\(\)=>{)(?=.{0,200}"discodo")/,
|
||||||
|
replace: "$self.closeFolders();"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
flux: {
|
||||||
|
CHANNEL_SELECT(data) {
|
||||||
|
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (lastGuildId !== data.guildId) {
|
||||||
|
lastGuildId = data.guildId;
|
||||||
|
const guildFolder = getGuildFolder(data.guildId);
|
||||||
|
|
||||||
|
if (guildFolder?.folderId) {
|
||||||
|
if (settings.store.forceOpen && !ExpandedGuildFolderStore.isFolderExpanded(guildFolder.folderId)) {
|
||||||
|
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
|
||||||
|
}
|
||||||
|
} else if (settings.store.closeAllFolders) {
|
||||||
|
closeFolders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
TOGGLE_GUILD_FOLDER_EXPAND(data) {
|
||||||
|
if (settings.store.closeOthers && !dispatchingFoldersClose) {
|
||||||
|
dispatchingFoldersClose = true;
|
||||||
|
|
||||||
|
FluxDispatcher.wait(() => {
|
||||||
|
const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();
|
||||||
|
|
||||||
|
if (expandedFolders.size > 1) {
|
||||||
|
for (const id of expandedFolders) if (id !== data.folderId)
|
||||||
|
FolderUtils.toggleGuildFolderExpand(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchingFoldersClose = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set<any>) {
|
||||||
|
if (!isBetterFolders || expandedFolderIds == null) return oldTree;
|
||||||
|
|
||||||
|
const newTree = new GuildsTree();
|
||||||
|
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
|
||||||
|
newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
|
||||||
|
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
|
||||||
|
newTree.nodes = Object.fromEntries(
|
||||||
|
Object.entries(oldTree.nodes)
|
||||||
|
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
|
||||||
|
);
|
||||||
|
|
||||||
|
return newTree;
|
||||||
|
},
|
||||||
|
|
||||||
|
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
||||||
|
return child => {
|
||||||
|
if (isBetterFolders) {
|
||||||
|
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
|
||||||
|
return child => {
|
||||||
|
if (isBetterFolders) {
|
||||||
|
return "onScroll" in child.props;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set<any>) {
|
||||||
|
if (!isBetterFolders) return true;
|
||||||
|
|
||||||
|
switch (settings.store.showFolderIcon) {
|
||||||
|
case FolderIconDisplay.Never:
|
||||||
|
return false;
|
||||||
|
case FolderIconDisplay.Always:
|
||||||
|
return true;
|
||||||
|
case FolderIconDisplay.MoreThanOneFolderExpanded:
|
||||||
|
return (expandedFolderIds?.size ?? 0) > 1;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
|
||||||
|
|
||||||
|
closeFolders
|
||||||
|
});
|
|
@ -34,16 +34,18 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".embedGallerySide",
|
find: ".Messages.GIF,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/,
|
match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
|
||||||
replace:
|
replace:
|
||||||
"?($1.alt='GIF',$self.altify($1))",
|
// rename prop so we can always use default value
|
||||||
|
"alt_$$:$1=$self.altify($3)||$2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
altify(props: any) {
|
altify(props: any) {
|
||||||
|
props.alt ??= "GIF";
|
||||||
if (props.alt !== "GIF") return props.alt;
|
if (props.alt !== "GIF") return props.alt;
|
||||||
|
|
||||||
let url: string = props.original || props.src;
|
let url: string = props.original || props.src;
|
||||||
|
|
|
@ -32,10 +32,16 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "hideNote:",
|
find: "hideNote:",
|
||||||
all: true,
|
all: true,
|
||||||
|
// Some modules match the find but the replacement is returned untouched
|
||||||
|
noWarn: true,
|
||||||
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
|
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /hideNote:.+?(?=[,}])/g,
|
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
||||||
replace: "hideNote:true",
|
replace: (m, rest) => {
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) return "hideNote:!0";
|
||||||
|
return m;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -48,8 +54,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.NOTE}",
|
find: ".Messages.NOTE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\.hideNote\?null/,
|
match: /(?<=return \i\?)null(?=:\(0,\i\.jsxs)/,
|
||||||
replace: "$1.hideNote?$self.patchPadding($1)"
|
replace: "$self.patchPadding(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -38,6 +38,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"dot"===',
|
find: '"dot"===',
|
||||||
all: true,
|
all: true,
|
||||||
|
noWarn: true,
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
||||||
|
|
|
@ -29,9 +29,8 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
// Discord merges multiple props here with Object.assign()
|
// Discord merges multiple props here with Object.assign()
|
||||||
// This patch passes a third object to it with which we override onClick and onContextMenu
|
// This patch passes a third object to it with which we override onClick and onContextMenu
|
||||||
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0)\},(.{1,3})\)/,
|
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
||||||
replace: (m, onDblClick, otherProps) =>
|
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||||
`${m.slice(0, -1)},{onClick:${onDblClick},onContextMenu:${otherProps}.onClick})`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -45,11 +45,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".embedWrapper,embed",
|
find: ".embedWrapper,embed",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /(\.renderEmbed=.+?(.)=.\.props)(.+?\.embedWrapper)/g,
|
match: /\.embedWrapper/g,
|
||||||
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
replace: "$&+(this.props.channel.nsfw?' vc-nsfw-img':'')"
|
||||||
}, {
|
|
||||||
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
|
|
||||||
replace: "$1,vcProps=$2$3+(vcProps.nsfw?' vc-nsfw-img':'')"
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
63
src/plugins/bottom/components/Indicator.tsx
Normal file
63
src/plugins/bottom/components/Indicator.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin was modified from code licensed under the following license:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-present Sebastian Law
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
|
export default function Indicator({ layers, bottom }: { layers: number; bottom: boolean; }) {
|
||||||
|
return (
|
||||||
|
<Tooltip color="black" position="top" text={layers <= 1 ? "🥺" : `Decoded from ${layers} nested bottom messages`}>
|
||||||
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
|
<span
|
||||||
|
className={`power-bottom-indicator ${findByPropsLazy("edited").edited}`}
|
||||||
|
style={{ color: "var(--text-muted)" }}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{bottom ? "(bottom)" : "(original)"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
73
src/plugins/bottom/encoding.ts
Normal file
73
src/plugins/bottom/encoding.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file was adapted from https://github.com/bottom-software-foundation/bottom-js
|
||||||
|
* Which is, hopefully, licensed under MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CHARACTER_VALUES: [number, string][] = [
|
||||||
|
[200, "🫂"],
|
||||||
|
[50, "💖"],
|
||||||
|
[10, "✨"],
|
||||||
|
[5, "🥺"],
|
||||||
|
[1, ","],
|
||||||
|
[0, "❤️"],
|
||||||
|
];
|
||||||
|
const SECTION_SEPERATOR = "👉👈";
|
||||||
|
const FINAL_TERMINATOR = new RegExp(`(${SECTION_SEPERATOR})?$`);
|
||||||
|
|
||||||
|
function encodeChar(charValue: number): string {
|
||||||
|
if (charValue === 0) return "";
|
||||||
|
const [val, currentCase]: [number, string] =
|
||||||
|
CHARACTER_VALUES.find(([val]) => charValue >= val) || CHARACTER_VALUES[-1];
|
||||||
|
return `${currentCase}${encodeChar(charValue - val)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encode(value: string): string {
|
||||||
|
return Array.from(new TextEncoder().encode(value))
|
||||||
|
.map((v: number) => encodeChar(v) + SECTION_SEPERATOR)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(value: string): string {
|
||||||
|
return new TextDecoder().decode(Uint8Array.from(
|
||||||
|
value
|
||||||
|
.trim()
|
||||||
|
.replace(FINAL_TERMINATOR, "")
|
||||||
|
.split(SECTION_SEPERATOR)
|
||||||
|
.map(letters => {
|
||||||
|
return Array.from(letters)
|
||||||
|
.map(character => {
|
||||||
|
const [value, emoji]: [number, string] = CHARACTER_VALUES.find(
|
||||||
|
([_, em]) => em === character
|
||||||
|
) || [-1, ""];
|
||||||
|
if (!emoji) {
|
||||||
|
throw new TypeError(`Invalid bottom text: '${character}'`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.reduce((p, c) => p + c);
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
encode: encode,
|
||||||
|
decode: decode
|
||||||
|
};
|
158
src/plugins/bottom/handler.ts
Normal file
158
src/plugins/bottom/handler.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin was modified from code licensed under the following license:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-present Sebastian Law
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FluxDispatcher, MessageStore } from "@webpack/common";
|
||||||
|
import type { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
import Bottom from "./encoding";
|
||||||
|
|
||||||
|
class BottomHandler {
|
||||||
|
|
||||||
|
cache: Record<string, Record<string, { originalContent: string; top?: boolean; layers?: number; }>>;
|
||||||
|
re: RegExp;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.cache = {};
|
||||||
|
this.re = /((?:((?:\uD83E\uDEC2)?(?:💖)*(?:✨)*(?:🥺)*(?:,)*(❤️)?)(?:👉👈|\u200b))+)/gm;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTranslated(message) {
|
||||||
|
if (
|
||||||
|
!this.cache[message.channel_id] ||
|
||||||
|
!this.cache[message.channel_id][message.id]
|
||||||
|
) { return false; }
|
||||||
|
|
||||||
|
return this.cache[message.channel_id][message.id].originalContent !== message.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
translate(text: string, notNested: boolean) {
|
||||||
|
var original = text;
|
||||||
|
var translated = text;
|
||||||
|
var layers = 0;
|
||||||
|
while (original.match(this.re)) {
|
||||||
|
translated = original.replace(this.re, (str, p1, offset, s) => Bottom.decode(p1) || p1);
|
||||||
|
|
||||||
|
// the regex can sometimes pick up invalid bottom in which case we want to return to avoid an infinite loop
|
||||||
|
if (translated === original || notNested) break;
|
||||||
|
else {
|
||||||
|
original = translated;
|
||||||
|
layers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
translated: translated,
|
||||||
|
layers: layers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
translateMessage(message: Message, decodeLayers: boolean) {
|
||||||
|
if (!message.content || message.content.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Build cache if it doesn't exist
|
||||||
|
if (!this.cache[message.channel_id]) {
|
||||||
|
this.cache[message.channel_id] = {};
|
||||||
|
}
|
||||||
|
if (!this.cache[message.channel_id][message.id]) {
|
||||||
|
this.cache[message.channel_id][message.id] = {
|
||||||
|
originalContent: message.content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = this.cache[message.channel_id][message.id];
|
||||||
|
|
||||||
|
if (this.isTranslated(message)) {
|
||||||
|
// if we're reverting back to original, just set the content back to original
|
||||||
|
message.content = cached.originalContent;
|
||||||
|
this.updateMessage(message);
|
||||||
|
} else {
|
||||||
|
// the message hasn't been edited, let's try to decode it
|
||||||
|
const { translated, layers } = this.translate(message.content, !decodeLayers);
|
||||||
|
if (translated === message.content) {
|
||||||
|
// we don't want to do anything if there is no bottom
|
||||||
|
// since the translation fails, mark this message to not show the indicator
|
||||||
|
cached.top = true;
|
||||||
|
throw new Error("No Bottom detected 🥺");
|
||||||
|
} else {
|
||||||
|
// let the indicator show how many layers of decoding we did
|
||||||
|
cached.layers = layers;
|
||||||
|
message.content = translated;
|
||||||
|
this.updateMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMessage(message: Message) {
|
||||||
|
console.log({
|
||||||
|
bottomTranslation: true,
|
||||||
|
type: "MESSAGE_UPDATE",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
bottomTranslation: true,
|
||||||
|
type: "MESSAGE_UPDATE",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
for (const channelID in this.cache) {
|
||||||
|
for (const messageID in this.cache[channelID]) {
|
||||||
|
this.removeMessage(channelID, messageID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.cache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(channelID: string, messageID: string, reset = true) {
|
||||||
|
const message = MessageStore.getMessage(channelID, messageID);
|
||||||
|
if (reset) {
|
||||||
|
message.content = this.cache[channelID][messageID].originalContent;
|
||||||
|
this.updateMessage(message);
|
||||||
|
}
|
||||||
|
delete this.cache[channelID][messageID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BottomHandler;
|
262
src/plugins/bottom/index.tsx
Normal file
262
src/plugins/bottom/index.tsx
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin was modified from code licensed under the following license:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-present Sebastian Law
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { findOption, RequiredMessageOption } from "@api/Commands";
|
||||||
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
import { definePluginSettings } from "@api/settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { ChannelStore, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
|
import Indicator from "./components/Indicator";
|
||||||
|
import Bottom from "./encoding";
|
||||||
|
import BottomHandler from "./handler";
|
||||||
|
|
||||||
|
const Handler = new BottomHandler();
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
"decode-layers": {
|
||||||
|
description: "Decode Layers",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
"auto-encode-send": {
|
||||||
|
description: "Automatically encode outgoing messages",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
"encode-send-type": {
|
||||||
|
description: "Automatic Encode Behavior",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: "All",
|
||||||
|
default: true,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Inline (Greedy)",
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Inline (Parsed)",
|
||||||
|
value: 2,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"inline-bottom-prefix": {
|
||||||
|
description: "Inline bottom prefix",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "👉",
|
||||||
|
},
|
||||||
|
"inline-bottom-suffix": {
|
||||||
|
description: "Inline bottom suffix",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "👈",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const escapeRegex: (string: string) => string = string => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
|
||||||
|
function count(string: string, subString: string): number {
|
||||||
|
var n = 0;
|
||||||
|
var pos = 0;
|
||||||
|
const step = subString.length;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
pos = string.indexOf(subString, pos);
|
||||||
|
if (pos >= 0) {
|
||||||
|
n++;
|
||||||
|
pos += step;
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
function inlineEncode(p: string, s: string, text: string): string {
|
||||||
|
var np = count(text, p);
|
||||||
|
var ns = count(text, s);
|
||||||
|
|
||||||
|
if (np === 0 || ns === 0) return text;
|
||||||
|
|
||||||
|
var pl = p.length;
|
||||||
|
var sl = s.length;
|
||||||
|
const result: string[] = [];
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var startIndex = text.indexOf(p, idx);
|
||||||
|
|
||||||
|
if (startIndex < 0) {
|
||||||
|
result.push(text.slice(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIndex = text.indexOf(s, startIndex + pl);
|
||||||
|
|
||||||
|
if (endIndex < 0) {
|
||||||
|
result.push(text.slice(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(text.slice(idx, startIndex));
|
||||||
|
startIndex += pl;
|
||||||
|
result.push(Bottom.encode(text.slice(startIndex, endIndex)));
|
||||||
|
endIndex += sl;
|
||||||
|
idx = endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Bottom",
|
||||||
|
description: "The Vencord plugin for bottom 🥺",
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
id: 1038096782963507210n,
|
||||||
|
name: "skyevg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dependencies: ["MessagePopoverAPI", "CommandsAPI", "MessageEventsAPI", "MessageAccessoriesAPI"],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
start() {
|
||||||
|
addButton("bottom", msg => {
|
||||||
|
return {
|
||||||
|
label: "Translate Bottom",
|
||||||
|
icon: () => (
|
||||||
|
<svg x="0" y="0" aria-hidden="false" width="22" height="22" viewBox="0 0 36 36" fill="currentColor" className="icon">
|
||||||
|
<circle fill="#FFCC4D" cx="18" cy="18" r="18" />
|
||||||
|
<path fill="#65471B" d="M20.996 27c-.103 0-.206-.016-.309-.049-1.76-.571-3.615-.571-5.375 0-.524.169-1.089-.117-1.26-.642-.171-.525.117-1.089.643-1.26 2.162-.702 4.447-.702 6.609 0 .525.171.813.735.643 1.26-.137.421-.529.691-.951.691z" />
|
||||||
|
<path fill="#FFF" d="M30.335 12.068c-.903 2.745-3.485 4.715-6.494 4.715-.144 0-.289-.005-.435-.014-1.477-.093-2.842-.655-3.95-1.584.036.495.076.997.136 1.54.152 1.388.884 2.482 2.116 3.163.82.454 1.8.688 2.813.752 1.734.109 3.57-.28 4.873-.909 1.377-.665 2.272-1.862 2.456-3.285.183-1.415-.354-2.924-1.515-4.378z" />
|
||||||
|
<path fill="#65471B" d="M21.351 7.583c-1.297.55-1.947 2.301-1.977 5.289l.039.068c.897 1.319 2.373 2.224 4.088 2.332.114.007.228.011.341.011 2.634 0 4.849-1.937 5.253-4.524-.115-.105-.221-.212-.343-.316-3.715-3.17-6.467-3.257-7.401-2.86z" />
|
||||||
|
<path fill="#F4900C" d="M23.841 16.783c3.009 0 5.591-1.97 6.494-4.715-.354-.443-.771-.88-1.241-1.309-.404 2.587-2.619 4.524-5.253 4.524-.113 0-.227-.004-.341-.011-1.715-.108-3.191-1.013-4.088-2.332l-.039-.068c-.007.701.021 1.473.083 2.313 1.108.929 2.473 1.491 3.95 1.584.146.01.291.014.435.014z" />
|
||||||
|
<circle fill="#FFF" cx="21.413" cy="10.705" r="1.107" />
|
||||||
|
<path fill="#FFF" d="M12.159 16.783c-3.009 0-5.591-1.97-6.494-4.715-1.161 1.454-1.697 2.963-1.515 4.377.185 1.423 1.079 2.621 2.456 3.285 1.303.629 3.138 1.018 4.873.909 1.013-.064 1.993-.297 2.813-.752 1.231-.681 1.963-1.775 2.116-3.163.06-.542.1-1.042.136-1.536-1.103.923-2.47 1.487-3.95 1.58-.146.011-.291.015-.435.015z" />
|
||||||
|
<path fill="#65471B" d="M12.159 15.283c.113 0 .227-.004.341-.011 1.715-.108 3.191-1.013 4.088-2.332l.039-.068c-.031-2.988-.68-4.739-1.977-5.289-.934-.397-3.687-.31-7.401 2.859-.122.104-.227.211-.343.316.404 2.588 2.619 4.525 5.253 4.525z" />
|
||||||
|
<path fill="#F4900C" d="M16.626 12.872l-.039.068c-.897 1.319-2.373 2.224-4.088 2.332-.114.007-.228.011-.341.011-2.634 0-4.849-1.937-5.253-4.524-.47.429-.887.866-1.241 1.309.903 2.745 3.485 4.715 6.494 4.715.144 0 .289-.005.435-.014 1.48-.093 2.847-.657 3.95-1.58.062-.841.091-1.614.083-2.317z" />
|
||||||
|
<path fill="#FFF" d="M9.781 11.81c.61-.038 1.074-.564 1.035-1.174-.038-.61-.564-1.074-1.174-1.036-.61.038-1.074.564-1.036 1.174.039.61.565 1.074 1.175 1.036z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
message: msg,
|
||||||
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
|
onClick: async () => {
|
||||||
|
try {
|
||||||
|
Handler.translateMessage(msg, settings.store["decode-layers"]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
Toasts.show(
|
||||||
|
{
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: e.message,
|
||||||
|
type: Toasts.Type.MESSAGE
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
addAccessory("bottom", props => {
|
||||||
|
try {
|
||||||
|
if (!Handler.cache[props.message.channel_id][props.message.id].top) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Indicator layers={Handler.cache[props.message.channel_id][props.message.id].layers ?? 0} bottom={!Handler.isTranslated(props.message)} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.preSend = addPreSendListener((_, msg) => {
|
||||||
|
if (settings.store["auto-encode-send"]) {
|
||||||
|
const sendType = settings.store["encode-send-type"];
|
||||||
|
var { content } = msg;
|
||||||
|
|
||||||
|
switch (sendType) {
|
||||||
|
case 0: // all
|
||||||
|
content = Bottom.encode(content);
|
||||||
|
break;
|
||||||
|
case 1: // inline greedy
|
||||||
|
var prefix = escapeRegex(settings.store["inline-bottom-prefix"]);
|
||||||
|
var suffix = escapeRegex(settings.store["inline-bottom-suffix"]);
|
||||||
|
var reg = new RegExp(`${prefix}(.+)${suffix}`, "gm");
|
||||||
|
content = content.replace(reg, (str, p1, o, s) => Bottom.encode(p1));
|
||||||
|
break;
|
||||||
|
case 2: // inline parsed
|
||||||
|
var prefix = settings.store["inline-bottom-prefix"];
|
||||||
|
var suffix = settings.store["inline-bottom-prefix"];
|
||||||
|
content = inlineEncode(prefix, suffix, content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg.content = content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
removeButton("bottom");
|
||||||
|
removeAccessory("bottom");
|
||||||
|
removePreSendListener(this.preSend);
|
||||||
|
},
|
||||||
|
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
name: "bottom",
|
||||||
|
description: "Translate and send text as bottom 🥺",
|
||||||
|
options: [RequiredMessageOption],
|
||||||
|
execute: opts => ({
|
||||||
|
content: Bottom.encode(findOption(opts, "message", "")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -73,9 +73,9 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".renderConnectionStatus=",
|
find: "renderConnectionStatus(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=renderConnectionStatus=.+\.channel,children:)\w/,
|
match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i/,
|
||||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -34,8 +34,9 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".AVATAR_STATUS_MOBILE_16;",
|
find: ".AVATAR_STATUS_MOBILE_16;",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\.fromIsMobile,.+?)\i.status/,
|
match: /(?<=fromIsMobile:\i=!0,.+?)status:(\i)/,
|
||||||
replace: (_, rest) => `${rest}"online"`
|
// Rename field to force it to always use "online"
|
||||||
|
replace: 'status_$:$1="online"'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -22,23 +22,16 @@ import { Devs } from "@utils/constants";
|
||||||
import { isTruthy } from "@utils/guards";
|
import { isTruthy } from "@utils/guards";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
import { FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const ActivityComponent = findByCodeLazy("onOpenGameProfile");
|
const ActivityComponent = findByCodeLazy("onOpenGameProfile");
|
||||||
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
||||||
const Colors = findByPropsLazy("profileColors");
|
const Colors = findByPropsLazy("profileColors");
|
||||||
|
|
||||||
const assetManager = mapMangledModuleLazy(
|
|
||||||
"getAssetImage: size must === [number, number] for Twitch",
|
|
||||||
{
|
|
||||||
getAsset: filters.byCode("apply("),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||||
return (await assetManager.getAsset(settings.store.appID, [key, undefined]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActivityAssets {
|
interface ActivityAssets {
|
||||||
|
@ -403,7 +396,7 @@ export default definePlugin({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
Go to <Link href="https://discord.com/developers/applications">Discord Deverloper Portal</Link> to create an application and
|
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||||
get the application ID.
|
get the application ID.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
|
|
|
@ -147,8 +147,8 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
// patch componentDidMount to replace embed thumbnail and title
|
// patch componentDidMount to replace embed thumbnail and title
|
||||||
{
|
{
|
||||||
match: /(\i).render=function.{0,50}\i\.embed/,
|
match: /render\(\)\{let\{embed:/,
|
||||||
replace: "$1.componentDidMount=$self.embedDidMount,$&"
|
replace: "componentDidMount=$self.embedDidMount;$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
// add dearrow button
|
// add dearrow button
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function \i\(\){)(?=.{1,100}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
||||||
replace: "return;"
|
replace: "return;"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||||
import { Promisable } from "type-fest";
|
import { Promisable } from "type-fest";
|
||||||
|
|
||||||
const StickersStore = findStoreLazy("StickersStore");
|
const StickersStore = findStoreLazy("StickersStore");
|
||||||
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
|
const EmojiManager = findByPropsLazy("fetchEmoji", "uploadEmoji", "deleteEmoji");
|
||||||
|
|
||||||
interface Sticker {
|
interface Sticker {
|
||||||
t: "Sticker";
|
t: "Sticker";
|
||||||
|
@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) {
|
||||||
reader.readAsDataURL(data);
|
reader.readAsDataURL(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
return uploadEmoji({
|
return EmojiManager.uploadEmoji({
|
||||||
guildId,
|
guildId,
|
||||||
name: emoji.name.split("~")[0],
|
name: emoji.name.split("~")[0],
|
||||||
image: dataUrl
|
image: dataUrl
|
||||||
|
@ -155,10 +155,15 @@ async function doClone(guildId: string, data: Sticker | Emoji) {
|
||||||
type: Toasts.Type.SUCCESS,
|
type: Toasts.Type.SUCCESS,
|
||||||
id: Toasts.genId()
|
id: Toasts.genId()
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
|
let message = "Something went wrong (check console!)";
|
||||||
|
try {
|
||||||
|
message = JSON.parse(e.text).message;
|
||||||
|
} catch { }
|
||||||
|
|
||||||
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
|
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
message: "Oopsie something went wrong :( Check console!!!",
|
message: "Failed to clone: " + message,
|
||||||
type: Toasts.Type.FAILURE,
|
type: Toasts.Type.FAILURE,
|
||||||
id: Toasts.genId()
|
id: Toasts.genId()
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "Object.defineProperties(this,{isDeveloper",
|
find: "Object.defineProperties(this,{isDeveloper",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<={isDeveloper:\{[^}]+?,get:function\(\)\{return )\w/,
|
match: /(?<={isDeveloper:\{[^}]+?,get:\(\)=>)\i/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -64,19 +64,28 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".isStaff=function(){",
|
find: ".isStaff=()",
|
||||||
predicate: () => settings.store.enableIsStaff,
|
predicate: () => settings.store.enableIsStaff,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /return\s*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
|
match: /=>*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
|
||||||
replace: (_, user, flags) => `return Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
|
replace: (_, user, flags) => `=>Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /hasFreePremium=function\(\){return this.isStaff\(\)\s*?\|\|/,
|
match: /hasFreePremium\(\){return this.isStaff\(\)\s*?\|\|/,
|
||||||
replace: "hasFreePremium=function(){return ",
|
replace: "hasFreePremium(){return ",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Fix search history being disabled / broken with isStaff
|
||||||
|
{
|
||||||
|
find: '("showNewSearch")',
|
||||||
|
predicate: () => settings.store.enableIsStaff,
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=showNewSearch"\);return)\s?/,
|
||||||
|
replace: "!1&&"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: 'H1,title:"Experiments"',
|
find: 'H1,title:"Experiments"',
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
|
@ -24,35 +24,33 @@ import { getCurrentGuild } from "@utils/discord";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
|
import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||||
import type { Message } from "discord-types/general";
|
import type { Message } from "discord-types/general";
|
||||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||||
import type { ReactElement, ReactNode } from "react";
|
import type { ReactElement, ReactNode } from "react";
|
||||||
|
|
||||||
const DRAFT_TYPE = 0;
|
const DRAFT_TYPE = 0;
|
||||||
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
|
|
||||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
|
||||||
const PreloadedUserSettingsProtoHandler = findLazy(m => m.ProtoClass?.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings");
|
|
||||||
const ReaderFactory = findByPropsLazy("readerFactory");
|
|
||||||
const StickerStore = findStoreLazy("StickersStore") as {
|
const StickerStore = findStoreLazy("StickersStore") as {
|
||||||
getPremiumPacks(): StickerPack[];
|
getPremiumPacks(): StickerPack[];
|
||||||
getAllGuildStickers(): Map<string, Sticker[]>;
|
getAllGuildStickers(): Map<string, Sticker[]>;
|
||||||
getStickerById(id: string): Sticker | undefined;
|
getStickerById(id: string): Sticker | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
function searchProtoClass(localName: string, parentProtoClass: any) {
|
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||||
if (!parentProtoClass) return;
|
const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS");
|
||||||
|
|
||||||
const field = parentProtoClass.fields.find(field => field.localName === localName);
|
function searchProtoClassField(localName: string, protoClass: any) {
|
||||||
|
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
|
||||||
if (!field) return;
|
if (!field) return;
|
||||||
|
|
||||||
const getter: any = Object.values(field).find(value => typeof value === "function");
|
const fieldGetter = Object.values(field).find(value => typeof value === "function") as any;
|
||||||
return getter?.();
|
return fieldGetter?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass));
|
const PreloadedUserSettingsActionCreators = proxyLazy(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators);
|
||||||
const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto));
|
const AppearanceSettingsActionCreators = proxyLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
|
||||||
|
const ClientThemeSettingsActionsCreators = proxyLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
|
||||||
|
|
||||||
const USE_EXTERNAL_EMOJIS = 1n << 18n;
|
const USE_EXTERNAL_EMOJIS = 1n << 18n;
|
||||||
const USE_EXTERNAL_STICKERS = 1n << 37n;
|
const USE_EXTERNAL_STICKERS = 1n << 37n;
|
||||||
|
@ -176,31 +174,37 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=(\i)=\i\.intention)/,
|
// Create a variable for the intention of listing the emoji
|
||||||
replace: (_, intention) => `,fakeNitroIntention=${intention}`
|
match: /(?<=,intention:(\i).+?;)/,
|
||||||
|
replace: (_, intention) => `let fakeNitroIntention=${intention};`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Send the intention of listing the emoji to the nitro permission check functions
|
||||||
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
|
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
|
||||||
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Disallow the emoji if the intention doesn't allow it
|
||||||
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||||
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Make the emoji always available if the intention allows it
|
||||||
match: /if\(!\i\.available/,
|
match: /if\(!\i\.available/,
|
||||||
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
|
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Allow emojis and animated emojis to be sent everywhere
|
||||||
{
|
{
|
||||||
find: "canUseAnimatedEmojis:function",
|
find: "canUseAnimatedEmojis:function",
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g,
|
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
|
||||||
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Allow stickers to be sent everywhere
|
||||||
{
|
{
|
||||||
find: "canUseStickersEverywhere:function",
|
find: "canUseStickersEverywhere:function",
|
||||||
predicate: () => settings.store.enableStickerBypass,
|
predicate: () => settings.store.enableStickerBypass,
|
||||||
|
@ -209,6 +213,7 @@ export default definePlugin({
|
||||||
replace: "$&return true;"
|
replace: "$&return true;"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Make stickers always available
|
||||||
{
|
{
|
||||||
find: "\"SENDABLE\"",
|
find: "\"SENDABLE\"",
|
||||||
predicate: () => settings.store.enableStickerBypass,
|
predicate: () => settings.store.enableStickerBypass,
|
||||||
|
@ -217,6 +222,7 @@ export default definePlugin({
|
||||||
replace: "true?"
|
replace: "true?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Allow streaming with high quality
|
||||||
{
|
{
|
||||||
find: "canUseHighVideoUploadQuality:function",
|
find: "canUseHighVideoUploadQuality:function",
|
||||||
predicate: () => settings.store.enableStreamQualityBypass,
|
predicate: () => settings.store.enableStreamQualityBypass,
|
||||||
|
@ -230,14 +236,16 @@ export default definePlugin({
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// Remove boost requirements to stream with high quality
|
||||||
{
|
{
|
||||||
find: "STREAM_FPS_OPTION.format",
|
find: "STREAM_FPS_OPTION.format",
|
||||||
predicate: () => settings.store.enableStreamQualityBypass,
|
predicate: () => settings.store.enableStreamQualityBypass,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
|
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Allow client themes to be changeable
|
||||||
{
|
{
|
||||||
find: "canUseClientThemes:function",
|
find: "canUseClientThemes:function",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -249,19 +257,22 @@ export default definePlugin({
|
||||||
find: '.displayName="UserSettingsProtoStore"',
|
find: '.displayName="UserSettingsProtoStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
// Overwrite incoming connection settings proto with our local settings
|
||||||
match: /CONNECTION_OPEN:function\((\i)\){/,
|
match: /CONNECTION_OPEN:function\((\i)\){/,
|
||||||
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
|
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /=(\i)\.local;/,
|
// Overwrite non local proto changes with our local settings
|
||||||
replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);`
|
match: /let{settings:/,
|
||||||
|
replace: "arguments[0].local||$self.handleProtoChange(arguments[0].settings.proto);$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Call our function to handle changing the gradient theme when selecting a new one
|
||||||
{
|
{
|
||||||
find: "updateTheme:function",
|
find: ",updateTheme(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/,
|
match: /(function \i\(\i\){let{backgroundGradientPresetId:(\i).+?)(\i\.\i\.updateAsync.+?theme=(.+?),.+?},\i\))/,
|
||||||
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
|
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -269,11 +280,13 @@ export default definePlugin({
|
||||||
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
|
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
// Call our function to decide whether the emoji link should be kept or not
|
||||||
predicate: () => settings.store.transformEmojis,
|
predicate: () => settings.store.transformEmojis,
|
||||||
match: /1!==(\i)\.length\|\|1!==\i\.length/,
|
match: /1!==(\i)\.length\|\|1!==\i\.length/,
|
||||||
replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])`
|
replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Patch the rendered message content to add fake nitro emojis or remove sticker links
|
||||||
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
||||||
match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/,
|
match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/,
|
||||||
replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);`
|
replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);`
|
||||||
|
@ -281,36 +294,41 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "renderEmbeds=function",
|
find: "renderEmbeds(",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
// Call our function to decide whether the embed should be ignored or not
|
||||||
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
||||||
match: /(renderEmbeds=function\((\i)\){)(.+?embeds\.map\(\(function\((\i)\){)/,
|
match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/,
|
||||||
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
|
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Patch the stickers array to add fake nitro stickers
|
||||||
predicate: () => settings.store.transformStickers,
|
predicate: () => settings.store.transformStickers,
|
||||||
match: /renderStickersAccessories=function\((\i)\){var (\i)=\(0,\i\.\i\)\(\i\),/,
|
match: /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/,
|
||||||
replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message}),`
|
replace: (_, message, stickers) => `${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Filter attachments to remove fake nitro stickers or emojis
|
||||||
predicate: () => settings.store.transformStickers,
|
predicate: () => settings.store.transformStickers,
|
||||||
match: /renderAttachments=function\(\i\){var \i=this,(\i)=\i.attachments.+?;/,
|
match: /renderAttachments\(\i\){let{attachments:(\i).+?;/,
|
||||||
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".STICKER_IN_MESSAGE_HOVER,",
|
find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format",
|
||||||
predicate: () => settings.store.transformStickers,
|
predicate: () => settings.store.transformStickers,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /var (\i)=\i\.renderableSticker,.{0,50}closePopout.+?channel:\i,closePopout:\i,/,
|
// Export the renderable sticker to be used in the fake nitro sticker notice
|
||||||
replace: (m, renderableSticker) => `${m}renderableSticker:${renderableSticker},`
|
match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/,
|
||||||
|
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/,
|
// Add the fake nitro sticker notice
|
||||||
replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!${props}.renderableSticker?.fake)`
|
match: /(let \i,{sticker:\i,channel:\i,closePopout:\i.+?}=(\i).+?;)(.+?description:)(\i)(?=,sticker:\i)/,
|
||||||
|
replace: (_, rest, props, rest2, reactNode) => `${rest}let{fakeNitroRenderableSticker}=${props};${rest2}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!fakeNitroRenderableSticker?.fake)`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -318,7 +336,8 @@ export default definePlugin({
|
||||||
find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,",
|
find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,",
|
||||||
predicate: () => settings.store.transformEmojis,
|
predicate: () => settings.store.transformEmojis,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<=(\i)=\i\.node.+?)/,
|
// Export the emoji node to be used in the fake nitro emoji notice
|
||||||
|
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<={node:(\i),.+?)/,
|
||||||
replace: (m, node) => `${m}fakeNitroNode:${node},`
|
replace: (m, node) => `${m}fakeNitroNode:${node},`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -326,10 +345,12 @@ export default definePlugin({
|
||||||
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
|
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
|
||||||
predicate: () => settings.store.transformEmojis,
|
predicate: () => settings.store.transformEmojis,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
|
// Add the fake nitro emoji notice
|
||||||
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
|
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
||||||
|
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Allow using custom app icons
|
||||||
{
|
{
|
||||||
find: "canUsePremiumAppIcons:function",
|
find: "canUsePremiumAppIcons:function",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -337,6 +358,7 @@ export default definePlugin({
|
||||||
replace: "$&return true;"
|
replace: "$&return true;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Separate patch for allowing using custom app icons
|
||||||
{
|
{
|
||||||
find: "location:\"AppIconHome\"",
|
find: "location:\"AppIconHome\"",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -359,26 +381,30 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
handleProtoChange(proto: any, user: any) {
|
handleProtoChange(proto: any, user: any) {
|
||||||
if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || (!proto.appearance && !AppearanceSettingsProto)) return;
|
if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || !PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators) return;
|
||||||
|
|
||||||
const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0;
|
const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0;
|
||||||
|
|
||||||
if (premiumType !== 2) {
|
if (premiumType !== 2) {
|
||||||
proto.appearance ??= AppearanceSettingsProto.create();
|
proto.appearance ??= AppearanceSettingsActionCreators.create();
|
||||||
|
|
||||||
if (UserSettingsProtoStore.settings.appearance?.theme != null) {
|
if (UserSettingsProtoStore.settings.appearance?.theme != null) {
|
||||||
proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme;
|
const appearanceSettingsDummy = AppearanceSettingsActionCreators.create({
|
||||||
|
theme: UserSettingsProtoStore.settings.appearance.theme
|
||||||
|
});
|
||||||
|
|
||||||
|
proto.appearance.theme = appearanceSettingsDummy.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null && ClientThemeSettingsProto) {
|
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
|
||||||
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
|
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
|
||||||
backgroundGradientPresetId: {
|
backgroundGradientPresetId: {
|
||||||
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
|
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto;
|
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy;
|
||||||
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
|
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -387,26 +413,26 @@ export default definePlugin({
|
||||||
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
|
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
|
||||||
if (premiumType === 2 || backgroundGradientPresetId == null) return original();
|
if (premiumType === 2 || backgroundGradientPresetId == null) return original();
|
||||||
|
|
||||||
if (!AppearanceSettingsProto || !ClientThemeSettingsProto || !ReaderFactory) return;
|
if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !ProtoUtils) return;
|
||||||
|
|
||||||
const currentAppearanceProto = PreloadedUserSettingsProtoHandler.getCurrentValue().appearance;
|
const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance;
|
||||||
|
|
||||||
const newAppearanceProto = currentAppearanceProto != null
|
const newAppearanceProto = currentAppearanceSettings != null
|
||||||
? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory)
|
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS)
|
||||||
: AppearanceSettingsProto.create();
|
: AppearanceSettingsActionCreators.create();
|
||||||
|
|
||||||
newAppearanceProto.theme = theme;
|
newAppearanceProto.theme = theme;
|
||||||
|
|
||||||
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
|
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
|
||||||
backgroundGradientPresetId: {
|
backgroundGradientPresetId: {
|
||||||
value: backgroundGradientPresetId
|
value: backgroundGradientPresetId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto;
|
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummy;
|
||||||
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
|
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
|
||||||
|
|
||||||
const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create();
|
const proto = PreloadedUserSettingsActionCreators.ProtoClass.create();
|
||||||
proto.appearance = newAppearanceProto;
|
proto.appearance = newAppearanceProto;
|
||||||
|
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
|
@ -532,7 +558,7 @@ export default definePlugin({
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return modifyChildren(window._.cloneDeep(content));
|
return modifyChildren(lodash.cloneDeep(content));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
new Logger("FakeNitro").error(err);
|
new Logger("FakeNitro").error(err);
|
||||||
return content;
|
return content;
|
||||||
|
@ -729,7 +755,7 @@ export default definePlugin({
|
||||||
gif.finish();
|
gif.finish();
|
||||||
|
|
||||||
const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" });
|
const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" });
|
||||||
promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
|
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
|
|
@ -87,15 +87,15 @@ export default definePlugin({
|
||||||
authors: [Devs.Alyxia, Devs.Remty],
|
authors: [Devs.Alyxia, Devs.Remty],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "getUserProfile=",
|
find: "UserProfileStore",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=getUserProfile=function\(\i\){return )(\i\[\i\])/,
|
match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/,
|
||||||
replace: "$self.colorDecodeHook($1)"
|
replace: "$self.colorDecodeHook($1)"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
find: ".USER_SETTINGS_PROFILE_THEME_ACCENT",
|
find: ".USER_SETTINGS_PROFILE_THEME_ACCENT",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /RESET_PROFILE_THEME}\)(?<=},color:(\i).+?},color:(\i).+?)/,
|
match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
|
||||||
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
|
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,22 +39,27 @@ export default definePlugin({
|
||||||
description: "Puts your favorite emoji first in the emoji autocomplete.",
|
description: "Puts your favorite emoji first in the emoji autocomplete.",
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".activeCommandOption",
|
find: "renderResults({results:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// = someFunc(a.selectedIndex); ...trackEmojiSearch({ state: theState, isInPopoutExperimental: someBool })
|
// https://regex101.com/r/N7kpLM/1
|
||||||
match: /=\i\(\i\.selectedIndex\);(?=.+?state:(\i),isInPopoutExperiment:\i)/,
|
match: /let \i=.{1,100}renderResults\({results:(\i)\.query\.results,/,
|
||||||
// self.sortEmojis(theState)
|
replace: "$self.sortEmojis($1);$&"
|
||||||
replace: "$&$self.sortEmojis($1);"
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
find: "MAX_AUTOCOMPLETE_RESULTS+",
|
||||||
|
replacement: [
|
||||||
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
|
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
|
||||||
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
|
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
|
||||||
{
|
{
|
||||||
|
// https://regex101.com/r/x2mobQ/1
|
||||||
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
|
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
|
||||||
match: /,maxCount:(\i)(.+?)=(\i)\.slice\(0,(\1-\i\.length)\)/,
|
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(\i-\i\.length)\)/,
|
||||||
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
|
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
|
||||||
replace: ",maxCount:Infinity$2=($3.sliceTo=$4,$3)"
|
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ interface Instance {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput");
|
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow");
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
searchOption: {
|
searchOption: {
|
||||||
|
@ -91,13 +91,13 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "renderCategoryExtras",
|
find: "renderHeaderContent()",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// https://regex101.com/r/4uHtTE/1
|
// https://regex101.com/r/07gpzP/1
|
||||||
// ($1 renderHeaderContent=function { ... switch (x) ... case FAVORITES:return) ($2) ($3 case default:return r.jsx(($<searchComp>), {...props}))
|
// ($1 renderHeaderContent=function { ... switch (x) ... case FAVORITES:return) ($2) ($3 case default:return r.jsx(($<searchComp>), {...props}))
|
||||||
match: /(renderHeaderContent=function.{1,150}FAVORITES:return)(.{1,150};)(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\.\i))/,
|
match: /(renderHeaderContent\(\).{1,150}FAVORITES:return)(.{1,150});(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\..{1,10}),)/,
|
||||||
replace: "$1 this.state.resultType === \"Favorites\" ? $self.renderSearchBar(this, $<searchComp>) : $2; $3"
|
replace: "$1 this.state.resultType === 'Favorites' ? $self.renderSearchBar(this, $<searchComp>) : $2;$3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// to persist filtered favorites when component re-renders.
|
// to persist filtered favorites when component re-renders.
|
||||||
|
|
|
@ -27,18 +27,17 @@ export default definePlugin({
|
||||||
authors: [Devs.D3SOX, Devs.Nickyux],
|
authors: [Devs.D3SOX, Devs.Nickyux],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// This is the logic where it decides whether to render the owner crown or not
|
find: "AVATAR_DECORATION_PADDING:",
|
||||||
find: ".MULTIPLE_AVATAR",
|
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)=(\i)\.isOwner,/,
|
match: /,isOwner:(\i),/,
|
||||||
replace: "$1=$self.isGuildOwner($2),"
|
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
isGuildOwner(props: { user: User, channel: Channel, guildId?: string; }) {
|
isGuildOwner(props: { user: User, channel: Channel, isOwner: boolean, guildId?: string; }) {
|
||||||
if (!props?.user?.id) return false;
|
if (!props?.user?.id) return props.isOwner;
|
||||||
if (props.channel?.type === 3 /* GROUP_DM */)
|
if (props.channel?.type === 3 /* GROUP_DM */)
|
||||||
return false;
|
return props.isOwner;
|
||||||
|
|
||||||
// guild id is in props twice, fallback if the first is undefined
|
// guild id is in props twice, fallback if the first is undefined
|
||||||
const guildId = props.guildId ?? props.channel?.guild_id;
|
const guildId = props.guildId ?? props.channel?.guild_id;
|
||||||
|
|
|
@ -16,16 +16,15 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getSettingStoreLazy } from "@api/SettingsStore";
|
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
|
import { StatusSettingsStores } from "@webpack/common";
|
||||||
|
|
||||||
import style from "./style.css?managed";
|
import style from "./style.css?managed";
|
||||||
|
|
||||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
|
|
||||||
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||||
|
|
||||||
function makeIcon(showCurrentGame?: boolean) {
|
function makeIcon(showCurrentGame?: boolean) {
|
||||||
|
@ -40,7 +39,7 @@ function makeIcon(showCurrentGame?: boolean) {
|
||||||
{!showCurrentGame && <>
|
{!showCurrentGame && <>
|
||||||
<mask id="gameActivityMask" >
|
<mask id="gameActivityMask" >
|
||||||
<rect fill="white" x="0" y="0" width="24" height="24" />
|
<rect fill="white" x="0" y="0" width="24" height="24" />
|
||||||
<path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z"/>
|
<path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z" />
|
||||||
</mask>
|
</mask>
|
||||||
<path fill="var(--status-danger)" d="M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z" />
|
<path fill="var(--status-danger)" d="M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z" />
|
||||||
</>}
|
</>}
|
||||||
|
@ -50,7 +49,7 @@ function makeIcon(showCurrentGame?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function GameActivityToggleButton() {
|
function GameActivityToggleButton() {
|
||||||
const showCurrentGame = ShowCurrentGame?.useSetting();
|
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
@ -58,7 +57,7 @@ function GameActivityToggleButton() {
|
||||||
icon={makeIcon(showCurrentGame)}
|
icon={makeIcon(showCurrentGame)}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={!showCurrentGame}
|
aria-checked={!showCurrentGame}
|
||||||
onClick={() => ShowCurrentGame?.updateSetting(old => !old)}
|
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,7 +66,6 @@ export default definePlugin({
|
||||||
name: "GameActivityToggle",
|
name: "GameActivityToggle",
|
||||||
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||||
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
||||||
dependencies: ["SettingsStoreAPI"],
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[class*="withTagAsButton"] {
|
[class*="withTagAsButton"] {
|
||||||
min-width: 88px;
|
min-width: 88px !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".handleSelectGIF=",
|
find: ".handleSelectGIF=",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.handleSelectGIF=function.+?\{/,
|
match: /\.handleSelectGIF=(\i)=>\{/,
|
||||||
replace: ".handleSelectGIF=function(gif){return $self.handleSelect(gif);"
|
replace: ".handleSelectGIF=$1=>{if (!this.props.className) return $self.handleSelect($1);"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByProps, findByPropsLazy } from "@webpack";
|
||||||
import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common";
|
import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ const settings = definePluginSettings({
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const MessageActions = findByPropsLazy("sendGreetMessage");
|
const MessageActions = findByPropsLazy("sendGreetMessage");
|
||||||
|
const WELCOME_STICKERS = proxyLazy(() => findByProps("WELCOME_STICKERS")?.WELCOME_STICKERS);
|
||||||
|
|
||||||
function greet(channel: Channel, message: Message, stickers: string[]) {
|
function greet(channel: Channel, message: Message, stickers: string[]) {
|
||||||
const options = MessageActions.getSendMessageOptionsForReply({
|
const options = MessageActions.getSendMessageOptionsForReply({
|
||||||
|
@ -75,7 +77,7 @@ function greet(channel: Channel, message: Message, stickers: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], message: Message, channel: Channel; }) {
|
function GreetMenu({ channel, message }: { message: Message, channel: Channel; }) {
|
||||||
const s = settings.use(["greetMode", "multiGreetChoices"]);
|
const s = settings.use(["greetMode", "multiGreetChoices"]);
|
||||||
const { greetMode, multiGreetChoices = [] } = s;
|
const { greetMode, multiGreetChoices = [] } = s;
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
|
||||||
<Menu.MenuGroup
|
<Menu.MenuGroup
|
||||||
label="Greet Stickers"
|
label="Greet Stickers"
|
||||||
>
|
>
|
||||||
{stickers.map(sticker => (
|
{WELCOME_STICKERS.map(sticker => (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
key={sticker.id}
|
key={sticker.id}
|
||||||
id={"greet-" + sticker.id}
|
id={"greet-" + sticker.id}
|
||||||
|
@ -123,7 +125,7 @@ function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], messag
|
||||||
label="Unholy Multi-Greet"
|
label="Unholy Multi-Greet"
|
||||||
id="unholy-multi-greet"
|
id="unholy-multi-greet"
|
||||||
>
|
>
|
||||||
{stickers.map(sticker => {
|
{WELCOME_STICKERS.map(sticker => {
|
||||||
const checked = multiGreetChoices.some(s => s === sticker.id);
|
const checked = multiGreetChoices.some(s => s === sticker.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -168,21 +170,20 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "Messages.WELCOME_CTA_LABEL",
|
find: "Messages.WELCOME_CTA_LABEL",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /innerClassName:\i\(\).welcomeCTAButton,(?<=%\i\.length;return (\i)\[\i\].+?)/,
|
match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
|
||||||
replace: "$&onContextMenu:(e)=>$self.pickSticker(e,$1,arguments[0]),"
|
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
pickSticker(
|
pickSticker(
|
||||||
event: React.UIEvent,
|
event: React.UIEvent,
|
||||||
stickers: Sticker[],
|
|
||||||
props: {
|
props: {
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
message: Message;
|
message: Message;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (!(props.message as any).deleted)
|
if (!(props.message as any).deleted)
|
||||||
ContextMenu.open(event, () => <GreetMenu stickers={stickers} {...props} />);
|
ContextMenu.open(event, () => <GreetMenu {...props} />);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,10 +25,10 @@ export default definePlugin({
|
||||||
authors: [Devs.botato, Devs.Animal],
|
authors: [Devs.botato, Devs.Animal],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "),{hasFlag:",
|
find: "hasFlag:{writable",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(if\((.{1,2})<=1<<30\)return)/,
|
match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
|
||||||
replace: "if($2===(1<<20)){return false};$1",
|
replace: "if($1===(1<<20))return false;$&",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,13 +6,12 @@
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
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 { findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { Tooltip } from "webpack/common";
|
import { StatusSettingsStores, Tooltip } from "webpack/common";
|
||||||
|
|
||||||
const enum ActivitiesTypes {
|
const enum ActivitiesTypes {
|
||||||
Game,
|
Game,
|
||||||
|
@ -26,7 +25,6 @@ interface IgnoredActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
|
|
||||||
|
|
||||||
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||||
const forceUpdate = useForceUpdater();
|
const forceUpdate = useForceUpdater();
|
||||||
|
@ -68,7 +66,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
||||||
|
|
||||||
// Trigger activities recalculation
|
// Trigger activities recalculation
|
||||||
ShowCurrentGame?.updateSetting(old => old);
|
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
|
||||||
forceUpdateButton();
|
forceUpdateButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +83,6 @@ export default definePlugin({
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
description: "Ignore activities from showing up on your status ONLY. 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,
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
@ -93,30 +90,31 @@ export default definePlugin({
|
||||||
find: '.displayName="LocalActivityStore"',
|
find: '.displayName="LocalActivityStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
|
match: /LISTENING.+?}\),(?<=(\i)\.push.+?)/,
|
||||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored),`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(\)\.removeGame.+?null(?<=(\i)\?\i=\i\.\i\.Messages\.SETTINGS_GAMES_NOW_PLAYING_STATE.+?=(\i)\.overlay.+?)/,
|
match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
|
||||||
replace: (m, nowPlaying, props) => `${m},$self.renderToggleGameActivityButton(${props},${nowPlaying})`
|
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.EMBEDDED_ACTIVITIES_DEVELOPER_SHELF_SUBTITLE",
|
find: ".activityTitleText,variant",
|
||||||
replacement: [
|
replacement: {
|
||||||
{
|
match: /(?<=\i\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
||||||
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||||
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=\(\)\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
|
find: ".activityCardDetails,children",
|
||||||
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
replacement: {
|
||||||
}
|
match: /(?<=\i\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
|
||||||
]
|
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,6 @@ export const settings = definePluginSettings({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
preventCarouselFromClosingOnClick: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
// Thanks chat gpt
|
|
||||||
description: "Allow the image modal in the image slideshow thing / carousel to remain open when clicking on the image",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
invertScroll: {
|
invertScroll: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Invert scroll",
|
description: "Invert scroll",
|
||||||
|
@ -101,7 +94,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||||
/>
|
/>
|
||||||
<Menu.MenuCheckboxItem
|
<Menu.MenuCheckboxItem
|
||||||
id="vc-nearest-neighbour"
|
id="vc-nearest-neighbour"
|
||||||
label="Nearset Neighbour"
|
label="Nearest Neighbour"
|
||||||
checked={settings.store.nearestNeighbour}
|
checked={settings.store.nearestNeighbour}
|
||||||
action={() => {
|
action={() => {
|
||||||
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
|
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
|
||||||
|
@ -163,10 +156,14 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"renderLinkComponent","maxWidth"',
|
find: "Messages.OPEN_IN_BROWSER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(return\(.{1,100}\(\)\.wrapper.{1,200})(src)/,
|
// there are 2 image thingies. one for carosuel and one for the single image.
|
||||||
replace: `$1id: '${ELEMENT_ID}',$2`
|
// so thats why i added global flag.
|
||||||
|
// also idk if this patch is good, should it be more specific?
|
||||||
|
// https://regex101.com/r/xfvNvV/1
|
||||||
|
match: /return.{1,200}\.wrapper.{1,200}src:\i,/g,
|
||||||
|
replace: `$&id: '${ELEMENT_ID}',`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -174,26 +171,26 @@ export default definePlugin({
|
||||||
find: "handleImageLoad=",
|
find: "handleImageLoad=",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /showThumbhashPlaceholder:/,
|
match: /showThumbhashPlaceholder:\i,/,
|
||||||
replace: "...$self.makeProps(this),$&"
|
replace: "...$self.makeProps(this),$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
match: /componentDidMount=function\(\){/,
|
match: /componentDidMount\(\){/,
|
||||||
replace: "$&$self.renderMagnifier(this);",
|
replace: "$&$self.renderMagnifier(this);",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
match: /componentWillUnmount=function\(\){/,
|
match: /componentWillUnmount\(\){/,
|
||||||
replace: "$&$self.unMountMagnifier();"
|
replace: "$&$self.unMountMagnifier();"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".carouselModal,",
|
find: ".carouselModal",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /onClick:(\i),/,
|
match: /(?<=\.carouselModal.{0,100}onClick:)\i,/,
|
||||||
replace: "onClick:$self.settings.store.preventCarouselFromClosingOnClick ? () => {} : $1,"
|
replace: "()=>{},"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -15,25 +15,17 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-imgzoom-nearest-neighbor > img {
|
.vc-imgzoom-nearest-neighbor>img {
|
||||||
image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
image-rendering: pixelated;
|
||||||
|
|
||||||
|
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
||||||
[class|="carouselModal"] {
|
[class*="modalCarouselWrapper_"] {
|
||||||
height: fit-content;
|
top: 0 !important;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="modalCarouselWrapper"] {
|
[class*="carouselModal_"] {
|
||||||
height: fit-content;
|
height: 0 !important;
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,15 +131,15 @@ export default definePlugin({
|
||||||
// Indicator
|
// Indicator
|
||||||
find: ".Messages.MESSAGE_EDITED,",
|
find: ".Messages.MESSAGE_EDITED,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /var .,.,.=(.)\.className,.=.\.message,.=.\.children,.=.\.content,.=.\.onUpdate/gm,
|
match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
|
||||||
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".activeCommandOption",
|
find: "ChannelTextAreaButtons",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}",
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -21,8 +21,8 @@ import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { FluxDispatcher, Forms } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||||
|
|
||||||
interface ActivityAssets {
|
interface ActivityAssets {
|
||||||
large_image?: string;
|
large_image?: string;
|
||||||
|
@ -86,15 +86,9 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||||
const logger = new Logger("LastFMRichPresence");
|
const logger = new Logger("LastFMRichPresence");
|
||||||
|
|
||||||
const presenceStore = findByPropsLazy("getLocalPresence");
|
const presenceStore = findByPropsLazy("getLocalPresence");
|
||||||
const assetManager = mapMangledModuleLazy(
|
|
||||||
"getAssetImage: size must === [number, number] for Twitch",
|
|
||||||
{
|
|
||||||
getAsset: filters.byCode("apply("),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActivity(activity: Activity | null) {
|
function setActivity(activity: Activity | null) {
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
// These are Xor encrypted to prevent you from spoiling yourself when you read the source code.
|
// These are Xor encrypted to prevent you from spoiling yourself when you read the source code.
|
||||||
// don't worry about it :P
|
// don't worry about it :P
|
||||||
|
@ -60,17 +61,35 @@ const quotes = [
|
||||||
"Wdn`khc'|f*eghl{%"
|
"Wdn`khc'|f*eghl{%"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
replaceEvents: {
|
||||||
|
description: "Replace Event Quotes too",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "LoadingQuotes",
|
name: "LoadingQuotes",
|
||||||
description: "Replace Discords loading quotes",
|
description: "Replace Discords loading quotes",
|
||||||
authors: [Devs.Ven, Devs.KraXen72],
|
authors: [Devs.Ven, Devs.KraXen72],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".LOADING_DID_YOU_KNOW",
|
find: ".LOADING_DID_YOU_KNOW}",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /\._loadingText=.+?random\(.+?;/s,
|
{
|
||||||
replace: "._loadingText=$self.quote;",
|
match: /\._loadingText=function\(\)\{/,
|
||||||
},
|
replace: "$&return $self.quote;",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /\._eventLoadingText=function\(\)\{/,
|
||||||
|
replace: "$&return $self.quote;",
|
||||||
|
predicate: () => settings.store.replaceEvents
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -105,10 +105,10 @@ export default definePlugin({
|
||||||
authors: [Devs.Ven, Devs.Commandtechno],
|
authors: [Devs.Ven, Devs.Commandtechno],
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".isSidebarVisible,",
|
find: "{isSidebarVisible:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(var (\i)=\i\.className.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||||
replace: "$1:[$2?.startsWith('members')?$self.render():null,$3"
|
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
import { addAccessory } from "@api/MessageAccessories";
|
import { addAccessory } from "@api/MessageAccessories";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
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.js";
|
import { Devs } from "@utils/constants.js";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
|
@ -36,6 +35,7 @@ import {
|
||||||
PermissionStore,
|
PermissionStore,
|
||||||
RestAPI,
|
RestAPI,
|
||||||
Text,
|
Text,
|
||||||
|
TextAndImagesSettingsStores,
|
||||||
UserStore
|
UserStore
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
import { Channel, Guild, Message } from "discord-types/general";
|
import { Channel, Guild, Message } from "discord-types/general";
|
||||||
|
@ -46,12 +46,11 @@ const messageCache = new Map<string, {
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed"));
|
const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed"));
|
||||||
const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes('["message","compact","className",')));
|
const AutoModEmbed = LazyComponent(() => findByCode(".withFooter]:", "childrenMessageContent:"));
|
||||||
|
const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes("renderSimpleAccessories)")));
|
||||||
|
|
||||||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||||
|
|
||||||
let AutoModEmbed: React.ComponentType<any> = () => null;
|
|
||||||
|
|
||||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||||
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
||||||
|
|
||||||
|
@ -319,10 +318,9 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const compactModeEnabled = getSettingStoreLazy<boolean>("textAndImages", "messageDisplayCompact")!;
|
|
||||||
|
|
||||||
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
const { message, channel, guildID } = props;
|
const { message, channel, guildID } = props;
|
||||||
|
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
||||||
const isDM = guildID === "@me";
|
const isDM = guildID === "@me";
|
||||||
const images = getImages(message);
|
const images = getImages(message);
|
||||||
const { parse } = Parser;
|
const { parse } = Parser;
|
||||||
|
@ -338,7 +336,7 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
compact={compactModeEnabled.getSetting()}
|
compact={compact}
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
{message.content || message.attachments.length <= images.length
|
{message.content || message.attachments.length <= images.length
|
||||||
|
@ -365,20 +363,7 @@ export default definePlugin({
|
||||||
name: "MessageLinkEmbeds",
|
name: "MessageLinkEmbeds",
|
||||||
description: "Adds a preview to messages that link another message",
|
description: "Adds a preview to messages that link another message",
|
||||||
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
||||||
dependencies: ["MessageAccessoriesAPI", "SettingsStoreAPI"],
|
dependencies: ["MessageAccessoriesAPI"],
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: ".embedCard",
|
|
||||||
replacement: [{
|
|
||||||
match: /function (\i)\(\i\){var \i=\i\.message,\i=\i\.channel.{0,200}\.hideTimestamp/,
|
|
||||||
replace: "$self.AutoModEmbed=$1;$&"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
set AutoModEmbed(e: any) {
|
|
||||||
AutoModEmbed = e;
|
|
||||||
},
|
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/* Message content highlighting */
|
/* Message content highlighting */
|
||||||
.messagelogger-deleted [class*="contents-"] > :is(div, h1, h2, h3, p) {
|
.messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) {
|
||||||
color: #f04747 !important;
|
color: #f04747 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bot "thinking" text highlighting */
|
/* Bot "thinking" text highlighting */
|
||||||
.messagelogger-deleted [class*="colorStandard-"] {
|
.messagelogger-deleted [class*="colorStandard"] {
|
||||||
color: #f04747 !important;
|
color: #f04747 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,7 @@ export default definePlugin({
|
||||||
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
|
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Based on canary 9ab8626bcebceaea6da570b9c586172d02b9c996
|
// Based on canary 63b8f1b4f2025213c5cf62f0966625bee3d53136
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// MessageStore
|
// MessageStore
|
||||||
|
@ -219,7 +219,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Add deleted=true to all target messages in the MESSAGE_DELETE event
|
// Add deleted=true to all target messages in the MESSAGE_DELETE event
|
||||||
match: /MESSAGE_DELETE:function\((\w)\){var .+?((?:\w{1,2}\.){2})getOrCreate.+?},/,
|
match: /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
|
||||||
replace:
|
replace:
|
||||||
"MESSAGE_DELETE:function($1){" +
|
"MESSAGE_DELETE:function($1){" +
|
||||||
" var cache = $2getOrCreate($1.channelId);" +
|
" var cache = $2getOrCreate($1.channelId);" +
|
||||||
|
@ -229,7 +229,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
|
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
|
||||||
match: /MESSAGE_DELETE_BULK:function\((\w)\){var .+?((?:\w{1,2}\.){2})getOrCreate.+?},/,
|
match: /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
|
||||||
replace:
|
replace:
|
||||||
"MESSAGE_DELETE_BULK:function($1){" +
|
"MESSAGE_DELETE_BULK:function($1){" +
|
||||||
" var cache = $2getOrCreate($1.channelId);" +
|
" var cache = $2getOrCreate($1.channelId);" +
|
||||||
|
@ -239,7 +239,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Add current cached content + new edit time to cached message's editHistory
|
// Add current cached content + new edit time to cached message's editHistory
|
||||||
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/,
|
match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/,
|
||||||
replace: "$1" +
|
replace: "$1" +
|
||||||
".update($3,m =>" +
|
".update($3,m =>" +
|
||||||
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
|
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
|
||||||
|
@ -251,8 +251,8 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// fix up key (edit last message) attempting to edit a deleted message
|
// fix up key (edit last message) attempting to edit a deleted message
|
||||||
match: /(?<=getLastEditableMessage=.{0,200}\.find\(\(function\((\i)\)\{)return/,
|
match: /(?<=getLastEditableMessage\(\i\)\{.{0,200}\.find\((\i)=>)/,
|
||||||
replace: "return !$1.deleted &&"
|
replace: "!$1.deleted &&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -260,13 +260,13 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Message domain model
|
// Message domain model
|
||||||
// Module 451
|
// Module 451
|
||||||
find: "isFirstMessageInForumPost=function",
|
find: "}addReaction(",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(\w)\.customRenderedContent=(\w)\.customRenderedContent;/,
|
match: /this\.customRenderedContent=(\i)\.customRenderedContent,/,
|
||||||
replace: "$1.customRenderedContent = $2.customRenderedContent;" +
|
replace: "this.customRenderedContent = $1.customRenderedContent," +
|
||||||
"$1.deleted = $2.deleted || false;" +
|
"this.deleted = $1.deleted || false," +
|
||||||
"$1.editHistory = $2.editHistory || [];"
|
"this.editHistory = $1.editHistory || [],"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -283,7 +283,7 @@ export default definePlugin({
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
||||||
match: /interactionData:(\w)\.interactionData/,
|
match: /interactionData:(\i)\.interactionData/,
|
||||||
replace:
|
replace:
|
||||||
"interactionData:$1.interactionData," +
|
"interactionData:$1.interactionData," +
|
||||||
"deleted:$1.deleted," +
|
"deleted:$1.deleted," +
|
||||||
|
@ -299,7 +299,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Construct new edited message and add editHistory & deleted (ref above)
|
// Construct new edited message and add editHistory & deleted (ref above)
|
||||||
// Pass in custom data to attachment parser to mark attachments deleted as well
|
// Pass in custom data to attachment parser to mark attachments deleted as well
|
||||||
match: /attachments:(\w{1,2})\((\w)\)/,
|
match: /attachments:(\i)\((\i)\)/,
|
||||||
replace:
|
replace:
|
||||||
"attachments: $1((() => {" +
|
"attachments: $1((() => {" +
|
||||||
" let old = arguments[1]?.attachments;" +
|
" let old = arguments[1]?.attachments;" +
|
||||||
|
@ -315,7 +315,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Preserve deleted attribute on attachments
|
// Preserve deleted attribute on attachments
|
||||||
match: /(\((\w)\){return null==\2\.attachments.+?)spoiler:/,
|
match: /(\((\i)\){return null==\2\.attachments.+?)spoiler:/,
|
||||||
replace:
|
replace:
|
||||||
"$1deleted: arguments[0]?.deleted," +
|
"$1deleted: arguments[0]?.deleted," +
|
||||||
"spoiler:"
|
"spoiler:"
|
||||||
|
@ -326,15 +326,15 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Attachment renderer
|
// Attachment renderer
|
||||||
// Module 96063
|
// Module 96063
|
||||||
find: "().removeAttachmentHoverButton",
|
find: ".removeAttachmentHoverButton",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /((\w)\.className,\w=\2\.attachment),/,
|
match: /(className:\i,attachment:\i),/,
|
||||||
replace: "$1,deleted=$2.attachment?.deleted,"
|
replace: "$1,attachment: {deleted},"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\["className","attachment".+?className:/,
|
match: /\[\i\.obscured\]:.+?,/,
|
||||||
replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +"
|
replace: "$& 'messagelogger-deleted-attachment': deleted,"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -371,11 +371,11 @@ export default definePlugin({
|
||||||
find: "displayName=\"ReferencedMessageStore\"",
|
find: "displayName=\"ReferencedMessageStore\"",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /MESSAGE_DELETE:function\((\w)\).+?},/,
|
match: /MESSAGE_DELETE:function\((\i)\).+?},/,
|
||||||
replace: "MESSAGE_DELETE:function($1){},"
|
replace: "MESSAGE_DELETE:function($1){},"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /MESSAGE_DELETE_BULK:function\((\w)\).+?},/,
|
match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/,
|
||||||
replace: "MESSAGE_DELETE_BULK:function($1){},"
|
replace: "MESSAGE_DELETE_BULK:function($1){},"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -384,7 +384,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Message context base menu
|
// Message context base menu
|
||||||
// Module 600300
|
// Module 600300
|
||||||
find: "id:\"remove-reactions\"",
|
find: "useMessageMenu:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Remove the first section if message is deleted
|
// Remove the first section if message is deleted
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Card, ChannelStore, Forms, GuildStore, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
||||||
import { RC } from "@webpack/types";
|
import { RC } from "@webpack/types";
|
||||||
import { Channel, Message, User } from "discord-types/general";
|
import { Channel, Message, User } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -58,7 +58,6 @@ const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole")
|
||||||
computePermissions({ ...args }): bigint;
|
computePermissions({ ...args }): bigint;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Permissions = findByPropsLazy("SEND_MESSAGES", "VIEW_CREATOR_MONETIZATION_ANALYTICS") as Record<PermissionName, bigint>;
|
|
||||||
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
||||||
|
|
||||||
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
||||||
|
@ -188,17 +187,14 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// add tags to the tag list
|
// add tags to the tag list
|
||||||
{
|
{
|
||||||
find: '.BOT=0]="BOT"',
|
find: "BotTagTypes:",
|
||||||
replacement: [
|
replacement: {
|
||||||
// add tags to the exported tags list (Tag.Types)
|
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
|
||||||
{
|
replace: "($1=$self.getTagTypes()))[$2.BOT"
|
||||||
match: /(\i)\[.\.BOT=0\]="BOT";/,
|
}
|
||||||
replace: "$&$1=$self.addTagVariants($1);"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP;",
|
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP,",
|
||||||
replacement: [
|
replacement: [
|
||||||
// make the tag show the right text
|
// make the tag show the right text
|
||||||
{
|
{
|
||||||
|
@ -213,25 +209,25 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// add HTML data attributes (for easier theming)
|
// add HTML data attributes (for easier theming)
|
||||||
{
|
{
|
||||||
match: /children:\[(?=\i\?null:\i,\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
|
match: /.botText,children:(\i)}\)]/,
|
||||||
replace: "'data-tag':$1.toLowerCase(),children:["
|
replace: "$&,'data-tag':$1.toLowerCase()"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// in messages
|
// in messages
|
||||||
{
|
{
|
||||||
find: ".Types.ORIGINAL_POSTER",
|
find: "renderSystemTag:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /return null==(\i)\?null:\(0,/,
|
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
||||||
replace: "$1=$self.getTag({...arguments[0],origType:$1,location:'chat'});$&"
|
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// in the member list
|
// in the member list
|
||||||
{
|
{
|
||||||
find: ".Messages.GUILD_OWNER,",
|
find: ".Messages.GUILD_OWNER,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<type>\i)=\(null==.{0,50}\.BOT,null!=(?<user>\i)&&\i\.bot/,
|
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
|
||||||
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }), typeof $<type> === 'number'"
|
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// pass channel id down props to be used in profiles
|
// pass channel id down props to be used in profiles
|
||||||
|
@ -251,11 +247,18 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// in profiles
|
// in profiles
|
||||||
{
|
{
|
||||||
find: "showStreamerModeTooltip:",
|
find: ",overrideDiscriminator:",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /,botType:(\i\((\i)\)),/g,
|
{
|
||||||
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
|
// prevent channel id from getting ghosted
|
||||||
}
|
// it's either this or extremely long lookbehind
|
||||||
|
match: /user:\i,nick:\i,/,
|
||||||
|
replace: "$&moreTags_channelId,"
|
||||||
|
}, {
|
||||||
|
match: /,botType:(\i\((\i)\)),/g,
|
||||||
|
replace: ",botType:$self.getTag({user:$2,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -295,24 +298,25 @@ export default definePlugin({
|
||||||
if (!guild) return [];
|
if (!guild) return [];
|
||||||
|
|
||||||
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||||
return Object.entries(Permissions)
|
return Object.entries(PermissionsBits)
|
||||||
.map(([perm, permInt]) =>
|
.map(([perm, permInt]) =>
|
||||||
permissions & permInt ? perm : ""
|
permissions & permInt ? perm : ""
|
||||||
)
|
)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
},
|
},
|
||||||
|
|
||||||
addTagVariants(tagConstant) {
|
getTagTypes() {
|
||||||
|
const obj = {};
|
||||||
let i = 100;
|
let i = 100;
|
||||||
tags.forEach(({ name }) => {
|
tags.forEach(({ name }) => {
|
||||||
tagConstant[name] = ++i;
|
obj[name] = ++i;
|
||||||
tagConstant[i] = name;
|
obj[i] = name;
|
||||||
tagConstant[`${name}-BOT`] = ++i;
|
obj[`${name}-BOT`] = ++i;
|
||||||
tagConstant[i] = `${name}-BOT`;
|
obj[i] = `${name}-BOT`;
|
||||||
tagConstant[`${name}-OP`] = ++i;
|
obj[`${name}-OP`] = ++i;
|
||||||
tagConstant[i] = `${name}-OP`;
|
obj[i] = `${name}-OP`;
|
||||||
});
|
});
|
||||||
return tagConstant;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
|
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
|
||||||
|
@ -377,7 +381,6 @@ export default definePlugin({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,16 +45,16 @@ export default definePlugin({
|
||||||
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
|
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ",acceptInvite:function",
|
find: ",acceptInvite(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /INVITE_ACCEPT_SUCCESS.+?;(\i)=null.+?;/,
|
match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/,
|
||||||
replace: (m, guildId) => `${m}$self.handleMute(${guildId});`
|
replace: (m, guildId) => `${m}$self.handleMute(${guildId});`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "{joinGuild:function",
|
find: "{joinGuild:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /guildId:(\w+),lurker:(\w+).{0,20}\)}\)\);/,
|
match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
|
||||||
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});`
|
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,17 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
||||||
replacement: [
|
replacement: {
|
||||||
{
|
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
|
||||||
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
|
replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||||
replace: '$1?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
find: ".UserProfileSections.USER_INFO_CONNECTIONS:",
|
||||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
|
replacement: {
|
||||||
}
|
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
||||||
]
|
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,11 @@ export default definePlugin({
|
||||||
authors: [Devs.rushii, Devs.Samu],
|
authors: [Devs.rushii, Devs.Samu],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: 'safety_prompt:"DMSpamExperiment",response:"show_redacted_messages"',
|
find: "Messages.BLOCKED_MESSAGES_HIDE",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\.collapsedReason;return/,
|
match: /let\{[^}]*collapsedReason[^}]*\}/,
|
||||||
replace: ".collapsedReason;return null;return;"
|
replace: "return null;$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,8 +26,8 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "setDevtoolsCallbacks",
|
find: "setDevtoolsCallbacks",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\(.{0,10}\|\|"0.0.0"!==.{0,2}\.remoteApp\.getVersion\(\)\)/,
|
match: /if\(null!=\i&&"0.0.0"===\i\.remoteApp\.getVersion\(\)\)/,
|
||||||
replace: "if(false)"
|
replace: "if(true)"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,37 +4,76 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
import style from "./styles.css?managed";
|
import style from "./styles.css?managed";
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
inlineVideo: {
|
||||||
|
description: "Play videos without carousel modal",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
mediaLayoutType: {
|
||||||
|
description: "Choose media layout type",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
restartNeeded: true,
|
||||||
|
options: [
|
||||||
|
{ label: "STATIC, render loading image but image isn't resposive, no problem unless discord window width is too small", value: "STATIC", default: true },
|
||||||
|
{ label: "RESPONSIVE, image is responsive but not render loading image, cause messages shift when loaded", value: "RESPONSIVE" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoMosaic",
|
name: "NoMosaic",
|
||||||
authors: [Devs.AutumnVN],
|
authors: [Devs.AutumnVN],
|
||||||
description: "Removes Discord new image mosaic",
|
description: "Removes Discord new image mosaic",
|
||||||
tags: ["image", "mosaic", "media"],
|
tags: ["image", "mosaic", "media"],
|
||||||
patches: [{
|
|
||||||
find: "Media Mosaic",
|
settings,
|
||||||
replacement: [
|
|
||||||
{
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".oneByTwoLayoutThreeGrid",
|
||||||
|
replacement: [{
|
||||||
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
||||||
replace: 'mediaLayoutType:"RESPONSIVE"',
|
replace: "mediaLayoutType:$self.mediaLayoutType()",
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /\i===\i\.\i\.MOSAIC/,
|
|
||||||
replace: "true",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
||||||
replace: '"INVALID"',
|
replace: '"INVALID"',
|
||||||
},
|
}]
|
||||||
],
|
},
|
||||||
}],
|
{
|
||||||
|
find: "renderAttachments(",
|
||||||
|
predicate: () => settings.store.inlineVideo,
|
||||||
|
replacement: {
|
||||||
|
match: /url:(\i)\.url\}\);return /,
|
||||||
|
replace: "$&$1.content_type?.startsWith('image/')&&"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
|
||||||
|
replacement: {
|
||||||
|
match: /\i===\i\.\i\.MOSAIC/,
|
||||||
|
replace: "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
mediaLayoutType() {
|
||||||
|
return settings.store.mediaLayoutType;
|
||||||
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
enableStyle(style);
|
enableStyle(style);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
disableStyle(style);
|
disableStyle(style);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[class^="nonMediaAttachmentsContainer-"] [class*="messageAttachment-"] {
|
[class^="nonMediaAttachmentsContainer_"] [class*="messageAttachment_"] {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,18 +55,18 @@ export default definePlugin({
|
||||||
// or by searching for "showProgressBadge:"
|
// or by searching for "showProgressBadge:"
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".getPendingCount=",
|
find: "getPendingCount(){",
|
||||||
predicate: () => settings.store.hideFriendRequestsCount,
|
predicate: () => settings.store.hideFriendRequestsCount,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.getPendingCount=function\(\)\{)/,
|
match: /(?<=getPendingCount\(\)\{)/,
|
||||||
replace: "return 0;"
|
replace: "return 0;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".getMessageRequestsCount=",
|
find: "getMessageRequestsCount(){",
|
||||||
predicate: () => settings.store.hideMessageRequestsCount,
|
predicate: () => settings.store.hideMessageRequestsCount,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.getMessageRequestsCount=function\(\)\{)/,
|
match: /(?<=getMessageRequestsCount\(\)\{)/,
|
||||||
replace: "return 0;"
|
replace: "return 0;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -84,8 +84,10 @@ export default definePlugin({
|
||||||
find: "showProgressBadge:",
|
find: "showProgressBadge:",
|
||||||
predicate: () => settings.store.hidePremiumOffersCount,
|
predicate: () => settings.store.hidePremiumOffersCount,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /=\i\.unviewedTrialCount\+\i\.unviewedDiscountCount/,
|
// The two groups inside the first group grab the minified names of the variables,
|
||||||
replace: "=0"
|
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
|
||||||
|
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,200}\i=)\1\+\2/,
|
||||||
|
replace: "0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -30,23 +30,23 @@ export default definePlugin({
|
||||||
// = isPremiumAtLeast(user.premiumType, TIER_2)
|
// = isPremiumAtLeast(user.premiumType, TIER_2)
|
||||||
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
|
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
|
||||||
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
|
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
|
||||||
replace: "=$1?.banner&&"
|
replace: "=(arguments[0]?.bannerSrc||$1?.banner)&&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "().avatarPositionPremiumNoBanner,default:",
|
find: ".avatarPositionPremiumNoBanner,default:",
|
||||||
replacement: {
|
replacement: {
|
||||||
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
|
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
|
||||||
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\(\)\.(\i))/,
|
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\.(\i))/,
|
||||||
// premiumUserWithoutBanner: foo().avatarPositionNormal...
|
// premiumUserWithoutBanner: foo().avatarPositionNormal...
|
||||||
replace: ".$1"
|
replace: ".$1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".hasThemeColors=function(){",
|
find: "hasThemeColors(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=key:"canUsePremiumProfileCustomization",get:function\(\){return)/,
|
match: /get canUsePremiumProfileCustomization\(\){return /,
|
||||||
replace: " false;"
|
replace: "$&false &&"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,14 +25,11 @@ export default definePlugin({
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '("ApplicationStreamPreviewUploadManager")',
|
find: '"ApplicationStreamPreviewUploadManager"',
|
||||||
replacement: [
|
replacement: {
|
||||||
String.raw`\i\.\i\.makeChunkedRequest\(`,
|
match: /await \i\.\i\.(makeChunkedRequest\(|post\(\{url:)\i\.\i\.STREAM_PREVIEW.+?\}\)/g,
|
||||||
String.raw`\i\.\i\.post\({url:`
|
replace: "0"
|
||||||
].map(match => ({
|
}
|
||||||
match: new RegExp(String.raw`(?=return\[(\d),${match}\i\.\i\.STREAM_PREVIEW.+?}\)\];)`),
|
|
||||||
replace: (_, code) => `return[${code},Promise.resolve({body:"",status:204})];`
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,15 +25,15 @@ export default definePlugin({
|
||||||
authors: [Devs.rushii],
|
authors: [Devs.rushii],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "setSystemTrayApplications:function",
|
find: ",setSystemTrayApplications",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /setBadge:function.+?},/,
|
match: /setBadge\(\i\).+?},/,
|
||||||
replace: "setBadge:function(){},"
|
replace: "setBadge(){},"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /setSystemTrayIcon:function.+?},/,
|
match: /setSystemTrayIcon\(\i\).+?},/,
|
||||||
replace: "setSystemTrayIcon:function(){},"
|
replace: "setSystemTrayIcon(){},"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "renderJumpButton=function()",
|
find: "renderJumpButton()",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
|
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
|
||||||
replace: "if(false)$1"
|
replace: "if(false)$1"
|
||||||
|
|
|
@ -45,20 +45,18 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".getDesktopType()===",
|
find: ".getDesktopType()===",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /if\((\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\){/,
|
match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
|
||||||
replace: "if($1){if(!$self.isPrivateChannelRead(arguments[0]?.message))return;"
|
replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /sound:(\i\?\i:void 0,volume:\i,onClick:)/,
|
match: /sound:(\i\?\i:void 0,volume:\i,onClick)/,
|
||||||
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||||
}]
|
}]
|
||||||
}],
|
}],
|
||||||
isPrivateChannelRead(message: MessageJSON) {
|
isPrivateChannelRead(message: MessageJSON) {
|
||||||
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||||
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
|
(channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) ||
|
||||||
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
|
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
|
||||||
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
|
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
|
||||||
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
|
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { showToast, Toasts } from "@webpack/common";
|
||||||
import type { MouseEvent } from "react";
|
import type { MouseEvent } from "react";
|
||||||
|
|
||||||
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||||
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user)\/(.+)(?:\?.+?)?$/;
|
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/;
|
||||||
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
||||||
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
||||||
|
|
||||||
|
@ -53,10 +53,10 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"MaskedLinkStore"',
|
find: "trackAnnouncementMessageLinkClicked({",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,250}\.trusted)/,
|
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/,
|
||||||
replace: "return $self.handleLink(...arguments).then(handled => handled||$1)"
|
replace: "async $& if(await $self.handleLink(...arguments)) return;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Make Spotify profile activity links open in app on web
|
// Make Spotify profile activity links open in app on web
|
||||||
|
@ -71,7 +71,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".CONNECTED_ACCOUNT_VIEWED,",
|
find: ".CONNECTED_ACCOUNT_VIEWED,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=href:\i,onClick:function\(\i\)\{)(?=\i=(\i)\.type,.{0,50}CONNECTED_ACCOUNT_VIEWED)/,
|
match: /(?<=href:\i,onClick:\i=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/,
|
||||||
replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);"
|
replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// Permission lockout, just set the check to true
|
// Permission lockout, just set the check to true
|
||||||
{
|
{
|
||||||
find: "Messages.SELF_DENY_PERMISSION_BODY",
|
find: ".showPermissionLockoutModal(",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
|
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
|
||||||
|
@ -42,11 +42,12 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Onboarding, same thing but we need to prevent the check
|
// Onboarding, same thing but we need to prevent the check
|
||||||
{
|
{
|
||||||
find: "Messages.ONBOARDING_CHANNEL_THRESHOLD_WARNING",
|
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /case 1:if\((?=!\i\.sent.{20,30}Messages\.CANNOT_CHANGE_CHANNEL_PERMS)/,
|
// are we java yet?
|
||||||
replace: "$&false&&"
|
match: /(?<=(?:isDefaultChannelThresholdMetAfterDelete|checkDefaultChannelThresholdMetAfterChannelPermissionDeny):function\(\)\{)return \i(?=\})/g,
|
||||||
|
replace: "return () => true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.onboarding
|
predicate: () => settings.store.onboarding
|
||||||
|
|
|
@ -135,9 +135,9 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
|
||||||
<Text variant="text-md/normal">
|
<Text variant="text-md/normal">
|
||||||
{
|
{
|
||||||
permission.type === PermissionType.Role
|
permission.type === PermissionType.Role
|
||||||
? role?.name || "Unknown Role"
|
? role?.name ?? "Unknown Role"
|
||||||
: permission.type === PermissionType.User
|
: permission.type === PermissionType.User
|
||||||
? (user && getUniqueUsername(user)) || "Unknown User"
|
? (user && getUniqueUsername(user)) ?? "Unknown User"
|
||||||
: (
|
: (
|
||||||
<Flex style={{ gap: "0.2em", justifyItems: "center" }}>
|
<Flex style={{ gap: "0.2em", justifyItems: "center" }}>
|
||||||
@owner
|
@owner
|
||||||
|
|
|
@ -161,7 +161,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS",
|
find: ".popularApplicationCommandIds,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /showBorder:.{0,60}}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
match: /showBorder:.{0,60}}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
||||||
replace: (m, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember}),`
|
replace: (m, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember}),`
|
||||||
|
|
|
@ -20,7 +20,8 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, Co
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { makeLazy } from "@utils/lazy";
|
import { makeLazy } from "@utils/lazy";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { UploadHandler, UserUtils } from "@webpack/common";
|
||||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||||
|
|
||||||
const DRAFT_TYPE = 0;
|
const DRAFT_TYPE = 0;
|
||||||
|
@ -35,8 +36,6 @@ const getFrames = makeLazy(() => Promise.all(
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchUser = findByCodeLazy(".USER(");
|
|
||||||
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
|
|
||||||
const UploadStore = findByPropsLazy("getUploads");
|
const UploadStore = findByPropsLazy("getUploads");
|
||||||
|
|
||||||
function loadImage(source: File | string) {
|
function loadImage(source: File | string) {
|
||||||
|
@ -70,7 +69,7 @@ async function resolveImage(options: Argument[], ctx: CommandContext, noServerPf
|
||||||
return opt.value;
|
return opt.value;
|
||||||
case "user":
|
case "user":
|
||||||
try {
|
try {
|
||||||
const user = await fetchUser(opt.value);
|
const user = await UserUtils.getUser(opt.value);
|
||||||
return user.getAvatarURL(noServerPfp ? void 0 : ctx.guild?.id, 2048).replace(/\?size=\d+$/, "?size=2048");
|
return user.getAvatarURL(noServerPfp ? void 0 : ctx.guild?.id, 2048).replace(/\?size=\d+$/, "?size=2048");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[petpet] Failed to fetch user\n", err);
|
console.error("[petpet] Failed to fetch user\n", err);
|
||||||
|
@ -175,7 +174,7 @@ export default definePlugin({
|
||||||
const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" });
|
const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" });
|
||||||
// Immediately after the command finishes, Discord clears all input, including pending attachments.
|
// Immediately after the command finishes, Discord clears all input, including pending attachments.
|
||||||
// Thus, setTimeout is needed to make this execute after Discord cleared the input
|
// Thus, setTimeout is needed to make this execute after Discord cleared the input
|
||||||
setTimeout(() => promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10);
|
setTimeout(() => UploadHandler.promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,13 +26,12 @@ export default definePlugin({
|
||||||
description: "Adds picture in picture to videos (next to the Download button)",
|
description: "Adds picture in picture to videos (next to the Download button)",
|
||||||
authors: [Devs.Lumap],
|
authors: [Devs.Lumap],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".onRemoveAttachment,",
|
find: ".nonMediaAttachment]",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.nonMediaAttachment,!(\i).{0,7}children:\[(\i),/,
|
match: /\.nonMediaAttachment\]:!(\i).{0,10}children:\[(\S)/,
|
||||||
replace: "$&$1&&$2&&$self.renderPiPButton(),"
|
replace: "$&,$1&&$2&&$self.renderPiPButton(),"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default definePlugin({
|
||||||
// filter Discord's privateChannelIds list to remove pins, and pass
|
// filter Discord's privateChannelIds list to remove pins, and pass
|
||||||
// pinCount as prop. This needs to be here so that the entire DM list receives
|
// pinCount as prop. This needs to be here so that the entire DM list receives
|
||||||
// updates on pin/unpin
|
// updates on pin/unpin
|
||||||
match: /privateChannelIds:(\i),/,
|
match: /(?<=\i,{channels:\i,)privateChannelIds:(\i),/,
|
||||||
replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c)),pinCount:$self.usePinCount($1),"
|
replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c)),pinCount:$self.usePinCount($1),"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -75,39 +75,39 @@ export default definePlugin({
|
||||||
// - Section 1: buttons for pages like Friends & Library
|
// - Section 1: buttons for pages like Friends & Library
|
||||||
// - Section 2: our pinned dms
|
// - Section 2: our pinned dms
|
||||||
// - Section 3: the normal dm list
|
// - Section 3: the normal dm list
|
||||||
match: /(?<=renderRow:(\i)\.renderRow,)sections:\[\i,/,
|
match: /(?<=renderRow:this\.renderRow,)sections:\[\i,/,
|
||||||
// For some reason, adding our sections when no private channels are ready yet
|
// For some reason, adding our sections when no private channels are ready yet
|
||||||
// makes DMs infinitely load. Thus usePinCount returns either a single element
|
// makes DMs infinitely load. Thus usePinCount returns either a single element
|
||||||
// array with the count, or an empty array. Due to spreading, only in the former
|
// array with the count, or an empty array. Due to spreading, only in the former
|
||||||
// case will an element be added to the outer array
|
// case will an element be added to the outer array
|
||||||
// Thanks for the fix, Strencher!
|
// Thanks for the fix, Strencher!
|
||||||
replace: "$&...($1.props.pinCount ?? []),"
|
replace: "$&...this.props.pinCount??[],"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages"
|
// Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages"
|
||||||
// lookbehind is used to lookup parameter name. We could use arguments[0], but
|
// lookbehind is used to lookup parameter name. We could use arguments[0], but
|
||||||
// if children ever is wrapped in an iife, it will break
|
// if children ever is wrapped in an iife, it will break
|
||||||
match: /children:(\i\.\i\.Messages.DIRECT_MESSAGES)(?<=renderSection=function\((\i)\).+?)/,
|
match: /children:(\i\.\i\.Messages.DIRECT_MESSAGES)(?<=renderSection=(\i)=>{.+?)/,
|
||||||
replace: "children:$2.section===1?'Pinned DMs':$1"
|
replace: "children:$2.section===1?'Pinned DMs':$1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Patch channel lookup inside renderDM
|
// Patch channel lookup inside renderDM
|
||||||
// channel=channels[channelIds[row]];
|
// channel=channels[channelIds[row]];
|
||||||
match: /(?<=preRenderedChildren,(\i)=)((\i)\[\i\[\i\]\]);/,
|
match: /(?<=renderDM=\((\i),(\i)\)=>{.*?this.state,\i=\i\[\i\],\i=)((\i)\[\i\]);/,
|
||||||
// section 1 is us, manually get our own channel
|
// section 1 is us, manually get our own channel
|
||||||
// section === 1 ? getChannel(channels, row) : channels[channelIds[row]];
|
// section === 1 ? getChannel(channels, row) : channels[channelIds[row]];
|
||||||
replace: "arguments[0]===1?$self.getChannel($3,arguments[1]):$2;"
|
replace: "$1===1?$self.getChannel($4,$2):$3;"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Fix getRowHeight's check for whether this is the DMs section
|
// Fix getRowHeight's check for whether this is the DMs section
|
||||||
// section === DMS
|
// section === DMS
|
||||||
match: /===\i.DMS&&0/,
|
match: /===\i\.DMS&&0/,
|
||||||
// section -1 === DMS
|
// section -1 === DMS
|
||||||
replace: "-1$&"
|
replace: "-1$&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Override scrollToChannel to properly account for pinned channels
|
// Override scrollToChannel to properly account for pinned channels
|
||||||
match: /(?<=else\{\i\+=)(\i)\*\(.+?(?=;)/,
|
match: /(?<=scrollTo\(\{to:\i\}\):\(\i\+=)(\d+)\*\(.+?(?=,)/,
|
||||||
replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)"
|
replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -115,19 +115,19 @@ export default definePlugin({
|
||||||
|
|
||||||
// Fix Alt Up/Down navigation
|
// Fix Alt Up/Down navigation
|
||||||
{
|
{
|
||||||
find: '"mod+alt+right"',
|
find: ".Routes.APPLICATION_STORE&&",
|
||||||
replacement: {
|
replacement: {
|
||||||
// channelIds = __OVERLAY__ ? stuff : toArray(getStaticPaths()).concat(toArray(channelIds))
|
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
|
||||||
match: /(?<=(\i)=__OVERLAY__\?\i:.{0,10})\.concat\((.{0,10})\)/,
|
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,
|
||||||
// ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
|
// ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
|
||||||
replace: ".concat($self.getSnapshot()).concat($2.filter(c=>!$self.isPinned(c)))"
|
replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// fix alt+shift+up/down
|
// fix alt+shift+up/down
|
||||||
{
|
{
|
||||||
find: '"alt+shift+down"',
|
find: ".getFlattenedGuildIds()],",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=return \i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/,
|
match: /(?<=\i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/,
|
||||||
replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
|
replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
|
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -55,13 +55,13 @@ const Icons = {
|
||||||
};
|
};
|
||||||
type Platform = keyof typeof Icons;
|
type Platform = keyof typeof Icons;
|
||||||
|
|
||||||
const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE");
|
const StatusUtils = findByPropsLazy("getStatusColor", "StatusTypes");
|
||||||
|
|
||||||
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
||||||
const tooltip = platform[0].toUpperCase() + platform.slice(1);
|
const tooltip = platform[0].toUpperCase() + platform.slice(1);
|
||||||
const Icon = Icons[platform] ?? Icons.desktop;
|
const Icon = Icons[platform] ?? Icons.desktop;
|
||||||
|
|
||||||
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} small={small} />;
|
return <Icon color={`var(--${StatusUtils.getStatusColor(status)}`} tooltip={tooltip} small={small} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
|
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
|
||||||
|
@ -198,13 +198,13 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
|
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
|
||||||
match: /(?<=return \i\.\i\.Masks\.STATUS_TYPING;)(.+?)(\i)\?(\i\.\i\.Masks\.STATUS_ONLINE_MOBILE):/,
|
match: /\.STATUS_TYPING;switch(?=.+?(if\(\i\)return \i\.\i\.Masks\.STATUS_ONLINE_MOBILE))/,
|
||||||
replace: (_, rest, isMobile, mobileMask) => `if(${isMobile})return ${mobileMask};${rest}`
|
replace: ".STATUS_TYPING;$1;switch"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
|
// Return the STATUS_ONLINE_MOBILE mask if the user is on mobile, no matter the status
|
||||||
match: /(switch\(\i\){case \i\.\i\.ONLINE:return )(\i)\?({.+?}):/,
|
match: /switch\(\i\)\{case \i\.\i\.ONLINE:(if\(\i\)return\{[^}]+\})/,
|
||||||
replace: (_, rest, isMobile, component) => `if(${isMobile})return${component};${rest}`
|
replace: "$1;$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -230,7 +230,7 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "isMobileOnline=function",
|
find: "}isMobileOnline(",
|
||||||
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
|
predicate: () => Settings.plugins.PlatformIndicators.colorMobileIndicator,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Make isMobileOnline return true no matter what is the user status
|
// Make isMobileOnline return true no matter what is the user status
|
||||||
|
|
|
@ -129,13 +129,13 @@ export default definePlugin({
|
||||||
authors: [Devs.Aria],
|
authors: [Devs.Aria],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".activeCommandOption",
|
find: "ChannelTextAreaButtons",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||||
replace: "$&;try{$2||$1.unshift($self.previewIcon(arguments[0]))}catch{}",
|
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
previewIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,15 +57,15 @@ export default definePlugin({
|
||||||
find: ".userTagNoNickname",
|
find: ".userTagNoNickname",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /,(\i)=(\i)\.pronouns/,
|
match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i;/,
|
||||||
replace: ",[$1,vcPronounSource]=$self.useProfilePronouns($2.user.id)"
|
replace: "$&let vcPronounSource;[$2,vcPronounSource]=$self.useProfilePronouns($1.id);"
|
||||||
},
|
},
|
||||||
PRONOUN_TOOLTIP_PATCH
|
PRONOUN_TOOLTIP_PATCH
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
|
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
|
||||||
{
|
{
|
||||||
find: ".USER_PROFILE_ACTIVITY",
|
find: ".nameTagSmall)",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\.getName\(\i\);(?<=displayProfile.{0,200})/,
|
match: /\.getName\(\i\);(?<=displayProfile.{0,200})/,
|
||||||
|
|
|
@ -37,7 +37,7 @@ export async function onRelationshipRemove({ relationship: { type, id } }: Relat
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await UserUtils.fetchUser(id)
|
const user = await UserUtils.getUser(id)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
|
|
|
@ -31,23 +31,23 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "removeRelationship:function(",
|
find: "removeRelationship:(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(removeRelationship:function\((\i),\i,\i\){)/,
|
match: /(removeRelationship:\((\i),\i,\i\)=>)/,
|
||||||
replace: "$1$self.removeFriend($2);"
|
replace: "$1($self.removeFriend($2),0)||"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "leaveGuild:function(",
|
find: "async leaveGuild(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(leaveGuild:function\((\i)\){)/,
|
match: /(leaveGuild\((\i)\){)/,
|
||||||
replace: "$1$self.removeGuild($2);"
|
replace: "$1$self.removeGuild($2);"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "closePrivateChannel:function(",
|
find: "},closePrivateChannel(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(closePrivateChannel:function\((\i)\){)/,
|
match: /(closePrivateChannel\((\i)\){)/,
|
||||||
replace: "$1$self.removeGroup($2);"
|
replace: "$1$self.removeGroup($2);"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ export async function syncAndRunChecks() {
|
||||||
for (const id of oldFriends.friends) {
|
for (const id of oldFriends.friends) {
|
||||||
if (friends.friends.includes(id)) continue;
|
if (friends.friends.includes(id)) continue;
|
||||||
|
|
||||||
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
const user = await UserUtils.getUser(id).catch(() => void 0);
|
||||||
if (user)
|
if (user)
|
||||||
notify(
|
notify(
|
||||||
`You are no longer friends with ${getUniqueUsername(user)}.`,
|
`You are no longer friends with ${getUniqueUsername(user)}.`,
|
||||||
|
@ -85,7 +85,7 @@ export async function syncAndRunChecks() {
|
||||||
[RelationshipType.FRIEND, RelationshipType.BLOCKED, RelationshipType.OUTGOING_REQUEST].includes(RelationshipStore.getRelationshipType(id))
|
[RelationshipType.FRIEND, RelationshipType.BLOCKED, RelationshipType.OUTGOING_REQUEST].includes(RelationshipStore.getRelationshipType(id))
|
||||||
) continue;
|
) continue;
|
||||||
|
|
||||||
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
const user = await UserUtils.getUser(id).catch(() => void 0);
|
||||||
if (user)
|
if (user)
|
||||||
notify(
|
notify(
|
||||||
`Friend request from ${getUniqueUsername(user)} has been revoked.`,
|
`Friend request from ${getUniqueUsername(user)} has been revoked.`,
|
||||||
|
|
|
@ -30,9 +30,9 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".removeObscurity=function",
|
find: ".removeObscurity=",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.removeObscurity=function\((\i)\){)/,
|
match: /(?<=\.removeObscurity=(\i)=>{)/,
|
||||||
replace: (_, event) => `$self.reveal(${event});`
|
replace: (_, event) => `$self.reveal(${event});`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +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 SupressionIcon = ({ enabled }: { enabled: boolean; }) => enabled
|
|
||||||
? <svg aria-hidden="true" role="img" width="20" height="20" viewBox="0 0 24 24"><path d="M10.889 4C10.889 3.44772 11.3367 3 11.889 3H12.1112C12.6635 3 13.1112 3.44772 13.1112 4V20C13.1112 20.5523 12.6635 21 12.1112 21H11.889C11.3367 21 10.889 20.5523 10.889 20V4Z" fill="currentColor"></path><path d="M6.44439 6.25C6.44439 5.69772 6.89211 5.25 7.44439 5.25H7.66661C8.2189 5.25 8.66661 5.69772 8.66661 6.25V17.75C8.66661 18.3023 8.2189 18.75 7.66661 18.75H7.44439C6.89211 18.75 6.44439 18.3023 6.44439 17.75V6.25Z" fill="currentColor"></path><path d="M3.22222 15.375C3.77451 15.375 4.22222 14.9273 4.22222 14.375L4.22222 9.625C4.22222 9.07272 3.77451 8.625 3.22222 8.625H3C2.44772 8.625 2 9.07272 2 9.625V14.375C2 14.9273 2.44772 15.375 3 15.375H3.22222Z" fill="currentColor"></path><path d="M22.0001 13.25C22.0001 13.8023 21.5523 14.25 21.0001 14.25H20.7778C20.2255 14.25 19.7778 13.8023 19.7778 13.25V10.75C19.7778 10.1977 20.2255 9.75 20.7778 9.75H21.0001C21.5523 9.75 22.0001 10.1977 22.0001 10.75V13.25Z" fill="currentColor"></path><path d="M16.3333 7.5C15.781 7.5 15.3333 7.94772 15.3333 8.5V15.5C15.3333 16.0523 15.781 16.5 16.3333 16.5H16.5555C17.1078 16.5 17.5555 16.0523 17.5555 15.5V8.5C17.5555 7.94772 17.1078 7.5 16.5555 7.5H16.3333Z" fill="currentColor"></path></svg>
|
|
||||||
: <svg aria-hidden="true" role="img" width="20" height="20" viewBox="0 0 48 48"><path d="M30.6666 24.9644L35.1111 20.5199V31C35.1111 32.1046 34.2156 33 33.1111 33H32.6666C31.562 33 30.6666 32.1046 30.6666 31V24.9644Z" fill="currentColor"></path><path d="M26.2224 14.1463V8C26.2224 6.89543 25.327 6 24.2224 6H23.7779C22.6734 6 21.7779 6.89543 21.7779 8V18.5907L26.2224 14.1463Z" fill="currentColor"></path><path d="M21.7779 33.8543L21.9254 33.7056L26.2224 29.4086V40C26.2224 41.1046 25.327 42 24.2224 42H23.7779C22.6734 42 21.7779 41.1046 21.7779 40V33.8543Z" fill="currentColor"></path><path d="M17.3332 23.0354L12.8888 27.4799V12.5C12.8888 11.3954 13.7842 10.5 14.8888 10.5H15.3332C16.4378 10.5 17.3332 11.3954 17.3332 12.5V23.0354Z" fill="currentColor"></path><path d="M8.44445 28.75C8.44445 29.8546 7.54902 30.75 6.44445 30.75H6C4.89543 30.75 4 29.8546 4 28.75V19.25C4 18.1454 4.89543 17.25 6 17.25H6.44445C7.54902 17.25 8.44445 18.1454 8.44445 19.25L8.44445 28.75Z" fill="currentColor"></path><path d="M44.0001 26.5C44.0001 27.6046 43.1047 28.5 42.0001 28.5H41.5557C40.4511 28.5 39.5557 27.6046 39.5557 26.5V21.5C39.5557 20.3954 40.4511 19.5 41.5557 19.5H42.0001C43.1047 19.5 44.0001 20.3954 44.0001 21.5V26.5Z" fill="currentColor"></path><path d="M42 8.54L39.46 6L6 39.46L8.54 42L16.92 33.64L19.38 31.16L22.7 27.84L29.98 20.56L42 8.54Z" fill="currentColor"></path></svg>;
|
|
|
@ -1,250 +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 "./styles.css";
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import { Switch } from "@components/Switch";
|
|
||||||
import { loadRnnoise, RnnoiseWorkletNode } from "@sapphi-red/web-noise-suppressor";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import { rnnoiseWasmSrc, rnnoiseWorkletSrc } from "@utils/dependencies";
|
|
||||||
import { makeLazy } from "@utils/lazy";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { LazyComponent } from "@utils/react";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { findByCode } from "@webpack";
|
|
||||||
import { FluxDispatcher, Popout, React } from "@webpack/common";
|
|
||||||
import { MouseEvent, ReactNode } from "react";
|
|
||||||
|
|
||||||
import { SupressionIcon } from "./icons";
|
|
||||||
|
|
||||||
const RNNOISE_OPTION = "RNNOISE";
|
|
||||||
|
|
||||||
interface PanelButtonProps {
|
|
||||||
tooltipText: string;
|
|
||||||
icon: () => ReactNode;
|
|
||||||
onClick: (event: MouseEvent<HTMLElement>) => void;
|
|
||||||
tooltipClassName?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
shouldShow?: boolean;
|
|
||||||
}
|
|
||||||
const PanelButton = LazyComponent<PanelButtonProps>(() => findByCode("Masks.PANEL_BUTTON"));
|
|
||||||
const enum SpinnerType {
|
|
||||||
SpinningCircle = "spinningCircle",
|
|
||||||
ChasingDots = "chasingDots",
|
|
||||||
LowMotion = "lowMotion",
|
|
||||||
PulsingEllipsis = "pulsingEllipsis",
|
|
||||||
WanderingCubes = "wanderingCubes",
|
|
||||||
}
|
|
||||||
export interface SpinnerProps {
|
|
||||||
type: SpinnerType;
|
|
||||||
animated?: boolean;
|
|
||||||
className?: string;
|
|
||||||
itemClassName?: string;
|
|
||||||
}
|
|
||||||
const Spinner = LazyComponent<SpinnerProps>(() => findByCode(".spinningCircleInner"));
|
|
||||||
|
|
||||||
function createExternalStore<S>(init: () => S) {
|
|
||||||
const subscribers = new Set<() => void>();
|
|
||||||
let state = init();
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: () => state,
|
|
||||||
set: (newStateGetter: (oldState: S) => S) => {
|
|
||||||
state = newStateGetter(state);
|
|
||||||
for (const cb of subscribers) cb();
|
|
||||||
},
|
|
||||||
use: () => {
|
|
||||||
return React.useSyncExternalStore<S>(onStoreChange => {
|
|
||||||
subscribers.add(onStoreChange);
|
|
||||||
return () => subscribers.delete(onStoreChange);
|
|
||||||
}, () => state);
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-rnnoise-");
|
|
||||||
|
|
||||||
const loadedStore = createExternalStore(() => ({
|
|
||||||
isLoaded: false,
|
|
||||||
isLoading: false,
|
|
||||||
isError: false,
|
|
||||||
}));
|
|
||||||
const getRnnoiseWasm = makeLazy(() => {
|
|
||||||
loadedStore.set(s => ({ ...s, isLoading: true }));
|
|
||||||
return loadRnnoise({
|
|
||||||
url: rnnoiseWasmSrc(),
|
|
||||||
simdUrl: rnnoiseWasmSrc(true),
|
|
||||||
}).then(buffer => {
|
|
||||||
// Check WASM magic number cus fetch doesnt throw on 4XX or 5XX
|
|
||||||
if (new DataView(buffer.slice(0, 4)).getUint32(0) !== 0x0061736D) throw buffer;
|
|
||||||
|
|
||||||
loadedStore.set(s => ({ ...s, isLoaded: true }));
|
|
||||||
return buffer;
|
|
||||||
}).catch(error => {
|
|
||||||
if (error instanceof ArrayBuffer) error = new TextDecoder().decode(error);
|
|
||||||
logger.error("Failed to load RNNoise WASM:", error);
|
|
||||||
loadedStore.set(s => ({ ...s, isError: true }));
|
|
||||||
return null;
|
|
||||||
}).finally(() => {
|
|
||||||
loadedStore.set(s => ({ ...s, isLoading: false }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const logger = new Logger("RNNoise");
|
|
||||||
const settings = definePluginSettings({}).withPrivateSettings<{ isEnabled: boolean; }>();
|
|
||||||
const setEnabled = (enabled: boolean) => {
|
|
||||||
settings.store.isEnabled = enabled;
|
|
||||||
FluxDispatcher.dispatch({ type: "AUDIO_SET_NOISE_SUPPRESSION", enabled });
|
|
||||||
};
|
|
||||||
|
|
||||||
function NoiseSupressionPopout() {
|
|
||||||
const { isEnabled } = settings.use();
|
|
||||||
const { isLoading, isError } = loadedStore.use();
|
|
||||||
const isWorking = isEnabled && !isError;
|
|
||||||
|
|
||||||
return <div className={cl("popout")}>
|
|
||||||
<div className={cl("popout-heading")}>
|
|
||||||
<span>Noise Supression</span>
|
|
||||||
<div style={{ flex: 1 }} />
|
|
||||||
{isLoading && <Spinner type={SpinnerType.PulsingEllipsis} />}
|
|
||||||
<Switch checked={isWorking} onChange={setEnabled} disabled={isError} />
|
|
||||||
</div>
|
|
||||||
<div className={cl("popout-desc")}>
|
|
||||||
Enable AI noise suppression! Make some noise—like becoming an air conditioner, or a vending machine fan—while speaking. Your friends will hear nothing but your beautiful voice ✨
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "AI Noise Suppression",
|
|
||||||
description: "Uses an open-source AI model (RNNoise) to remove background noise from your microphone",
|
|
||||||
authors: [Devs.Vap],
|
|
||||||
settings,
|
|
||||||
enabledByDefault: true,
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
// Pass microphone stream to RNNoise
|
|
||||||
find: "window.webkitAudioContext",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=\i\.acquire=function\((\i)\)\{return )navigator\.mediaDevices\.getUserMedia\(\1\)(?=\})/,
|
|
||||||
replace: "$&.then(stream => $self.connectRnnoise(stream, $1.audio))"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Noise suppression button in call modal
|
|
||||||
find: "renderNoiseCancellation()",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=(\i)\.jsxs?.{0,70}children:\[)(?=\i\?\i\.renderNoiseCancellation\(\))/,
|
|
||||||
replace: (_, react) => `${react}.jsx($self.NoiseSupressionButton, {}),`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Give noise suppression component a "shouldShow" prop
|
|
||||||
find: "Masks.PANEL_BUTTON",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<==(\i)\.tooltipForceOpen.{0,100})(?=tooltipClassName:)/,
|
|
||||||
replace: (_, props) => `shouldShow: ${props}.shouldShow,`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Noise suppression option in voice settings
|
|
||||||
find: "Messages.USER_SETTINGS_NOISE_CANCELLATION_KRISP",
|
|
||||||
replacement: [{
|
|
||||||
match: /(?<=(\i)=\i\?\i\.KRISP:\i.{1,20}?;)/,
|
|
||||||
replace: (_, option) => `if ($self.isEnabled()) ${option} = ${JSON.stringify(RNNOISE_OPTION)};`,
|
|
||||||
}, {
|
|
||||||
match: /(?=\i&&(\i)\.push\(\{name:(?:\i\.){1,2}Messages.USER_SETTINGS_NOISE_CANCELLATION_KRISP)/,
|
|
||||||
replace: (_, options) => `${options}.push({ name: "AI (RNNoise)", value: "${RNNOISE_OPTION}" });`,
|
|
||||||
}, {
|
|
||||||
match: /(?<=onChange:function\((\i)\)\{)(?=(?:\i\.){1,2}setNoiseCancellation)/,
|
|
||||||
replace: (_, option) => `$self.setEnabled(${option}.value === ${JSON.stringify(RNNOISE_OPTION)});`,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
setEnabled,
|
|
||||||
isEnabled: () => settings.store.isEnabled,
|
|
||||||
async connectRnnoise(stream: MediaStream, isAudio: boolean): Promise<MediaStream> {
|
|
||||||
if (!isAudio) return stream;
|
|
||||||
if (!settings.store.isEnabled) return stream;
|
|
||||||
|
|
||||||
const audioCtx = new AudioContext();
|
|
||||||
await audioCtx.audioWorklet.addModule(rnnoiseWorkletSrc);
|
|
||||||
|
|
||||||
const rnnoiseWasm = await getRnnoiseWasm();
|
|
||||||
if (!rnnoiseWasm) {
|
|
||||||
logger.warn("Failed to load RNNoise, noise suppression won't work");
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rnnoise = new RnnoiseWorkletNode(audioCtx, {
|
|
||||||
wasmBinary: rnnoiseWasm,
|
|
||||||
maxChannels: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const source = audioCtx.createMediaStreamSource(stream);
|
|
||||||
source.connect(rnnoise);
|
|
||||||
|
|
||||||
const dest = audioCtx.createMediaStreamDestination();
|
|
||||||
rnnoise.connect(dest);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
const onEnded = () => {
|
|
||||||
rnnoise.disconnect();
|
|
||||||
source.disconnect();
|
|
||||||
audioCtx.close();
|
|
||||||
rnnoise.destroy();
|
|
||||||
};
|
|
||||||
stream.addEventListener("inactive", onEnded, { once: true });
|
|
||||||
|
|
||||||
return dest.stream;
|
|
||||||
},
|
|
||||||
NoiseSupressionButton(): ReactNode {
|
|
||||||
const { isEnabled } = settings.use();
|
|
||||||
const { isLoading, isError } = loadedStore.use();
|
|
||||||
|
|
||||||
return <Popout
|
|
||||||
key="rnnoise-popout"
|
|
||||||
align="center"
|
|
||||||
animation={Popout.Animation.TRANSLATE}
|
|
||||||
autoInvert={true}
|
|
||||||
nudgeAlignIntoViewport={true}
|
|
||||||
position="top"
|
|
||||||
renderPopout={() => <NoiseSupressionPopout />}
|
|
||||||
spacing={8}
|
|
||||||
>
|
|
||||||
{(props, { isShown }) => (
|
|
||||||
<PanelButton
|
|
||||||
{...props}
|
|
||||||
tooltipText="Noise Suppression powered by RNNoise"
|
|
||||||
tooltipClassName={cl("tooltip")}
|
|
||||||
shouldShow={!isShown}
|
|
||||||
icon={() => <div style={{
|
|
||||||
color: isError ? "var(--status-danger)" : "inherit",
|
|
||||||
opacity: isLoading ? 0.5 : 1,
|
|
||||||
}}>
|
|
||||||
<SupressionIcon enabled={isEnabled} />
|
|
||||||
</div>}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Popout>;
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,29 +0,0 @@
|
||||||
.vc-rnnoise-popout {
|
|
||||||
background: var(--background-floating);
|
|
||||||
border-radius: 0.25em;
|
|
||||||
padding: 1em;
|
|
||||||
width: 16em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rnnoise-popout-heading {
|
|
||||||
color: var(--text-normal);
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
gap: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rnnoise-popout-desc {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.9em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-rnnoise-tooltip {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
|
@ -49,10 +49,10 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// Chat Mentions
|
// Chat Mentions
|
||||||
{
|
{
|
||||||
find: 'className:"mention"',
|
find: "CLYDE_AI_MENTION_COLOR:null,",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /user:(\i),channel:(\i).{0,300}?"@"\.concat\(.+?\)/,
|
match: /user:(\i),channel:(\i).{0,400}?"@"\.concat\(.+?\)/,
|
||||||
replace: "$&,color:$self.getUserColor($1?.id,{channelId:$2?.id})"
|
replace: "$&,color:$self.getUserColor($1?.id,{channelId:$2?.id})"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -60,37 +60,34 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Slate
|
// Slate
|
||||||
{
|
{
|
||||||
// taken from CommandsAPI
|
find: ".userTooltip,children",
|
||||||
find: ".source,children",
|
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /function \i\((\i)\).{5,20}id.{5,20}guildId.{5,10}channelId.{100,150}hidePersonalInformation.{5,50}jsx.{5,20},{/,
|
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.default,{(?=children)/,
|
||||||
replace: "$&color:$self.getUserColor($1.id,{guildId:$1?.guildId}),"
|
replace: "$&color:$self.getUserColor($1,{guildId:$2}),"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.chatMentions,
|
predicate: () => settings.store.chatMentions,
|
||||||
},
|
},
|
||||||
// Member List Role Names
|
|
||||||
{
|
{
|
||||||
find: ".memberGroupsPlaceholder",
|
find: 'tutorialId:"whos-online',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(memo\(\(function\((\i)\).{300,500}CHANNEL_MEMBERS_A11Y_LABEL.{100,200}roleIcon.{5,20}null,).," \u2014 ",.\]/,
|
match: /\i.roleIcon,\.\.\.\i/,
|
||||||
replace: "$1$self.roleGroupColor($2)]"
|
replace: "$&,color:$self.roleGroupColor(arguments[0])"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /children:\[.," \u2014 ",.\]/,
|
match: /null,\i," — ",\i\]/,
|
||||||
replace: "children:[$self.roleGroupColor(arguments[0])]"
|
replace: "null,$self.roleGroupColor(arguments[0])]"
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.memberList,
|
predicate: () => settings.store.memberList,
|
||||||
},
|
},
|
||||||
// Voice chat users
|
|
||||||
{
|
{
|
||||||
find: "renderPrioritySpeaker",
|
find: "renderPrioritySpeaker",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /renderName=function\(\).{50,75}speaking.{50,100}jsx.{5,10}{/,
|
match: /renderName\(\).{0,100}speaking:.{50,100}jsx.{5,10}{/,
|
||||||
replace: "$&...$self.getVoiceProps(this.props),"
|
replace: "$&...$self.getVoiceProps(this.props),"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -113,11 +110,15 @@ export default definePlugin({
|
||||||
const guild = GuildStore.getGuild(guildId);
|
const guild = GuildStore.getGuild(guildId);
|
||||||
const role = guild?.roles[id];
|
const role = guild?.roles[id];
|
||||||
|
|
||||||
return <span style={{
|
return (
|
||||||
color: role?.colorString,
|
<span style={{
|
||||||
fontWeight: "unset",
|
color: role?.colorString,
|
||||||
letterSpacing: ".05em"
|
fontWeight: "unset",
|
||||||
}}>{title ?? label} — {count}</span>;
|
letterSpacing: ".05em"
|
||||||
|
}}>
|
||||||
|
{title ?? label} — {count}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
|
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue