merge: dev

This commit is contained in:
Lewis Crichton 2023-10-26 21:45:00 +01:00
commit 4dbffcb8b8
No known key found for this signature in database
139 changed files with 1265 additions and 1453 deletions

View file

@ -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 }],

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.5.6", "version": "1.6.0",
"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": {

View file

@ -43,7 +43,7 @@ const nodeCommonOpts = {
format: "cjs", format: "cjs",
platform: "node", platform: "node",
target: ["esnext"], target: ["esnext"],
external: ["electron", ...commonOpts.external], external: ["electron", "original-fs", ...commonOpts.external],
define: defines, define: defines,
}; };

View file

@ -61,6 +61,13 @@ const report = {
otherErrors: [] as string[] otherErrors: [] as string[]
}; };
const IGNORED_DISCORD_ERRORS = [
"KeybindStore: Looking for callback action",
"Unable to process domain list delta: Client revision number is null",
"Downloading the full bad domains file",
/\[GatewaySocket\].{0,110}Cannot access '/
] as Array<string | RegExp>;
function toCodeBlock(s: string) { function toCodeBlock(s: string) {
s = s.replace(/```/g, "`\u200B`\u200B`"); s = s.replace(/```/g, "`\u200B`\u200B`");
return "```" + s + " ```"; return "```" + s + " ```";
@ -86,6 +93,8 @@ async function printReport() {
console.log(` - Error: ${toCodeBlock(p.error)}`); console.log(` - Error: ${toCodeBlock(p.error)}`);
}); });
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
console.log("## Discord Errors"); console.log("## Discord Errors");
report.otherErrors.forEach(e => { report.otherErrors.forEach(e => {
console.log(`- ${toCodeBlock(e)}`); console.log(`- ${toCodeBlock(e)}`);
@ -259,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())

View file

@ -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 });
} }

View file

@ -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));

View file

@ -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));
}

View file

@ -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

View file

@ -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 });

View file

@ -17,6 +17,7 @@
font-size: 20px; font-size: 20px;
height: 20px; height: 20px;
position: relative; position: relative;
text-wrap: nowrap;
} }
.vc-author-modal-name::before { .vc-author-modal-name::before {

View file

@ -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[];

View file

@ -33,7 +33,7 @@ import { useAwaiter } from "@utils/react";
import type { ThemeHeader } from "@utils/themes"; import type { ThemeHeader } from "@utils/themes";
import { getThemeInfo, stripBOM, type UserThemeHeader } from "@utils/themes/bd"; import { getThemeInfo, stripBOM, type UserThemeHeader } from "@utils/themes/bd";
import { usercssParse } from "@utils/themes/usercss"; import { usercssParse } from "@utils/themes/usercss";
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack"; import { findByPropsLazy, findLazy } from "@webpack";
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common"; import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common";
import type { ComponentType, Ref, SyntheticEvent } from "react"; import type { ComponentType, Ref, SyntheticEvent } from "react";
import type { UserstyleHeader } from "usercss-meta"; import type { UserstyleHeader } from "usercss-meta";
@ -49,8 +49,7 @@ type FileInput = ComponentType<{
}>; }>;
const InviteActions = findByPropsLazy("resolveInvite"); const InviteActions = findByPropsLazy("resolveInvite");
const FileInput: FileInput = findByCodeLazy("activateUploadDialogue="); const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const TextAreaProps = findLazy(m => typeof m.textarea === "string");
const cl = classNameFactory("vc-settings-theme-"); const cl = classNameFactory("vc-settings-theme-");

View file

@ -62,6 +62,10 @@ if (IS_VESKTOP || !IS_VANILLA) {
} catch { } } catch { }
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
};
// Remove CSP // Remove CSP
type PolicyResult = Record<string, string[]>; type PolicyResult = Record<string, string[]>;
@ -73,6 +77,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
result[directiveKey] = directiveValue; result[directiveKey] = directiveValue;
} }
}); });
return result; return result;
}; };
const stringifyPolicy = (policy: PolicyResult): string => const stringifyPolicy = (policy: PolicyResult): string =>
@ -81,31 +86,39 @@ if (IS_VESKTOP || !IS_VANILLA) {
.map(directive => directive.flat().join(" ")) .map(directive => directive.flat().join(" "))
.join("; "); .join("; ");
function patchCsp(headers: Record<string, string[]>, header: string) { const patchCsp = (headers: Record<string, string[]>) => {
if (header in headers) { const header = findHeader(headers, "content-security-policy");
if (header) {
const csp = parsePolicy(headers[header][0]); const csp = parsePolicy(headers[header][0]);
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
csp[directive] = ["*", "blob:", "data:", "vencord:", "'unsafe-inline'"]; csp[directive] ??= [];
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
} }
// TODO: Restrict this to only imported packages with fixed version. // TODO: Restrict this to only imported packages with fixed version.
// Perhaps auto generate with esbuild // Perhaps auto generate with esbuild
csp["script-src"] ??= []; csp["script-src"] ??= [];
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com"); csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
headers[header] = [stringifyPolicy(csp)]; headers[header] = [stringifyPolicy(csp)];
} }
} };
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
if (responseHeaders) { if (responseHeaders) {
if (resourceType === "mainFrame") if (resourceType === "mainFrame")
patchCsp(responseHeaders, "content-security-policy"); patchCsp(responseHeaders);
// Fix hosts that don't properly set the css content type, such as // Fix hosts that don't properly set the css content type, such as
// raw.githubusercontent.com // raw.githubusercontent.com
if (resourceType === "stylesheet") if (resourceType === "stylesheet") {
responseHeaders["content-type"] = ["text/css"]; const header = findHeader(responseHeaders, "content-type");
if (header)
responseHeaders[header] = ["text/css"];
}
} }
cb({ cancel: false, responseHeaders }); cb({ cancel: false, responseHeaders });
}); });

View file

@ -17,7 +17,7 @@
*/ */
import { app } from "electron"; import { app } from "electron";
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs"; import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "original-fs";
import { basename, dirname, join } from "path"; import { basename, dirname, join } from "path";
function isNewer($new: string, old: string) { function isNewer($new: string, old: string) {

View file

@ -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 }),$&"
} }
] ]
} }

View file

@ -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"

View file

@ -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]);$&"
} }
}, },
{ {

View file

@ -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]"
}
]
} }
], ],
}); });

View file

@ -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}`,
}, },
}, },
], ],

View file

@ -25,10 +25,10 @@ export default definePlugin({
authors: [Devs.TheSun], authors: [Devs.TheSun],
patches: [ patches: [
{ {
find: ".withMentionPrefix", find: '"Message Username"',
replacement: { replacement: {
match: /(.roleDot.{10,50}{children:.{1,2})}\)/, match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/,
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})" replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))"
} }
} }
], ],

View file

@ -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});`
} }
} }
] ]

View file

@ -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);$&"
} }
] ]
} }

View file

@ -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($&)"
} }
} }
] ]

View file

@ -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]$&"
}
]
}
]
});

View file

@ -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;"
} }
}, },
] ]

View file

@ -63,26 +63,26 @@ export default definePlugin({
replacement: { replacement: {
get match() { get match() {
switch (Settings.plugins.Settings.settingsLocation) { switch (Settings.plugins.Settings.settingsLocation) {
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/; case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/; case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/; case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/; case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/; case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
case "aboveActivity": case "aboveActivity":
default: default:
return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/; return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
} }
}, },
replace: "...$self.makeSettingsCategories($1),$&" replace: "...$self.makeSettingsCategories($1),$&"
} }
}], }],
customSections: [] as ((ID: Record<string, unknown>) => any)[], customSections: [] as ((SectionTypes: Record<string, unknown>) => any)[],
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) { makeSettingsCategories(SectionTypes: Record<string, unknown>) {
return [ return [
{ {
section: ID.HEADER, section: SectionTypes.HEADER,
label: "Vencord", label: "Vencord",
className: "vc-settings-header" className: "vc-settings-header"
}, },
@ -128,9 +128,9 @@ export default definePlugin({
element: require("@components/VencordSettings/PatchHelperTab").default, element: require("@components/VencordSettings/PatchHelperTab").default,
className: "vc-patch-helper" className: "vc-patch-helper"
}, },
...this.customSections.map(func => func(ID)), ...this.customSections.map(func => func(SectionTypes)),
{ {
section: ID.DIVIDER section: SectionTypes.DIVIDER
} }
].filter(Boolean); ].filter(Boolean);
}, },

View file

@ -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;"
} }
} }
] ]

View file

@ -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;
} }

View file

@ -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"
} }
} }

View file

@ -34,17 +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) {
if (props.alt !== "GIF") return props.alt; if (props.alt && props.alt !== "GIF") return props.alt;
let url: string = props.original || props.src; let url: string = props.original || props.src;
try { try {

View file

@ -19,6 +19,9 @@
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
export default definePlugin({ export default definePlugin({
name: "BetterNotesBox", name: "BetterNotesBox",
@ -29,17 +32,31 @@ 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;
}
} }
}, { },
{
find: "Messages.NOTE_PLACEHOLDER", find: "Messages.NOTE_PLACEHOLDER",
replacement: { replacement: {
match: /\.NOTE_PLACEHOLDER,/, match: /\.NOTE_PLACEHOLDER,/,
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck," replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
} }
},
{
find: ".Messages.NOTE}",
replacement: {
match: /(?<=return \i\?)null(?=:\(0,\i\.jsxs)/,
replace: "$self.patchPadding(arguments[0])"
}
} }
], ],
@ -56,5 +73,12 @@ export default definePlugin({
disabled: () => Settings.plugins.BetterNotesBox.hide, disabled: () => Settings.plugins.BetterNotesBox.hide,
default: false default: false
} }
},
patchPadding(e: any) {
if (!e.lastSection) return;
return (
<div className={UserPopoutSectionCssClasses.lastSection}></div>
);
} }
}); });

View file

@ -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,

View file

@ -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})`,
}, },
}, },
], ],

View file

@ -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':'')"
}] }]
} }
], ],

View file

@ -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)]"
} }
}], }],

View file

@ -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"'
} }
} }
] ]

View file

@ -22,23 +22,15 @@ 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)\//, ""); return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
return (await assetManager.getAsset(settings.store.appID, [key, undefined]))[0];
} }
interface ActivityAssets { interface ActivityAssets {

View file

@ -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

View file

@ -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;"
} }
} }

View file

@ -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

View file

@ -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,26 +64,26 @@ 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 // Fix search history being disabled / broken with isStaff
{ {
find: 'get("disable_new_search")', find: '("showNewSearch")',
predicate: () => settings.store.enableIsStaff, predicate: () => settings.store.enableIsStaff,
replacement: { replacement: {
match: /(?<=showNewSearch"\);return)\s?!/, match: /(?<=showNewSearch"\);return)\s?/,
replace: "!1&&!" replace: "!1&&"
} }
}, },
{ {

View file

@ -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)=\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,8 +345,25 @@ 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",
replacement: {
match: /canUsePremiumAppIcons:function\(\i\){/,
replace: "$&return true;"
}
},
// Separate patch for allowing using custom app icons
{
find: "location:\"AppIconHome\"",
replacement: {
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
replace: "true"
} }
} }
], ],
@ -345,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;
} }
} }
}, },
@ -373,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({
@ -518,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;
@ -715,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() {

View file

@ -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})"
} }
} }

View file

@ -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)"
} }
] ]
} }

View file

@ -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.
@ -182,7 +182,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
ref={ref} ref={ref}
autoFocus={true} autoFocus={true}
className={containerClasses.searchBar} className={containerClasses.searchBar}
size={SearchBarComponent.Sizes.MEDIUM} size={SearchBarComponent.Sizes.SMALL}
onChange={onChange} onChange={onChange}
onClear={() => { onClear={() => {
setQuery(""); setQuery("");

View file

@ -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;

View file

@ -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: [
{ {

View file

@ -1,3 +1,3 @@
[class*="withTagAsButton"] { [class*="withTagAsButton"] {
min-width: 88px; min-width: 88px !important;
} }

View file

@ -33,7 +33,7 @@ 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=function(gif){return $self.handleSelect(gif);"
} }
}], }],

View file

@ -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} />);
} }
}); });

View file

@ -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;$&",
}, },
}, },
], ],

View file

@ -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_HAVE_PLAYED_ONE_KNOWN", 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}),`
}
} }
], ],

View file

@ -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,28 +171,21 @@ 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,",
replacement: {
match: /onClick:(\i),/,
replace: "onClick:$self.settings.store.preventCarouselFromClosingOnClick ? () => {} : $1,"
}
}
], ],
settings, settings,

View file

@ -25,12 +25,6 @@
box-shadow: none; box-shadow: none;
} }
[class*="modalCarouselWrapper"] {
height: fit-content;
top: 50%;
transform: translateY(-50%);
}
[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) { [class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) {
position: absolute; position: absolute;
left: 50%; left: 50%;

View file

@ -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{}})()",
} }
}, },
], ],

View file

@ -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) {

View file

@ -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
}
],
}, },
], ],

View file

@ -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"
} }
}], }],

View file

@ -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,

View file

@ -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;
} }

View file

@ -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,"
} }
] ]
}, },
@ -360,7 +360,7 @@ export default definePlugin({
{ {
// Render editHistory in the deepest div for message content // Render editHistory in the deepest div for message content
match: /(\)\("div",\{id:.+?children:\[)/, match: /(\)\("div",\{id:.+?children:\[)/,
replace: "$1 (arguments[0].message.editHistory.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), " replace: "$1 (arguments[0].message.editHistory?.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
} }
] ]
}, },
@ -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

View file

@ -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;
} }
}); });

View file

