Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
7f29a10ecb
46 changed files with 838 additions and 458 deletions
|
@ -21,7 +21,7 @@ import esbuild from "esbuild";
|
||||||
import { readdir } from "fs/promises";
|
import { readdir } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, watch } from "./common.mjs";
|
||||||
|
|
||||||
const defines = {
|
const defines = {
|
||||||
IS_STANDALONE,
|
IS_STANDALONE,
|
||||||
|
@ -76,22 +76,20 @@ const globNativesPlugin = {
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
const dirPath = join("src", dir);
|
const dirPath = join("src", dir);
|
||||||
if (!await exists(dirPath)) continue;
|
if (!await exists(dirPath)) continue;
|
||||||
const plugins = await readdir(dirPath);
|
const plugins = await readdir(dirPath, { withFileTypes: true });
|
||||||
for (const p of plugins) {
|
for (const file of plugins) {
|
||||||
const nativePath = join(dirPath, p, "native.ts");
|
const fileName = file.name;
|
||||||
const indexNativePath = join(dirPath, p, "native/index.ts");
|
const nativePath = join(dirPath, fileName, "native.ts");
|
||||||
|
const indexNativePath = join(dirPath, fileName, "native/index.ts");
|
||||||
|
|
||||||
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
|
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const nameParts = p.split(".");
|
const pluginName = await resolvePluginName(dirPath, file);
|
||||||
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
|
|
||||||
// pluginName.thing.desktop -> PluginName.thing
|
|
||||||
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
|
|
||||||
|
|
||||||
const mod = `p${i}`;
|
const mod = `p${i}`;
|
||||||
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
|
code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`;
|
||||||
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
|
natives += `${JSON.stringify(pluginName)}:${mod},\n`;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,32 @@ export const banner = {
|
||||||
`.trim()
|
`.trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
||||||
|
/**
|
||||||
|
* @param {string} base
|
||||||
|
* @param {import("fs").Dirent} dirent
|
||||||
|
*/
|
||||||
|
export async function resolvePluginName(base, dirent) {
|
||||||
|
const fullPath = join(base, dirent.name);
|
||||||
|
const content = dirent.isFile()
|
||||||
|
? await readFile(fullPath, "utf-8")
|
||||||
|
: await (async () => {
|
||||||
|
for (const file of ["index.ts", "index.tsx"]) {
|
||||||
|
try {
|
||||||
|
return await readFile(join(fullPath, file), "utf-8");
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid plugin ${fullPath}: could not resolve entry point`);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return PluginDefinitionNameMatcher.exec(content)?.[3]
|
||||||
|
?? (() => {
|
||||||
|
throw new Error(`Invalid plugin ${fullPath}: must contain definePlugin call with simple string name property as first property`);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
export async function exists(path) {
|
export async function exists(path) {
|
||||||
return await access(path, FsConstants.F_OK)
|
return await access(path, FsConstants.F_OK)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
|
@ -88,31 +114,48 @@ export const globPlugins = kind => ({
|
||||||
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
||||||
let code = "";
|
let code = "";
|
||||||
let plugins = "\n";
|
let pluginsCode = "\n";
|
||||||
|
let metaCode = "\n";
|
||||||
|
let excludedCode = "\n";
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
if (!await exists(`./src/${dir}`)) continue;
|
const userPlugin = dir === "userplugins";
|
||||||
const files = await readdir(`./src/${dir}`);
|
|
||||||
for (const file of files) {
|
const fullDir = `./src/${dir}`;
|
||||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
if (!await exists(fullDir)) continue;
|
||||||
if (file === "index.ts") continue;
|
const files = await readdir(fullDir, { withFileTypes: true });
|
||||||
|
for (const file of files) {
|
||||||
|
const fileName = file.name;
|
||||||
|
if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
|
||||||
|
if (fileName === "index.ts") continue;
|
||||||
|
|
||||||
|
const target = getPluginTarget(fileName);
|
||||||
|
|
||||||
const target = getPluginTarget(file);
|
|
||||||
if (target && !IS_REPORTER) {
|
if (target && !IS_REPORTER) {
|
||||||
if (target === "dev" && !watch) continue;
|
const excluded =
|
||||||
if (target === "web" && kind === "discordDesktop") continue;
|
(target === "dev" && !IS_DEV) ||
|
||||||
if (target === "desktop" && kind === "web") continue;
|
(target === "web" && kind === "discordDesktop") ||
|
||||||
if (target === "discordDesktop" && kind !== "discordDesktop") continue;
|
(target === "desktop" && kind === "web") ||
|
||||||
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
|
(target === "discordDesktop" && kind !== "discordDesktop") ||
|
||||||
|
(target === "vencordDesktop" && kind !== "vencordDesktop");
|
||||||
|
|
||||||
|
if (excluded) {
|
||||||
|
const name = await resolvePluginName(fullDir, file);
|
||||||
|
excludedCode += `${JSON.stringify(name)}:${JSON.stringify(target)},\n`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
|
||||||
|
|
||||||
const mod = `p${i}`;
|
const mod = `p${i}`;
|
||||||
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`;
|
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
|
||||||
plugins += `[${mod}.name]:${mod},\n`;
|
pluginsCode += `[${mod}.name]:${mod},\n`;
|
||||||
|
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code += `export default {${plugins}};`;
|
code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`;
|
||||||
return {
|
return {
|
||||||
contents: code,
|
contents: code,
|
||||||
resolveDir: "./src"
|
resolveDir: "./src"
|
||||||
|
|
|
@ -39,7 +39,7 @@ interface PluginData {
|
||||||
hasCommands: boolean;
|
hasCommands: boolean;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
enabledByDefault: boolean;
|
enabledByDefault: boolean;
|
||||||
target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
|
target: "discordDesktop" | "vencordDesktop" | "desktop" | "web" | "dev";
|
||||||
filePath: string;
|
filePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { proxyLazy } from "@utils/lazy";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
|
||||||
|
|
||||||
import { Settings } from "./Settings";
|
|
||||||
|
|
||||||
interface Setting<T> {
|
|
||||||
/**
|
|
||||||
* Get the setting value
|
|
||||||
*/
|
|
||||||
getSetting(): T;
|
|
||||||
/**
|
|
||||||
* Update the setting value
|
|
||||||
* @param value The new value
|
|
||||||
*/
|
|
||||||
updateSetting(value: T | ((old: T) => T)): Promise<void>;
|
|
||||||
/**
|
|
||||||
* React hook for automatically updating components when the setting is updated
|
|
||||||
*/
|
|
||||||
useSetting(): T;
|
|
||||||
settingsStoreApiGroup: string;
|
|
||||||
settingsStoreApiName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SettingsStores: Array<Setting<any>> | undefined = proxyLazyWebpack(() => {
|
|
||||||
const modId = findModuleId('"textAndImages","renderSpoilers"') as any;
|
|
||||||
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
|
|
||||||
|
|
||||||
const mod = wreq(modId);
|
|
||||||
if (mod == null) return;
|
|
||||||
|
|
||||||
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the store for a setting
|
|
||||||
* @param group The setting group
|
|
||||||
* @param name The name of the setting
|
|
||||||
*/
|
|
||||||
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
|
|
||||||
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
|
|
||||||
|
|
||||||
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getSettingStore but lazy
|
|
||||||
*/
|
|
||||||
export function getSettingStoreLazy<T = any>(group: string, name: string) {
|
|
||||||
return proxyLazy(() => getSettingStore<T>(group, name));
|
|
||||||
}
|
|
81
src/api/UserSettings.ts
Normal file
81
src/api/UserSettings.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { proxyLazy } from "@utils/lazy";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||||
|
|
||||||
|
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;
|
||||||
|
userSettingsAPIGroup: string;
|
||||||
|
userSettingsAPIName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
|
||||||
|
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
||||||
|
if (modId == null) return new Logger("UserSettingsAPI ").error("Didn't find settings module.");
|
||||||
|
|
||||||
|
return wreq(modId as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the setting with the given setting group and name.
|
||||||
|
*
|
||||||
|
* @param group The setting group
|
||||||
|
* @param name The name of the setting
|
||||||
|
*/
|
||||||
|
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
|
||||||
|
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency.");
|
||||||
|
|
||||||
|
for (const key in UserSettings) {
|
||||||
|
const userSetting = UserSettings[key];
|
||||||
|
|
||||||
|
if (userSetting.userSettingsAPIGroup === group && userSetting.userSettingsAPIName === name) {
|
||||||
|
return userSetting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getUserSettingDefinition}, lazy.
|
||||||
|
*
|
||||||
|
* Get the setting with the given setting group and name.
|
||||||
|
*
|
||||||
|
* @param group The setting group
|
||||||
|
* @param name The name of the setting
|
||||||
|
*/
|
||||||
|
export function getUserSettingLazy<T = any>(group: string, name: string) {
|
||||||
|
return proxyLazy(() => getUserSetting<T>(group, name));
|
||||||
|
}
|
|
@ -31,8 +31,8 @@ import * as $Notices from "./Notices";
|
||||||
import * as $Notifications from "./Notifications";
|
import * as $Notifications from "./Notifications";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
import * as $Settings from "./Settings";
|
import * as $Settings from "./Settings";
|
||||||
import * as $SettingsStores from "./SettingsStores";
|
|
||||||
import * as $Styles from "./Styles";
|
import * as $Styles from "./Styles";
|
||||||
|
import * as $UserSettings from "./UserSettings";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An API allowing you to listen to Message Clicks or run your own logic
|
* An API allowing you to listen to Message Clicks or run your own logic
|
||||||
|
@ -118,4 +118,7 @@ export const ChatButtons = $ChatButtons;
|
||||||
*/
|
*/
|
||||||
export const MessageUpdater = $MessageUpdater;
|
export const MessageUpdater = $MessageUpdater;
|
||||||
|
|
||||||
export const SettingsStores = $SettingsStores;
|
/**
|
||||||
|
* An API allowing you to get an user setting
|
||||||
|
*/
|
||||||
|
export const UserSettings = $UserSettings;
|
||||||
|
|
|
@ -11,20 +11,16 @@ import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { DevsById } from "@utils/constants";
|
import { DevsById } from "@utils/constants";
|
||||||
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
|
import { fetchUserProfile } from "@utils/discord";
|
||||||
import { pluralise } from "@utils/misc";
|
import { classes, pluralise } from "@utils/misc";
|
||||||
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
||||||
import { Forms, MaskedLink, showToast, Tooltip, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
import { PluginCard } from ".";
|
import { PluginCard } from ".";
|
||||||
|
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-author-modal-");
|
const cl = classNameFactory("vc-author-modal-");
|
||||||
|
|
||||||
|
@ -40,16 +36,6 @@ export function openContributorModal(user: User) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GithubIcon() {
|
|
||||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
|
||||||
return <img src={src} alt="GitHub" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function WebsiteIcon() {
|
|
||||||
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
|
||||||
return <img src={src} alt="Website" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContributorModal({ user }: { user: User; }) {
|
function ContributorModal({ user }: { user: User; }) {
|
||||||
useSettings();
|
useSettings();
|
||||||
|
|
||||||
|
@ -86,24 +72,18 @@ function ContributorModal({ user }: { user: User; }) {
|
||||||
/>
|
/>
|
||||||
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("links")}>
|
<div className={classes("vc-settings-modal-links", cl("links"))}>
|
||||||
{website && (
|
{website && (
|
||||||
<Tooltip text={website}>
|
<WebsiteButton
|
||||||
{props => (
|
text={website}
|
||||||
<MaskedLink {...props} href={"https://" + website}>
|
href={`https://${website}`}
|
||||||
<WebsiteIcon />
|
/>
|
||||||
</MaskedLink>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
{githubName && (
|
{githubName && (
|
||||||
<Tooltip text={githubName}>
|
<GithubButton
|
||||||
{props => (
|
text={githubName}
|
||||||
<MaskedLink {...props} href={`https://github.com/${githubName}`}>
|
href={`https://github.com/${githubName}`}
|
||||||
<GithubIcon />
|
/>
|
||||||
</MaskedLink>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
12
src/components/PluginSettings/LinkIconButton.css
Normal file
12
src/components/PluginSettings/LinkIconButton.css
Normal 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;
|
||||||
|
}
|
45
src/components/PluginSettings/LinkIconButton.tsx
Normal file
45
src/components/PluginSettings/LinkIconButton.tsx
Normal 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} />;
|
7
src/components/PluginSettings/PluginModal.css
Normal file
7
src/components/PluginSettings/PluginModal.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.vc-plugin-modal-info {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-plugin-modal-description {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
|
@ -16,10 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./PluginModal.css";
|
||||||
|
|
||||||
import { generateId } from "@api/Commands";
|
import { generateId } from "@api/Commands";
|
||||||
import { useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
|
import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
|
@ -30,6 +34,8 @@ import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserSto
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
import { Constructor } from "type-fest";
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
|
@ -40,6 +46,9 @@ import {
|
||||||
SettingTextComponent
|
SettingTextComponent
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import { openContributorModal } from "./ContributorModal";
|
import { openContributorModal } from "./ContributorModal";
|
||||||
|
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-plugin-modal-");
|
||||||
|
|
||||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||||
|
@ -180,16 +189,54 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function switchToPopout() {
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`;
|
||||||
|
PopoutActions.open(
|
||||||
|
PopoutKey,
|
||||||
|
() => <PluginModal
|
||||||
|
transitionState={transitionState}
|
||||||
|
plugin={plugin}
|
||||||
|
onRestartNeeded={onRestartNeeded}
|
||||||
|
onClose={() => PopoutActions.close(PopoutKey)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const pluginMeta = PluginMeta[plugin.name];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
|
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
|
||||||
<ModalHeader separator={false}>
|
<ModalHeader separator={false}>
|
||||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<Button look={Button.Looks.BLANK} onClick={switchToPopout}>
|
||||||
|
<OpenExternalIcon aria-label="Open in Popout" />
|
||||||
|
</Button>
|
||||||
|
*/}
|
||||||
<ModalCloseButton onClick={onClose} />
|
<ModalCloseButton onClick={onClose} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
|
<Flex className={cl("info")}>
|
||||||
<Forms.FormText>{plugin.description}</Forms.FormText>
|
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
|
||||||
|
{!pluginMeta.userPlugin && (
|
||||||
|
<div className="vc-settings-modal-links">
|
||||||
|
<WebsiteButton
|
||||||
|
text="View more info"
|
||||||
|
href={`https://vencord.dev/plugins/${plugin.name}`}
|
||||||
|
/>
|
||||||
|
<GithubButton
|
||||||
|
text="View source code"
|
||||||
|
href={`https://github.com/${gitRemote}/tree/main/src/plugins/${pluginMeta.folderName}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
|
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
|
||||||
<div style={{ width: "fit-content", marginBottom: 8 }}>
|
<div style={{ width: "fit-content", marginBottom: 8 }}>
|
||||||
<UserSummaryItem
|
<UserSummaryItem
|
||||||
|
|
|
@ -42,16 +42,6 @@
|
||||||
|
|
||||||
.vc-author-modal-links {
|
.vc-author-modal-links {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: flex;
|
|
||||||
gap: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-author-modal-links img {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 4px solid var(--background-tertiary);
|
|
||||||
box-sizing: border-box
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-plugins {
|
.vc-author-modal-plugins {
|
||||||
|
|
|
@ -35,9 +35,9 @@ import { openModalLazy } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||||
|
|
||||||
// Avoid circular dependency
|
// Avoid circular dependency
|
||||||
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
||||||
|
@ -177,6 +177,37 @@ const enum SearchStatus {
|
||||||
NEW
|
NEW
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ExcludedPluginsList({ search }: { search: string; }) {
|
||||||
|
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
|
||||||
|
.filter(([name]) => name.toLowerCase().includes(search));
|
||||||
|
|
||||||
|
const ExcludedReasons: Record<"web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev", string> = {
|
||||||
|
desktop: "Discord Desktop app or Vesktop",
|
||||||
|
discordDesktop: "Discord Desktop app",
|
||||||
|
vencordDesktop: "Vesktop app",
|
||||||
|
web: "Vesktop app and the Web version of Discord",
|
||||||
|
dev: "Developer version of Vencord"
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text variant="text-md/normal" className={Margins.top16}>
|
||||||
|
{matchingExcludedPlugins.length
|
||||||
|
? <>
|
||||||
|
<Forms.FormText>Are you looking for:</Forms.FormText>
|
||||||
|
<ul>
|
||||||
|
{matchingExcludedPlugins.map(([name, reason]) => (
|
||||||
|
<li key={name}>
|
||||||
|
<b>{name}</b>: Only available on the {ExcludedReasons[reason]}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
: "No plugins meet the search criteria."
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function PluginSettings() {
|
export default function PluginSettings() {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||||
|
@ -215,26 +246,27 @@ export default function PluginSettings() {
|
||||||
return o;
|
return o;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
|
const sortedPlugins = useMemo(() => Object.values(Plugins)
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
|
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
|
||||||
|
|
||||||
|
const search = searchValue.value.toLowerCase();
|
||||||
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
||||||
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
||||||
|
|
||||||
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
||||||
const enabled = settings.plugins[plugin.name]?.enabled;
|
const { status } = searchValue;
|
||||||
if (enabled && searchValue.status === SearchStatus.DISABLED) return false;
|
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
|
||||||
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
|
if (enabled && status === SearchStatus.DISABLED) return false;
|
||||||
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
|
if (!enabled && status === SearchStatus.ENABLED) return false;
|
||||||
if (!searchValue.value.length) return true;
|
if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
|
||||||
|
if (!search.length) return true;
|
||||||
|
|
||||||
const v = searchValue.value.toLowerCase();
|
|
||||||
return (
|
return (
|
||||||
plugin.name.toLowerCase().includes(v) ||
|
plugin.name.toLowerCase().includes(search) ||
|
||||||
plugin.description.toLowerCase().includes(v) ||
|
plugin.description.toLowerCase().includes(search) ||
|
||||||
plugin.tags?.some(t => t.toLowerCase().includes(v))
|
plugin.tags?.some(t => t.toLowerCase().includes(search))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -255,13 +287,10 @@ export default function PluginSettings() {
|
||||||
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type P = JSX.Element | JSX.Element[];
|
const plugins = [] as JSX.Element[];
|
||||||
let plugins: P, requiredPlugins: P;
|
const requiredPlugins = [] as JSX.Element[];
|
||||||
if (sortedPlugins?.length) {
|
|
||||||
plugins = [];
|
|
||||||
requiredPlugins = [];
|
|
||||||
|
|
||||||
const showApi = searchValue.value === "API";
|
const showApi = searchValue.value.includes("API");
|
||||||
for (const p of sortedPlugins) {
|
for (const p of sortedPlugins) {
|
||||||
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
||||||
continue;
|
continue;
|
||||||
|
@ -284,6 +313,7 @@ export default function PluginSettings() {
|
||||||
onRestartNeeded={name => changes.handleChange(name)}
|
onRestartNeeded={name => changes.handleChange(name)}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
plugin={p}
|
plugin={p}
|
||||||
|
key={p.name}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -299,10 +329,6 @@ export default function PluginSettings() {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -333,9 +359,18 @@ export default function PluginSettings() {
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
||||||
|
|
||||||
|
{plugins.length || requiredPlugins.length
|
||||||
|
? (
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{plugins}
|
{plugins.length
|
||||||
|
? plugins
|
||||||
|
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
: <ExcludedPluginsList search={search} />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top20} />
|
<Forms.FormDivider className={Margins.top20} />
|
||||||
|
|
||||||
|
@ -343,7 +378,10 @@ export default function PluginSettings() {
|
||||||
Required Plugins
|
Required Plugins
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{requiredPlugins}
|
{requiredPlugins.length
|
||||||
|
? requiredPlugins
|
||||||
|
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</SettingsTab >
|
</SettingsTab >
|
||||||
);
|
);
|
||||||
|
|
|
@ -39,9 +39,8 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
if (searchType === "waitForStore") method = "findStore";
|
if (searchType === "waitForStore") method = "findStore";
|
||||||
|
|
||||||
try {
|
|
||||||
let result: any;
|
let result: any;
|
||||||
|
try {
|
||||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
const [factory] = args;
|
const [factory] = args;
|
||||||
result = factory();
|
result = factory();
|
||||||
|
@ -50,16 +49,26 @@ async function runReporter() {
|
||||||
|
|
||||||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||||
if (result === false) result = null;
|
if (result === false) result = null;
|
||||||
|
} else if (method === "mapMangledModule") {
|
||||||
|
const [code, mapper] = args;
|
||||||
|
|
||||||
|
result = Webpack.mapMangledModule(code, mapper);
|
||||||
|
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
result = Webpack[method](...args);
|
result = Webpack[method](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||||
|
else if (method === "mapMangledModule") {
|
||||||
|
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
||||||
|
|
||||||
|
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||||
|
}
|
||||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
|
|
5
src/modules.d.ts
vendored
5
src/modules.d.ts
vendored
|
@ -22,6 +22,11 @@
|
||||||
declare module "~plugins" {
|
declare module "~plugins" {
|
||||||
const plugins: Record<string, import("./utils/types").Plugin>;
|
const plugins: Record<string, import("./utils/types").Plugin>;
|
||||||
export default plugins;
|
export default plugins;
|
||||||
|
export const PluginMeta: Record<string, {
|
||||||
|
folderName: string;
|
||||||
|
userPlugin: boolean;
|
||||||
|
}>;
|
||||||
|
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev">;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "~pluginNatives" {
|
declare module "~pluginNatives" {
|
||||||
|
|
|
@ -20,23 +20,30 @@ import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SettingsStoreAPI",
|
name: "UserSettingsAPI",
|
||||||
description: "Patches Discord's SettingsStores to expose their group and name",
|
description: "Patches Discord's UserSettings to expose their group and name.",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ",updateSetting:",
|
find: ",updateSetting:",
|
||||||
replacement: [
|
replacement: [
|
||||||
|
// Main setting definition
|
||||||
{
|
{
|
||||||
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:/,
|
match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/,
|
||||||
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
|
replace: "userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1],$&"
|
||||||
},
|
},
|
||||||
// some wrapper. just make it copy the group and name
|
// Selective wrapper
|
||||||
{
|
{
|
||||||
match: /updateSetting:.{0,20}shouldSync/,
|
match: /updateSetting:.{0,100}SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE/,
|
||||||
replace: "settingsStoreApiGroup:arguments[0].settingsStoreApiGroup,settingsStoreApiName:arguments[0].settingsStoreApiName,$&"
|
replace: "userSettingsAPIGroup:arguments[0].userSettingsAPIGroup,userSettingsAPIName:arguments[0].userSettingsAPIName,$&"
|
||||||
|
},
|
||||||
|
// Override wrapper
|
||||||
|
{
|
||||||
|
match: /updateSetting:.{0,60}USER_SETTINGS_OVERRIDE_CLEAR/,
|
||||||
|
replace: "userSettingsAPIGroup:arguments[0].userSettingsAPIGroup,userSettingsAPIName:arguments[0].userSettingsAPIName,$&"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -56,33 +56,18 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Discord Stable
|
|
||||||
// FIXME: remove once change merged to stable
|
|
||||||
{
|
{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
noWarn: true,
|
replacement: [
|
||||||
replacement: {
|
|
||||||
get match() {
|
|
||||||
switch (Settings.plugins.Settings.settingsLocation) {
|
|
||||||
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
|
|
||||||
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
|
|
||||||
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
|
|
||||||
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
|
|
||||||
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
|
|
||||||
case "aboveActivity":
|
|
||||||
default:
|
|
||||||
return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
replace: "...$self.makeSettingsCategories($1),$&"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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",
|
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||||
|
|
|
@ -16,24 +16,33 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { addAccessory } from "@api/MessageAccessories";
|
||||||
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||||
|
import { sendMessage } from "@utils/discord";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev, tryOrElse } from "@utils/misc";
|
||||||
import { relaunch } from "@utils/native";
|
import { relaunch } from "@utils/native";
|
||||||
|
import { onlyOnce } from "@utils/onlyOnce";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { isOutdated, update } from "@utils/updater";
|
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||||
import { Alerts, Card, ChannelStore, Forms, GuildMemberStore, NavigationRouter, Parser, RelationshipStore, UserStore } from "@webpack/common";
|
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Toasts, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import plugins from "~plugins";
|
import plugins, { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
|
|
||||||
const VENCORD_GUILD_ID = "1015060230222131221";
|
const VENCORD_GUILD_ID = "1015060230222131221";
|
||||||
|
const VENBOT_USER_ID = "1017176847865352332";
|
||||||
|
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
||||||
|
const CodeBlockRe = /```js\n(.+?)```/s;
|
||||||
|
|
||||||
const AllowedChannelIds = [
|
const AllowedChannelIds = [
|
||||||
SUPPORT_CHANNEL_ID,
|
SUPPORT_CHANNEL_ID,
|
||||||
|
@ -47,26 +56,21 @@ const TrustedRolesIds = [
|
||||||
"1042507929485586532", // donor
|
"1042507929485586532", // donor
|
||||||
];
|
];
|
||||||
|
|
||||||
export default definePlugin({
|
const AsyncFunction = async function () { }.constructor;
|
||||||
name: "SupportHelper",
|
|
||||||
required: true,
|
|
||||||
description: "Helps us provide support to you",
|
|
||||||
authors: [Devs.Ven],
|
|
||||||
dependencies: ["CommandsAPI"],
|
|
||||||
|
|
||||||
patches: [{
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
find: ".BEGINNING_DM.format",
|
|
||||||
replacement: {
|
async function forceUpdate() {
|
||||||
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
|
const outdated = await checkForUpdates();
|
||||||
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
|
if (outdated) {
|
||||||
|
await update();
|
||||||
|
relaunch();
|
||||||
}
|
}
|
||||||
}],
|
|
||||||
|
|
||||||
commands: [{
|
return outdated;
|
||||||
name: "vencord-debug",
|
}
|
||||||
description: "Send Vencord Debug info",
|
|
||||||
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
async function generateDebugInfoMessage() {
|
||||||
async execute() {
|
|
||||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||||
|
|
||||||
const client = (() => {
|
const client = (() => {
|
||||||
|
@ -79,10 +83,6 @@ export default definePlugin({
|
||||||
return `${name} (${navigator.userAgent})`;
|
return `${name} (${navigator.userAgent})`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
|
|
||||||
|
|
||||||
const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));
|
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
Vencord:
|
Vencord:
|
||||||
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
||||||
|
@ -92,22 +92,76 @@ export default definePlugin({
|
||||||
};
|
};
|
||||||
|
|
||||||
if (IS_DISCORD_DESKTOP) {
|
if (IS_DISCORD_DESKTOP) {
|
||||||
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
|
info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A";
|
||||||
}
|
}
|
||||||
|
|
||||||
const debugInfo = `
|
const commonIssues = {
|
||||||
>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}
|
"NoRPC enabled": Vencord.Plugins.isPluginEnabled("NoRPC"),
|
||||||
|
"Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false),
|
||||||
Enabled Plugins (${enabledPlugins.length}):
|
"Vencord DevBuild": !IS_STANDALONE,
|
||||||
${makeCodeblock(enabledPlugins.join(", "))}
|
"Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin),
|
||||||
`;
|
"More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5,
|
||||||
|
|
||||||
return {
|
|
||||||
content: debugInfo.trim().replaceAll("```\n", "```")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let content = `>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}`;
|
||||||
|
content += "\n" + Object.entries(commonIssues)
|
||||||
|
.filter(([, v]) => v).map(([k]) => `⚠️ ${k}`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return content.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePluginList() {
|
||||||
|
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
|
||||||
|
|
||||||
|
const enabledPlugins = Object.keys(plugins)
|
||||||
|
.filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));
|
||||||
|
|
||||||
|
const enabledStockPlugins = enabledPlugins.filter(p => !PluginMeta[p].userPlugin);
|
||||||
|
const enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin);
|
||||||
|
|
||||||
|
|
||||||
|
let content = `**Enabled Plugins (${enabledStockPlugins.length}):**\n${makeCodeblock(enabledStockPlugins.join(", "))}`;
|
||||||
|
|
||||||
|
if (enabledUserPlugins.length) {
|
||||||
|
content += `**Enabled UserPlugins (${enabledUserPlugins.length}):**\n${makeCodeblock(enabledUserPlugins.join(", "))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkForUpdatesOnce = onlyOnce(checkForUpdates);
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "SupportHelper",
|
||||||
|
required: true,
|
||||||
|
description: "Helps us provide support to you",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
dependencies: ["CommandsAPI", "UserSettingsAPI"],
|
||||||
|
|
||||||
|
patches: [{
|
||||||
|
find: ".BEGINNING_DM.format",
|
||||||
|
replacement: {
|
||||||
|
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
|
||||||
|
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
name: "vencord-debug",
|
||||||
|
description: "Send Vencord debug info",
|
||||||
|
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||||
|
execute: async () => ({ content: await generateDebugInfoMessage() })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vencord-plugins",
|
||||||
|
description: "Send Vencord plugin list",
|
||||||
|
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||||
|
execute: () => ({ content: generatePluginList() })
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
async CHANNEL_SELECT({ channelId }) {
|
async CHANNEL_SELECT({ channelId }) {
|
||||||
if (channelId !== SUPPORT_CHANNEL_ID) return;
|
if (channelId !== SUPPORT_CHANNEL_ID) return;
|
||||||
|
@ -115,6 +169,9 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
const selfId = UserStore.getCurrentUser()?.id;
|
const selfId = UserStore.getCurrentUser()?.id;
|
||||||
if (!selfId || isPluginDev(selfId)) return;
|
if (!selfId || isPluginDev(selfId)) return;
|
||||||
|
|
||||||
|
if (!IS_UPDATER_DISABLED) {
|
||||||
|
await checkForUpdatesOnce().catch(() => { });
|
||||||
|
|
||||||
if (isOutdated) {
|
if (isOutdated) {
|
||||||
return Alerts.show({
|
return Alerts.show({
|
||||||
title: "Hold on!",
|
title: "Hold on!",
|
||||||
|
@ -127,13 +184,11 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
onCancel: () => openUpdaterModal!(),
|
onCancel: () => openUpdaterModal!(),
|
||||||
cancelText: "View Updates",
|
cancelText: "View Updates",
|
||||||
confirmText: "Update & Restart Now",
|
confirmText: "Update & Restart Now",
|
||||||
async onConfirm() {
|
onConfirm: forceUpdate,
|
||||||
await update();
|
|
||||||
relaunch();
|
|
||||||
},
|
|
||||||
secondaryConfirmText: "I know what I'm doing or I can't update"
|
secondaryConfirmText: "I know what I'm doing or I can't update"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore outdated type
|
// @ts-ignore outdated type
|
||||||
const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles;
|
const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles;
|
||||||
|
@ -148,8 +203,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
||||||
contact your package maintainer for support instead.
|
contact your package maintainer for support instead.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>,
|
</div>
|
||||||
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +217,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
||||||
contact your package maintainer for support instead.
|
contact your package maintainer for support instead.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>,
|
</div>
|
||||||
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +225,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
|
|
||||||
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
||||||
if (!isPluginDev(userId)) return null;
|
if (!isPluginDev(userId)) return null;
|
||||||
if (RelationshipStore.isFriend(userId)) return null;
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
|
@ -182,5 +235,86 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}, { noop: true })
|
}, { noop: true }),
|
||||||
|
|
||||||
|
start() {
|
||||||
|
addAccessory("vencord-debug", props => {
|
||||||
|
const buttons = [] as JSX.Element[];
|
||||||
|
|
||||||
|
const shouldAddUpdateButton =
|
||||||
|
!IS_UPDATER_DISABLED
|
||||||
|
&& (
|
||||||
|
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
||||||
|
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
||||||
|
)
|
||||||
|
&& props.message.content?.includes("update");
|
||||||
|
|
||||||
|
if (shouldAddUpdateButton) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
key="vc-update"
|
||||||
|
color={Button.Colors.GREEN}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
if (await forceUpdate())
|
||||||
|
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
||||||
|
else
|
||||||
|
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
||||||
|
} catch (e) {
|
||||||
|
new Logger(this.name).error("Error while updating:", e);
|
||||||
|
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update Now
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
||||||
|
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
key="vc-dbg"
|
||||||
|
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
||||||
|
>
|
||||||
|
Run /vencord-debug
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="vc-plg-list"
|
||||||
|
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
||||||
|
>
|
||||||
|
Run /vencord-plugins
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.message.author.id === VENBOT_USER_ID) {
|
||||||
|
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
||||||
|
if (match) {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
key="vc-run-snippet"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await AsyncFunction(match[1])();
|
||||||
|
showToast("Success!", Toasts.Type.SUCCESS);
|
||||||
|
} catch (e) {
|
||||||
|
new Logger(this.name).error("Error while running snippet:", e);
|
||||||
|
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Run Snippet
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons.length
|
||||||
|
? <Flex>{buttons}</Flex>
|
||||||
|
: null;
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||||
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||||
|
|
||||||
const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative<typeof import("./native")>;
|
const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
interface ActivityAssets {
|
interface ActivityAssets {
|
||||||
large_image?: string;
|
large_image?: string;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import { ImageIcon } from "@components/Icons";
|
import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
||||||
|
@ -15,7 +15,7 @@ import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
|
||||||
|
|
||||||
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
||||||
|
|
||||||
const DeveloperMode = getSettingStoreLazy("appearance", "developerMode")!;
|
const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!;
|
||||||
|
|
||||||
function PencilIcon() {
|
function PencilIcon() {
|
||||||
return (
|
return (
|
||||||
|
@ -65,7 +65,7 @@ export default definePlugin({
|
||||||
name: "BetterRoleContext",
|
name: "BetterRoleContext",
|
||||||
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
||||||
authors: [Devs.Ven, Devs.goodbee],
|
authors: [Devs.Ven, Devs.goodbee],
|
||||||
dependencies: ["SettingsStoreAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -33,8 +33,7 @@ const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gra
|
||||||
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
||||||
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
||||||
|
|
||||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||||
|
@ -394,7 +393,7 @@ export default definePlugin({
|
||||||
name: "CustomRPC",
|
name: "CustomRPC",
|
||||||
description: "Allows you to set a custom rich presence.",
|
description: "Allows you to set a custom rich presence.",
|
||||||
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||||
dependencies: ["SettingsStoreAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
start: setRpc,
|
start: setRpc,
|
||||||
stop: () => setRpc(true),
|
stop: () => setRpc(true),
|
||||||
settings,
|
settings,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||||
import { getCurrentGuild } from "@utils/discord";
|
import { getCurrentGuild } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
||||||
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||||
import type { Emoji } from "@webpack/types";
|
import type { Emoji } from "@webpack/types";
|
||||||
import type { Message } from "discord-types/general";
|
import type { Message } from "discord-types/general";
|
||||||
|
@ -52,6 +52,7 @@ const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsA
|
||||||
const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
|
const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
|
||||||
const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
|
const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
|
||||||
|
|
||||||
|
const isUnusableRoleSubscriptionEmoji = findByCodeLazy(".getUserIsAdmin(");
|
||||||
|
|
||||||
const enum EmojiIntentions {
|
const enum EmojiIntentions {
|
||||||
REACTION,
|
REACTION,
|
||||||
|
@ -234,16 +235,14 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// FIXME
|
|
||||||
// Allows the usage of subscription-locked emojis
|
// Allows the usage of subscription-locked emojis
|
||||||
/* {
|
{
|
||||||
find: ".getUserIsAdmin(",
|
find: ".getUserIsAdmin(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?=.+?\.getUserIsAdmin\((?<=function (\i)\(\i,\i\){.+?))(\i):function\(\){return \1}/,
|
match: /(function \i\(\i,\i)\){(.{0,250}.getUserIsAdmin\(.+?return!1})/,
|
||||||
// Replace the original export with a func that always returns false and alias the original
|
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
|
||||||
replace: "$2:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function(){return $1}"
|
|
||||||
}
|
}
|
||||||
}, */
|
},
|
||||||
// Allow stickers to be sent everywhere
|
// Allow stickers to be sent everywhere
|
||||||
{
|
{
|
||||||
find: "canUseCustomStickersEverywhere:function",
|
find: "canUseCustomStickersEverywhere:function",
|
||||||
|
@ -817,9 +816,7 @@ export default definePlugin({
|
||||||
if (e.type === 0) return true;
|
if (e.type === 0) return true;
|
||||||
if (e.available === false) return false;
|
if (e.available === false) return false;
|
||||||
|
|
||||||
// FIXME
|
if (isUnusableRoleSubscriptionEmoji(e, this.guildId, true)) return false;
|
||||||
/* const isUnusableRoleSubEmoji = isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
|
|
||||||
if (isUnusableRoleSubEmoji(e, this.guildId)) return false; */
|
|
||||||
|
|
||||||
if (this.canUseEmotes)
|
if (this.canUseEmotes)
|
||||||
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
|
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -28,7 +28,7 @@ import style from "./style.css?managed";
|
||||||
|
|
||||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||||
|
|
||||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
function makeIcon(showCurrentGame?: boolean) {
|
function makeIcon(showCurrentGame?: boolean) {
|
||||||
const { oldIcon } = settings.use(["oldIcon"]);
|
const { oldIcon } = settings.use(["oldIcon"]);
|
||||||
|
@ -87,7 +87,7 @@ export default definePlugin({
|
||||||
name: "GameActivityToggle",
|
name: "GameActivityToggle",
|
||||||
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||||
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
||||||
dependencies: ["SettingsStoreAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[class*="withTagAsButton"] {
|
[class*="panels"] [class*="avatarWrapper"] {
|
||||||
min-width: 88px !important;
|
min-width: 88px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -28,7 +28,7 @@ interface IgnoredActivity {
|
||||||
|
|
||||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||||
|
|
||||||
const ShowCurrentGame = getSettingStoreLazy("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!;
|
||||||
|
|
||||||
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||||
return (
|
return (
|
||||||
|
@ -208,7 +208,7 @@ export default definePlugin({
|
||||||
name: "IgnoreActivities",
|
name: "IgnoreActivities",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
|
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
|
||||||
dependencies: ["SettingsStoreAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,18 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux
|
||||||
|
|
||||||
logger.debug("Subscribing to flux events of plugin", p.name);
|
logger.debug("Subscribing to flux events of plugin", p.name);
|
||||||
for (const [event, handler] of Object.entries(p.flux)) {
|
for (const [event, handler] of Object.entries(p.flux)) {
|
||||||
fluxDispatcher.subscribe(event as FluxEvents, handler);
|
const wrappedHandler = p.flux[event] = function () {
|
||||||
|
try {
|
||||||
|
const res = handler.apply(p, arguments as any);
|
||||||
|
return res instanceof Promise
|
||||||
|
? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e))
|
||||||
|
: res;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`${p.name}: Error while handling ${event}\n`, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fluxDispatcher.subscribe(event as FluxEvents, wrappedHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants.js";
|
import { Devs } from "@utils/constants.js";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
|
@ -54,7 +54,7 @@ const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hid
|
||||||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||||
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
||||||
|
|
||||||
const MessageDisplayCompact = getSettingStoreLazy("textAndImages", "messageDisplayCompact")!;
|
const MessageDisplayCompact = getUserSettingLazy("textAndImages", "messageDisplayCompact")!;
|
||||||
|
|
||||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||||
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
||||||
|
@ -366,7 +366,7 @@ export default definePlugin({
|
||||||
name: "MessageLinkEmbeds",
|
name: "MessageLinkEmbeds",
|
||||||
description: "Adds a preview to messages that link another message",
|
description: "Adds a preview to messages that link another message",
|
||||||
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
||||||
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "SettingsStoreAPI"],
|
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
|
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL,shouldHideMediaOptions",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /favoriteableType:\i,(?<=(\i)\.getAttribute\("data-type"\).+?)/,
|
match: /favoriteableType:\i,(?<=(\i)\.getAttribute\("data-type"\).+?)/,
|
||||||
replace: (m, target) => `${m}reverseImageSearchType:${target}.getAttribute("data-role"),`
|
replace: (m, target) => `${m}reverseImageSearchType:${target}.getAttribute("data-role"),`
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, GuildStore } from "@webpack/common";
|
import { ChannelStore, GuildStore } from "@webpack/common";
|
||||||
|
|
||||||
const SummaryStore = findByPropsLazy("allSummaries", "findSummary");
|
const SummaryStore = findByPropsLazy("allSummaries", "findSummary");
|
||||||
const createSummaryFromServer = findByCodeLazy(".people)),startId:");
|
const createSummaryFromServer = findByCodeLazy(".people)),startId:", ".type}");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
summaryExpiryThresholdDays: {
|
summaryExpiryThresholdDays: {
|
||||||
|
|
|
@ -48,10 +48,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "showTaglessAccountPanel:",
|
find: '"AccountConnected"',
|
||||||
replacement: {
|
replacement: {
|
||||||
// react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
|
// react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
|
||||||
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?showTaglessAccountPanel:)/,
|
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,hidePrivateData:)/,
|
||||||
// react.jsx(WrapperComponent, { VencordOriginal: AccountPanel, ...
|
// react.jsx(WrapperComponent, { VencordOriginal: AccountPanel, ...
|
||||||
replace: "$self.PanelWrapper,{VencordOriginal:$1,"
|
replace: "$self.PanelWrapper,{VencordOriginal:$1,"
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { Button, showToast, Toasts, useState } from "@webpack/common";
|
||||||
|
|
||||||
import type { VoiceRecorder } from ".";
|
import type { VoiceRecorder } from ".";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
|
import { MediaEngineStore } from "./utils";
|
||||||
|
|
||||||
const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative<typeof import("./native")>;
|
const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC
|
||||||
{
|
{
|
||||||
echoCancellation: settings.store.echoCancellation,
|
echoCancellation: settings.store.echoCancellation,
|
||||||
noiseCancellation: settings.store.noiseSuppression,
|
noiseCancellation: settings.store.noiseSuppression,
|
||||||
|
deviceId: MediaEngineStore.getInputDeviceId(),
|
||||||
},
|
},
|
||||||
(success: boolean) => {
|
(success: boolean) => {
|
||||||
if (success)
|
if (success)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { Button, useState } from "@webpack/common";
|
||||||
|
|
||||||
import type { VoiceRecorder } from ".";
|
import type { VoiceRecorder } from ".";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
|
import { MediaEngineStore } from "./utils";
|
||||||
|
|
||||||
export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => {
|
export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => {
|
||||||
const [recording, setRecording] = useState(false);
|
const [recording, setRecording] = useState(false);
|
||||||
|
@ -40,6 +41,7 @@ export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChang
|
||||||
audio: {
|
audio: {
|
||||||
echoCancellation: settings.store.echoCancellation,
|
echoCancellation: settings.store.echoCancellation,
|
||||||
noiseSuppression: settings.store.noiseSuppression,
|
noiseSuppression: settings.store.noiseSuppression,
|
||||||
|
deviceId: MediaEngineStore.getInputDeviceId()
|
||||||
}
|
}
|
||||||
}).then(stream => {
|
}).then(stream => {
|
||||||
const chunks = [] as Blob[];
|
const chunks = [] as Blob[];
|
||||||
|
|
|
@ -17,5 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
|
|
||||||
|
export const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||||
export const cl = classNameFactory("vc-vmsg-");
|
export const cl = classNameFactory("vc-vmsg-");
|
||||||
|
|
|
@ -136,7 +136,7 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const Native = VencordNative.pluginHelpers.XsOverlay as PluginNative<typeof import("./native")>;
|
const Native = VencordNative.pluginHelpers.XSOverlay as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "XSOverlay",
|
name: "XSOverlay",
|
||||||
|
@ -154,8 +154,6 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MESSAGE_CREATE({ message, optimistic }: { message: Message; optimistic: boolean; }) {
|
MESSAGE_CREATE({ message, optimistic }: { message: Message; optimistic: boolean; }) {
|
||||||
// Apparently without this try/catch, discord's socket connection dies if any part of this errors
|
|
||||||
try {
|
|
||||||
if (optimistic) return;
|
if (optimistic) return;
|
||||||
const channel = ChannelStore.getChannel(message.channel_id);
|
const channel = ChannelStore.getChannel(message.channel_id);
|
||||||
if (!shouldNotify(message, message.channel_id)) return;
|
if (!shouldNotify(message, message.channel_id)) return;
|
||||||
|
@ -249,9 +247,6 @@ export default definePlugin({
|
||||||
|
|
||||||
if (shouldIgnoreForChannelType(channel)) return;
|
if (shouldIgnoreForChannelType(channel)) return;
|
||||||
sendMsgNotif(titleString, finalMsg, message);
|
sendMsgNotif(titleString, finalMsg, message);
|
||||||
} catch (err) {
|
|
||||||
XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -120,7 +120,7 @@ export function openImageModal(url: string, props?: Partial<React.ComponentProps
|
||||||
placeholder={url}
|
placeholder={url}
|
||||||
src={url}
|
src={url}
|
||||||
renderLinkComponent={props => <MaskedLink {...props} />}
|
renderLinkComponent={props => <MaskedLink {...props} />}
|
||||||
// FIXME: wtf is this? do we need to pass some proper component??
|
// Don't render forward message button
|
||||||
renderForwardComponent={() => null}
|
renderForwardComponent={() => null}
|
||||||
shouldHideMediaOptions={false}
|
shouldHideMediaOptions={false}
|
||||||
shouldAnimate
|
shouldAnimate
|
||||||
|
|
|
@ -99,3 +99,14 @@ export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id);
|
||||||
export function pluralise(amount: number, singular: string, plural = singular + "s") {
|
export function pluralise(amount: number, singular: string, plural = singular + "s") {
|
||||||
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
|
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function tryOrElse<T>(func: () => T, fallback: T): T {
|
||||||
|
try {
|
||||||
|
const res = func();
|
||||||
|
return res instanceof Promise
|
||||||
|
? res.catch(() => fallback) as T
|
||||||
|
: res;
|
||||||
|
} catch {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ const enum ModalTransitionState {
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
transitionState: ModalTransitionState;
|
transitionState: ModalTransitionState;
|
||||||
onClose(): Promise<void>;
|
onClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModalOptions {
|
export interface ModalOptions {
|
||||||
|
|
|
@ -131,3 +131,18 @@ export function makeCodeblock(text: string, language?: string) {
|
||||||
const chars = "```";
|
const chars = "```";
|
||||||
return `${chars}${language || ""}\n${text.replaceAll("```", "\\`\\`\\`")}\n${chars}`;
|
return `${chars}${language || ""}\n${text.replaceAll("```", "\\`\\`\\`")}\n${chars}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripIndent(strings: TemplateStringsArray, ...values: any[]) {
|
||||||
|
const string = String.raw({ raw: strings }, ...values);
|
||||||
|
|
||||||
|
const match = string.match(/^[ \t]*(?=\S)/gm);
|
||||||
|
if (!match) return string.trim();
|
||||||
|
|
||||||
|
const minIndent = match.reduce((r, a) => Math.min(r, a.length), Infinity);
|
||||||
|
return string.replace(new RegExp(`^[ \\t]{${minIndent}}`, "gm"), "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZWSP = "\u200b";
|
||||||
|
export function toInlineCode(s: string) {
|
||||||
|
return "``" + ZWSP + s.replaceAll("`", ZWSP + "`" + ZWSP) + ZWSP + "``";
|
||||||
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ export interface PluginDef {
|
||||||
* Allows you to subscribe to Flux events
|
* Allows you to subscribe to Flux events
|
||||||
*/
|
*/
|
||||||
flux?: {
|
flux?: {
|
||||||
[E in FluxEvents]?: (event: any) => void;
|
[E in FluxEvents]?: (event: any) => void | Promise<void>;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Allows you to manipulate context menus
|
* Allows you to manipulate context menus
|
||||||
|
|
|
@ -20,9 +20,9 @@ export * from "./classes";
|
||||||
export * from "./components";
|
export * from "./components";
|
||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
export * from "./react";
|
export * from "./react";
|
||||||
export * from "./settingsStores";
|
|
||||||
export * from "./stores";
|
export * from "./stores";
|
||||||
export * as ComponentTypes from "./types/components.d";
|
export * as ComponentTypes from "./types/components.d";
|
||||||
export * as MenuTypes from "./types/menu.d";
|
export * as MenuTypes from "./types/menu.d";
|
||||||
export * as UtilTypes from "./types/utils.d";
|
export * as UtilTypes from "./types/utils.d";
|
||||||
|
export * from "./userSettings";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
|
1
src/webpack/common/types/index.d.ts
vendored
1
src/webpack/common/types/index.d.ts
vendored
|
@ -21,6 +21,5 @@ export * from "./components";
|
||||||
export * from "./fluxEvents";
|
export * from "./fluxEvents";
|
||||||
export * from "./i18nMessages";
|
export * from "./i18nMessages";
|
||||||
export * from "./menu";
|
export * from "./menu";
|
||||||
export * from "./settingsStores";
|
|
||||||
export * from "./stores";
|
export * from "./stores";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface SettingsStore<T = any> {
|
|
||||||
getSetting(): T;
|
|
||||||
updateSetting(value: T): void;
|
|
||||||
useSetting(): T;
|
|
||||||
}
|
|
37
src/webpack/common/types/utils.d.ts
vendored
37
src/webpack/common/types/utils.d.ts
vendored
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import { Guild, GuildMember } from "discord-types/general";
|
import { Guild, GuildMember } from "discord-types/general";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
import type { FluxEvents } from "./fluxEvents";
|
import type { FluxEvents } from "./fluxEvents";
|
||||||
import { i18nMessages } from "./i18nMessages";
|
import { i18nMessages } from "./i18nMessages";
|
||||||
|
@ -81,7 +82,7 @@ interface RestRequestData {
|
||||||
retries?: number;
|
retries?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RestAPI = Record<"delete" | "get" | "patch" | "post" | "put", (data: RestRequestData) => Promise<any>>;
|
export type RestAPI = Record<"del" | "get" | "patch" | "post" | "put", (data: RestRequestData) => Promise<any>>;
|
||||||
|
|
||||||
export type Permissions = "CREATE_INSTANT_INVITE"
|
export type Permissions = "CREATE_INSTANT_INVITE"
|
||||||
| "KICK_MEMBERS"
|
| "KICK_MEMBERS"
|
||||||
|
@ -221,3 +222,37 @@ export interface Constants {
|
||||||
UserFlags: Record<string, number>;
|
UserFlags: Record<string, number>;
|
||||||
FriendsSections: Record<string, string>;
|
FriendsSections: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExpressionPickerStore {
|
||||||
|
closeExpressionPicker(activeViewType?: any): void;
|
||||||
|
openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrowserWindowFeatures {
|
||||||
|
toolbar?: boolean;
|
||||||
|
menubar?: boolean;
|
||||||
|
location?: boolean;
|
||||||
|
directories?: boolean;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
defaultWidth?: number;
|
||||||
|
defaultHeight?: number;
|
||||||
|
left?: number;
|
||||||
|
top?: number;
|
||||||
|
defaultAlwaysOnTop?: boolean;
|
||||||
|
movable?: boolean;
|
||||||
|
resizable?: boolean;
|
||||||
|
frame?: boolean;
|
||||||
|
alwaysOnTop?: boolean;
|
||||||
|
hasShadow?: boolean;
|
||||||
|
transparent?: boolean;
|
||||||
|
skipTaskbar?: boolean;
|
||||||
|
titleBarStyle?: string | null;
|
||||||
|
backgroundColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopoutActions {
|
||||||
|
open(key: string, render: (windowKey: string) => ReactNode, features?: BrowserWindowFeatures);
|
||||||
|
close(key: string): void;
|
||||||
|
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
||||||
|
}
|
||||||
|
|
|
@ -160,9 +160,15 @@ export const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
|
|
||||||
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
||||||
|
|
||||||
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i/);
|
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/);
|
||||||
// TODO: type
|
// TODO: type
|
||||||
export const ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
|
export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
|
||||||
closeExpressionPicker: filters.byCode("setState({activeView:null"),
|
closeExpressionPicker: filters.byCode("setState({activeView:null"),
|
||||||
openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
|
openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {
|
||||||
|
open: filters.byCode('type:"POPOUT_WINDOW_OPEN"'),
|
||||||
|
close: filters.byCode('type:"POPOUT_WINDOW_CLOSE"'),
|
||||||
|
setAlwaysOnTop: filters.byCode('type:"POPOUT_WINDOW_SET_ALWAYS_ON_TOP"'),
|
||||||
|
});
|
||||||
|
|
|
@ -279,7 +279,7 @@ export function findModuleFactory(...code: string[]) {
|
||||||
return wreq.m[id];
|
return wreq.m[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack" | "extractAndLoadChunks", any[]]>;
|
export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack" | "extractAndLoadChunks" | "mapMangledModule", any[]]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds.
|
* This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds.
|
||||||
|
@ -483,6 +483,8 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
export function mapMangledModuleLazy<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
|
export function mapMangledModuleLazy<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
|
||||||
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers]]);
|
||||||
|
|
||||||
return proxyLazy(() => mapMangledModule(code, mappers));
|
return proxyLazy(() => mapMangledModule(code, mappers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue