Merge branch 'dev' into immediate-finds

This commit is contained in:
Nuckyz 2024-06-21 03:03:07 -03:00
commit a82a0636cf
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
111 changed files with 870 additions and 546 deletions

View file

@ -65,8 +65,3 @@ Regardless, if your account is very important to you and it getting disabled wou
Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it
</details> </details>
```math
\ce{$\unicode[goombafont; color:red; z-index: -10; position: fixed; top: 0; left: 0; height: 100%; object-fit: cover; width: 100%; opacity: 1; background: url('https://github.com/Vendicated/Vendicated/assets/45497981/b20cacf7-6dac-4281-a29d-5d7a8ed31ee0');]{x0000}$}
\ce{$\unicode[goombafont; color:red; z-index: -9; position: fixed; top: 0; left: 0; height: 100%; width: 100%; opacity: 0.9; background: var(--bgColor-default);]{x0000}$}
```

View file

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

@ -21,7 +21,7 @@ import esbuild from "esbuild";
import { readdir } from "fs/promises"; import { readdir } from "fs/promises";
import { join } from "path"; import { join } from "path";
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, watch } from "./common.mjs";
const defines = { const defines = {
IS_STANDALONE, IS_STANDALONE,
@ -76,22 +76,20 @@ const globNativesPlugin = {
for (const dir of pluginDirs) { for (const dir of pluginDirs) {
const dirPath = join("src", dir); const dirPath = join("src", dir);
if (!await exists(dirPath)) continue; if (!await exists(dirPath)) continue;
const plugins = await readdir(dirPath); const plugins = await readdir(dirPath, { withFileTypes: true });
for (const p of plugins) { for (const file of plugins) {
const nativePath = join(dirPath, p, "native.ts"); const fileName = file.name;
const indexNativePath = join(dirPath, p, "native/index.ts"); const nativePath = join(dirPath, fileName, "native.ts");
const indexNativePath = join(dirPath, fileName, "native/index.ts");
if (!(await exists(nativePath)) && !(await exists(indexNativePath))) if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
continue; continue;
const nameParts = p.split("."); const pluginName = await resolvePluginName(dirPath, file);
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
// pluginName.thing.desktop -> PluginName.thing
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
const mod = `p${i}`; const mod = `p${i}`;
code += `import * as ${mod} from "./${dir}/${p}/native";\n`; code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`;
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`; natives += `${JSON.stringify(pluginName)}:${mod},\n`;
i++; i++;
} }
} }

View file

@ -53,6 +53,32 @@ export const banner = {
`.trim() `.trim()
}; };
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
/**
* @param {string} base
* @param {import("fs").Dirent} dirent
*/
export async function resolvePluginName(base, dirent) {
const fullPath = join(base, dirent.name);
const content = dirent.isFile()
? await readFile(fullPath, "utf-8")
: await (async () => {
for (const file of ["index.ts", "index.tsx"]) {
try {
return await readFile(join(fullPath, file), "utf-8");
} catch {
continue;
}
}
throw new Error(`Invalid plugin ${fullPath}: could not resolve entry point`);
})();
return PluginDefinitionNameMatcher.exec(content)?.[3]
?? (() => {
throw new Error(`Invalid plugin ${fullPath}: must contain definePlugin call with simple string name property as first property`);
})();
}
export async function exists(path) { export async function exists(path) {
return await access(path, FsConstants.F_OK) return await access(path, FsConstants.F_OK)
.then(() => true) .then(() => true)
@ -88,31 +114,48 @@ export const globPlugins = kind => ({
build.onLoad({ filter, namespace: "import-plugins" }, async () => { build.onLoad({ filter, namespace: "import-plugins" }, async () => {
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"]; const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
let code = ""; let code = "";
let plugins = "\n"; let pluginsCode = "\n";
let metaCode = "\n";
let excludedCode = "\n";
let i = 0; let i = 0;
for (const dir of pluginDirs) { for (const dir of pluginDirs) {
if (!await exists(`./src/${dir}`)) continue; const userPlugin = dir === "userplugins";
const files = await readdir(`./src/${dir}`);
for (const file of files) { const fullDir = `./src/${dir}`;
if (file.startsWith("_") || file.startsWith(".")) continue; if (!await exists(fullDir)) continue;
if (file === "index.ts") continue; const files = await readdir(fullDir, { withFileTypes: true });
for (const file of files) {
const fileName = file.name;
if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
if (fileName === "index.ts") continue;
const target = getPluginTarget(fileName);
const target = getPluginTarget(file);
if (target && !IS_REPORTER) { if (target && !IS_REPORTER) {
if (target === "dev" && !watch) continue; const excluded =
if (target === "web" && kind === "discordDesktop") continue; (target === "dev" && !IS_DEV) ||
if (target === "desktop" && kind === "web") continue; (target === "web" && kind === "discordDesktop") ||
if (target === "discordDesktop" && kind !== "discordDesktop") continue; (target === "desktop" && kind === "web") ||
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue; (target === "discordDesktop" && kind !== "discordDesktop") ||
(target === "vencordDesktop" && kind !== "vencordDesktop");
if (excluded) {
const name = await resolvePluginName(fullDir, file);
excludedCode += `${JSON.stringify(name)}:${JSON.stringify(target)},\n`;
continue;
}
} }
const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
const mod = `p${i}`; const mod = `p${i}`;
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`; code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
plugins += `[${mod}.name]:${mod},\n`; pluginsCode += `[${mod}.name]:${mod},\n`;
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
i++; i++;
} }
} }
code += `export default {${plugins}};`; code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`;
return { return {
contents: code, contents: code,
resolveDir: "./src" resolveDir: "./src"

View file

@ -39,7 +39,7 @@ interface PluginData {
hasCommands: boolean; hasCommands: boolean;
required: boolean; required: boolean;
enabledByDefault: boolean; enabledByDefault: boolean;
target: "discordDesktop" | "vencordDesktop" | "web" | "dev"; target: "discordDesktop" | "vencordDesktop" | "desktop" | "web" | "dev";
filePath: string; filePath: string;
} }

View file

@ -46,7 +46,8 @@ await page.setBypassCSP(true);
async function maybeGetError(handle: JSHandle): Promise<string | undefined> { async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
return await (handle as JSHandle<Error>)?.getProperty("message") return await (handle as JSHandle<Error>)?.getProperty("message")
.then(m => m?.jsonValue()); .then(m => m?.jsonValue())
.catch(() => undefined);
} }
const report = { const report = {

View file

@ -17,14 +17,14 @@
*/ */
import { mergeDefaults } from "@utils/mergeDefaults"; import { mergeDefaults } from "@utils/mergeDefaults";
import { findByProps } from "@webpack"; import { findByCode } from "@webpack";
import { MessageActions, SnowflakeUtils } from "@webpack/common"; import { MessageActions, 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 MessageCreator = findByProps("createBotMessage"); const createBotMessage = findByCode('username:"Clyde"');
export function generateId() { export function generateId() {
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`; return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
@ -37,7 +37,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 = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] }); const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage)); MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage));

View file

@ -0,0 +1,77 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { proxyLazy } from "@utils/lazy";
import { findByFactoryCode } from "@webpack";
import { Settings } from "./Settings";
interface UserSettingDefinition<T> {
/**
* Get the setting value
*/
getSetting(): T;
/**
* Update the setting value
* @param value The new value
*/
updateSetting(value: T): Promise<void>;
/**
* Update the setting value
* @param value A callback that accepts the old value as the first argument, and returns the new value
*/
updateSetting(value: (old: T) => T): Promise<void>;
/**
* Stateful React hook for this setting value
*/
useSetting(): T;
userSettingDefinitionsAPIGroup: string;
userSettingDefinitionsAPIName: string;
}
export const UserSettingsDefinitions = findByFactoryCode<Record<PropertyKey, UserSettingDefinition<any>>>('"textAndImages","renderSpoilers"');
/**
* Get the definition for a setting.
*
* @param group The setting group
* @param name The name of the setting
*/
export function getUserSettingDefinition<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
if (!Settings.plugins.UserSettingDefinitionsAPI.enabled) throw new Error("Cannot use UserSettingDefinitionsAPI without setting as dependency.");
for (const key in UserSettingsDefinitions) {
const userSettingDefinition = UserSettingsDefinitions[key];
if (userSettingDefinition.userSettingDefinitionsAPIGroup === group && userSettingDefinition.userSettingDefinitionsAPIName === name) {
return userSettingDefinition;
}
}
}
/**
* {@link getUserSettingDefinition}, lazy.
*
* Get the definition for a setting.
*
* @param group The setting group
* @param name The name of the setting
*/
export function getUserSettingDefinitionLazy<T = any>(group: string, name: string) {
return proxyLazy(() => getUserSettingDefinition<T>(group, name));
}

View file

@ -32,6 +32,7 @@ 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 $Styles from "./Styles"; import * as $Styles from "./Styles";
import * as $UserSettingDefinitions from "./UserSettingDefinitions";
/** /**
* An API allowing you to listen to Message Clicks or run your own logic * An API allowing you to listen to Message Clicks or run your own logic
@ -116,3 +117,8 @@ export const ChatButtons = $ChatButtons;
* An API allowing you to update and re-render messages * An API allowing you to update and re-render messages
*/ */
export const MessageUpdater = $MessageUpdater; export const MessageUpdater = $MessageUpdater;
/**
* An API allowing you to get the definition for an user setting
*/
export const UserSettingDefinitions = $UserSettingDefinitions;

View file

@ -11,20 +11,16 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { DevsById } from "@utils/constants"; import { DevsById } from "@utils/constants";
import { fetchUserProfile, getTheme, Theme } from "@utils/discord"; import { fetchUserProfile } from "@utils/discord";
import { pluralise } from "@utils/misc"; import { classes, pluralise } from "@utils/misc";
import { ModalContent, ModalRoot, openModal } from "@utils/modal"; import { ModalContent, ModalRoot, openModal } from "@utils/modal";
import { Forms, MaskedLink, showToast, Tooltip, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import Plugins from "~plugins"; import Plugins from "~plugins";
import { PluginCard } from "."; import { PluginCard } from ".";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
const cl = classNameFactory("vc-author-modal-"); const cl = classNameFactory("vc-author-modal-");
@ -40,16 +36,6 @@ export function openContributorModal(user: User) {
); );
} }
function GithubIcon() {
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
return <img src={src} alt="GitHub" />;
}
function WebsiteIcon() {
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
return <img src={src} alt="Website" />;
}
function ContributorModal({ user }: { user: User; }) { function ContributorModal({ user }: { user: User; }) {
useSettings(); useSettings();
@ -86,24 +72,18 @@ function ContributorModal({ user }: { user: User; }) {
/> />
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle> <Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
<div className={cl("links")}> <div className={classes("vc-settings-modal-links", cl("links"))}>
{website && ( {website && (
<Tooltip text={website}> <WebsiteButton
{props => ( text={website}
<MaskedLink {...props} href={"https://" + website}> href={`https://${website}`}
<WebsiteIcon /> />
</MaskedLink>
)}
</Tooltip>
)} )}
{githubName && ( {githubName && (
<Tooltip text={githubName}> <GithubButton
{props => ( text={githubName}
<MaskedLink {...props} href={`https://github.com/${githubName}`}> href={`https://github.com/${githubName}`}
<GithubIcon /> />
</MaskedLink>
)}
</Tooltip>
)} )}
</div> </div>
</div> </div>

View file

@ -0,0 +1,12 @@
.vc-settings-modal-link-icon {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-tertiary);
box-sizing: border-box
}
.vc-settings-modal-links {
display: flex;
gap: 0.2em;
}

View file

@ -0,0 +1,45 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./LinkIconButton.css";
import { getTheme, Theme } from "@utils/discord";
import { MaskedLink, Tooltip } from "@webpack/common";
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
export function GithubIcon() {
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
}
export function WebsiteIcon() {
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
}
interface Props {
text: string;
href: string;
}
function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) {
return (
<Tooltip text={text}>
{props => (
<MaskedLink {...props} href={href}>
<Icon />
</MaskedLink>
)}
</Tooltip>
);
}
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteIcon} />;
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubIcon} />;

View file

@ -0,0 +1,7 @@
.vc-plugin-modal-info {
align-items: center;
}
.vc-plugin-modal-description {
flex-grow: 1;
}

View file

@ -16,10 +16,14 @@
* 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 "./PluginModal.css";
import { generateId } from "@api/Commands"; import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { gitRemote } from "@shared/vencordUserAgent";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc"; import { classes, isObjectEmpty } from "@utils/misc";
@ -30,6 +34,8 @@ import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserSto
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { Constructor } from "type-fest"; import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins";
import { import {
ISettingElementProps, ISettingElementProps,
SettingBooleanComponent, SettingBooleanComponent,
@ -40,6 +46,9 @@ import {
SettingTextComponent SettingTextComponent
} from "./components"; } from "./components";
import { openContributorModal } from "./ContributorModal"; import { openContributorModal } from "./ContributorModal";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
const cl = classNameFactory("vc-plugin-modal-");
const UserSummaryItem = findComponentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const AvatarStyles = findByProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const AvatarStyles = findByProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
@ -180,16 +189,54 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
); );
} }
/*
function switchToPopout() {
onClose();
const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`;
PopoutActions.open(
PopoutKey,
() => <PluginModal
transitionState={transitionState}
plugin={plugin}
onRestartNeeded={onRestartNeeded}
onClose={() => PopoutActions.close(PopoutKey)}
/>
);
}
*/
const pluginMeta = PluginMeta[plugin.name];
return ( return (
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable"> <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
<ModalHeader separator={false}> <ModalHeader separator={false}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text> <Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
{/*
<Button look={Button.Looks.BLANK} onClick={switchToPopout}>
<OpenExternalIcon aria-label="Open in Popout" />
</Button>
*/}
<ModalCloseButton onClick={onClose} /> <ModalCloseButton onClick={onClose} />
</ModalHeader> </ModalHeader>
<ModalContent> <ModalContent>
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle> <Flex className={cl("info")}>
<Forms.FormText>{plugin.description}</Forms.FormText> <Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
{!pluginMeta.userPlugin && (
<div className="vc-settings-modal-links">
<WebsiteButton
text="View more info"
href={`https://vencord.dev/plugins/${plugin.name}`}
/>
<GithubButton
text="View source code"
href={`https://github.com/${gitRemote}/tree/main/src/plugins/${pluginMeta.folderName}`}
/>
</div>
)}
</Flex>
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle> <Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
<div style={{ width: "fit-content", marginBottom: 8 }}> <div style={{ width: "fit-content", marginBottom: 8 }}>
<UserSummaryItem <UserSummaryItem

View file

@ -42,16 +42,6 @@
.vc-author-modal-links { .vc-author-modal-links {
margin-left: auto; margin-left: auto;
display: flex;
gap: 0.2em;
}
.vc-author-modal-links img {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-tertiary);
box-sizing: border-box
} }
.vc-author-modal-plugins { .vc-author-modal-plugins {

View file

@ -35,9 +35,9 @@ 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 { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
import Plugins from "~plugins"; import Plugins, { ExcludedPlugins } from "~plugins";
// Avoid circular dependency // Avoid circular dependency
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins")) as typeof import("../../plugins"); const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins")) as typeof import("../../plugins");
@ -177,6 +177,37 @@ const enum SearchStatus {
NEW NEW
} }
function ExcludedPluginsList({ search }: { search: string; }) {
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
.filter(([name]) => name.toLowerCase().includes(search));
const ExcludedReasons: Record<"web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev", string> = {
desktop: "Discord Desktop app or Vesktop",
discordDesktop: "Discord Desktop app",
vencordDesktop: "Vesktop app",
web: "Vesktop app and the Web version of Discord",
dev: "Developer version of Vencord"
};
return (
<Text variant="text-md/normal" className={Margins.top16}>
{matchingExcludedPlugins.length
? <>
<Forms.FormText>Are you looking for:</Forms.FormText>
<ul>
{matchingExcludedPlugins.map(([name, reason]) => (
<li key={name}>
<b>{name}</b>: Only available on the {ExcludedReasons[reason]}
</li>
))}
</ul>
</>
: "No plugins meet the search criteria."
}
</Text>
);
}
export default function PluginSettings() { export default function PluginSettings() {
const settings = useSettings(); const settings = useSettings();
const changes = React.useMemo(() => new ChangeList<string>(), []); const changes = React.useMemo(() => new ChangeList<string>(), []);
@ -215,26 +246,27 @@ export default function PluginSettings() {
return o; return o;
}, []); }, []);
const sortedPlugins = React.useMemo(() => Object.values(Plugins) const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []); .sort((a, b) => a.name.localeCompare(b.name)), []);
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL }); const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
const search = searchValue.value.toLowerCase();
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query })); const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status })); const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => { const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
const enabled = settings.plugins[plugin.name]?.enabled; const { status } = searchValue;
if (enabled && searchValue.status === SearchStatus.DISABLED) return false; const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; if (enabled && status === SearchStatus.DISABLED) return false;
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false; if (!enabled && status === SearchStatus.ENABLED) return false;
if (!searchValue.value.length) return true; if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (!search.length) return true;
const v = searchValue.value.toLowerCase();
return ( return (
plugin.name.toLowerCase().includes(v) || plugin.name.toLowerCase().includes(search) ||
plugin.description.toLowerCase().includes(v) || plugin.description.toLowerCase().includes(search) ||
plugin.tags?.some(t => t.toLowerCase().includes(v)) plugin.tags?.some(t => t.toLowerCase().includes(search))
); );
}; };
@ -255,13 +287,10 @@ export default function PluginSettings() {
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins; return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
})); }));
type P = JSX.Element | JSX.Element[]; const plugins = [] as JSX.Element[];
let plugins: P, requiredPlugins: P; const requiredPlugins = [] as JSX.Element[];
if (sortedPlugins?.length) {
plugins = [];
requiredPlugins = [];
const showApi = searchValue.value === "API"; const showApi = searchValue.value.includes("API");
for (const p of sortedPlugins) { for (const p of sortedPlugins) {
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi)) if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
continue; continue;
@ -284,6 +313,7 @@ export default function PluginSettings() {
onRestartNeeded={name => changes.handleChange(name)} onRestartNeeded={name => changes.handleChange(name)}
disabled={true} disabled={true}
plugin={p} plugin={p}
key={p.name}
/> />
)} )}
</Tooltip> </Tooltip>
@ -299,10 +329,6 @@ export default function PluginSettings() {
/> />
); );
} }
}
} else {
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
} }
return ( return (
@ -333,9 +359,18 @@ export default function PluginSettings() {
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle> <Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
{plugins.length || requiredPlugins.length
? (
<div className={cl("grid")}> <div className={cl("grid")}>
{plugins} {plugins.length
? plugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div> </div>
)
: <ExcludedPluginsList search={search} />
}
<Forms.FormDivider className={Margins.top20} /> <Forms.FormDivider className={Margins.top20} />
@ -343,7 +378,10 @@ export default function PluginSettings() {
Required Plugins Required Plugins
</Forms.FormTitle> </Forms.FormTitle>
<div className={cl("grid")}> <div className={cl("grid")}>
{requiredPlugins} {requiredPlugins.length
? requiredPlugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div> </div>
</SettingsTab > </SettingsTab >
); );

View file

@ -25,7 +25,7 @@ export async function loadLazyChunks() {
// True if resolved, false otherwise // True if resolved, false otherwise
const chunksSearchPromises = [] as Array<() => boolean>; const chunksSearchPromises = [] as Array<() => boolean>;
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g); const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
async function searchAndLoadLazyChunks(factoryCode: string) { async function searchAndLoadLazyChunks(factoryCode: string) {
const lazyChunks = factoryCode.matchAll(LazyChunkRegex); const lazyChunks = factoryCode.matchAll(LazyChunkRegex);

View file

@ -33,6 +33,7 @@ async function runReporter() {
await Promise.all(Webpack.webpackSearchHistory.map(async ([searchType, args]) => { await Promise.all(Webpack.webpackSearchHistory.map(async ([searchType, args]) => {
args = [...args]; args = [...args];
let result: any;
try { try {
let result = null as any; let result = null as any;
@ -45,11 +46,12 @@ async function runReporter() {
} }
case "extractAndLoadChunks": { case "extractAndLoadChunks": {
const [code, matcher] = args; const [code, matcher] = args;
result = true;
result = await Webpack.extractAndLoadChunks(code, matcher); /* result = await Webpack.extractAndLoadChunks(code, matcher);
if (result === false) { if (result === false) {
result = null; result = null;
} } */
break; break;
} }

5
src/modules.d.ts vendored
View file

@ -22,6 +22,11 @@
declare module "~plugins" { declare module "~plugins" {
const plugins: Record<string, import("./utils/types").Plugin>; const plugins: Record<string, import("./utils/types").Plugin>;
export default plugins; export default plugins;
export const PluginMeta: Record<string, {
folderName: string;
userPlugin: boolean;
}>;
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev">;
} }
declare module "~pluginNatives" { declare module "~pluginNatives" {

View file

@ -93,7 +93,7 @@ export default definePlugin({
{ {
find: ".PANEL]:14", find: ".PANEL]:14",
replacement: { replacement: {
match: /(?<=(\i)=\(0,\i\.default\)\(\i\);)return 0===\i.length\?/, match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/,
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&" replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&"
} }
}, },

View file

@ -15,8 +15,8 @@ export default definePlugin({
patches: [{ patches: [{
find: '"sticker")', find: '"sticker")',
replacement: { replacement: {
match: /!\i\.isMobile(?=.+?(\i)\.push\(.{0,50}"gift")/, match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
replace: "$& &&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)" replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
} }
}] }]
}); });

View file

@ -35,7 +35,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".handleSendMessage", find: ".handleSendMessage,onResize",
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)

View file

@ -0,0 +1,50 @@
/*
* 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: "UserSettingDefinitionsAPI",
description: "Patches Discord's UserSettingDefinitions to expose their group and name.",
authors: [Devs.Nuckyz],
patches: [
{
find: ",updateSetting:",
replacement: [
// Main setting definition
{
match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/,
replace: "userSettingDefinitionsAPIGroup:arguments[0],userSettingDefinitionsAPIName:arguments[1],$&"
},
// Selective wrapper
{
match: /updateSetting:.{0,100}SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE/,
replace: "userSettingDefinitionsAPIGroup:arguments[0].userSettingDefinitionsAPIGroup,userSettingDefinitionsAPIName:arguments[0].userSettingDefinitionsAPIName,$&"
},
// Override wrapper
{
match: /updateSetting:.{0,60}USER_SETTINGS_OVERRIDE_CLEAR/,
replace: "userSettingDefinitionsAPIGroup:arguments[0].userSettingDefinitionsAPIGroup,userSettingDefinitionsAPIName:arguments[0].userSettingDefinitionsAPIName,$&"
}
]
}
]
});

View file

@ -72,46 +72,24 @@ export default definePlugin({
} }
] ]
}, },
// Discord Stable
// FIXME: remove once change merged to stable
{ {
find: "Messages.ACTIVITY_SETTINGS", find: "Messages.ACTIVITY_SETTINGS",
noWarn: true, replacement: [
replacement: {
get match() {
switch (settings.store.settingsLocation) {
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
case "aboveActivity":
default:
return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/;
}
},
replace: "...$self.makeSettingsCategories($1),$&"
}
},
{ {
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/, match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
}
}, },
{ {
find: "useDefaultUserSettingsSections:function", match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replacement: { replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
match: /(?<=useDefaultUserSettingsSections:function\(\){return )(\i)\}/,
replace: "$self.wrapSettingsHook($1)}"
} }
]
}, },
{ {
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: { replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/, match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.default.open($1);return;" replace: "$2.open($1);return;"
} }
} }
], ],
@ -198,7 +176,7 @@ export default definePlugin({
patchedSettings: new WeakSet(), patchedSettings: new WeakSet(),
addSettings(elements: any[], element: { header?: string; settings: string[]; }, sectionTypes: SectionTypes) { addSettings(elements: any[], element: { header?: string; settings: string[]; }, sectionTypes: SectionTypes) {
if (this.patchedSettings.has(elements)) return; if (this.patchedSettings.has(elements) || !this.isRightSpot(element)) return;
this.patchedSettings.add(elements); this.patchedSettings.add(elements);

View file

@ -49,7 +49,7 @@ export default definePlugin({
predicate: () => settings.store.domain predicate: () => settings.store.domain
}, },
{ {
find: "isSuspiciousDownload:", find: "bitbucket.org",
replacement: { replacement: {
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/, match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
replace: "$&return null;" replace: "$&return null;"

View file

@ -9,7 +9,7 @@ import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative<typeof import("./native")>; const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>;
interface ActivityAssets { interface ActivityAssets {
large_image?: string; large_image?: string;

View file

@ -20,10 +20,10 @@ 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, { ReporterTestable } from "@utils/types"; import definePlugin, { ReporterTestable } from "@utils/types";
import { findByProps } from "@webpack"; import { findByCode } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
const RpcUtils = findByProps("fetchApplicationsRPC", "getRemoteIconURL"); const fetchApplicationsRPC = findByCode("APPLICATION_RPC(", "Client ID");
async function lookupAsset(applicationId: string, key: string): Promise<string> { async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
@ -32,7 +32,7 @@ async function lookupAsset(applicationId: string, key: string): Promise<string>
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 RpcUtils.fetchApplicationsRPC(socket, applicationId); await fetchApplicationsRPC(socket, applicationId);
return socket.application; return socket.application;
} }

View file

@ -28,8 +28,8 @@ export default definePlugin({
{ {
find: "BAN_CONFIRM_TITLE.", find: "BAN_CONFIRM_TITLE.",
replacement: { replacement: {
match: /src:\i\("\d+"\)/g, match: /src:\i\("?\d+"?\)/g,
replace: "src: $self.settings.store.source" replace: "src: Vencord.Settings.plugins.BANger.source"
} }
} }
], ],

View file

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findStore } from "@webpack"; import { find, findByProps, findStore } from "@webpack";
import { FluxDispatcher, i18n, useMemo } from "@webpack/common"; import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
import FolderSideBar from "./FolderSideBar"; import FolderSideBar from "./FolderSideBar";
@ -30,7 +30,7 @@ enum FolderIconDisplay {
MoreThanOneFolderExpanded MoreThanOneFolderExpanded
} }
const { GuildsTree } = findByProps("GuildsTree"); const GuildsTree = find(m => m.prototype?.moveNextTo);
const SortedGuildStore = findStore("SortedGuildStore"); const SortedGuildStore = findStore("SortedGuildStore");
export const ExpandedGuildFolderStore = findStore("ExpandedGuildFolderStore"); export const ExpandedGuildFolderStore = findStore("ExpandedGuildFolderStore");
const FolderUtils = findByProps("move", "toggleGuildFolderExpand"); const FolderUtils = findByProps("move", "toggleGuildFolderExpand");
@ -117,7 +117,7 @@ export default definePlugin({
}, },
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders // If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{ {
match: /\[(\i)\]=(\(0,\i\.useStateFromStoresArray\).{0,40}getGuildsTree\(\).+?}\))(?=,)/, match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)` replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
}, },
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
@ -139,13 +139,13 @@ export default definePlugin({
}, },
{ {
// This is the parent folder component // This is the parent folder component
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,", find: ".toggleGuildFolderExpand(",
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
replacement: [ replacement: [
{ {
// Modify the expanded state to instead return the list of expanded folders // Modify the expanded state to instead return the list of expanded folders
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/, match: /(\],\(\)=>)(\i\.\i)\.isFolderExpanded\(\i\)\)/,
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`, replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders())`,
}, },
{ {
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds // Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
@ -196,7 +196,7 @@ export default definePlugin({
] ]
}, },
{ {
find: "APPLICATION_LIBRARY,render", find: "APPLICATION_LIBRARY,render:",
predicate: () => settings.store.sidebar, predicate: () => settings.store.sidebar,
replacement: { replacement: {
// Render the Better Folders sidebar // Render the Better Folders sidebar

View file

@ -36,7 +36,7 @@ export default definePlugin({
{ {
find: ".Messages.GIF,", find: ".Messages.GIF,",
replacement: { replacement: {
match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/, match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
replace: replace:
// rename prop so we can always use default value // rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2", "alt_$$:$1=$self.altify($3)||$2",

View file

@ -13,7 +13,7 @@ export default definePlugin({
authors: [Devs.Samwich], authors: [Devs.Samwich],
patches: [ patches: [
{ {
find: ".GIFPickerResultTypes.SEARCH", find: '"state",{resultType:',
replacement: [{ replacement: [{
match: /(?<="state",{resultType:)null/, match: /(?<="state",{resultType:)null/,
replace: '"Favorites"' replace: '"Favorites"'

View file

@ -5,15 +5,18 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import { ImageIcon } from "@components/Icons"; import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentGuild, openImageModal } from "@utils/discord"; import { getCurrentGuild, openImageModal } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common"; import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
const GuildSettingsActions = findByProps("open", "selectRole", "updateGuild"); const GuildSettingsActions = findByProps("open", "selectRole", "updateGuild");
const DeveloperMode = getUserSettingDefinitionLazy("appearance", "developerMode")!;
function PencilIcon() { function PencilIcon() {
return ( return (
<svg <svg
@ -62,12 +65,13 @@ export default definePlugin({
name: "BetterRoleContext", name: "BetterRoleContext",
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile", description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
authors: [Devs.Ven, Devs.goodbee], authors: [Devs.Ven, Devs.goodbee],
dependencies: ["UserSettingDefinitionsAPI"],
settings, settings,
start() { start() {
// DeveloperMode needs to be enabled for the context menu to be shown // DeveloperMode needs to be enabled for the context menu to be shown
TextAndImagesSettingsStores.DeveloperMode.updateSetting(true); DeveloperMode.updateSetting(true);
}, },
contextMenus: { contextMenus: {

View file

@ -77,15 +77,6 @@ export default definePlugin({
replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&" replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&"
} }
] ]
},
{
// Add the ability to change BlobMask's lower badge height
// (it allows changing width so we can mirror that logic)
find: "this.getBadgePositionInterpolation(",
replacement: {
match: /(\i\.animated\.rect,{id:\i,x:48-\(\i\+8\)\+4,y:)28(,width:\i\+8,height:)24,/,
replace: (_, leftPart, rightPart) => `${leftPart} 48 - ((this.props.lowerBadgeHeight ?? 16) + 8) + 4 ${rightPart} (this.props.lowerBadgeHeight ?? 16) + 8,`
}
} }
], ],
@ -153,14 +144,16 @@ export default definePlugin({
<PlatformIcon width={14} height={14} /> <PlatformIcon width={14} height={14} />
</div> </div>
} }
lowerBadgeWidth={20} lowerBadgeSize={{
lowerBadgeHeight={20} width: 20,
height: 20
}}
> >
<div <div
className={SessionIconClasses.sessionIcon} className={SessionIconClasses.sessionIcon}
style={{ backgroundColor: GetOsColor(session.client_info.os) }} style={{ backgroundColor: GetOsColor(session.client_info.os) }}
> >
<DeviceIcon width={28} height={28} /> <DeviceIcon width={28} height={28} color="currentColor" />
</div> </div>
</BlobMask> </BlobMask>
); );

View file

@ -84,19 +84,19 @@ export default definePlugin({
find: "this.renderArtisanalHack()", find: "this.renderArtisanalHack()",
replacement: [ replacement: [
{ // Fade in on layer { // Fade in on layer
match: /(?<=\((\i),"contextType",\i\.AccessibilityPreferencesContext\);)/, match: /(?<=\((\i),"contextType",\i\.\i\);)/,
replace: "$1=$self.Layer;", replace: "$1=$self.Layer;",
predicate: () => settings.store.disableFade predicate: () => settings.store.disableFade
}, },
{ // Lazy-load contents { // Lazy-load contents
match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g, match: /createPromise:\(\)=>([^:}]*?),webpackId:"?\d+"?,name:(?!="CollectiblesShop")"[^"]+"/g,
replace: "$&,_:$1", replace: "$&,_:$1",
predicate: () => settings.store.eagerLoad predicate: () => settings.store.eagerLoad
} }
] ]
}, },
{ // For some reason standardSidebarView also has a small fade-in { // For some reason standardSidebarView also has a small fade-in
find: "DefaultCustomContentScroller:function()", find: 'minimal:"contentColumnMinimal"',
replacement: [ replacement: [
{ {
match: /\(0,\i\.useTransition\)\((\i)/, match: /\(0,\i\.useTransition\)\((\i)/,
@ -112,7 +112,7 @@ export default definePlugin({
{ // Load menu TOC eagerly { // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format", find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
replacement: { replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/, match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)()," replace: "$&(async ()=>$2)(),"
}, },
predicate: () => settings.store.eagerLoad predicate: () => settings.store.eagerLoad
@ -120,8 +120,8 @@ export default definePlugin({
{ // Settings cog context menu { // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: { replacement: {
match: /\(0,\i.useDefaultUserSettingsSections\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/, match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
replace: "$self.wrapMenu($&)" replace: "$1$self.wrapMenu($2)"
} }
} }
], ],

View file

@ -11,7 +11,7 @@ import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin, { OptionType, StartAt } from "@utils/types"; import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByProps, findComponentByCode, findStore } from "@webpack"; import { findByCode, findComponentByCode, findStore } from "@webpack";
import { Button, Forms, useStateFromStores } from "@webpack/common"; import { Button, Forms, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
@ -30,7 +30,7 @@ function onPickColor(color: number) {
updateColorVars(hexColor); updateColorVars(hexColor);
} }
const { saveClientTheme } = findByProps("saveClientTheme"); const saveClientTheme = findByCode('type:"UNSYNCED_USER_SETTINGS_UPDATE",settings:{useSystemTheme:"system"===');
function setTheme(theme: string) { function setTheme(theme: string) {
saveClientTheme({ theme }); saveClientTheme({ theme });

View file

@ -24,12 +24,11 @@ import { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater"; import { maybePromptToUpdate } from "@utils/updater";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { DraftType, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
const CrashHandlerLogger = new Logger("CrashHandler"); const CrashHandlerLogger = new Logger("CrashHandler");
const ModalStack = findByProps("pushLazy", "popAll"); const ModalStack = findByProps("pushLazy", "popAll");
const DraftManager = findByProps("clearDraft", "saveDraft"); const DraftManager = findByProps("clearDraft", "saveDraft");
const { closeExpressionPicker } = findByProps("closeExpressionPicker", "openExpressionPicker");
const settings = definePluginSettings({ const settings = definePluginSettings({
attemptToPreventCrashes: { attemptToPreventCrashes: {
@ -134,7 +133,7 @@ export default definePlugin({
CrashHandlerLogger.debug("Failed to clear drafts.", err); CrashHandlerLogger.debug("Failed to clear drafts.", err);
} }
try { try {
closeExpressionPicker(); ExpressionPickerStore.closeExpressionPicker();
} }
catch (err) { catch (err) {
CrashHandlerLogger.debug("Failed to close expression picker.", err); CrashHandlerLogger.debug("Failed to close expression picker.", err);

View file

@ -40,9 +40,9 @@ export default definePlugin({
}), }),
patches: [ patches: [
{ {
find: "KeyboardKeys.ENTER&&(!", find: ".ENTER&&(!",
replacement: { replacement: {
match: /(?<=(\i)\.which===\i\.KeyboardKeys.ENTER&&).{0,100}(\(0,\i\.hasOpenPlainTextCodeBlock\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
replace: "$self.shouldSubmit($1, $2)" replace: "$self.shouldSubmit($1, $2)"
} }
} }

View file

@ -17,6 +17,7 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -26,12 +27,14 @@ import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCode, findByProps, findComponentByCode } from "@webpack"; import { findByCode, findByProps, findComponentByCode } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common"; import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const useProfileThemeStyle = findByCode("profileThemeStyle:", "--profile-gradient-primary-color"); const useProfileThemeStyle = findByCode("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCode("onOpenGameProfile"); const ActivityComponent = findComponentByCode("onOpenGameProfile");
const ActivityClassName = findByProps("activity", "buttonColor"); const ActivityClassName = findByProps("activity", "buttonColor");
const ShowCurrentGame = getUserSettingDefinitionLazy<boolean>("status", "showCurrentGame")!;
async function getApplicationAsset(key: string): Promise<string> { async function getApplicationAsset(key: string): Promise<string> {
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, ""); if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
@ -390,13 +393,14 @@ export default definePlugin({
name: "CustomRPC", name: "CustomRPC",
description: "Allows you to set a custom rich presence.", description: "Allows you to set a custom rich presence.",
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
dependencies: ["UserSettingDefinitionsAPI"],
start: setRpc, start: setRpc,
stop: () => setRpc(true), stop: () => setRpc(true),
settings, settings,
settingsAboutComponent: () => { settingsAboutComponent: () => {
const activity = useAwaiter(createActivity); const activity = useAwaiter(createActivity);
const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting(); const gameActivityEnabled = ShowCurrentGame.useSetting();
const { profileThemeStyle } = useProfileThemeStyle({}); const { profileThemeStyle } = useProfileThemeStyle({});
return ( return (
@ -412,7 +416,7 @@ export default definePlugin({
<Button <Button
color={Button.Colors.TRANSPARENT} color={Button.Colors.TRANSPARENT}
className={Margins.top8} className={Margins.top8}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(true)} onClick={() => ShowCurrentGame.updateSetting(true)}
> >
Enable Enable
</Button> </Button>

View file

@ -33,26 +33,23 @@ export default definePlugin({
authors: [Devs.newwares], authors: [Devs.newwares],
settings, settings,
patches: [ patches: [
{
find: "IDLE_DURATION:function(){return",
replacement: {
match: /(IDLE_DURATION:function\(\){return )\i/,
replace: "$1$self.getIdleTimeout()"
}
},
{ {
find: 'type:"IDLE",idle:', find: 'type:"IDLE",idle:',
replacement: [ replacement: [
{ {
match: /Math\.min\((\i\.AfkTimeout\.getSetting\(\)\*\i\.default\.Millis\.SECOND),\i\.IDLE_DURATION\)/, match: /(?<=Date\.now\(\)-\i>)\i\.\i/,
replace: "$self.getIdleTimeout()"
},
{
match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum) replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
}, },
{ {
match: /\i\.default\.dispatch\({type:"IDLE",idle:!1}\)/, match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/,
replace: "$self.handleOnline()" replace: "$self.handleOnline()"
}, },
{ {
match: /(setInterval\(\i,\.25\*)\i\.IDLE_DURATION/, match: /(setInterval\(\i,\.25\*)\i\.\i/,
replace: "$1$self.getIntervalDelay()" // For web installs replace: "$1$self.getIntervalDelay()" // For web installs
} }
] ]

View file

@ -9,7 +9,6 @@ import "./ui/styles.css";
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 { findByProps } from "@webpack";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants"; import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants";
@ -20,7 +19,6 @@ import { settings } from "./settings";
import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components"; import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components";
import DecorSection from "./ui/components/DecorSection"; import DecorSection from "./ui/components/DecorSection";
const { isAnimatedAvatarDecoration } = findByProps("isAnimatedAvatarDecoration");
export interface AvatarDecoration { export interface AvatarDecoration {
asset: string; asset: string;
skuId: string; skuId: string;
@ -61,7 +59,7 @@ export default definePlugin({
}, },
// Remove NEW label from decor avatar decorations // Remove NEW label from decor avatar decorations
{ {
match: /(?<=\.Section\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/, match: /(?<=\.\i\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/,
replace: "||$1.skuId===$self.SKU_ID" replace: "||$1.skuId===$self.SKU_ID"
} }
] ]
@ -93,7 +91,7 @@ export default definePlugin({
replacement: [ replacement: [
// Use Decor avatar decoration hook // Use Decor avatar decoration hook
{ {
match: /(?<=getAvatarDecorationURL\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/, match: /(?<=\i\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/,
replace: "$self.useUserDecorAvatarDecoration($1)??$&" replace: "$self.useUserDecorAvatarDecoration($1)??$&"
} }
] ]
@ -133,7 +131,7 @@ export default definePlugin({
if (avatarDecoration?.skuId === SKU_ID) { if (avatarDecoration?.skuId === SKU_ID) {
const parts = avatarDecoration.asset.split("_"); const parts = avatarDecoration.asset.split("_");
// Remove a_ prefix if it's animated and animation is disabled // Remove a_ prefix if it's animated and animation is disabled
if (isAnimatedAvatarDecoration(avatarDecoration.asset) && !canAnimate) parts.shift(); if (avatarDecoration.asset.startsWith("a_") && !canAnimate) parts.shift();
return `${CDN_URL}/${parts.join("_")}.png`; return `${CDN_URL}/${parts.join("_")}.png`;
} else if (avatarDecoration?.skuId === RAW_SKU_ID) { } else if (avatarDecoration?.skuId === RAW_SKU_ID) {
return avatarDecoration.asset; return avatarDecoration.asset;

View file

@ -10,5 +10,5 @@ import { extractAndLoadChunksLazy, findByProps } from "@webpack";
export const cl = classNameFactory("vc-decor-"); export const cl = classNameFactory("vc-decor-");
export const DecorationModalStyles = findByProps("modalFooterShopButton"); export const DecorationModalStyles = findByProps("modalFooterShopButton");
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]); export const requireAvatarDecorationModal = extractAndLoadChunksLazy([".COLLECTIBLES_SHOP_FULLSCREEN&&"]);
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]); export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);

View file

@ -9,7 +9,7 @@ import { Link } from "@components/Link";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { findByProps, findComponentByCode } from "@webpack"; import { filters, findComponentByCode, mapMangledModule } from "@webpack";
import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common"; import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common";
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants"; import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
@ -19,7 +19,10 @@ import { AvatarDecorationModalPreview } from "../components";
const FileUpload = findComponentByCode("fileUploadInput,"); const FileUpload = findComponentByCode("fileUploadInput,");
const { default: HelpMessage, HelpMessageTypes } = findByProps("HelpMessageTypes"); const { HelpMessage, HelpMessageTypes } = mapMangledModule('POSITIVE=3]="POSITIVE', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"),
HelpMessage: filters.byCode(".iconDiv")
});
function useObjectURL(object: Blob | MediaSource | null) { function useObjectURL(object: Blob | MediaSource | null) {
const [url, setUrl] = useState<string | null>(null); const [url, setUrl] = useState<string | null>(null);

View file

@ -29,7 +29,7 @@ export default definePlugin({
{ {
find: ".Messages.BOT_CALL_IDLE_DISCONNECT", find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
replacement: { replacement: {
match: /,?(?=\i\(this,"idleTimeout",new \i\.Timeout\))/, match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
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 { findByProps, findStore } from "@webpack"; import { findByCode, findStore } from "@webpack";
import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Constants, 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 = findStore("StickersStore"); const StickersStore = findStore("StickersStore");
const EmojiManager = findByProps("fetchEmoji", "uploadEmoji", "deleteEmoji"); const uploadEmoji = findByCode(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
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 EmojiManager.uploadEmoji({ return uploadEmoji({
guildId, guildId,
name: emoji.name.split("~")[0], name: emoji.name.split("~")[0],
image: dataUrl image: dataUrl

View file

@ -28,7 +28,7 @@ import { Forms, React } from "@webpack/common";
import hideBugReport from "./hideBugReport.css?managed"; import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByProps("key", "removeBuildOverride"); const KbdStyles = findByProps("key", "combo");
const settings = definePluginSettings({ const settings = definePluginSettings({
toolbarDevMenu: { toolbarDevMenu: {
@ -106,9 +106,11 @@ export default definePlugin({
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle> <Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
<Forms.FormText variant="text-md/normal"> <Forms.FormText variant="text-md/normal">
You can open Discord's DevTools via {" "} You can open Discord's DevTools via {" "}
<div className={KbdStyles.combo} style={{ display: "inline-flex" }}>
<kbd className={KbdStyles.key}>{modKey}</kbd> +{" "} <kbd className={KbdStyles.key}>{modKey}</kbd> +{" "}
<kbd className={KbdStyles.key}>{altKey}</kbd> +{" "} <kbd className={KbdStyles.key}>{altKey}</kbd> +{" "}
<kbd className={KbdStyles.key}>O</kbd>{" "} <kbd className={KbdStyles.key}>O</kbd>{" "}
</div>
</Forms.FormText> </Forms.FormText>
</React.Fragment> </React.Fragment>
); );

View file

@ -23,7 +23,7 @@ import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findStore, webpackDependantLazy } from "@webpack"; import { findByCode, findByProps, findStore, webpackDependantLazy } from "@webpack";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Emoji } from "@webpack/types"; import type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
@ -37,8 +37,8 @@ const StickerStore = findStore("StickersStore") as {
}; };
const UserSettingsProtoStore = findStore("UserSettingsProtoStore"); const UserSettingsProtoStore = findStore("UserSettingsProtoStore");
const ProtoUtils = findByProps("BINARY_READ_OPTIONS");
const RoleSubscriptionEmojiUtils = findByProps("isUnusableRoleSubscriptionEmoji"); const BINARY_READ_OPTIONS = findByProps("readerFactory");
function searchProtoClassField(localName: string, protoClass: any) { function searchProtoClassField(localName: string, protoClass: any) {
const field = protoClass?.fields?.find((field: any) => field.localName === localName); const field = protoClass?.fields?.find((field: any) => field.localName === localName);
@ -52,6 +52,8 @@ const PreloadedUserSettingsActionCreators = webpackDependantLazy(() => UserSetti
const AppearanceSettingsActionCreators = webpackDependantLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); const AppearanceSettingsActionCreators = webpackDependantLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
const ClientThemeSettingsActionsCreators = webpackDependantLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); const ClientThemeSettingsActionsCreators = webpackDependantLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
const isUnusableRoleSubscriptionEmoji = findByCode(".getUserIsAdmin(");
const enum EmojiIntentions { const enum EmojiIntentions {
REACTION, REACTION,
STATUS, STATUS,
@ -235,11 +237,10 @@ export default definePlugin({
}, },
// Allows the usage of subscription-locked emojis // Allows the usage of subscription-locked emojis
{ {
find: "isUnusableRoleSubscriptionEmoji:function", find: ".getUserIsAdmin(",
replacement: { replacement: {
match: /isUnusableRoleSubscriptionEmoji:function/, match: /(function \i\(\i,\i)\){(.{0,250}.getUserIsAdmin\(.+?return!1})/,
// Replace the original export with a func that always returns false and alias the original replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
} }
}, },
// Allow stickers to be sent everywhere // Allow stickers to be sent everywhere
@ -360,7 +361,7 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Export the renderable sticker to be used in the fake nitro sticker notice // Export the renderable sticker to be used in the fake nitro sticker notice
match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/, match: /let{renderableSticker:(\i).{0,270}sticker:\i,channel:\i,/,
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},` replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
}, },
{ {
@ -398,7 +399,7 @@ export default definePlugin({
}, },
// Separate patch for allowing using custom app icons // Separate patch for allowing using custom app icons
{ {
find: ".FreemiumAppIconIds.DEFAULT&&(", find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
replacement: { replacement: {
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/, match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
replace: "true" replace: "true"
@ -471,12 +472,12 @@ 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 (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !ProtoUtils) return; if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !BINARY_READ_OPTIONS) return;
const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance; const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance;
const newAppearanceProto = currentAppearanceSettings != null const newAppearanceProto = currentAppearanceSettings != null
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS) ? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), BINARY_READ_OPTIONS)
: AppearanceSettingsActionCreators.create(); : AppearanceSettingsActionCreators.create();
newAppearanceProto.theme = theme; newAppearanceProto.theme = theme;
@ -815,8 +816,7 @@ export default definePlugin({
if (e.type === 0) return true; if (e.type === 0) return true;
if (e.available === false) return false; if (e.available === false) return false;
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji; if (isUnusableRoleSubscriptionEmoji(e, this.guildId, true)) return false;
if (isUnusableRoleSubEmoji(e, this.guildId)) return false;
if (this.canUseEmotes) if (this.canUseEmotes)
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId); return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);

View file

@ -111,7 +111,7 @@ interface ProfileModalProps {
const ColorPicker = findComponentByCode<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCode<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ProfileModal = findComponentByCode<ProfileModalProps>('"ProfileCustomizationPreview"'); const ProfileModal = findComponentByCode<ProfileModalProps>('"ProfileCustomizationPreview"');
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("(.+?)"\).then\(\i\.bind\(\i,"(.+?)"\)\)/); const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
export default definePlugin({ export default definePlugin({
name: "FakeProfileThemes", name: "FakeProfileThemes",

View file

@ -50,7 +50,7 @@ export default definePlugin({
}, },
{ {
find: "MAX_AUTOCOMPLETE_RESULTS+", find: "numLockedEmojiResults:",
replacement: [ 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

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.D3SOX, Devs.Nickyux], authors: [Devs.D3SOX, Devs.Nickyux],
patches: [ patches: [
{ {
find: "AVATAR_DECORATION_PADDING:", find: ".PREMIUM_GUILD_SUBSCRIPTION_TOOLTIP",
replacement: { replacement: {
match: /,isOwner:(\i),/, match: /,isOwner:(\i),/,
replace: ",_isOwner:$1=$self.isGuildOwner(e)," replace: ",_isOwner:$1=$self.isGuildOwner(e),"

View file

@ -16,14 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { Constants, RestAPI, UserStore } from "@webpack/common";
const FriendInvites = findByProps("createFriendInvite"); const FriendInvites = findByProps("createFriendInvite");
const { uuid4 } = findByProps("uuid4");
export default definePlugin({ export default definePlugin({
name: "FriendInvites", name: "FriendInvites",
@ -35,47 +33,9 @@ export default definePlugin({
name: "create friend invite", name: "create friend invite",
description: "Generates a friend invite link.", description: "Generates a friend invite link.",
inputType: ApplicationCommandInputType.BOT, inputType: ApplicationCommandInputType.BOT,
options: [{
name: "Uses",
description: "How many uses?",
choices: [
{ label: "1", name: "1", value: "1" },
{ label: "5", name: "5", value: "5" }
],
required: false,
type: ApplicationCommandOptionType.INTEGER
}],
execute: async (args, ctx) => { execute: async (args, ctx) => {
const uses = findOption<number>(args, "Uses", 5); const invite = await FriendInvites.createFriendInvite();
if (uses === 1 && !UserStore.getCurrentUser().phone)
return sendBotMessage(ctx.channel.id, {
content: "You need to have a phone number connected to your account to create a friend invite with 1 use!"
});
let invite: any;
if (uses === 1) {
const random = uuid4();
const { body: { invite_suggestions } } = await RestAPI.post({
url: Constants.Endpoints.FRIEND_FINDER,
body: {
modified_contacts: {
[random]: [1, "", ""]
},
phone_contact_methods_count: 1
}
});
invite = await FriendInvites.createFriendInvite({
code: invite_suggestions[0][3],
recipient_phone_number_or_email: random,
contact_visibility: 1,
filter_visibilities: [],
filtered_invite_suggestions_index: 1
});
} else {
invite = await FriendInvites.createFriendInvite();
}
sendBotMessage(ctx.channel.id, { sendBotMessage(ctx.channel.id, {
content: ` content: `

View file

@ -4,21 +4,23 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByProps } from "@webpack"; import { findByCode, findByProps } from "@webpack";
import { Heading, React, RelationshipStore, Text } from "@webpack/common"; import { Heading, React, RelationshipStore, Text } from "@webpack/common";
const container = findByProps("memberSinceWrapper"); const container = findByProps("memberSinceWrapper");
const { getCreatedAtDate } = findByProps("getCreatedAtDate"); const getCreatedAtDate = findByCode('month:"short",day:"numeric"');
const clydeMoreInfo = findByProps("clydeMoreInfo");
const locale = findByProps("getLocale"); const locale = findByProps("getLocale");
const lastSection = findByProps("lastSection"); const lastSection = findByProps("lastSection");
const cl = classNameFactory("vc-friendssince-");
export default definePlugin({ export default definePlugin({
name: "FriendsSince", name: "FriendsSince",
description: "Shows when you became friends with someone in the user popout", description: "Shows when you became friends with someone in the user popout",
@ -26,17 +28,17 @@ export default definePlugin({
patches: [ patches: [
// User popup // User popup
{ {
find: ".AnalyticsSections.USER_PROFILE}", find: ".USER_PROFILE}};return",
replacement: { replacement: {
match: /\i.default,\{userId:(\i.id).{0,30}}\)/, match: /,{userId:(\i.id).{0,30}}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })" replace: "$&,$self.friendsSince({ userId: $1 })"
} }
}, },
// User DMs "User Profile" popup in the right // User DMs "User Profile" popup in the right
{ {
find: ".UserPopoutUpsellSource.PROFILE_PANEL,", find: ".PROFILE_PANEL,",
replacement: { replacement: {
match: /\i.default,\{userId:([^,]+?)}\)/, match: /,{userId:([^,]+?)}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })" replace: "$&,$self.friendsSince({ userId: $1 })"
} }
}, },
@ -69,7 +71,7 @@ export default definePlugin({
return ( return (
<div className={lastSection.section}> <div className={lastSection.section}>
<Heading variant="eyebrow" className={clydeMoreInfo.title}> <Heading variant="eyebrow" className={cl("title")}>
Friends Since Friends Since
</Heading> </Heading>
@ -86,7 +88,7 @@ export default definePlugin({
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" /> <path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
</svg> </svg>
)} )}
<Text variant="text-sm/normal" className={classes(clydeMoreInfo.body, textClassName)}> <Text variant="text-sm/normal" className={classes(cl("body"), textClassName)}>
{getCreatedAtDate(friendsSince, locale.getLocale())} {getCreatedAtDate(friendsSince, locale.getLocale())}
</Text> </Text>
</div> </div>

View file

@ -0,0 +1,12 @@
/* copy pasted from discord */
.vc-friendssince-title {
display: flex;
font-weight: 700;
margin-bottom: 6px
}
.vc-friendssince-body {
font-size: 14px;
line-height: 18px
}

View file

@ -18,16 +18,18 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
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 { findComponentByCode } from "@webpack"; import { findComponentByCode } from "@webpack";
import { StatusSettingsStores } from "@webpack/common";
import style from "./style.css?managed"; import style from "./style.css?managed";
const Button = findComponentByCode("Button.Sizes.NONE,disabled:"); const Button = findComponentByCode("Button.Sizes.NONE,disabled:");
const ShowCurrentGame = getUserSettingDefinitionLazy<boolean>("status", "showCurrentGame")!;
function makeIcon(showCurrentGame?: boolean) { function makeIcon(showCurrentGame?: boolean) {
const { oldIcon } = settings.use(["oldIcon"]); const { oldIcon } = settings.use(["oldIcon"]);
@ -60,7 +62,7 @@ function makeIcon(showCurrentGame?: boolean) {
} }
function GameActivityToggleButton() { function GameActivityToggleButton() {
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting(); const showCurrentGame = ShowCurrentGame.useSetting();
return ( return (
<Button <Button
@ -68,7 +70,7 @@ function GameActivityToggleButton() {
icon={makeIcon(showCurrentGame)} icon={makeIcon(showCurrentGame)}
role="switch" role="switch"
aria-checked={!showCurrentGame} aria-checked={!showCurrentGame}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)} onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/> />
); );
} }
@ -85,6 +87,7 @@ 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: ["UserSettingDefinitionsAPI"],
settings, settings,
patches: [ patches: [

View file

@ -19,9 +19,7 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { insertTextIntoChatInputBox } from "@utils/discord"; import { insertTextIntoChatInputBox } from "@utils/discord";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByProps } from "@webpack"; import { ExpressionPickerStore } from "@webpack/common";
const { closeExpressionPicker } = findByProps("closeExpressionPicker");
export default definePlugin({ export default definePlugin({
name: "GifPaste", name: "GifPaste",
@ -39,7 +37,7 @@ export default definePlugin({
handleSelect(gif?: { url: string; }) { handleSelect(gif?: { url: string; }) {
if (gif) { if (gif) {
insertTextIntoChatInputBox(gif.url + " "); insertTextIntoChatInputBox(gif.url + " ");
closeExpressionPicker(); ExpressionPickerStore.closeExpressionPicker();
} }
} }
}); });

View file

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack"; import { find } from "@webpack";
import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common"; import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
@ -49,7 +49,7 @@ const settings = definePluginSettings({
unholyMultiGreetEnabled?: boolean; unholyMultiGreetEnabled?: boolean;
}>(); }>();
const { WELCOME_STICKERS } = findByProps("WELCOME_STICKERS"); const WELCOME_STICKERS = find(m => Array.isArray(m) && m[0]?.name === "Wave");
function greet(channel: Channel, message: Message, stickers: string[]) { function greet(channel: Channel, message: Message, stickers: string[]) {
const options = MessageActions.getSendMessageOptionsForReply({ const options = MessageActions.getSendMessageOptionsForReply({

View file

@ -6,13 +6,14 @@
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findStore } from "@webpack"; import { findStore } from "@webpack";
import { Button, Forms, showToast, StatusSettingsStores, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common"; import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
const enum ActivitiesTypes { const enum ActivitiesTypes {
Game, Game,
@ -27,6 +28,8 @@ interface IgnoredActivity {
const RunningGameStore = findStore("RunningGameStore"); const RunningGameStore = findStore("RunningGameStore");
const ShowCurrentGame = getUserSettingDefinitionLazy("status", "showCurrentGame")!;
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) { function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
return ( return (
<Tooltip text={tooltipText}> <Tooltip text={tooltipText}>
@ -68,7 +71,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
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old); ShowCurrentGame.updateSetting(old => old);
} }
function ImportCustomRPCComponent() { function ImportCustomRPCComponent() {
@ -205,6 +208,7 @@ export default definePlugin({
name: "IgnoreActivities", name: "IgnoreActivities",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
dependencies: ["UserSettingDefinitionsAPI"],
settings, settings,

View file

@ -16,7 +16,8 @@ export default definePlugin({
{ {
find: "unknownUserMentionPlaceholder:", find: "unknownUserMentionPlaceholder:",
replacement: { replacement: {
match: /\(0,\i\.isEmbedInline\)\(\i\)/, // SimpleEmbedTypes.has(embed.type) && isEmbedInline(embed)
match: /\i\.has\(\i\.type\)&&\(0,\i\.\i\)\(\i\)/,
replace: "false", replace: "false",
} }
} }

View file

@ -19,11 +19,10 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findStore } from "@webpack"; import { findStore } from "@webpack";
import { ChannelStore, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common"; import { ChannelStore, Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common";
const UserAffinitiesStore = findStore("UserAffinitiesStore"); const UserAffinitiesStore = findStore("UserAffinitiesStore");
const { FriendsSections } = findByProps("FriendsSections");
const settings = definePluginSettings({ const settings = definePluginSettings({
sortByAffinity: { sortByAffinity: {
@ -75,7 +74,7 @@ export default definePlugin({
}, },
// Piggyback relationship fetch // Piggyback relationship fetch
{ {
find: ".fetchRelationships()", find: '"FriendsStore',
replacement: { replacement: {
match: /(\i\.\i)\.fetchRelationships\(\)/, match: /(\i\.\i)\.fetchRelationships\(\)/,
// This relationship fetch is actually completely useless, but whatevs // This relationship fetch is actually completely useless, but whatevs
@ -177,6 +176,6 @@ export default definePlugin({
}, },
start() { start() {
FriendsSections.IMPLICIT = "IMPLICIT"; Constants.FriendsSections.IMPLICIT = "IMPLICIT";
} }
}); });

View file

@ -19,7 +19,6 @@
import { registerCommand, unregisterCommand } from "@api/Commands"; import { registerCommand, unregisterCommand } from "@api/Commands";
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu"; import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { onceDefined } from "@shared/onceDefined";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches"; import { canonicalizeFind } from "@utils/patches";
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types"; import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
@ -34,7 +33,7 @@ const logger = new Logger("PluginManager", "#a6d189");
export const PMLogger = logger; export const PMLogger = logger;
export const plugins = Plugins; export const plugins = Plugins;
export let patches = [] as Patch[]; export const patches = [] as Patch[];
/** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */ /** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
let enabledPluginsSubscribedFlux = false; let enabledPluginsSubscribedFlux = false;
@ -43,16 +42,6 @@ const subscribedFluxEventsPlugins = new Set<string>();
const pluginsValues = Object.values(Plugins); const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins; const settings = Settings.plugins;
const forceDisabled = new Set([
"MessageLogger",
"ShowHiddenChannels",
"MoreUserTags",
"Decor",
"IgnoreActivities",
"NoBlockedMessages",
"BetterFolders",
"NoPendingCount"
]);
export function isPluginEnabled(p: string) { export function isPluginEnabled(p: string) {
return ( return (
Plugins[p]?.required || Plugins[p]?.required ||
@ -133,17 +122,9 @@ for (const p of pluginsValues) {
} }
} }
onceDefined(window, "GLOBAL_ENV", v => {
if (v.SENTRY_TAGS.buildId !== "366c746173a6ca0a801e9f4a4d7b6745e6de45d4") {
patches = patches.filter(p => !forceDisabled.has(p.plugin));
}
});
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) { export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) {
logger.info(`Starting plugins (stage ${target})`); logger.info(`Starting plugins (stage ${target})`);
for (const name in Plugins) { for (const name in Plugins) {
if (window.GLOBAL_ENV?.SENTRY_TAGS.buildId !== "366c746173a6ca0a801e9f4a4d7b6745e6de45d4" && forceDisabled.has(name)) continue;
if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) { if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) {
const p = Plugins[name]; const p = Plugins[name];
@ -214,7 +195,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
} }
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p; const { name, commands, contextMenus } = p;
if (p.start) { if (p.start) {
logger.info("Starting plugin", name); logger.info("Starting plugin", name);
@ -260,7 +241,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
}, p => `startPlugin ${p.name}`); }, p => `startPlugin ${p.name}`);
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p; const { name, commands, contextMenus } = p;
if (p.stop) { if (p.stop) {
logger.info("Stopping plugin", name); logger.info("Stopping plugin", name);

View file

@ -10,19 +10,21 @@ import { findByProps } from "@webpack";
const linkRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/; const linkRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
const { SlateTransforms } = findByProps("SlateTransforms"); const SlateTransforms = findByProps("insertText", "selectCommandOption");
export default definePlugin({ export default definePlugin({
name: "MaskedLinkPaste", name: "MaskedLinkPaste",
authors: [Devs.TheSun], authors: [Devs.TheSun],
description: "Pasting a link while having text selected will paste a hyperlink", description: "Pasting a link while having text selected will paste a hyperlink",
patches: [{ patches: [
{
find: ".selection,preventEmojiSurrogates:", find: ".selection,preventEmojiSurrogates:",
replacement: { replacement: {
match: /(?<=SlateTransforms.delete.{0,50})(\i)\.insertText\((\i)\)/, match: /(?<=\i.delete.{0,50})(\i)\.insertText\((\i)\)/,
replace: "$self.handlePaste($1, $2, () => $&)" replace: "$self.handlePaste($1, $2, () => $&)"
} }
}], }
],
handlePaste(editor, content: string, originalBehavior: () => void) { handlePaste(editor, content: string, originalBehavior: () => void) {
if (content && linkRegex.test(content) && editor.operations?.[0]?.type === "remove_text") { if (content && linkRegex.test(content) && editor.operations?.[0]?.type === "remove_text") {

View file

@ -19,6 +19,7 @@
import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { addAccessory, removeAccessory } from "@api/MessageAccessories";
import { updateMessage } from "@api/MessageUpdater"; import { updateMessage } from "@api/MessageUpdater";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
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";
@ -37,7 +38,6 @@ import {
PermissionStore, PermissionStore,
RestAPI, RestAPI,
Text, Text,
TextAndImagesSettingsStores,
UserStore UserStore
} from "@webpack/common"; } from "@webpack/common";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
@ -49,11 +49,13 @@ const messageCache = new Map<string, {
const Embed = findComponentByCode(".inlineMediaEmbed"); const Embed = findComponentByCode(".inlineMediaEmbed");
const AutoModEmbed = findComponentByCode(".withFooter]:", "childrenMessageContent:"); const AutoModEmbed = findComponentByCode(".withFooter]:", "childrenMessageContent:");
const ChannelMessage = findComponentByCode("renderSimpleAccessories)"); const ChannelMessage = findComponentByCode("childrenExecutedCommand:", ".hideAccessories");
const SearchResultClasses = findByProps("message", "searchResult"); const SearchResultClasses = findByProps("message", "searchResult");
const EmbedClasses = findByProps("embedAuthorIcon", "embedAuthor", "embedAuthor"); const EmbedClasses = findByProps("embedAuthorIcon", "embedAuthor", "embedAuthor");
const MessageDisplayCompact = getUserSettingDefinitionLazy("textAndImages", "messageDisplayCompact")!;
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\//;
@ -280,7 +282,7 @@ function getChannelLabelAndIconUrl(channel: Channel) {
} }
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null { function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting(); const compact = MessageDisplayCompact.useSetting();
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
@ -316,7 +318,7 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
const { message, channel } = props; const { message, channel } = props;
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting(); const compact = MessageDisplayCompact.useSetting();
const images = getImages(message); const images = getImages(message);
const { parse } = Parser; const { parse } = Parser;
@ -363,7 +365,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", "MessageUpdaterAPI"], dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "UserSettingDefinitionsAPI"],
settings, settings,

View file

@ -22,9 +22,10 @@ import { Devs } from "@utils/constants";
import { LazyComponentType } from "@utils/lazyReact"; import { LazyComponentType } from "@utils/lazyReact";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findComponentByCode } from "@webpack"; import { findByCode, findComponentByCode } from "@webpack";
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState, UtilTypes } from "@webpack/common"; import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
import { Channel, Message, User } from "discord-types/general"; import type { Permissions } from "@webpack/types";
import type { Channel, Guild, Message, User } from "discord-types/general";
interface Tag { interface Tag {
// name used for identifying, must be alphanumeric + underscores // name used for identifying, must be alphanumeric + underscores
@ -32,7 +33,7 @@ interface Tag {
// name shown on the tag itself, can be anything probably; automatically uppercase'd // name shown on the tag itself, can be anything probably; automatically uppercase'd
displayName: string; displayName: string;
description: string; description: string;
permissions?: UtilTypes.Permissions[]; permissions?: Permissions[];
condition?(message: Message | null, user: User, channel: Channel): boolean; condition?(message: Message | null, user: User, channel: Channel): boolean;
} }
@ -52,10 +53,14 @@ interface TagSettings {
[k: string]: TagSetting; [k: string]: TagSetting;
} }
// PermissionStore.computePermissions is not the same function and doesn't work here // PermissionStore.computePermissions will not work here since it only gets permissions for the current user
const PermissionUtil = findByProps("computePermissions", "canEveryoneRole") as { const computePermissions: (options: {
computePermissions({ ...args }): bigint; user?: { id: string; } | string | null;
}; context?: Guild | Channel | null;
overwrites?: Channel["permissionOverwrites"] | null;
checkElevated?: boolean /* = true */;
excludeGuildPermissions?: boolean /* = false */;
}) => bigint = findByCode(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
const Tag = findComponentByCode(".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL,") as LazyComponentType<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; }; const Tag = findComponentByCode(".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL,") as LazyComponentType<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
@ -191,7 +196,7 @@ export default definePlugin({
patches: [ patches: [
// add tags to the tag list // add tags to the tag list
{ {
find: "BotTagTypes:", find: ".ORIGINAL_POSTER=",
replacement: { replacement: {
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/, match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
replace: "($1=$self.getTagTypes()))[$2.BOT" replace: "($1=$self.getTagTypes()))[$2.BOT"
@ -220,7 +225,7 @@ export default definePlugin({
}, },
// in messages // in messages
{ {
find: "renderSystemTag:", find: ".Types.ORIGINAL_POSTER",
replacement: { replacement: {
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/, match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null" replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
@ -281,7 +286,7 @@ export default definePlugin({
const guild = GuildStore.getGuild(channel?.guild_id); const guild = GuildStore.getGuild(channel?.guild_id);
if (!guild) return []; if (!guild) return [];
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites }); const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
return Object.entries(PermissionsBits) return Object.entries(PermissionsBits)
.map(([perm, permInt]) => .map(([perm, permInt]) =>
permissions & permInt ? perm : "" permissions & permInt ? perm : ""
@ -328,7 +333,7 @@ export default definePlugin({
}: { }: {
message?: Message, message?: Message,
user: User & { isClyde(): boolean; }, user: User & { isClyde(): boolean; },
channel?: Channel & { isForumPost(): boolean; }, channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
channelId?: string; channelId?: string;
origType?: number; origType?: number;
location: "chat" | "not-chat"; location: "chat" | "not-chat";
@ -365,7 +370,7 @@ export default definePlugin({
tag.permissions?.some(perm => perms.includes(perm)) || tag.permissions?.some(perm => perms.includes(perm)) ||
(tag.condition?.(message!, user, channel)) (tag.condition?.(message!, user, channel))
) { ) {
if (channel.isForumPost() && channel.ownerId === user.id) if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id)
type = Tag.Types[`${tag.name}-OP`]; type = Tag.Types[`${tag.name}-OP`];
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag) else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
type = Tag.Types[`${tag.name}-BOT`]; type = Tag.Types[`${tag.name}-BOT`];

View file

@ -53,7 +53,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".UserProfileSections.USER_INFO_CONNECTIONS:", find: ".USER_INFO_CONNECTIONS:case",
replacement: { replacement: {
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});" replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"

View file

@ -19,11 +19,16 @@
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings, migratePluginSettings } 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 { findByProps } from "@webpack"; import { findByCode, findByProps, mapMangledModule } from "@webpack";
const { updateGuildNotificationSettings } = findByProps("updateGuildNotificationSettings"); const { updateGuildNotificationSettings } = findByProps("updateGuildNotificationSettings");
const { toggleShowAllChannels } = findByProps("toggleShowAllChannels"); const { toggleShowAllChannels } = mapMangledModule(".onboardExistingMember(", {
const { isOptInEnabledForGuild } = findByProps("isOptInEnabledForGuild"); toggleShowAllChannels: m => {
const s = String(m);
return s.length < 100 && !s.includes("onboardExistingMember") && !s.includes("getOptedInChannels");
}
});
const isOptInEnabledForGuild = findByCode(".COMMUNITY)||", ".isOptInEnabled(");
const settings = definePluginSettings({ const settings = definePluginSettings({
guild: { guild: {

View file

@ -18,8 +18,10 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
import { Message } from "discord-types/general";
const RelationshipStore = findByProps("getRelationships", "isBlocked"); const RelationshipStore = findByProps("getRelationships", "isBlocked");
@ -62,6 +64,20 @@ export default definePlugin({
] ]
})) }))
], ],
isBlocked: message => options: {
RelationshipStore.isBlocked(message.author.id) ignoreBlockedMessages: {
description: "Completely ignores (recent) incoming messages from blocked users (locally).",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true,
},
},
isBlocked(message: Message) {
try {
return RelationshipStore.isBlocked(message.author.id);
} catch (e) {
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e);
}
}
}); });

View file

@ -14,7 +14,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "HangStatusTypes.CHILLING)", find: ".CHILLING)",
replacement: { replacement: {
match: /{enableHangStatus:(\i),/, match: /{enableHangStatus:(\i),/,
replace: "{_enableHangStatus:$1=false," replace: "{_enableHangStatus:$1=false,"

View file

@ -27,7 +27,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "isGroupableMedia:function()", find: '=>"IMAGE"===',
replacement: { replacement: {
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/, match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/,
replace: "=>false;" replace: "=>false;"

View file

@ -64,7 +64,7 @@ export default definePlugin({
}, },
// New message requests hook // New message requests hook
{ {
find: "useNewMessageRequestsCount:", find: 'location:"use-message-requests-count"',
predicate: () => settings.store.hideMessageRequestsCount, predicate: () => settings.store.hideMessageRequestsCount,
replacement: { replacement: {
match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /, match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /,

View file

@ -63,8 +63,8 @@ export default definePlugin({
{ {
find: "trackAnnouncementMessageLinkClicked({", find: "trackAnnouncementMessageLinkClicked({",
replacement: { replacement: {
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/, match: /function (\i\(\i,\i\)\{)(?=.{0,100}trusted:)/,
replace: "async $& if(await $self.handleLink(...arguments)) return;" replace: "async function $1 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

View file

@ -19,10 +19,10 @@
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 { findByProps } from "@webpack"; import { find } from "@webpack";
import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common"; import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common";
const { InvitesDisabledExperiment } = findByProps("InvitesDisabledExperiment"); const InvitesDisabledExperiment = find(m => m.definition?.id === "2022-07_invites_disabled");
function showDisableInvites(guildId: string) { function showDisableInvites(guildId: string) {
// Once the experiment is removed, this should keep working // Once the experiment is removed, this should keep working
@ -56,8 +56,8 @@ export default definePlugin({
replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})", replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})",
}, },
{ {
match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/, match: /\.INVITES_DISABLED\)(?=.+?\.Messages\.INVITES_PERMANENTLY_DISABLED_TIP.+?checked:(\i)).+?\[\1,(\i)\]=\i.useState\(\i\)/,
replace: "$1,setChecked=$2" replace: "$&,setChecked=$2"
} }
] ]
} }

View file

@ -6,6 +6,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({ const settings = definePluginSettings({
@ -31,7 +32,7 @@ export default definePlugin({
patches: [ patches: [
// Permission lockout, just set the check to true // Permission lockout, just set the check to true
{ {
find: ".showPermissionLockoutModal(", find: ".STAGE_CHANNEL_CANNOT_OVERWRITE_PERMISSION",
replacement: [ replacement: [
{ {
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/, match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
@ -45,9 +46,8 @@ export default definePlugin({
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING", find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
replacement: [ replacement: [
{ {
// are we java yet? match: /{(\i:function\(\){return \i},?){2}}/,
match: /(?<=(?:isDefaultChannelThresholdMetAfterDelete|checkDefaultChannelThresholdMetAfterChannelPermissionDeny):function\(\)\{)return \i(?=\})/g, replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)")
replace: "return () => true"
} }
], ],
predicate: () => settings.store.onboarding predicate: () => settings.store.onboarding

View file

@ -33,7 +33,7 @@ interface ColorPickerWithSwatchesProps {
const ColorPicker = findComponentByCode<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCode<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPickerWithSwatches = findExportedComponent<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker"); const ColorPickerWithSwatches = findExportedComponent<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker");
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\).{0,50}"UserSettings"/); export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\("?.+?"?\).+?\])\).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
const cl = classNameFactory("vc-pindms-modal-"); const cl = classNameFactory("vc-pindms-modal-");

View file

@ -82,7 +82,7 @@ export default definePlugin({
// Rendering // Rendering
{ {
match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.default),\{channel:.+?)/, match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.\i),\{channel:.+?)/,
replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();" replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();"
}, },
{ {
@ -131,7 +131,7 @@ export default definePlugin({
// Fix Alt Up/Down navigation // Fix Alt Up/Down navigation
{ {
find: ".Routes.APPLICATION_STORE&&", find: ".APPLICATION_STORE&&",
replacement: { replacement: {
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)] // channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/, match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,

View file

@ -9,13 +9,16 @@ import "./style.css";
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 { findByProps } from "@webpack"; import { filters, findByProps, mapMangledModule } from "@webpack";
import { Timestamp } from "@webpack/common"; import { Timestamp } from "@webpack/common";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import type { HTMLAttributes } from "react"; import type { HTMLAttributes } from "react";
const { getMessageTimestampId } = findByProps("getMessageTimestampId"); const { calendarFormat, dateFormat, isSameDay } = mapMangledModule("millisecondsInUnit:", {
const { calendarFormat, dateFormat, isSameDay } = findByProps("calendarFormat", "dateFormat", "isSameDay", "accessibilityLabelCalendarFormat"); calendarFormat: filters.byCode("sameElse"),
dateFormat: filters.byCode('":'),
isSameDay: filters.byCode("Math.abs(+"),
});
const MessageClasses = findByProps("separator", "latin24CompactTimeStamp"); const MessageClasses = findByProps("separator", "latin24CompactTimeStamp");
function Sep(props: HTMLAttributes<HTMLElement>) { function Sep(props: HTMLAttributes<HTMLElement>) {
@ -42,7 +45,6 @@ function ReplyTimestamp({
const baseTimestamp = baseMessage.timestamp as any; const baseTimestamp = baseMessage.timestamp as any;
return ( return (
<Timestamp <Timestamp
id={getMessageTimestampId(referencedMessage.message)}
className="vc-reply-timestamp" className="vc-reply-timestamp"
compact={isSameDay(refTimestamp, baseTimestamp)} compact={isSameDay(refTimestamp, baseTimestamp)}
timestamp={refTimestamp} timestamp={refTimestamp}
@ -65,7 +67,7 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "renderSingleLineMessage:function()", find: ".REPLY_QUOTE_MESSAGE_BLOCKED",
replacement: { replacement: {
match: /(?<="aria-label":\i,children:\[)(?=\i,\i,\i\])/, match: /(?<="aria-label":\i,children:\[)(?=\i,\i,\i\])/,
replace: "$self.ReplyTimestamp(arguments[0])," replace: "$self.ReplyTimestamp(arguments[0]),"

View file

@ -21,7 +21,7 @@ import definePlugin from "@utils/types";
import { findByProps } from "@webpack"; import { findByProps } from "@webpack";
const SpoilerClasses = findByProps("spoilerContent"); const SpoilerClasses = findByProps("spoilerContent");
const MessagesClasses = findByProps("messagesWrapper", "messages"); const MessagesClasses = findByProps("messagesWrapper");
export default definePlugin({ export default definePlugin({
name: "RevealAllSpoilers", name: "RevealAllSpoilers",

View file

@ -34,7 +34,7 @@ const messageClasses = findByProps("cozyMessage");
const containerClasses = findByProps("container", "isHeader"); const containerClasses = findByProps("container", "isHeader");
const avatarClasses = findByProps("avatar", "zalgo"); const avatarClasses = findByProps("avatar", "zalgo");
const buttonClasses = findByProps("button", "wrapper", "selected"); const buttonClasses = findByProps("button", "wrapper", "selected");
const botTagClasses = findByProps("botTag", "botTagRegular"); const botTagClasses = findByProps("botTagRegular");
const dateFormat = new Intl.DateTimeFormat(); const dateFormat = new Intl.DateTimeFormat();
@ -130,7 +130,7 @@ export default function ReviewComponent({ review, refetch, profileId }: { review
{review.type === ReviewType.System && ( {review.type === ReviewType.System && (
<span <span
className={classes(botTagClasses.botTagVerified, botTagClasses.botTagRegular, botTagClasses.botTag, botTagClasses.px, botTagClasses.rem)} className={classes(botTagClasses.botTagVerified, botTagClasses.botTagRegular, botTagClasses.px, botTagClasses.rem)}
style={{ marginLeft: "4px" }}> style={{ marginLeft: "4px" }}>
<span className={botTagClasses.botText}> <span className={botTagClasses.botText}>
System System

View file

@ -17,7 +17,7 @@
*/ */
import { useAwaiter, useForceUpdater } from "@utils/react"; import { useAwaiter, useForceUpdater } from "@utils/react";
import { findByProps, findComponentByCode } from "@webpack"; import { findByCode, findByProps, findComponentByCode } from "@webpack";
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
import { Auth, authorize } from "../auth"; import { Auth, authorize } from "../auth";
@ -27,12 +27,11 @@ import { settings } from "../settings";
import { cl, showToast } from "../utils"; import { cl, showToast } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
const Transforms = findByProps("insertNodes", "textToText");
const { Editor, Transforms } = findByProps("Editor", "Transforms"); const Editor = findByProps("start", "end", "toSlateRange");
const { ChatInputTypes } = findByProps("ChatInputTypes"); const ChatInputTypes = findByProps("FORM");
const InputComponent = findComponentByCode("disableThemedBackground", "CHANNEL_TEXT_AREA");
const InputComponent = findComponentByCode("default.CHANNEL_TEXT_AREA", "input"); const createChannelRecordFromServer = findByCode(".GUILD_TEXT])", "fromServer)");
const { createChannelRecordFromServer } = findByProps("createChannelRecordFromServer");
interface UserProps { interface UserProps {
discordId: string; discordId: string;

View file

@ -71,7 +71,7 @@ export default definePlugin({
find: ".userTooltip,children", find: ".userTooltip,children",
replacement: [ replacement: [
{ {
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.default,{(?=children)/, match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
replace: "$&color:$self.getUserColor($1,{guildId:$2})," replace: "$&color:$self.getUserColor($1,{guildId:$2}),"
} }
], ],
@ -110,7 +110,7 @@ export default definePlugin({
{ {
find: ".reactorDefault", find: ".reactorDefault",
replacement: { replacement: {
match: /\.openUserContextMenu\)\((\i),(\i),\i\).{0,250}tag:"strong"/, match: /,onContextMenu:e=>.{0,15}\((\i),(\i),(\i)\).{0,250}tag:"strong"/,
replace: "$&,style:{color:$self.getColor($2?.id,$1)}" replace: "$&,style:{color:$self.getColor($2?.id,$1)}"
}, },
predicate: () => settings.store.reactorsList, predicate: () => settings.store.reactorsList,

View file

@ -20,12 +20,11 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { ReplyIcon } from "@components/Icons"; import { ReplyIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByProps } from "@webpack"; import { findByCode } from "@webpack";
import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common"; import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const replyToMessage = findByCode(".TEXTAREA_FOCUS)", "showMentionToggle:");
const messageUtils = findByProps("replyToMessage");
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
// make sure the message is in the selected channel // make sure the message is in the selected channel
@ -43,7 +42,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
id="reply" id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY} label={i18n.Messages.MESSAGE_ACTION_REPLY}
icon={ReplyIcon} icon={ReplyIcon}
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} action={(e: React.MouseEvent) => replyToMessage(channel, message, e)}
/> />
)); ));
return; return;
@ -57,7 +56,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
id="reply" id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY} label={i18n.Messages.MESSAGE_ACTION_REPLY}
icon={ReplyIcon} icon={ReplyIcon}
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} action={(e: React.MouseEvent) => replyToMessage(channel, message, e)}
/> />
)); ));
return; return;

View file

@ -8,11 +8,11 @@ import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack"; import { findByCode, findByProps } from "@webpack";
import { ChannelStore, GuildStore } from "@webpack/common"; import { ChannelStore, GuildStore } from "@webpack/common";
const SummaryStore = findByProps("allSummaries", "findSummary"); const SummaryStore = findByProps("allSummaries", "findSummary");
const { createSummaryFromServer } = findByProps("createSummaryFromServer"); const createSummaryFromServer = findByCode(".people)),startId:");
const settings = definePluginSettings({ const settings = definePluginSettings({
summaryExpiryThresholdDays: { summaryExpiryThresholdDays: {
@ -55,9 +55,9 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
{ {
find: "ChannelTypesSets.SUMMARIZEABLE.has", find: "SUMMARIZEABLE.has",
replacement: { replacement: {
match: /\i\.hasFeature\(\i\.GuildFeatures\.SUMMARIES_ENABLED\w+?\)/g, match: /\i\.hasFeature\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g,
replace: "true" replace: "true"
} }
}, },

View file

@ -11,12 +11,12 @@ 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 { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findByProps, findExportedComponent } from "@webpack"; import { findByProps, findComponentByCode } from "@webpack";
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, IconUtils, 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 IconClasses = findByProps("icon", "acronym", "childWrapper"); const IconClasses = findByProps("icon", "acronym", "childWrapper");
const FriendRow = findExportedComponent("FriendRow"); const FriendRow = findComponentByCode(".listName,discriminatorClass");
const cl = classNameFactory("vc-gp-"); const cl = classNameFactory("vc-gp-");

View file

@ -33,7 +33,8 @@ import { VerifiedIcon } from "./VerifiedIcon";
const Section = findComponentByCode(".lastSection", "children:"); const Section = findComponentByCode(".lastSection", "children:");
const ThemeStore = findStore("ThemeStore"); const ThemeStore = findStore("ThemeStore");
const platformHooks: { useLegacyPlatformType(platform: string): string; } = findByProps("useLegacyPlatformType");
const useLegacyPlatformType: (platform: string) => string = findByCode(".TWITTER_LEGACY:");
const platforms: { get(type: string): ConnectionPlatform; } = findByProps("isSupported", "getByUrl"); const platforms: { get(type: string): ConnectionPlatform; } = findByProps("isSupported", "getByUrl");
const getProfileThemeProps = findByCode(".getPreviewThemeColors", "primaryColor:"); const getProfileThemeProps = findByCode(".getPreviewThemeColors", "primaryColor:");
@ -132,7 +133,7 @@ function ConnectionsComponent({ id, theme, simplified }: { id: string, theme: st
} }
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) { function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
const platform = platforms.get(platformHooks.useLegacyPlatformType(connection.type)); const platform = platforms.get(useLegacyPlatformType(connection.type));
const url = platform.getPlatformUserUrl?.(connection); const url = platform.getPlatformUserUrl?.(connection);
const img = ( const img = (
@ -202,7 +203,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".UserPopoutUpsellSource.PROFILE_PANEL,", find: ".PROFILE_PANEL,",
replacement: { replacement: {
// createElement(Divider, {}), createElement(NoteComponent) // createElement(Divider, {}), createElement(NoteComponent)
match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/, match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/,
@ -210,7 +211,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".UserProfileTypes.BITE_SIZE,onOpenProfile", find: ".BITE_SIZE,onOpenProfile",
replacement: { replacement: {
match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/, match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/,
replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })" replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })"

View file

@ -77,7 +77,7 @@ const enum ChannelFlags {
} }
const ChatScrollClasses = findByProps("auto", "content", "scrollerBase"); const ChatScrollClasses = findByProps("auto", "managedReactiveScroller");
const ChatClasses = findByProps("chat", "content", "noChat", "chatContent"); const ChatClasses = findByProps("chat", "content", "noChat", "chatContent");
const ChannelBeginHeader = findComponentByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE"); const ChannelBeginHeader = findComponentByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE");
const TagComponent = findComponentByCode(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG"); const TagComponent = findComponentByCode(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG");

View file

@ -116,14 +116,14 @@ export default definePlugin({
}, },
// Prevent Discord from trying to connect to hidden stage channels // Prevent Discord from trying to connect to hidden stage channels
{ {
find: ".MAX_STAGE_VOICE_USER_LIMIT})", find: ".AUDIENCE),{isSubscriptionGated",
replacement: { replacement: {
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})` replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
} }
}, },
{ {
find: "ChannelItemEditButton:function(){", find: 'tutorialId:"instant-invite"',
replacement: [ replacement: [
// Render null instead of the buttons if the channel is hidden // Render null instead of the buttons if the channel is hidden
...[ ...[
@ -195,7 +195,7 @@ export default definePlugin({
// Hide the new version of unreads box for hidden channels // Hide the new version of unreads box for hidden channels
find: '="ChannelListUnreadsStore",', find: '="ChannelListUnreadsStore",',
replacement: { replacement: {
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i)\))/g, // Global because Discord has multiple methods like that in the same module match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/,
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
} }
}, },
@ -203,15 +203,15 @@ export default definePlugin({
// Make the old version of unreads box not visible for hidden channels // Make the old version of unreads box not visible for hidden channels
find: "renderBottomUnread(){", find: "renderBottomUnread(){",
replacement: { replacement: {
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i\.record)\))/, match: /(?<=!0\))(?=&&\(0,\i\.\i\)\((\i\.record)\))/,
replace: "&&!$self.isHiddenChannel($1)" replace: "&&!$self.isHiddenChannel($1)"
} }
}, },
{ {
// Make the state of the old version of unreads box not include hidden channels // Make the state of the old version of unreads box not include hidden channels
find: ".useFlattenedChannelIdListWithThreads)", find: "ignoreRecents:!0",
replacement: { replacement: {
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i)\))/, match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/,
replace: "&&!$self.isHiddenChannel($1)" replace: "&&!$self.isHiddenChannel($1)"
} }
}, },
@ -257,7 +257,7 @@ export default definePlugin({
{ {
find: '"alt+shift+down"', find: '"alt+shift+down"',
replacement: { replacement: {
match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?getHasImportantUnread\)\(\i\))/, match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?>0\)&&\(0,\i\.\i\)\(\i\))/,
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
} }
}, },
@ -289,7 +289,7 @@ export default definePlugin({
}, },
{ {
// 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\(\)(?<=channel:(\i).+?)/, match: /forceRoles:.+?.value\(\)(?<=channel:(\i).+?)/,
replace: (m, channel) => `${m}.reduce(...$self.makeAllowedRolesReduce(${channel}.guild_id))` replace: (m, channel) => `${m}.reduce(...$self.makeAllowedRolesReduce(${channel}.guild_id))`
}, },
{ {
@ -422,7 +422,7 @@ export default definePlugin({
}, },
{ {
// Avoid filtering out hidden channels from the channel list // Avoid filtering out hidden channels from the channel list
match: /(?<=queryChannels\(\i\){.+?isGuildChannelType\)\((\i)\.type\))(?=&&!\i\.\i\.can\()/, match: /(?<=queryChannels\(\i\){.+?\)\((\i)\.type\))(?=&&!\i\.\i\.can\()/,
replace: "&&!$self.isHiddenChannel($1)" replace: "&&!$self.isHiddenChannel($1)"
} }
] ]

View file

@ -64,18 +64,18 @@ export default definePlugin({
}, },
}, },
{ {
find: "useShouldShowInvitesDisabledNotif:", find: "2022-07_invites_disabled",
predicate: () => settings.store.showInvitesPaused, predicate: () => settings.store.showInvitesPaused,
replacement: { replacement: {
match: /\i\.\i\.can\(\i\.Permissions.MANAGE_GUILD,\i\)/, match: /\i\.\i\.can\(\i\.\i.MANAGE_GUILD,\i\)/,
replace: "true", replace: "true",
}, },
}, },
{ {
find: "canAccessGuildMemberModViewWithExperiment:", find: /context:\i,checkElevated:!1\}\),\i\.\i.{0,200}autoTrackExposure/,
predicate: () => settings.store.showModView, predicate: () => settings.store.showModView,
replacement: { replacement: {
match: /return \i\.hasAny\(\i\.computePermissions\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.MemberSafetyPagePermissions\)/, match: /return \i\.\i\(\i\.\i\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.\i\)/,
replace: "return true", replace: "return true",
} }
}, },
@ -87,28 +87,31 @@ export default definePlugin({
replace: "{}" replace: "{}"
} }
}, },
// remove the 200 server minimum
{ {
find: "MINIMUM_MEMBER_COUNT:", find: '">200"',
predicate: () => settings.store.disableDiscoveryFilters, predicate: () => settings.store.disableDiscoveryFilters,
replacement: { replacement: {
match: /MINIMUM_MEMBER_COUNT:function\(\)\{return \i}/, match: '">200"',
replace: "MINIMUM_MEMBER_COUNT:() => \">0\"" replace: '">0"'
} }
}, },
// empty word filter (why would anyone search "horny" in fucking server discovery... please... why are we patching this again??)
{ {
find: "DiscoveryBannedSearchWords.includes", find: '"horny","fart"',
predicate: () => settings.store.disableDisallowedDiscoveryFilters, predicate: () => settings.store.disableDisallowedDiscoveryFilters,
replacement: { replacement: {
match: /(?<=function\(\){)(?=.{0,130}DiscoveryBannedSearchWords\.includes)/, match: /=\["egirl",.+?\]/,
replace: "return false;" replace: "=[]"
} }
}, },
// patch request that queries if term is allowed
{ {
find: "Endpoints.GUILD_DISCOVERY_VALID_TERM", find: ".GUILD_DISCOVERY_VALID_TERM",
predicate: () => settings.store.disableDisallowedDiscoveryFilters, predicate: () => settings.store.disableDisallowedDiscoveryFilters,
all: true, all: true,
replacement: { replacement: {
match: /\i\.HTTP\.get\(\{url:\i\.Endpoints\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g, match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g,
replace: "Promise.resolve({ body: { valid: true } });" replace: "Promise.resolve({ body: { valid: true } });"
} }
} }

View file

@ -48,7 +48,7 @@ export default definePlugin({
authors: [Devs.Rini, Devs.TheKodeToad], authors: [Devs.Rini, Devs.TheKodeToad],
patches: [ patches: [
{ {
find: ".useCanSeeRemixBadge)", find: '?"@":"")',
replacement: { replacement: {
match: /(?<=onContextMenu:\i,children:).*?\)}/, match: /(?<=onContextMenu:\i,children:).*?\)}/,
replace: "$self.renderUsername(arguments[0])}" replace: "$self.renderUsername(arguments[0])}"

View file

@ -68,7 +68,7 @@ interface Device {
type Repeat = "off" | "track" | "context"; type Repeat = "off" | "track" | "context";
const SpotifySocket = findByProps("getActiveSocketAndDevice"); const SpotifySocket = findByProps("getActiveSocketAndDevice");
const SpotifyUtils = findByProps("SpotifyAPI"); const SpotifyAPI = findByProps("vcSpotifyMarker");
// Don't wanna run before Flux and Dispatcher are ready! // Don't wanna run before Flux and Dispatcher are ready!
export const SpotifyStore = webpackDependantLazy(() => { export const SpotifyStore = webpackDependantLazy(() => {
@ -171,7 +171,7 @@ export const SpotifyStore = webpackDependantLazy(() => {
(data.query ??= {}).device_id = this.device.id; (data.query ??= {}).device_id = this.device.id;
const { socket } = SpotifySocket.getActiveSocketAndDevice(); const { socket } = SpotifySocket.getActiveSocketAndDevice();
return SpotifyUtils.SpotifyAPI[method](socket.accountId, socket.accessToken, { return SpotifyAPI[method](socket.accountId, socket.accessToken, {
url: API_BASE + route, url: API_BASE + route,
...data ...data
}); });

View file

@ -64,7 +64,7 @@ export default definePlugin({
replacement: [{ replacement: [{
// Adds POST and a Marker to the SpotifyAPI (so we can easily find it) // Adds POST and a Marker to the SpotifyAPI (so we can easily find it)
match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/, match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/,
replace: "post:$1.bind(null,$2.post),$&" replace: "post:$1.bind(null,$2.post),vcSpotifyMarker:1,$&"
}, },
{ {
// Spotify Connect API returns status 202 instead of 204 when skipping tracks. // Spotify Connect API returns status 202 instead of 204 when skipping tracks.
@ -84,7 +84,7 @@ export default definePlugin({
{ {
find: "artists.filter", find: "artists.filter",
replacement: { replacement: {
match: /\(0,(\i)\.isNotNullish\)\((\i)\.id\)&&/, match: /(?<=artists.filter\(\i=>).{0,10}\i\.id\)&&/,
replace: "" replace: ""
} }
} }

View file

@ -47,9 +47,9 @@ export default definePlugin({
} }
}, },
{ {
find: ".trackEmojiSearchEmpty,200", find: ".EMOJI_PICKER_CONSTANTS_EMOJI_CONTAINER_PADDING_HORIZONTAL)",
replacement: { replacement: {
match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, match: /(openPopoutType:void 0(?=.+?isBurstReaction:(\i).+?(\i===\i\.\i.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/,
replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})` replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})`
} }
} }

View file

@ -58,19 +58,13 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".NITRO_BANNER,", find: ".NITRO_BANNER,",
replacement: [ replacement: {
{
match: /(\i)\.premiumType/,
replace: "$self.patchPremiumType($1)||$&"
},
{
match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/, match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/,
replace: "&&$self.shouldShowBadge(arguments[0])$&" replace: "&&$self.shouldShowBadge(arguments[0])$&"
} }
]
}, },
{ {
find: "BannerLoadingStatus:function", find: ".banner)==null",
replacement: { replacement: {
match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/,
replace: "$self.patchBannerUrl(arguments[0])||$&" replace: "$self.patchBannerUrl(arguments[0])||$&"
@ -115,10 +109,6 @@ export default definePlugin({
if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId); if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId);
}, },
patchPremiumType({ userId }: any) {
if (this.userHasBackground(userId)) return 2;
},
shouldShowBadge({ displayProfile, user }: any) { shouldShowBadge({ displayProfile, user }: any) {
return displayProfile?.banner && (!this.userHasBackground(user.id) || settings.store.nitroFirst); return displayProfile?.banner && (!this.userHasBackground(user.id) || settings.store.nitroFirst);
}, },

View file

@ -6,7 +6,7 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByProps } from "@webpack"; import { findByCode } from "@webpack";
import { FluxDispatcher, RestAPI } from "@webpack/common"; import { FluxDispatcher, RestAPI } from "@webpack/common";
import { Message, User } from "discord-types/general"; import { Message, User } from "discord-types/general";
import { Channel } from "discord-types/general/index.js"; import { Channel } from "discord-types/general/index.js";
@ -29,7 +29,7 @@ interface Reply {
const fetching = new Map<string, string>(); const fetching = new Map<string, string>();
let ReplyStore: any; let ReplyStore: any;
const { createMessageRecord } = findByProps("createMessageRecord"); const createMessageRecord = findByCode(".createFromServer(", ".isBlockedForMessage", "messageReference:");
export default definePlugin({ export default definePlugin({
name: "ValidReply", name: "ValidReply",

View file

@ -184,7 +184,7 @@ export default definePlugin({
patches: [ patches: [
// Profiles Modal pfp // Profiles Modal pfp
...[".UserProfileTypes.MODAL,hasProfileEffect", ".UserProfileTypes.FULL_SIZE,hasProfileEffect:"].map(find => ({ ...[".MODAL,hasProfileEffect", ".FULL_SIZE,hasProfileEffect:"].map(find => ({
find, find,
replacement: { replacement: {
match: /\{src:(\i)(?=,avatarDecoration)/, match: /\{src:(\i)(?=,avatarDecoration)/,
@ -222,7 +222,7 @@ export default definePlugin({
{ {
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/, find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
replacement: { replacement: {
match: /null==\i\.icon\?.+?src:(\(0,\i\.getChannelIconURL\).+?\))(?=[,}])/, match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/,
replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})` replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})`
} }
}, },

View file

@ -21,6 +21,7 @@ import { Button, showToast, Toasts, useState } from "@webpack/common";
import type { VoiceRecorder } from "."; import type { VoiceRecorder } from ".";
import { settings } from "./settings"; import { settings } from "./settings";
import { MediaEngineStore } from "./utils";
const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative<typeof import("./native")>; const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative<typeof import("./native")>;
@ -41,6 +42,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC
{ {
echoCancellation: settings.store.echoCancellation, echoCancellation: settings.store.echoCancellation,
noiseCancellation: settings.store.noiseSuppression, noiseCancellation: settings.store.noiseSuppression,
deviceId: MediaEngineStore.getInputDeviceId(),
}, },
(success: boolean) => { (success: boolean) => {
if (success) if (success)

View file

@ -20,6 +20,7 @@ import { Button, useState } from "@webpack/common";
import type { VoiceRecorder } from "."; import type { VoiceRecorder } from ".";
import { settings } from "./settings"; import { settings } from "./settings";
import { MediaEngineStore } from "./utils";
export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => { export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => {
const [recording, setRecording] = useState(false); const [recording, setRecording] = useState(false);
@ -40,6 +41,7 @@ export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChang
audio: { audio: {
echoCancellation: settings.store.echoCancellation, echoCancellation: settings.store.echoCancellation,
noiseSuppression: settings.store.noiseSuppression, noiseSuppression: settings.store.noiseSuppression,
deviceId: MediaEngineStore.getInputDeviceId()
} }
}).then(stream => { }).then(stream => {
const chunks = [] as Blob[]; const chunks = [] as Blob[];

View file

@ -27,7 +27,7 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { chooseFile } from "@utils/web"; import { chooseFile } from "@utils/web";
import { findByProps, findStore } from "@webpack"; import { find, findByProps, findStore } from "@webpack";
import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { ComponentType } from "react"; import { ComponentType } from "react";
@ -37,7 +37,7 @@ import { cl } from "./utils";
import { VoicePreview } from "./VoicePreview"; import { VoicePreview } from "./VoicePreview";
import { VoiceRecorderWeb } from "./WebRecorder"; import { VoiceRecorderWeb } from "./WebRecorder";
const CloudUtils = findByProps("CloudUpload"); const CloudUpload = find(m => m.prototype?.trackUploadFinished);
const PendingReplyStore = findStore("PendingReplyStore"); const PendingReplyStore = findStore("PendingReplyStore");
const OptionClasses = findByProps("optionName", "optionIcon", "optionLabel"); const OptionClasses = findByProps("optionName", "optionIcon", "optionLabel");
@ -89,9 +89,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
const reply = PendingReplyStore.getPendingReply(channelId); const reply = PendingReplyStore.getPendingReply(channelId);
if (reply) FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId }); if (reply) FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId });
const upload = new CloudUtils.CloudUpload({ const upload = new CloudUpload({
file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }), file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }),
isClip: false,
isThumbnail: false, isThumbnail: false,
platform: 1, platform: 1,
}, channelId, false, 0); }, channelId, false, 0);

View file

@ -17,5 +17,7 @@
*/ */
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { findStore } from "@webpack";
export const MediaEngineStore = findStore("MediaEngineStore");
export const cl = classNameFactory("vc-vmsg-"); export const cl = classNameFactory("vc-vmsg-");

View file

@ -197,8 +197,8 @@ export default definePlugin({
{ {
find: '"MediaEngineWebRTC");', find: '"MediaEngineWebRTC");',
replacement: { replacement: {
match: /supports\(\i\)\{switch\(\i\)\{case (\i).Features/, match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/,
replace: "$&.DISABLE_VIDEO:return true;case $1.Features" replace: "$&.DISABLE_VIDEO:return true;$1"
} }
} }
], ],

View file

@ -9,11 +9,11 @@ import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { findByProps } from "@webpack"; import { find, findByCode } from "@webpack";
import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
const { ChannelTypes } = findByProps("ChannelTypes"); const ChannelTypes = find(m => m.ANNOUNCEMENT_THREAD === 10);
interface Message { interface Message {
guild_id: string, guild_id: string,
@ -68,7 +68,7 @@ interface Call {
ringing: string[]; ringing: string[];
} }
const Notifs = findByProps("makeTextChatNotification"); const notificationsShouldNotify = findByCode(".SUPPRESS_NOTIFICATIONS))return!1");
const XSLog = new Logger("XSOverlay"); const XSLog = new Logger("XSOverlay");
const settings = definePluginSettings({ const settings = definePluginSettings({
@ -136,7 +136,7 @@ const settings = definePluginSettings({
}, },
}); });
const Native = VencordNative.pluginHelpers.XsOverlay as PluginNative<typeof import("./native")>; const Native = VencordNative.pluginHelpers.XSOverlay as PluginNative<typeof import("./native")>;
export default definePlugin({ export default definePlugin({
name: "XSOverlay", name: "XSOverlay",
@ -304,7 +304,7 @@ function shouldNotify(message: Message, channel: string) {
const currentUser = UserStore.getCurrentUser(); const currentUser = UserStore.getCurrentUser();
if (message.author.id === currentUser.id) return false; if (message.author.id === currentUser.id) return false;
if (message.author.bot && !settings.store.botNotifications) return false; if (message.author.bot && !settings.store.botNotifications) return false;
return Notifs.shouldNotify(message, channel); return notificationsShouldNotify(message, channel);
} }
function calculateHeight(content: string) { function calculateHeight(content: string) {

View file

@ -120,6 +120,8 @@ export function openImageModal(url: string, props?: Partial<ImageModalProps>): s
placeholder={url} placeholder={url}
src={url} src={url}
renderLinkComponent={props => <MaskedLink {...props} />} renderLinkComponent={props => <MaskedLink {...props} />}
// Don't render forward message button
renderForwardComponent={() => null}
shouldHideMediaOptions={false} shouldHideMediaOptions={false}
shouldAnimate shouldAnimate
{...props} {...props}

View file

@ -16,7 +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 { filters, find, findByProps, findExportedComponent } from "@webpack"; import { filters, find, findByProps, findComponentByCode } from "@webpack";
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
import { NoopComponent } from "./react"; import { NoopComponent } from "./react";
@ -38,7 +38,7 @@ const enum ModalTransitionState {
export interface ModalProps { export interface ModalProps {
transitionState: ModalTransitionState; transitionState: ModalTransitionState;
onClose(): Promise<void>; onClose(): void;
} }
export interface ModalOptions { export interface ModalOptions {
@ -124,6 +124,7 @@ export type ImageModalProps = {
animated?: boolean; animated?: boolean;
responsive?: boolean; responsive?: boolean;
renderLinkComponent(props: any): ReactNode; renderLinkComponent(props: any): ReactNode;
renderForwardComponent(props: any): ReactNode;
maxWidth?: number; maxWidth?: number;
maxHeight?: number; maxHeight?: number;
shouldAnimate?: boolean; shouldAnimate?: boolean;
@ -131,7 +132,7 @@ export type ImageModalProps = {
shouldHideMediaOptions?: boolean; shouldHideMediaOptions?: boolean;
}; };
export const ImageModal = findExportedComponent<ImageModalProps>("ImageModal"); export const ImageModal = findComponentByCode<ImageModalProps>(".MEDIA_MODAL_CLOSE", "responsive");
const ModalAPI = findByProps("openModalLazy"); const ModalAPI = findByProps("openModalLazy");

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