@ -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});`
} }
} }

View file

@ -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);"
}
} }
], ],

View file

@ -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;$&"
} }
] ]
}, },

View file

@ -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)"
} }
}] }]
}); });

View file

@ -15,23 +15,25 @@ export default definePlugin({
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: [{ patches: [
find: "Media Mosaic", {
replacement: [ find: ".oneByTwoLayoutThreeGrid",
{ replacement: [{
match: /mediaLayoutType:\i\.\i\.MOSAIC/, match: /mediaLayoutType:\i\.\i\.MOSAIC/,
replace: 'mediaLayoutType:"RESPONSIVE"', replace: 'mediaLayoutType:"RESPONSIVE"'
},
{
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: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
replacement: {
match: /\i===\i\.\i\.MOSAIC/,
replace: "true"
}
}],
start() { start() {
enableStyle(style); enableStyle(style);
}, },

View file

@ -1,3 +1,3 @@
[class^="nonMediaAttachmentsContainer-"] [class*="messageAttachment-"] { [class^="nonMediaAttachmentsContainer_"] [class*="messageAttachment_"] {
position: relative; position: relative;
} }

View file

@ -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: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/, // The two groups inside the first group grab the minified names of the variables,
replace: "(function(){return 0})" // they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,200}\i=)\1\+\2/,
replace: "0"
} }
} }
], ],

View file

@ -34,19 +34,19 @@ export default definePlugin({
} }
}, },
{ {
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 &&"
} }
} }
] ]

View file

@ -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})];`
}))
} }
] ]
}); });

View file

@ -0,0 +1,21 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoTypingAnimation",
authors: [Devs.AutumnVN],
description: "Disables the CPU-intensive typing dots animation",
patches: [{
find: "dotCycle",
replacement: {
match: /document.hasFocus\(\)/,
replace: "false"
}
}]
});

View file

@ -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"

View file

@ -4,36 +4,68 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
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";
import { ChannelStore, ReadStateStore } from "@webpack/common"; import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { MessageJSON } from "discord-types/general";
const enum ChannelType { const enum ChannelType {
DM = 1, DM = 1,
GROUP_DM = 3 GROUP_DM = 3
} }
const settings = definePluginSettings({
channelToAffect: {
type: OptionType.SELECT,
description: "Select the type of DM for the plugin to affect",
options: [
{ label: "Both", value: "both_dms", default: true },
{ label: "User DMs", value: "user_dm" },
{ label: "Group DMs", value: "group_dm" },
]
},
allowMentions: {
type: OptionType.BOOLEAN,
description: "Receive audio pings for @mentions",
default: false,
},
allowEveryone: {
type: OptionType.BOOLEAN,
description: "Receive audio pings for @everyone and @here in group DMs",
default: false,
},
});
export default definePlugin({ export default definePlugin({
name: "OnePingPerDM", name: "OnePingPerDM",
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit", description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
authors: [Devs.ProffDea], authors: [Devs.ProffDea],
settings,
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: Message) { 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) { if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
return false; return false;
} }
if (
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
(settings.store.allowEveryone && message.mention_everyone)
) {
return true;
}
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id; return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
}, },
}); });

View file

@ -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)\}.+?)async function \1\(.+?\)\{/,
replace: "return $self.handleLink(...arguments).then(handled => handled||$1)" replace: "$& 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);"
} }
} }

View file

@ -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

View file

@ -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}),`

View file

@ -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);
}, },
}, },
] ]

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import "./styles.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -26,13 +24,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\].{0,10}children:\[\S/,
replace: "$&$1&&$2&&$self.renderPiPButton()," replace: "$&&&$self.renderPiPButton(),"
}, },
}, },
], ],
@ -43,7 +40,6 @@ export default definePlugin({
{tooltipProps => ( {tooltipProps => (
<div <div
{...tooltipProps} {...tooltipProps}
className="vc-pip-button"
role="button" role="button"
style={{ style={{
cursor: "pointer", cursor: "pointer",
@ -74,7 +70,7 @@ export default definePlugin({
> >
<svg width="24px" height="24px" viewBox="0 0 24 24"> <svg width="24px" height="24px" viewBox="0 0 24 24">
<path <path
fill="currentColor" fill="var(--interactive-normal)"
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z" d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
/> />
</svg> </svg>

View file

@ -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)))"
} }
}, },

View file

@ -23,20 +23,20 @@ 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";
const SessionsStore = findStoreLazy("SessionsStore"); const SessionsStore = findStoreLazy("SessionsStore");
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) { function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
return ({ color, tooltip }: { color: string; tooltip: string; }) => ( return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => (
<Tooltip text={tooltip} > <Tooltip text={tooltip} >
{(tooltipProps: any) => ( {(tooltipProps: any) => (
<svg <svg
{...tooltipProps} {...tooltipProps}
height={opts?.height ?? 20} height={(opts?.height ?? 20) - (small ? 3 : 0)}
width={opts?.width ?? 20} width={(opts?.width ?? 20) - (small ? 3 : 0)}
viewBox={opts?.viewBox ?? "0 0 24 24"} viewBox={opts?.viewBox ?? "0 0 24 24"}
fill={color} fill={color}
> >
@ -55,18 +55,18 @@ 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 }: { platform: Platform, status: string; }) => { 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} />; 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];
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; }) => { const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
if (!user || user.bot) return null; if (!user || user.bot) return null;
if (user.id === UserStore.getCurrentUser().id) { if (user.id === UserStore.getCurrentUser().id) {
@ -99,6 +99,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: {
key={platform} key={platform}
platform={platform as Platform} platform={platform as Platform}
status={status} status={status}
small={small}
/> />
)); ));
@ -137,7 +138,7 @@ const indicatorLocations = {
description: "In the member list", description: "In the member list",
onEnable: () => addDecorator("platform-indicator", props => onEnable: () => addDecorator("platform-indicator", props =>
<ErrorBoundary noop> <ErrorBoundary noop>
<PlatformIndicator user={props.user} /> <PlatformIndicator user={props.user} small={true} />
</ErrorBoundary> </ErrorBoundary>
), ),
onDisable: () => removeDecorator("platform-indicator") onDisable: () => removeDecorator("platform-indicator")
@ -197,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;$&"
} }
] ]
}, },
@ -229,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

View file

@ -129,10 +129,10 @@ 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{}})()",
} }
}, },
], ],

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
@ -39,17 +40,17 @@ function shouldShow(message: Message): boolean {
return true; return true;
} }
export function PronounsChatComponentWrapper({ message }: { message: Message; }) { export const PronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
return shouldShow(message) return shouldShow(message)
? <PronounsChatComponent message={message} /> ? <PronounsChatComponent message={message} />
: null; : null;
} }, { noop: true });
export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) { export const CompactPronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
return shouldShow(message) return shouldShow(message)
? <CompactPronounsChatComponent message={message} /> ? <CompactPronounsChatComponent message={message} />
: null; : null;
} }, { noop: true });
function PronounsChatComponent({ message }: { message: Message; }) { function PronounsChatComponent({ message }: { message: Message; }) {
const [result] = useFormattedPronouns(message.author.id); const [result] = useFormattedPronouns(message.author.id);
@ -63,7 +64,7 @@ function PronounsChatComponent({ message }: { message: Message; }) {
: null; : null;
} }
export function CompactPronounsChatComponent({ message }: { message: Message; }) { export const CompactPronounsChatComponent = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
const [result] = useFormattedPronouns(message.author.id); const [result] = useFormattedPronouns(message.author.id);
return result return result
@ -73,4 +74,4 @@ export function CompactPronounsChatComponent({ message }: { message: Message; })
> {result}</span> > {result}</span>
) )
: null; : null;
} }, { noop: true });

View file

@ -41,7 +41,7 @@ export default definePlugin({
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
replacement: { replacement: {
match: /("span",{id:\i,className:\i,children:\i}\))/, match: /("span",{id:\i,className:\i,children:\i}\))/,
replace: "$1, $self.CompactPronounsChatComponentWrapper(e)" replace: "$1, $self.CompactPronounsChatComponentWrapper(arguments[0])"
} }
}, },
// Patch the chat timestamp element (normal mode) // Patch the chat timestamp element (normal mode)
@ -49,7 +49,7 @@ export default definePlugin({
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
replacement: { replacement: {
match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/, match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/,
replace: "[$1, $self.PronounsChatComponentWrapper(e)]" replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]"
} }
}, },
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns // Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
@ -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})/,

View file

@ -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;

View file

@ -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);"
} }
} }

View file

@ -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.`,

