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
</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",
"private": "true",
"version": "1.8.9",
"version": "1.9.0",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {

View file

@ -21,7 +21,7 @@ import esbuild from "esbuild";
import { readdir } from "fs/promises";
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 = {
IS_STANDALONE,
@ -76,22 +76,20 @@ const globNativesPlugin = {
for (const dir of pluginDirs) {
const dirPath = join("src", dir);
if (!await exists(dirPath)) continue;
const plugins = await readdir(dirPath);
for (const p of plugins) {
const nativePath = join(dirPath, p, "native.ts");
const indexNativePath = join(dirPath, p, "native/index.ts");
const plugins = await readdir(dirPath, { withFileTypes: true });
for (const file of plugins) {
const fileName = file.name;
const nativePath = join(dirPath, fileName, "native.ts");
const indexNativePath = join(dirPath, fileName, "native/index.ts");
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
continue;
const nameParts = p.split(".");
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 pluginName = await resolvePluginName(dirPath, file);
const mod = `p${i}`;
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`;
natives += `${JSON.stringify(pluginName)}:${mod},\n`;
i++;
}
}

View file

@ -53,6 +53,32 @@ export const banner = {
`.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) {
return await access(path, FsConstants.F_OK)
.then(() => true)
@ -88,31 +114,48 @@ export const globPlugins = kind => ({
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
let code = "";
let plugins = "\n";
let pluginsCode = "\n";
let metaCode = "\n";
let excludedCode = "\n";
let i = 0;
for (const dir of pluginDirs) {
if (!await exists(`./src/${dir}`)) continue;
const files = await readdir(`./src/${dir}`);
for (const file of files) {
if (file.startsWith("_") || file.startsWith(".")) continue;
if (file === "index.ts") continue;
const userPlugin = dir === "userplugins";
const fullDir = `./src/${dir}`;
if (!await exists(fullDir)) 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 === "dev" && !watch) continue;
if (target === "web" && kind === "discordDesktop") continue;
if (target === "desktop" && kind === "web") continue;
if (target === "discordDesktop" && kind !== "discordDesktop") continue;
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
const excluded =
(target === "dev" && !IS_DEV) ||
(target === "web" && kind === "discordDesktop") ||
(target === "desktop" && kind === "web") ||
(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}`;
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`;
plugins += `[${mod}.name]:${mod},\n`;
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
pluginsCode += `[${mod}.name]:${mod},\n`;
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
i++;
}
}
code += `export default {${plugins}};`;
code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`;
return {
contents: code,
resolveDir: "./src"

View file

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

View file

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

View file

@ -17,14 +17,14 @@
*/
import { mergeDefaults } from "@utils/mergeDefaults";
import { findByProps } from "@webpack";
import { findByCode } from "@webpack";
import { MessageActions, SnowflakeUtils } from "@webpack/common";
import { Message } from "discord-types/general";
import type { PartialDeep } from "type-fest";
import { Argument } from "./types";
const MessageCreator = findByProps("createBotMessage");
const createBotMessage = findByCode('username:"Clyde"');
export function generateId() {
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
@ -37,7 +37,7 @@ export function generateId() {
* @returns {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));

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 $Settings from "./Settings";
import * as $Styles from "./Styles";
import * as $UserSettingDefinitions from "./UserSettingDefinitions";
/**
* 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
*/
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 { Link } from "@components/Link";
import { DevsById } from "@utils/constants";
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
import { pluralise } from "@utils/misc";
import { fetchUserProfile } from "@utils/discord";
import { classes, pluralise } from "@utils/misc";
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 Plugins from "~plugins";
import { PluginCard } from ".";
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
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; }) {
useSettings();
@ -86,24 +72,18 @@ function ContributorModal({ user }: { user: User; }) {
/>
<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 && (
<Tooltip text={website}>
{props => (
<MaskedLink {...props} href={"https://" + website}>
<WebsiteIcon />
</MaskedLink>
)}
</Tooltip>
<WebsiteButton
text={website}
href={`https://${website}`}
/>
)}
{githubName && (
<Tooltip text={githubName}>
{props => (
<MaskedLink {...props} href={`https://github.com/${githubName}`}>
<GithubIcon />
</MaskedLink>
)}
</Tooltip>
<GithubButton
text={githubName}
href={`https://github.com/${githubName}`}
/>
)}
</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/>.
*/
import "./PluginModal.css";
import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { gitRemote } from "@shared/vencordUserAgent";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
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 { Constructor } from "type-fest";
import { PluginMeta } from "~plugins";
import {
ISettingElementProps,
SettingBooleanComponent,
@ -40,6 +46,9 @@ import {
SettingTextComponent
} from "./components";
import { openContributorModal } from "./ContributorModal";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
const cl = classNameFactory("vc-plugin-modal-");
const UserSummaryItem = findComponentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers");
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 (
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
<ModalHeader separator={false}>
<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} />
</ModalHeader>
<ModalContent>
<Forms.FormSection>
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
<Forms.FormText>{plugin.description}</Forms.FormText>
<Flex className={cl("info")}>
<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>
<div style={{ width: "fit-content", marginBottom: 8 }}>
<UserSummaryItem

View file

@ -42,16 +42,6 @@
.vc-author-modal-links {
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 {

View file

@ -35,9 +35,9 @@ import { openModalLazy } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
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
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins")) as typeof import("../../plugins");
@ -177,6 +177,37 @@ const enum SearchStatus {
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() {
const settings = useSettings();
const changes = React.useMemo(() => new ChangeList<string>(), []);
@ -215,26 +246,27 @@ export default function PluginSettings() {
return o;
}, []);
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []);
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
const search = searchValue.value.toLowerCase();
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
const enabled = settings.plugins[plugin.name]?.enabled;
if (enabled && searchValue.status === SearchStatus.DISABLED) return false;
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (!searchValue.value.length) return true;
const { status } = searchValue;
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
if (enabled && status === SearchStatus.DISABLED) return false;
if (!enabled && status === SearchStatus.ENABLED) return false;
if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (!search.length) return true;
const v = searchValue.value.toLowerCase();
return (
plugin.name.toLowerCase().includes(v) ||
plugin.description.toLowerCase().includes(v) ||
plugin.tags?.some(t => t.toLowerCase().includes(v))
plugin.name.toLowerCase().includes(search) ||
plugin.description.toLowerCase().includes(search) ||
plugin.tags?.some(t => t.toLowerCase().includes(search))
);
};
@ -255,13 +287,10 @@ export default function PluginSettings() {
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
}));
type P = JSX.Element | JSX.Element[];
let plugins: P, requiredPlugins: P;
if (sortedPlugins?.length) {
plugins = [];
requiredPlugins = [];
const plugins = [] as JSX.Element[];
const requiredPlugins = [] as JSX.Element[];
const showApi = searchValue.value === "API";
const showApi = searchValue.value.includes("API");
for (const p of sortedPlugins) {
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
continue;
@ -284,6 +313,7 @@ export default function PluginSettings() {
onRestartNeeded={name => changes.handleChange(name)}
disabled={true}
plugin={p}
key={p.name}
/>
)}
</Tooltip>
@ -299,10 +329,6 @@ export default function PluginSettings() {
/>
);
}
}
} else {
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
}
return (
@ -333,9 +359,18 @@ export default function PluginSettings() {
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
{plugins.length || requiredPlugins.length
? (
<div className={cl("grid")}>
{plugins}
{plugins.length
? plugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div>
)
: <ExcludedPluginsList search={search} />
}
<Forms.FormDivider className={Margins.top20} />
@ -343,7 +378,10 @@ export default function PluginSettings() {
Required Plugins
</Forms.FormTitle>
<div className={cl("grid")}>
{requiredPlugins}
{requiredPlugins.length
? requiredPlugins
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
}
</div>
</SettingsTab >
);

View file

@ -25,7 +25,7 @@ export async function loadLazyChunks() {
// True if resolved, false otherwise
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) {
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);

View file

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

5
src/modules.d.ts vendored
View file

@ -22,6 +22,11 @@
declare module "~plugins" {
const plugins: Record<string, import("./utils/types").Plugin>;
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" {

View file

@ -93,7 +93,7 @@ export default definePlugin({
{
find: ".PANEL]:14",
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));$&"
}
},

View file

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

View file

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

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",
noWarn: true,
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),$&"
}
},
replacement: [
{
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
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}`
}
},
{
find: "useDefaultUserSettingsSections:function",
replacement: {
match: /(?<=useDefaultUserSettingsSections:function\(\){return )(\i)\}/,
replace: "$self.wrapSettingsHook($1)}"
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
}
]
},
{
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/,
replace: "$2.default.open($1);return;"
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.open($1);return;"
}
}
],
@ -198,7 +176,7 @@ export default definePlugin({
patchedSettings: new WeakSet(),
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);

View file

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

View file

@ -9,7 +9,7 @@ import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
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 {
large_image?: string;

View file

@ -20,10 +20,10 @@ import { popNotice, showNotice } from "@api/Notices";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import definePlugin, { ReporterTestable } from "@utils/types";
import { findByProps } from "@webpack";
import { findByCode } from "@webpack";
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> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
@ -32,7 +32,7 @@ async function lookupAsset(applicationId: string, key: string): Promise<string>
const apps: any = {};
async function lookupApp(applicationId: string): Promise<string> {
const socket: any = {};
await RpcUtils.fetchApplicationsRPC(socket, applicationId);
await fetchApplicationsRPC(socket, applicationId);
return socket.application;
}

View file

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

View file

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
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 FolderSideBar from "./FolderSideBar";
@ -30,7 +30,7 @@ enum FolderIconDisplay {
MoreThanOneFolderExpanded
}
const { GuildsTree } = findByProps("GuildsTree");
const GuildsTree = find(m => m.prototype?.moveNextTo);
const SortedGuildStore = findStore("SortedGuildStore");
export const ExpandedGuildFolderStore = findStore("ExpandedGuildFolderStore");
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
{
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)`
},
// 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
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
find: ".toggleGuildFolderExpand(",
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
replacement: [
{
// Modify the expanded state to instead return the list of expanded folders
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
match: /(\],\(\)=>)(\i\.\i)\.isFolderExpanded\(\i\)\)/,
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders())`,
},
{
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
@ -196,7 +196,7 @@ export default definePlugin({
]
},
{
find: "APPLICATION_LIBRARY,render",
find: "APPLICATION_LIBRARY,render:",
predicate: () => settings.store.sidebar,
replacement: {
// Render the Better Folders sidebar

View file

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

View file

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

View file

@ -5,15 +5,18 @@
*/
import { definePluginSettings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { getCurrentGuild, openImageModal } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
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 DeveloperMode = getUserSettingDefinitionLazy("appearance", "developerMode")!;
function PencilIcon() {
return (
<svg
@ -62,12 +65,13 @@ export default definePlugin({
name: "BetterRoleContext",
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],
dependencies: ["UserSettingDefinitionsAPI"],
settings,
start() {
// DeveloperMode needs to be enabled for the context menu to be shown
TextAndImagesSettingsStores.DeveloperMode.updateSetting(true);
DeveloperMode.updateSetting(true);
},
contextMenus: {

View file

@ -77,15 +77,6 @@ export default definePlugin({
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} />
</div>
}
lowerBadgeWidth={20}
lowerBadgeHeight={20}
lowerBadgeSize={{
width: 20,
height: 20
}}
>
<div
className={SessionIconClasses.sessionIcon}
style={{ backgroundColor: GetOsColor(session.client_info.os) }}
>
<DeviceIcon width={28} height={28} />
<DeviceIcon width={28} height={28} color="currentColor" />
</div>
</BlobMask>
);

View file

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

View file

@ -11,7 +11,7 @@ import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
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";
const ColorPicker = findComponentByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
@ -30,7 +30,7 @@ function onPickColor(color: number) {
updateColorVars(hexColor);
}
const { saveClientTheme } = findByProps("saveClientTheme");
const saveClientTheme = findByCode('type:"UNSYNCED_USER_SETTINGS_UPDATE",settings:{useSystemTheme:"system"===');
function setTheme(theme: string) {
saveClientTheme({ theme });

View file

@ -24,12 +24,11 @@ import { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater";
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 ModalStack = findByProps("pushLazy", "popAll");
const DraftManager = findByProps("clearDraft", "saveDraft");
const { closeExpressionPicker } = findByProps("closeExpressionPicker", "openExpressionPicker");
const settings = definePluginSettings({
attemptToPreventCrashes: {
@ -134,7 +133,7 @@ export default definePlugin({
CrashHandlerLogger.debug("Failed to clear drafts.", err);
}
try {
closeExpressionPicker();
ExpressionPickerStore.closeExpressionPicker();
}
catch (err) {
CrashHandlerLogger.debug("Failed to close expression picker.", err);

View file

@ -40,9 +40,9 @@ export default definePlugin({
}),
patches: [
{
find: "KeyboardKeys.ENTER&&(!",
find: ".ENTER&&(!",
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)"
}
}

View file

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

View file

@ -33,26 +33,23 @@ export default definePlugin({
authors: [Devs.newwares],
settings,
patches: [
{
find: "IDLE_DURATION:function(){return",
replacement: {
match: /(IDLE_DURATION:function\(\){return )\i/,
replace: "$1$self.getIdleTimeout()"
}
},
{
find: 'type:"IDLE",idle:',
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)
},
{
match: /\i\.default\.dispatch\({type:"IDLE",idle:!1}\)/,
match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/,
replace: "$self.handleOnline()"
},
{
match: /(setInterval\(\i,\.25\*)\i\.IDLE_DURATION/,
match: /(setInterval\(\i,\.25\*)\i\.\i/,
replace: "$1$self.getIntervalDelay()" // For web installs
}
]

View file

@ -9,7 +9,6 @@ import "./ui/styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByProps } from "@webpack";
import { UserStore } from "@webpack/common";
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 DecorSection from "./ui/components/DecorSection";
const { isAnimatedAvatarDecoration } = findByProps("isAnimatedAvatarDecoration");
export interface AvatarDecoration {
asset: string;
skuId: string;
@ -61,7 +59,7 @@ export default definePlugin({
},
// 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"
}
]
@ -93,7 +91,7 @@ export default definePlugin({
replacement: [
// Use Decor avatar decoration hook
{
match: /(?<=getAvatarDecorationURL\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/,
match: /(?<=\i\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/,
replace: "$self.useUserDecorAvatarDecoration($1)??$&"
}
]
@ -133,7 +131,7 @@ export default definePlugin({
if (avatarDecoration?.skuId === SKU_ID) {
const parts = avatarDecoration.asset.split("_");
// 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`;
} else if (avatarDecoration?.skuId === RAW_SKU_ID) {
return avatarDecoration.asset;

View file

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

View file

@ -9,7 +9,7 @@ import { Link } from "@components/Link";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
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 { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
@ -19,7 +19,10 @@ import { AvatarDecorationModalPreview } from "../components";
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) {
const [url, setUrl] = useState<string | null>(null);

View file

@ -29,7 +29,7 @@ export default definePlugin({
{
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
replacement: {
match: /,?(?=\i\(this,"idleTimeout",new \i\.Timeout\))/,
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
replace: ";return;"
}
},

View file

@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
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 { Promisable } from "type-fest";
const StickersStore = findStore("StickersStore");
const EmojiManager = findByProps("fetchEmoji", "uploadEmoji", "deleteEmoji");
const uploadEmoji = findByCode(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
interface Sticker {
t: "Sticker";
@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) {
reader.readAsDataURL(data);
});
return EmojiManager.uploadEmoji({
return uploadEmoji({
guildId,
name: emoji.name.split("~")[0],
image: dataUrl

View file

@ -28,7 +28,7 @@ import { Forms, React } from "@webpack/common";
import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByProps("key", "removeBuildOverride");
const KbdStyles = findByProps("key", "combo");
const settings = definePluginSettings({
toolbarDevMenu: {
@ -106,9 +106,11 @@ export default definePlugin({
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
<Forms.FormText variant="text-md/normal">
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}>{altKey}</kbd> +{" "}
<kbd className={KbdStyles.key}>O</kbd>{" "}
</div>
</Forms.FormText>
</React.Fragment>
);

View file

@ -23,7 +23,7 @@ import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
import { Logger } from "@utils/Logger";
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 type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general";
@ -37,8 +37,8 @@ const StickerStore = findStore("StickersStore") as {
};
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) {
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 ClientThemeSettingsActionsCreators = webpackDependantLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
const isUnusableRoleSubscriptionEmoji = findByCode(".getUserIsAdmin(");
const enum EmojiIntentions {
REACTION,
STATUS,
@ -235,11 +237,10 @@ export default definePlugin({
},
// Allows the usage of subscription-locked emojis
{
find: "isUnusableRoleSubscriptionEmoji:function",
find: ".getUserIsAdmin(",
replacement: {
match: /isUnusableRoleSubscriptionEmoji:function/,
// Replace the original export with a func that always returns false and alias the original
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
match: /(function \i\(\i,\i)\){(.{0,250}.getUserIsAdmin\(.+?return!1})/,
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
}
},
// Allow stickers to be sent everywhere
@ -360,7 +361,7 @@ export default definePlugin({
replacement: [
{
// 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},`
},
{
@ -398,7 +399,7 @@ export default definePlugin({
},
// Separate patch for allowing using custom app icons
{
find: ".FreemiumAppIconIds.DEFAULT&&(",
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
replacement: {
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
replace: "true"
@ -471,12 +472,12 @@ export default definePlugin({
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
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 newAppearanceProto = currentAppearanceSettings != null
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS)
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), BINARY_READ_OPTIONS)
: AppearanceSettingsActionCreators.create();
newAppearanceProto.theme = theme;
@ -815,8 +816,7 @@ export default definePlugin({
if (e.type === 0) return true;
if (e.available === false) return false;
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
if (isUnusableRoleSubEmoji(e, this.guildId)) return false;
if (isUnusableRoleSubscriptionEmoji(e, this.guildId, true)) return false;
if (this.canUseEmotes)
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 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({
name: "FakeProfileThemes",

View file

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

View file

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

View file

@ -16,14 +16,12 @@
* 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 definePlugin from "@utils/types";
import { findByProps } from "@webpack";
import { Constants, RestAPI, UserStore } from "@webpack/common";
const FriendInvites = findByProps("createFriendInvite");
const { uuid4 } = findByProps("uuid4");
export default definePlugin({
name: "FriendInvites",
@ -35,47 +33,9 @@ export default definePlugin({
name: "create friend invite",
description: "Generates a friend invite link.",
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) => {
const uses = findOption<number>(args, "Uses", 5);
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();
}
const invite = await FriendInvites.createFriendInvite();
sendBotMessage(ctx.channel.id, {
content: `

View file

@ -4,21 +4,23 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
import { findByProps } from "@webpack";
import { findByCode, findByProps } from "@webpack";
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
const container = findByProps("memberSinceWrapper");
const { getCreatedAtDate } = findByProps("getCreatedAtDate");
const clydeMoreInfo = findByProps("clydeMoreInfo");
const getCreatedAtDate = findByCode('month:"short",day:"numeric"');
const locale = findByProps("getLocale");
const lastSection = findByProps("lastSection");
const cl = classNameFactory("vc-friendssince-");
export default definePlugin({
name: "FriendsSince",
description: "Shows when you became friends with someone in the user popout",
@ -26,17 +28,17 @@ export default definePlugin({
patches: [
// User popup
{
find: ".AnalyticsSections.USER_PROFILE}",
find: ".USER_PROFILE}};return",
replacement: {
match: /\i.default,\{userId:(\i.id).{0,30}}\)/,
match: /,{userId:(\i.id).{0,30}}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })"
}
},
// User DMs "User Profile" popup in the right
{
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
find: ".PROFILE_PANEL,",
replacement: {
match: /\i.default,\{userId:([^,]+?)}\)/,
match: /,{userId:([^,]+?)}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })"
}
},
@ -69,7 +71,7 @@ export default definePlugin({
return (
<div className={lastSection.section}>
<Heading variant="eyebrow" className={clydeMoreInfo.title}>
<Heading variant="eyebrow" className={cl("title")}>
Friends Since
</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" />
</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())}
</Text>
</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 { disableStyle, enableStyle } from "@api/Styles";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCode } from "@webpack";
import { StatusSettingsStores } from "@webpack/common";
import style from "./style.css?managed";
const Button = findComponentByCode("Button.Sizes.NONE,disabled:");
const ShowCurrentGame = getUserSettingDefinitionLazy<boolean>("status", "showCurrentGame")!;
function makeIcon(showCurrentGame?: boolean) {
const { oldIcon } = settings.use(["oldIcon"]);
@ -60,7 +62,7 @@ function makeIcon(showCurrentGame?: boolean) {
}
function GameActivityToggleButton() {
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting();
const showCurrentGame = ShowCurrentGame.useSetting();
return (
<Button
@ -68,7 +70,7 @@ function GameActivityToggleButton() {
icon={makeIcon(showCurrentGame)}
role="switch"
aria-checked={!showCurrentGame}
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)}
onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/>
);
}
@ -85,6 +87,7 @@ export default definePlugin({
name: "GameActivityToggle",
description: "Adds a button next to the mic and deafen button to toggle game activity.",
authors: [Devs.Nuckyz, Devs.RuukuLada],
dependencies: ["UserSettingDefinitionsAPI"],
settings,
patches: [

View file

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

View file

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack";
import { find } from "@webpack";
import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common";
import { Channel, Message } from "discord-types/general";
@ -49,7 +49,7 @@ const settings = definePluginSettings({
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[]) {
const options = MessageActions.getSendMessageOptionsForReply({

View file

@ -6,13 +6,14 @@
import * as DataStore from "@api/DataStore";
import { definePluginSettings, Settings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
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 {
Game,
@ -27,6 +28,8 @@ interface IgnoredActivity {
const RunningGameStore = findStore("RunningGameStore");
const ShowCurrentGame = getUserSettingDefinitionLazy("status", "showCurrentGame")!;
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
return (
<Tooltip text={tooltipText}>
@ -68,7 +71,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
// Trigger activities recalculation
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
ShowCurrentGame.updateSetting(old => old);
}
function ImportCustomRPCComponent() {
@ -205,6 +208,7 @@ export default definePlugin({
name: "IgnoreActivities",
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.",
dependencies: ["UserSettingDefinitionsAPI"],
settings,

View file

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

View file

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

View file

@ -19,7 +19,6 @@
import { registerCommand, unregisterCommand } from "@api/Commands";
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings";
import { onceDefined } from "@shared/onceDefined";
import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches";
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
@ -34,7 +33,7 @@ const logger = new Logger("PluginManager", "#a6d189");
export const PMLogger = logger;
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 */
let enabledPluginsSubscribedFlux = false;
@ -43,16 +42,6 @@ const subscribedFluxEventsPlugins = new Set<string>();
const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins;
const forceDisabled = new Set([
"MessageLogger",
"ShowHiddenChannels",
"MoreUserTags",
"Decor",
"IgnoreActivities",
"NoBlockedMessages",
"BetterFolders",
"NoPendingCount"
]);
export function isPluginEnabled(p: string) {
return (
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) {
logger.info(`Starting plugins (stage ${target})`);
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))) {
const p = Plugins[name];
@ -214,7 +195,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
}
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p;
const { name, commands, contextMenus } = p;
if (p.start) {
logger.info("Starting plugin", name);
@ -260,7 +241,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
}, p => `startPlugin ${p.name}`);
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p;
const { name, commands, contextMenus } = p;
if (p.stop) {
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 { SlateTransforms } = findByProps("SlateTransforms");
const SlateTransforms = findByProps("insertText", "selectCommandOption");
export default definePlugin({
name: "MaskedLinkPaste",
authors: [Devs.TheSun],
description: "Pasting a link while having text selected will paste a hyperlink",
patches: [{
patches: [
{
find: ".selection,preventEmojiSurrogates:",
replacement: {
match: /(?<=SlateTransforms.delete.{0,50})(\i)\.insertText\((\i)\)/,
match: /(?<=\i.delete.{0,50})(\i)\.insertText\((\i)\)/,
replace: "$self.handlePaste($1, $2, () => $&)"
}
}],
}
],
handlePaste(editor, content: string, originalBehavior: () => void) {
if (content && linkRegex.test(content) && editor.operations?.[0]?.type === "remove_text") {

View file

@ -19,6 +19,7 @@
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
import { updateMessage } from "@api/MessageUpdater";
import { definePluginSettings } from "@api/Settings";
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js";
import { classes } from "@utils/misc";
@ -37,7 +38,6 @@ import {
PermissionStore,
RestAPI,
Text,
TextAndImagesSettingsStores,
UserStore
} from "@webpack/common";
import { Channel, Message } from "discord-types/general";
@ -49,11 +49,13 @@ const messageCache = new Map<string, {
const Embed = findComponentByCode(".inlineMediaEmbed");
const AutoModEmbed = findComponentByCode(".withFooter]:", "childrenMessageContent:");
const ChannelMessage = findComponentByCode("renderSimpleAccessories)");
const ChannelMessage = findComponentByCode("childrenExecutedCommand:", ".hideAccessories");
const SearchResultClasses = findByProps("message", "searchResult");
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 tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
@ -280,7 +282,7 @@ function getChannelLabelAndIconUrl(channel: Channel) {
}
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]);
@ -316,7 +318,7 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
const { message, channel } = props;
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
const compact = MessageDisplayCompact.useSetting();
const images = getImages(message);
const { parse } = Parser;
@ -363,7 +365,7 @@ export default definePlugin({
name: "MessageLinkEmbeds",
description: "Adds a preview to messages that link another message",
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI"],
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "UserSettingDefinitionsAPI"],
settings,

View file

@ -22,9 +22,10 @@ import { Devs } from "@utils/constants";
import { LazyComponentType } from "@utils/lazyReact";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findComponentByCode } from "@webpack";
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState, UtilTypes } from "@webpack/common";
import { Channel, Message, User } from "discord-types/general";
import { findByCode, findComponentByCode } from "@webpack";
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
import type { Permissions } from "@webpack/types";
import type { Channel, Guild, Message, User } from "discord-types/general";
interface Tag {
// 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
displayName: string;
description: string;
permissions?: UtilTypes.Permissions[];
permissions?: Permissions[];
condition?(message: Message | null, user: User, channel: Channel): boolean;
}
@ -52,10 +53,14 @@ interface TagSettings {
[k: string]: TagSetting;
}
// PermissionStore.computePermissions is not the same function and doesn't work here
const PermissionUtil = findByProps("computePermissions", "canEveryoneRole") as {
computePermissions({ ...args }): bigint;
};
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
const computePermissions: (options: {
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>; };
@ -191,7 +196,7 @@ export default definePlugin({
patches: [
// add tags to the tag list
{
find: "BotTagTypes:",
find: ".ORIGINAL_POSTER=",
replacement: {
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
replace: "($1=$self.getTagTypes()))[$2.BOT"
@ -220,7 +225,7 @@ export default definePlugin({
},
// in messages
{
find: "renderSystemTag:",
find: ".Types.ORIGINAL_POSTER",
replacement: {
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"
@ -281,7 +286,7 @@ export default definePlugin({
const guild = GuildStore.getGuild(channel?.guild_id);
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)
.map(([perm, permInt]) =>
permissions & permInt ? perm : ""
@ -328,7 +333,7 @@ export default definePlugin({
}: {
message?: Message,
user: User & { isClyde(): boolean; },
channel?: Channel & { isForumPost(): boolean; },
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
channelId?: string;
origType?: number;
location: "chat" | "not-chat";
@ -365,7 +370,7 @@ export default definePlugin({
tag.permissions?.some(perm => perms.includes(perm)) ||
(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`];
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
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: {
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"

View file

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

View file

@ -18,8 +18,10 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps } from "@webpack";
import { Message } from "discord-types/general";
const RelationshipStore = findByProps("getRelationships", "isBlocked");
@ -62,6 +64,20 @@ export default definePlugin({
]
}))
],
isBlocked: message =>
RelationshipStore.isBlocked(message.author.id)
options: {
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: [
{
find: "HangStatusTypes.CHILLING)",
find: ".CHILLING)",
replacement: {
match: /{enableHangStatus:(\i),/,
replace: "{_enableHangStatus:$1=false,"

View file

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

View file

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

View file

@ -63,8 +63,8 @@ export default definePlugin({
{
find: "trackAnnouncementMessageLinkClicked({",
replacement: {
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/,
replace: "async $& if(await $self.handleLink(...arguments)) return;"
match: /function (\i\(\i,\i\)\{)(?=.{0,100}trusted:)/,
replace: "async function $1 if(await $self.handleLink(...arguments)) return;"
}
},
// Make Spotify profile activity links open in app on web

View file

@ -19,10 +19,10 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByProps } from "@webpack";
import { find } from "@webpack";
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) {
// 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})",
},
{
match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/,
replace: "$1,setChecked=$2"
match: /\.INVITES_DISABLED\)(?=.+?\.Messages\.INVITES_PERMANENTLY_DISABLED_TIP.+?checked:(\i)).+?\[\1,(\i)\]=\i.useState\(\i\)/,
replace: "$&,setChecked=$2"
}
]
}

View file

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

View file

@ -82,7 +82,7 @@ export default definePlugin({
// 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)();"
},
{
@ -131,7 +131,7 @@ export default definePlugin({
// Fix Alt Up/Down navigation
{
find: ".Routes.APPLICATION_STORE&&",
find: ".APPLICATION_STORE&&",
replacement: {
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@
*/
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 { Auth, authorize } from "../auth";
@ -27,12 +27,11 @@ import { settings } from "../settings";
import { cl, showToast } from "../utils";
import ReviewComponent from "./ReviewComponent";
const { Editor, Transforms } = findByProps("Editor", "Transforms");
const { ChatInputTypes } = findByProps("ChatInputTypes");
const InputComponent = findComponentByCode("default.CHANNEL_TEXT_AREA", "input");
const { createChannelRecordFromServer } = findByProps("createChannelRecordFromServer");
const Transforms = findByProps("insertNodes", "textToText");
const Editor = findByProps("start", "end", "toSlateRange");
const ChatInputTypes = findByProps("FORM");
const InputComponent = findComponentByCode("disableThemedBackground", "CHANNEL_TEXT_AREA");
const createChannelRecordFromServer = findByCode(".GUILD_TEXT])", "fromServer)");
interface UserProps {
discordId: string;

View file

@ -71,7 +71,7 @@ export default definePlugin({
find: ".userTooltip,children",
replacement: [
{
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.default,{(?=children)/,
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
replace: "$&color:$self.getUserColor($1,{guildId:$2}),"
}
],
@ -110,7 +110,7 @@ export default definePlugin({
{
find: ".reactorDefault",
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)}"
},
predicate: () => settings.store.reactorsList,

View file

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

View file

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

View file

@ -11,12 +11,12 @@ import { openImageModal, openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
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 { Guild, User } from "discord-types/general";
const IconClasses = findByProps("icon", "acronym", "childWrapper");
const FriendRow = findExportedComponent("FriendRow");
const FriendRow = findComponentByCode(".listName,discriminatorClass");
const cl = classNameFactory("vc-gp-");

View file

@ -33,7 +33,8 @@ import { VerifiedIcon } from "./VerifiedIcon";
const Section = findComponentByCode(".lastSection", "children:");
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 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; }) {
const platform = platforms.get(platformHooks.useLegacyPlatformType(connection.type));
const platform = platforms.get(useLegacyPlatformType(connection.type));
const url = platform.getPlatformUserUrl?.(connection);
const img = (
@ -202,7 +203,7 @@ export default definePlugin({
}
},
{
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
find: ".PROFILE_PANEL,",
replacement: {
// createElement(Divider, {}), createElement(NoteComponent)
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: {
match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/,
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 ChannelBeginHeader = findComponentByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE");
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
{
find: ".MAX_STAGE_VOICE_USER_LIMIT})",
find: ".AUDIENCE),{isSubscriptionGated",
replacement: {
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
}
},
{
find: "ChannelItemEditButton:function(){",
find: 'tutorialId:"instant-invite"',
replacement: [
// 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
find: '="ChannelListUnreadsStore",',
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})`
}
},
@ -203,15 +203,15 @@ export default definePlugin({
// Make the old version of unreads box not visible for hidden channels
find: "renderBottomUnread(){",
replacement: {
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i\.record)\))/,
match: /(?<=!0\))(?=&&\(0,\i\.\i\)\((\i\.record)\))/,
replace: "&&!$self.isHiddenChannel($1)"
}
},
{
// Make the state of the old version of unreads box not include hidden channels
find: ".useFlattenedChannelIdListWithThreads)",
find: "ignoreRecents:!0",
replacement: {
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i)\))/,
match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/,
replace: "&&!$self.isHiddenChannel($1)"
}
},
@ -257,7 +257,7 @@ export default definePlugin({
{
find: '"alt+shift+down"',
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})`
}
},
@ -289,7 +289,7 @@ export default definePlugin({
},
{
// 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))`
},
{
@ -422,7 +422,7 @@ export default definePlugin({
},
{
// 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)"
}
]

View file

@ -64,18 +64,18 @@ export default definePlugin({
},
},
{
find: "useShouldShowInvitesDisabledNotif:",
find: "2022-07_invites_disabled",
predicate: () => settings.store.showInvitesPaused,
replacement: {
match: /\i\.\i\.can\(\i\.Permissions.MANAGE_GUILD,\i\)/,
match: /\i\.\i\.can\(\i\.\i.MANAGE_GUILD,\i\)/,
replace: "true",
},
},
{
find: "canAccessGuildMemberModViewWithExperiment:",
find: /context:\i,checkElevated:!1\}\),\i\.\i.{0,200}autoTrackExposure/,
predicate: () => settings.store.showModView,
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",
}
},
@ -87,28 +87,31 @@ export default definePlugin({
replace: "{}"
}
},
// remove the 200 server minimum
{
find: "MINIMUM_MEMBER_COUNT:",
find: '">200"',
predicate: () => settings.store.disableDiscoveryFilters,
replacement: {
match: /MINIMUM_MEMBER_COUNT:function\(\)\{return \i}/,
replace: "MINIMUM_MEMBER_COUNT:() => \">0\""
match: '">200"',
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,
replacement: {
match: /(?<=function\(\){)(?=.{0,130}DiscoveryBannedSearchWords\.includes)/,
replace: "return false;"
match: /=\["egirl",.+?\]/,
replace: "=[]"
}
},
// patch request that queries if term is allowed
{
find: "Endpoints.GUILD_DISCOVERY_VALID_TERM",
find: ".GUILD_DISCOVERY_VALID_TERM",
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
all: true,
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 } });"
}
}

View file

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

View file

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

View file

@ -64,7 +64,7 @@ export default definePlugin({
replacement: [{
// Adds POST and a Marker to the SpotifyAPI (so we can easily find it)
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.
@ -84,7 +84,7 @@ export default definePlugin({
{
find: "artists.filter",
replacement: {
match: /\(0,(\i)\.isNotNullish\)\((\i)\.id\)&&/,
match: /(?<=artists.filter\(\i=>).{0,10}\i\.id\)&&/,
replace: ""
}
}

View file

@ -47,9 +47,9 @@ export default definePlugin({
}
},
{
find: ".trackEmojiSearchEmpty,200",
find: ".EMOJI_PICKER_CONSTANTS_EMOJI_CONTAINER_PADDING_HORIZONTAL)",
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})`
}
}

View file

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

View file

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

View file

@ -184,7 +184,7 @@ export default definePlugin({
patches: [
// Profiles Modal pfp
...[".UserProfileTypes.MODAL,hasProfileEffect", ".UserProfileTypes.FULL_SIZE,hasProfileEffect:"].map(find => ({
...[".MODAL,hasProfileEffect", ".FULL_SIZE,hasProfileEffect:"].map(find => ({
find,
replacement: {
match: /\{src:(\i)(?=,avatarDecoration)/,
@ -222,7 +222,7 @@ export default definePlugin({
{
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
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})`
}
},

View file

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

View file

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

View file

@ -27,7 +27,7 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa
import { useAwaiter } from "@utils/react";
import definePlugin from "@utils/types";
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 { ComponentType } from "react";
@ -37,7 +37,7 @@ import { cl } from "./utils";
import { VoicePreview } from "./VoicePreview";
import { VoiceRecorderWeb } from "./WebRecorder";
const CloudUtils = findByProps("CloudUpload");
const CloudUpload = find(m => m.prototype?.trackUploadFinished);
const PendingReplyStore = findStore("PendingReplyStore");
const OptionClasses = findByProps("optionName", "optionIcon", "optionLabel");
@ -89,9 +89,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
const reply = PendingReplyStore.getPendingReply(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" }),
isClip: false,
isThumbnail: false,
platform: 1,
}, channelId, false, 0);

View file

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

View file

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

View file

@ -9,11 +9,11 @@ import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
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 type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general";
const { ChannelTypes } = findByProps("ChannelTypes");
const ChannelTypes = find(m => m.ANNOUNCEMENT_THREAD === 10);
interface Message {
guild_id: string,
@ -68,7 +68,7 @@ interface Call {
ringing: string[];
}
const Notifs = findByProps("makeTextChatNotification");
const notificationsShouldNotify = findByCode(".SUPPRESS_NOTIFICATIONS))return!1");
const XSLog = new Logger("XSOverlay");
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({
name: "XSOverlay",
@ -304,7 +304,7 @@ function shouldNotify(message: Message, channel: string) {
const currentUser = UserStore.getCurrentUser();
if (message.author.id === currentUser.id) return false;
if (message.author.bot && !settings.store.botNotifications) return false;
return Notifs.shouldNotify(message, channel);
return notificationsShouldNotify(message, channel);
}
function calculateHeight(content: string) {

View file

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

View file

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

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