View file

@ -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});`
} }
} }

View file

@ -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>;

View file

@ -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&mdash;like becoming an air conditioner, or a vending machine fan&mdash;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>;
},
});

View file

@ -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;
}

View file

@ -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} &mdash; {count}</span>; letterSpacing: ".05em"
}}>
{title ?? label} &mdash; {count}
</span>
);
}, },
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) { getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {

View file

@ -1,35 +1,24 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors * Copyright (c) 2023 Vendicated and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* This program is free software: you can redistribute it and/or modify */
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
// NOTE - Ultimately should probably be turned into a ringtone picker plugin
export default definePlugin({ export default definePlugin({
name: "SecretRingToneEnabler", name: "SecretRingToneEnabler",
description: "Always play the secret version of the discord ringtone", description: "Always play the secret version of the discord ringtone (except during special ringtone events)",
authors: [Devs.AndrewDLO], authors: [Devs.AndrewDLO, Devs.FieryFlames],
patches: [ patches: [
{ {
find: "84a1b4e11d634dbfa1e5dd97a96de3ad", find: "call_ringing_beat\"",
replacement: { replacement: {
match: "84a1b4e11d634dbfa1e5dd97a96de3ad.mp3", match: /500===\i\.random\(1,1e3\)/,
replace: "b9411af07f154a6fef543e7e442e4da9.mp3", replace: "true"
}, }
}, },
], ],
}); });

View file

@ -121,10 +121,10 @@ export default definePlugin({
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.push($self.chatBarIcon(arguments[0]))}catch{}", replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
} }
}, },
], ],

View file

@ -11,13 +11,13 @@ import { openImageModal, openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { LazyComponent, useAwaiter } from "@utils/react"; import { LazyComponent, useAwaiter } from "@utils/react";
import { findByCode, findByPropsLazy } from "@webpack"; import { findByProps, findByPropsLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
const IconUtils = findByPropsLazy("getGuildBannerURL"); const IconUtils = findByPropsLazy("getGuildBannerURL");
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
const UserRow = LazyComponent(() => findByCode(".listDiscriminator")); const FriendRow = LazyComponent(() => findByProps("FriendRow").FriendRow);
const cl = classNameFactory("vc-gp-"); const cl = classNameFactory("vc-gp-");
@ -162,7 +162,7 @@ function Owner(guildId: string, owner: User) {
} }
function ServerInfoTab({ guild }: GuildProps) { function ServerInfoTab({ guild }: GuildProps) {
const [owner] = useAwaiter(() => UserUtils.fetchUser(guild.ownerId), { const [owner] = useAwaiter(() => UserUtils.getUser(guild.ownerId), {
deps: [guild.ownerId], deps: [guild.ownerId],
fallbackValue: null fallbackValue: null
}); });
@ -235,7 +235,7 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC
return ( return (
<ScrollerThin fade className={cl("scroller")}> <ScrollerThin fade className={cl("scroller")}>
{members.map(id => {members.map(id =>
<UserRow <FriendRow
user={UserStore.getUser(id)} user={UserStore.getUser(id)}
status={PresenceStore.getStatus(id) || "offline"} status={PresenceStore.getStatus(id) || "offline"}
onSelect={() => openUserProfile(id)} onSelect={() => openUserProfile(id)}

View file

@ -37,12 +37,12 @@ export default definePlugin({
authors: [Devs.Vap], authors: [Devs.Vap],
patches: [ patches: [
{ {
find: "codeBlock:{react:function", find: "codeBlock:{react(",
replacement: { replacement: {
match: /codeBlock:\{react:function\((\i),(\i),(\i)\)\{/, match: /codeBlock:\{react\((\i),(\i),(\i)\)\{/,
replace: "$&return $self.renderHighlighter($1,$2,$3);", replace: "$&return $self.renderHighlighter($1,$2,$3);"
}, }
}, }
], ],
start: async () => { start: async () => {
if (settings.store.useDevIcon !== DeviconSetting.Disabled) if (settings.store.useDevIcon !== DeviconSetting.Disabled)

View file

@ -28,9 +28,8 @@ export default definePlugin({
{ {
find: ".Messages.MESSAGE_UTILITIES_A11Y_LABEL", find: ".Messages.MESSAGE_UTILITIES_A11Y_LABEL",
replacement: { replacement: {
// isExpanded: V, (?<=,V = shiftKeyDown && !H...,|;) match: /isExpanded:\i&&(.+?),/,
match: /isExpanded:(\i),(?<=,\1=\i&&(?=(!.+?)[,;]).+?)/, replace: "isExpanded:$1,"
replace: "isExpanded:$2,"
} }
} }
] ]

View file

@ -32,7 +32,7 @@ import { User } from "discord-types/general";
import { VerifiedIcon } from "./VerifiedIcon"; import { VerifiedIcon } from "./VerifiedIcon";
const Section = LazyComponent(() => findByCode("().lastSection")); const Section = LazyComponent(() => findByCode(".lastSection]:"));
const ThemeStore = findStoreLazy("ThemeStore"); const ThemeStore = findStoreLazy("ThemeStore");
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"'); const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
@ -74,12 +74,12 @@ interface ConnectionPlatform {
icon: { lightSVG: string, darkSVG: string; }; icon: { lightSVG: string, darkSVG: string; };
} }
const profilePopoutComponent = ErrorBoundary.wrap(e => const profilePopoutComponent = ErrorBoundary.wrap(({ user, displayProfile }: { user: User, displayProfile; }) =>
<ConnectionsComponent id={e.user.id} theme={getTheme(e.user, e.displayProfile).profileTheme} /> <ConnectionsComponent id={user.id} theme={getTheme(user, displayProfile).profileTheme} />
); );
const profilePanelComponent = ErrorBoundary.wrap(e => const profilePanelComponent = ErrorBoundary.wrap(({ id }: { id: string; }) =>
<ConnectionsComponent id={e.channel.recipients[0]} theme={ThemeStore.theme} /> <ConnectionsComponent id={id} theme={ThemeStore.theme} />
); );
function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
@ -175,18 +175,18 @@ export default definePlugin({
authors: [Devs.TheKodeToad], authors: [Devs.TheKodeToad],
patches: [ patches: [
{ {
find: ".Messages.BOT_PROFILE_SLASH_COMMANDS", find: "{isUsingGuildBio:null!==(",
replacement: { replacement: {
match: /,theme:\i\}\)(?=,.{0,100}setNote:)/, match: /,theme:\i\}\)(?=,.{0,150}setNote:)/,
replace: "$&,$self.profilePopoutComponent(arguments[0])" replace: "$&,$self.profilePopoutComponent({ user: arguments[0].user, displayProfile: arguments[0].displayProfile })"
} }
}, },
{ {
find: "\"Profile Panel: user cannot be undefined\"", find: "\"Profile Panel: user cannot be undefined\"",
replacement: { replacement: {
// createElement(Divider, {}), createElement(NoteComponent) // createElement(Divider, {}), createElement(NoteComponent)
match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:/, match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/,
replace: "$self.profilePanelComponent(arguments[0]),$&" replace: "$self.profilePanelComponent({ id: $1.recipients[0] }),$&"
} }
} }
], ],

View file

@ -20,14 +20,13 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { formatDuration } from "@utils/text"; import { formatDuration } from "@utils/text";
import { find, findByPropsLazy } from "@webpack"; import { find, findByCode, findByPropsLazy } from "@webpack";
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
import type { Channel } from "discord-types/general"; import type { Channel } from "discord-types/general";
import type { ComponentType } from "react";
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
import { sortPermissionOverwrites } from "../../permissionsViewer/utils"; import { sortPermissionOverwrites } from "../../permissionsViewer/utils";
import { settings, VIEW_CHANNEL } from ".."; import { settings } from "..";
const enum SortOrderTypes { const enum SortOrderTypes {
LATEST_ACTIVITY = 0, LATEST_ACTIVITY = 0,
@ -79,19 +78,15 @@ const enum ChannelFlags {
REQUIRE_TAG = 1 << 4 REQUIRE_TAG = 1 << 4
} }
let ChannelBeginHeader: ComponentType<any>;
export function setChannelBeginHeaderComponent(component: ComponentType<any>) {
ChannelBeginHeader = component;
}
const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase");
const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent"); const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent");
const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"));
const TagComponent = LazyComponent(() => find(m => { const TagComponent = LazyComponent(() => find(m => {
if (typeof m !== "function") return false; if (typeof m !== "function") return false;
const code = Function.prototype.toString.call(m); const code = Function.prototype.toString.call(m);
// Get the component which doesn't include increasedActivity logic // Get the component which doesn't include increasedActivity
return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill");
})); }));
@ -185,7 +180,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> <img className="shc-lock-screen-logo" src={HiddenChannelLogo} />
<div className="shc-lock-screen-heading-container"> <div className="shc-lock-screen-heading-container">
<Text variant="heading-xxl/bold">This is a {!PermissionStore.can(VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel.</Text> <Text variant="heading-xxl/bold">This is a {!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel.</Text>
{channel.isNSFW() && {channel.isNSFW() &&
<Tooltip text="NSFW"> <Tooltip text="NSFW">
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (

View file

@ -24,16 +24,13 @@ import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common"; import { ChannelStore, PermissionsBits, PermissionStore, Tooltip } from "@webpack/common";
import type { Channel, Role } from "discord-types/general"; import type { Channel, Role } from "discord-types/general";
import HiddenChannelLockScreen, { setChannelBeginHeaderComponent } from "./components/HiddenChannelLockScreen"; import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
const ChannelListClasses = findByPropsLazy("channelEmoji", "unread", "icon"); const ChannelListClasses = findByPropsLazy("channelEmoji", "unread", "icon");
export const VIEW_CHANNEL = 1n << 10n;
const CONNECT = 1n << 20n;
const enum ShowMode { const enum ShowMode {
LockIcon, LockIcon,
HiddenIconWithMutedStyle HiddenIconWithMutedStyle
@ -72,16 +69,10 @@ export default definePlugin({
{ {
// RenderLevel defines if a channel is hidden, collapsed in category, visible, etc // RenderLevel defines if a channel is hidden, collapsed in category, visible, etc
find: ".CannotShow=", find: ".CannotShow=",
// These replacements only change the necessary CannotShow's
replacement: [ replacement: [
{ {
match: /(?<=isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(\i)\..+?(?=,)/, match: /if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{if\(this\.id===\i\).+?threadIds:\i}}/,
replace: (_, RenderLevels) => `this.category.isCollapsed?${RenderLevels}.WouldShowIfUncollapsed:${RenderLevels}.Show` replace: ""
},
// Move isChannelGatedAndVisible renderLevel logic to the bottom to not show hidden channels in case they are muted
{
match: /(?<=(if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{)if\(this\.id===\i\).+?};)(if\(!\i\.\i\.isChannelGatedAndVisible\(.+?})(.+?)(?=return{renderLevel:\i\.Show.{0,40}?return \i)/,
replace: (_, permissionCheck, isChannelGatedAndVisibleCondition, rest) => `${rest}${permissionCheck}${isChannelGatedAndVisibleCondition}}`
}, },
{ {
match: /(?<=renderLevel:(\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?(?=,)/, match: /(?<=renderLevel:(\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?(?=,)/,
@ -92,8 +83,8 @@ export default definePlugin({
replace: (_, RenderLevels) => `${RenderLevels}.Show` replace: (_, RenderLevels) => `${RenderLevels}.Show`
}, },
{ {
match: /(?<=getRenderLevel=function.+?return ).+?\?(.+?):\i\.CannotShow(?=})/, match: /(?<=getRenderLevel\(\i\){.+?return)!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL,this\.record\)\|\|/,
replace: (_, renderLevelExpressionWithoutPermCheck) => renderLevelExpressionWithoutPermCheck replace: " "
} }
] ]
}, },
@ -102,13 +93,13 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel // Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel
match: /(?<=getCurrentClientVoiceChannelId\((\i)\.guild_id\);if\()/, match: /(?<=getCurrentClientVoiceChannelId\((\i)\.guild_id\);return)/,
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&` replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
}, },
{ {
// Prevent Discord from trying to connect to hidden channels // Prevent Discord from trying to connect to hidden voice channels
match: /(?=\|\|\i\.default\.selectVoiceChannel\((\i)\.id\))/, match: /(?=&&\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
replace: (_, channel) => `||$self.isHiddenChannel(${channel})` replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
}, },
{ {
// Make Discord show inside the channel if clicking on a hidden or locked channel // Make Discord show inside the channel if clicking on a hidden or locked channel
@ -117,26 +108,42 @@ export default definePlugin({
} }
] ]
}, },
// Prevent Discord from trying to connect to hidden stage channels
{ {
find: "VoiceChannel.renderPopout: There must always be something to render", find: ".MAX_STAGE_VOICE_USER_LIMIT})",
replacement: {
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
}
},
{
find: "ChannelItemEditButton:function(){",
replacement: [ replacement: [
// Render null instead of the buttons if the channel is hidden // Render null instead of the buttons if the channel is hidden
...[ ...[
"renderEditButton", "renderEditButton",
"renderInviteButton", "renderInviteButton",
"renderOpenChatButton"
].map(func => ({ ].map(func => ({
match: new RegExp(`(?<=${func}=function\\(\\){)`, "g"), // Global because Discord has multiple declarations of the same functions match: new RegExp(`(?<=${func}\\(\\){)`, "g"), // Global because Discord has multiple declarations of the same functions
replace: "if($self.isHiddenChannel(this.props.channel))return null;" replace: "if($self.isHiddenChannel(this.props.channel))return null;"
})) }))
] ]
}, },
{
find: "VoiceChannel.renderPopout: There must always be something to render",
all: true,
// Render null instead of the buttons if the channel is hidden
replacement: {
match: /(?<=renderOpenChatButton=\(\)=>{)/,
replace: "if($self.isHiddenChannel(this.props.channel))return null;"
}
},
{ {
find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY", find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY",
predicate: () => settings.store.showMode === ShowMode.LockIcon, predicate: () => settings.store.showMode === ShowMode.LockIcon,
replacement: { replacement: {
// Lock Icon // Lock Icon
match: /(?=switch\((\i)\.type\).{0,30}\.GUILD_ANNOUNCEMENT.{0,30}\(0,\i\.\i\))/, match: /(?=switch\((\i)\.type\).{0,30}\.GUILD_ANNOUNCEMENT.{0,70}\(0,\i\.\i\))/,
replace: (_, channel) => `if($self.isHiddenChannel(${channel}))return $self.LockIcon;` replace: (_, channel) => `if($self.isHiddenChannel(${channel}))return $self.LockIcon;`
} }
}, },
@ -146,18 +153,18 @@ export default definePlugin({
replacement: [ replacement: [
// Make the channel appear as muted if it's hidden // Make the channel appear as muted if it's hidden
{ {
match: /(?<=\i\.name,\i=)(?=(\i)\.muted)/, match: /(?<={channel:(\i),name:\i,muted:(\i).+?;)/,
replace: (_, props) => `$self.isHiddenChannel(${props}.channel)?true:` replace: (_, channel, muted) => `${muted}=$self.isHiddenChannel(${channel})?true:${muted};`
}, },
// Add the hidden eye icon if the channel is hidden // Add the hidden eye icon if the channel is hidden
{ {
match: /\(\).children.+?:null(?<=(\i)=\i\.channel,.+?)/, match: /\i\.children.+?:null(?<=,channel:(\i).+?)/,
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null` replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
}, },
// Make voice channels also appear as muted if they are muted // Make voice channels also appear as muted if they are muted
{ {
match: /(?<=\.wrapper:\i\(\)\.notInteractive,)(.+?)((\i)\?\i\.MUTED)/, match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)\)return (\i\.MUTED);/,
replace: (_, otherClasses, mutedClassExpression, isMuted) => `${mutedClassExpression}:"",${otherClasses}${isMuted}?""` replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return "";`
} }
] ]
}, },
@ -167,14 +174,14 @@ export default definePlugin({
{ {
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
match: /\.LOCKED:\i(?<=(\i)=\i\.channel,.+?)/, match: /\.LOCKED;if\((?<={channel:(\i).+?)/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})` replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
}, },
{ {
// Hide unreads // Hide unreads
predicate: () => settings.store.hideUnreads === true, predicate: () => settings.store.hideUnreads === true,
match: /(?<=\i\.connected,\i=)(?=(\i)\.unread)/, match: /(?<={channel:(\i),name:\i,.+?unread:(\i).+?;)/,
replace: (_, props) => `$self.isHiddenChannel(${props}.channel)?false:` replace: (_, channel, unread) => `${unread}=$self.isHiddenChannel(${channel})?false:${unread};`
} }
] ]
}, },
@ -182,8 +189,8 @@ export default definePlugin({
// Hide New unreads box for hidden channels // Hide New unreads box for hidden channels
find: '.displayName="ChannelListUnreadsStore"', find: '.displayName="ChannelListUnreadsStore"',
replacement: { replacement: {
match: /(?<=return null!=(\i))(?=.{0,130}?hasRelevantUnread\(\i\))/g, // Global because Discord has multiple methods like that in the same module match: /(?<=if\(null==(\i))(?=.{0,160}?hasRelevantUnread\(\i\))/g, // Global because Discord has multiple methods like that in the same module
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
} }
}, },
// Only render the channel header and buttons that work when transitioning to a hidden channel // Only render the channel header and buttons that work when transitioning to a hidden channel
@ -191,27 +198,27 @@ export default definePlugin({
find: "Missing channel in Channel.renderHeaderToolbar", find: "Missing channel in Channel.renderHeaderToolbar",
replacement: [ replacement: [
{ {
match: /(?<=renderHeaderToolbar=function.+?case \i\.\i\.GUILD_TEXT:)(?=.+?;(.+?{channel:(\i)},"notifications"\)\);))/, match: /(?<=renderHeaderToolbar=\(\)=>{.+?case \i\.\i\.GUILD_TEXT:)(?=.+?(\i\.push.{0,50}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
replace: (_, pushNotificationButtonExpression, channel) => `if($self.isHiddenChannel(${channel})){${pushNotificationButtonExpression}break;}` replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
}, },
{ {
match: /(?<=renderHeaderToolbar=function.+?case \i\.\i\.GUILD_FORUM:.+?if\(!\i\){)(?=.+?;(.+?{channel:(\i)},"notifications"\)\)))/, match: /(?<=renderHeaderToolbar=\(\)=>{.+?case \i\.\i\.GUILD_MEDIA:)(?=.+?(\i\.push.{0,40}channel:(\i)},"notifications"\)\)))(?<=isLurking:(\i).+?)/,
replace: (_, pushNotificationButtonExpression, channel) => `if($self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}` replace: (_, pushNotificationButtonExpression, channel, isLurking) => `if(!${isLurking}&&$self.isHiddenChannel(${channel})){${pushNotificationButtonExpression};break;}`
}, },
{ {
match: /renderMobileToolbar=function.+?case \i\.\i\.GUILD_FORUM:(?<=(\i)\.renderMobileToolbar.+?)/, match: /renderMobileToolbar=\(\)=>{.+?case \i\.\i\.GUILD_DIRECTORY:(?<=let{channel:(\i).+?)/,
replace: (m, that) => `${m}if($self.isHiddenChannel(${that}.props.channel))break;` replace: (m, channel) => `${m}if($self.isHiddenChannel(${channel}))break;`
}, },
{ {
match: /(?<=renderHeaderBar=function.+?hideSearch:(\i)\.isDirectory\(\))/, match: /(?<=renderHeaderBar=\(\)=>{.+?hideSearch:(\i)\.isDirectory\(\))/,
replace: (_, channel) => `||$self.isHiddenChannel(${channel})` replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
}, },
{ {
match: /(?<=renderSidebar=function\(\){)/, match: /(?<=renderSidebar\(\){)/,
replace: "if($self.isHiddenChannel(this.props.channel))return null;" replace: "if($self.isHiddenChannel(this.props.channel))return null;"
}, },
{ {
match: /(?<=renderChat=function\(\){)/, match: /(?<=renderChat\(\){)/,
replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);" replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);"
} }
] ]
@ -220,7 +227,7 @@ export default definePlugin({
{ {
find: '"MessageManager"', find: '"MessageManager"',
replacement: { replacement: {
match: /"Skipping fetch because channelId is a static route"\);else{(?=.+?getChannel\((\i)\))/, match: /"Skipping fetch because channelId is a static route"\);return}(?=.+?getChannel\((\i)\))/,
replace: (m, channelId) => `${m}if($self.isHiddenChannel({channelId:${channelId}}))return;` replace: (m, channelId) => `${m}if($self.isHiddenChannel({channelId:${channelId}}))return;`
} }
}, },
@ -228,51 +235,47 @@ export default definePlugin({
{ {
find: '"alt+shift+down"', find: '"alt+shift+down"',
replacement: { replacement: {
match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,130}?hasRelevantUnread\(\i\))/, match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?hasRelevantUnread\(\i\))/,
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
} }
}, },
// Patch keybind handlers so you can't accidentally jump to hidden channels
{ {
find: '"alt+down"', find: ".APPLICATION_STORE&&null!=",
replacement: { replacement: {
match: /(?<=getState\(\)\.channelId.{0,30}?\(0,\i\.\i\)\(\i\))(?=\.map\()/, match: /(?<=getState\(\)\.channelId.{0,30}?\(0,\i\.\i\)\(\i\))(?=\.map\()/,
replace: ".filter(ch=>!$self.isHiddenChannel(ch))" replace: ".filter(e=>!$self.isHiddenChannel(e))"
} }
}, },
{ {
find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE", find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE",
replacement: [ replacement: [
{
// Export the channel beginning header
match: /computePermissionsForRoles.+?}\)}(?<=function (\i)\(.+?)(?=var)/,
replace: (m, component) => `${m}$self.setChannelBeginHeaderComponent(${component});`
},
{ {
// Change the role permission check to CONNECT if the channel is locked // Change the role permission check to CONNECT if the channel is locked
match: /ADMINISTRATOR\)\|\|(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/, match: /ADMINISTRATOR\)\|\|(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/,
replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):` replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${PermissionsBits.CONNECT}n,${channel})?${permCheck}CONNECT):`
}, },
{ {
// Change the permissionOverwrite check to CONNECT if the channel is locked // Change the permissionOverwrite check to CONNECT if the channel is locked
match: /permissionOverwrites\[.+?\i=(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/, match: /permissionOverwrites\[.+?\i=(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/,
replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):` replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${PermissionsBits.CONNECT}n,${channel})?${permCheck}CONNECT):`
}, },
{ {
// Include the @everyone role in the allowed roles list for Hidden Channels // Include the @everyone role in the allowed roles list for Hidden Channels
match: /sortBy.{0,100}?return (?<=var (\i)=\i\.channel.+?)(?=\i\.id)/, match: /sortBy.{0,30}?\.filter\(\i=>(?<=channel:(\i).+?)/,
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?true:` replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?true:`
}, },
{ {
// If the @everyone role has the required permissions, make the array only contain it // If the @everyone role has the required permissions, make the array only contain it
match: /computePermissionsForRoles.+?.value\(\)(?<=var (\i)=\i\.channel.+?)/, match: /computePermissionsForRoles.+?.value\(\)(?<=channel:(\i).+?)/,
replace: (m, channel) => `${m}.reduce(...$self.makeAllowedRolesReduce(${channel}.guild_id))` replace: (m, channel) => `${m}.reduce(...$self.makeAllowedRolesReduce(${channel}.guild_id))`
}, },
{ {
// Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen) // Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen)
match: /MANAGE_ROLES.{0,60}?return(?=\(.+?(\(0,\i\.jsxs\)\("div",{className:\i\(\)\.members.+?guildId:(\i)\.guild_id.+?roleColor.+?]}\)))/, match: /MANAGE_ROLES.{0,90}?return(?=\(.+?(\(0,\i\.jsxs\)\("div",{className:\i\.members.+?guildId:(\i)\.guild_id.+?roleColor.+?\]}\)))/,
replace: (m, component, channel) => { replace: (m, component, channel) => {
// Export the channel for the users allowed component patch // Export the channel for the users allowed component patch
component = component.replace(canonicalizeMatch(/(?<=users:\i)/), `,channel:${channel}`); component = component.replace(canonicalizeMatch(/(?<=users:\i)/), `,shcChannel:${channel}`);
// Always render the component for multiple allowed users // Always render the component for multiple allowed users
component = component.replace(canonicalizeMatch(/1!==\i\.length/), "true"); component = component.replace(canonicalizeMatch(/1!==\i\.length/), "true");
@ -282,88 +285,98 @@ export default definePlugin({
] ]
}, },
{ {
find: "().avatars),children", find: ".avatars),children",
replacement: [ replacement: [
{ {
// Create a variable for the channel prop // Create a variable for the channel prop
match: /=(\i)\.maxUsers,/, match: /maxUsers:\i,users:\i.+?=(\i).+?;/,
replace: (m, props) => `${m}channel=${props}.channel,` replace: (m, props) => `${m}let{shcChannel}=${props};`
}, },
{ {
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
match: /\i>0(?=&&.{0,60}renderPopout)/, match: /\i>0(?=&&.{0,60}renderPopout)/,
replace: m => `($self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)?true:${m})` replace: m => `($self.isHiddenChannel(shcChannel,true)?true:${m})`
}, },
{ {
// Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen // Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/, match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/,
replace: (_, amount) => `($self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)&&${amount}<=0?0:1)` replace: (_, amount) => `($self.isHiddenChannel(shcChannel,true)&&${amount}<=0?0:1)`
}, },
{ {
// Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen // Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
match: /(?<="\+",)(\i)\+1/, match: /(?<="\+",)(\i)\+1/,
replace: (m, amount) => `$self.isHiddenChannel(typeof channel!=="undefined"?channel:void 0,true)&&${amount}<=0?"":${m}` replace: (m, amount) => `$self.isHiddenChannel(shcChannel,true)&&${amount}<=0?"":${m}`
} }
] ]
}, },
{ {
find: ".Messages.SHOW_CHAT", find: ".Messages.CHANNEL_CALL_CURRENT_SPEAKER.format",
replacement: [ replacement: [
{ {
// Remove the divider and the open chat button for the HiddenChannelLockScreen // Remove the divider and the open chat button for the HiddenChannelLockScreen
match: /"more-options-popout"\)\);if\((?<=function \i\((\i)\).+?)/, match: /"more-options-popout"\)\),(?<=let{channel:(\i).+?inCall:(\i).+?)/,
replace: (m, props) => `${m}!${props}.inCall&&$self.isHiddenChannel(${props}.channel,true)){}else if(` replace: (m, channel, inCall) => `${m}${inCall}||!$self.isHiddenChannel(${channel},true)&&`
}, },
{ {
// Remove invite users button for the HiddenChannelLockScreen // Remove invite users button for the HiddenChannelLockScreen
match: /"popup".{0,100}?if\((?<=(\i)\.channel.+?)/, match: /"popup".{0,100}?if\((?<=let{channel:(\i).+?inCall:(\i).+?)/,
replace: (m, props) => `${m}(${props}.inCall||!$self.isHiddenChannel(${props}.channel,true))&&` replace: (m, channel, inCall) => `${m}(${inCall}||!$self.isHiddenChannel(${channel},true))&&`
}, },
]
},
{
find: ".Messages.EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY_SHELF_FETCH_ERROR",
replacement: [
{ {
// Render our HiddenChannelLockScreen component instead of the main voice channel component // Render our HiddenChannelLockScreen component instead of the main voice channel component
match: /this\.renderVoiceChannelEffects.+?children:(?<=renderContent=function.+?)/, match: /renderContent\(\i\){.+?this\.renderVoiceChannelEffects.+?children:/,
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?$self.HiddenChannelLockScreen(this.props.channel):" replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?$self.HiddenChannelLockScreen(this.props.channel):"
}, },
{ {
// Disable gradients for the HiddenChannelLockScreen of voice channels // Disable gradients for the HiddenChannelLockScreen of voice channels
match: /this\.renderVoiceChannelEffects.+?disableGradients:(?<=renderContent=function.+?)/, match: /renderContent\(\i\){.+?disableGradients:/,
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)||" replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)||"
}, },
{ {
// Disable useless components for the HiddenChannelLockScreen of voice channels // Disable useless components for the HiddenChannelLockScreen of voice channels
match: /(?:{|,)render(?!Header|ExternalHeader).{0,30}?:(?<=renderContent=function.+?)(?!void)/g, match: /(?:{|,)render(?!Header|ExternalHeader).{0,30}?:/g,
replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?null:" replace: "$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?()=>null:"
}, },
{ {
// Disable bad CSS class which mess up hidden voice channels styling // Disable bad CSS class which mess up hidden voice channels styling
match: /callContainer,(?<=\(\)\.callContainer,)/, match: /callContainer,(?<=\i\.callContainer,)/,
replace: '$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?"":' replace: '$&!this.props.inCall&&$self.isHiddenChannel(this.props.channel,true)?"":'
} }
] ]
}, },
{ {
find: "useNotificationSettingsItem: channel cannot be undefined", find: '"HasBeenInStageChannel"',
replacement: [ replacement: [
{ {
// Render our HiddenChannelLockScreen component instead of the main stage channel component // Render our HiddenChannelLockScreen component instead of the main stage channel component
match: /"124px".+?children:(?<=var (\i)=\i\.channel.+?)(?=.{0,20}?}\)}function)/, match: /"124px".+?children:(?<=let \i,{channel:(\i).+?)(?=.{0,20}?}\)}function)/,
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):` replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?$self.HiddenChannelLockScreen(${channel}):`
}, },
{ {
// Disable useless components for the HiddenChannelLockScreen of stage channels // Disable useless components for the HiddenChannelLockScreen of stage channels
match: /render(?:BottomLeft|BottomCenter|BottomRight|ChatToasts):(?<=var (\i)=\i\.channel.+?)/g, match: /render(?:BottomLeft|BottomCenter|BottomRight|ChatToasts):\(\)=>(?<=let \i,{channel:(\i).+?)/g,
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?null:` replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?null:`
}, },
{ {
// Disable gradients for the HiddenChannelLockScreen of stage channels // Disable gradients for the HiddenChannelLockScreen of stage channels
match: /"124px".+?disableGradients:(?<=(\i)\.getGuildId\(\).+?)/, match: /"124px".+?disableGradients:(?<=let \i,{channel:(\i).+?)/,
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})||` replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})||`
}, },
{ {
// Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels // Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels
match: /"124px".+?style:(?<=(\i)\.getGuildId\(\).+?)/, match: /"124px".+?style:(?<=let \i,{channel:(\i).+?)/,
replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?void 0:` replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?void 0:`
}, }
]
},
{
find: ".Messages.STAGE_FULL_MODERATOR_TITLE",
replacement: [
{ {
// Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen // Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen
match: /\(0,\i\.jsx\)\(\i\.\i\.Divider.+?}\)]}\)(?=.+?:(\i)\.guild_id)/, match: /\(0,\i\.jsx\)\(\i\.\i\.Divider.+?}\)]}\)(?=.+?:(\i)\.guild_id)/,
@ -374,7 +387,7 @@ export default definePlugin({
match: /"recents".+?&&(?=\(.+?channelId:(\i)\.id,showRequestToSpeakSidebar)/, match: /"recents".+?&&(?=\(.+?channelId:(\i)\.id,showRequestToSpeakSidebar)/,
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&` replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
} }
], ]
}, },
{ {
find: "\"^/guild-stages/(\\\\d+)(?:/)?(\\\\d+)?\"", find: "\"^/guild-stages/(\\\\d+)(?:/)?(\\\\d+)?\"",
@ -388,8 +401,8 @@ export default definePlugin({
find: ".shouldCloseDefaultModals", find: ".shouldCloseDefaultModals",
replacement: { replacement: {
// Show inside voice channel instead of trying to join them when clicking on a channel mention // Show inside voice channel instead of trying to join them when clicking on a channel mention
match: /(?<=getChannel\((\i)\)\)(?=.{0,100}?selectVoiceChannel))/, match: /(?<=getChannel\(\i\);if\(null!=(\i))(?=.{0,100}?selectVoiceChannel)/,
replace: (_, channelId) => `&&!$self.isHiddenChannel({channelId:${channelId}})` replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
} }
}, },
{ {
@ -397,13 +410,13 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Make GuildChannelStore contain hidden channels // Make GuildChannelStore contain hidden channels
match: /isChannelGated\(.+?\)(?=\|\|)/, match: /isChannelGated\(.+?\)(?=&&)/,
replace: m => `${m}||true` replace: m => `${m}&&false`
}, },
{ {
// Filter hidden channels from GuildChannelStore.getChannels unless told otherwise // Filter hidden channels from GuildChannelStore.getChannels unless told otherwise
match: /(?<=getChannels=function\(\i)\).+?(?=return (\i)})/, match: /(?<=getChannels\(\i)(\){.+?)return (.+?)}/,
replace: (rest, channels) => `,shouldIncludeHidden=false${rest}${channels}=$self.resolveGuildChannels(${channels},shouldIncludeHidden);` replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??false);}`
} }
] ]
}, },
@ -418,22 +431,20 @@ export default definePlugin({
{ {
find: '.displayName="NowPlayingViewStore"', find: '.displayName="NowPlayingViewStore"',
replacement: { replacement: {
// Make active now voice states on hiddenl channels // Make active now voice states on hidden channels
match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/, match: /(getVoiceStateForUser.{0,150}?)&&\i\.\i\.canWithPartialContext.{0,20}VIEW_CHANNEL.+?}\)(?=\?)/,
replace: "$1" replace: "$1"
} }
} }
], ],
setChannelBeginHeaderComponent,
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) { isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
if (!channel) return false; if (!channel) return false;
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
return !PermissionStore.can(VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(CONNECT, channel); return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
}, },
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) { resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {

Some files were not shown because too many files have changed in this diff Show more