Compare commits
35 Commits
feat/telem
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
ea11f2244f | ||
|
96126fa39f | ||
|
9bd82943e3 | ||
|
5edc94062c | ||
|
394d2060eb | ||
|
119b628f33 | ||
|
32f2043193 | ||
|
04d2dd26c4 | ||
|
86e94343cc | ||
|
dd44ac1ad2 | ||
|
370b3d366d | ||
|
a67c7f841d | ||
|
2c9793202d | ||
|
a257926609 | ||
|
77659be4f0 | ||
|
37b9a62460 | ||
|
7f73e13364 | ||
|
fcf2bdda70 | ||
|
fa9da2d693 | ||
|
44b21394b3 | ||
|
27fffc8bc3 | ||
|
098da8c3fd | ||
|
9cf88d4232 | ||
|
dd61b0c999 | ||
|
5dc0d06be1 | ||
|
9de6c2d4ff | ||
|
e37f62ac0a | ||
|
56a9d79f85 | ||
|
9d78233afa | ||
|
9af2ec65ae | ||
|
584885acf5 | ||
|
18fdc33ee7 | ||
|
522fdcd15d | ||
|
af135b9245 | ||
|
4b958d17fd |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -14,7 +14,8 @@ body:
|
|||||||
DO NOT USE THIS FORM, unless
|
DO NOT USE THIS FORM, unless
|
||||||
- you are a vencord contributor
|
- you are a vencord contributor
|
||||||
- you were given explicit permission to use this form by a moderator in our support server
|
- you were given explicit permission to use this form by a moderator in our support server
|
||||||
- you are filing a security related report
|
|
||||||
|
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: discord
|
id: discord
|
||||||
|
@ -28,6 +28,14 @@ Visit https://vencord.dev/download
|
|||||||
|
|
||||||
https://discord.gg/D9uwnFnqmd
|
https://discord.gg/D9uwnFnqmd
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|
||||||
|
|:--:|
|
||||||
|
| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) |
|
||||||
|
| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |
|
||||||
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
<a href="https://star-history.com/#Vendicated/Vencord&Timeline">
|
<a href="https://star-history.com/#Vendicated/Vencord&Timeline">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.6.1",
|
"version": "1.6.3",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@ -70,7 +70,7 @@
|
|||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"zip-local": "^0.3.5"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.1.1",
|
"packageManager": "pnpm@8.10.2",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
|
import { readdir } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
|
||||||
|
|
||||||
const defines = {
|
const defines = {
|
||||||
IS_STANDALONE: isStandalone,
|
IS_STANDALONE: isStandalone,
|
||||||
@ -43,13 +45,59 @@ const nodeCommonOpts = {
|
|||||||
format: "cjs",
|
format: "cjs",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
external: ["electron", "original-fs", ...commonOpts.external],
|
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
||||||
define: defines,
|
define: defines,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||||
const sourcemap = watch ? "inline" : "external";
|
const sourcemap = watch ? "inline" : "external";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import("esbuild").Plugin}
|
||||||
|
*/
|
||||||
|
const globNativesPlugin = {
|
||||||
|
name: "glob-natives-plugin",
|
||||||
|
setup: build => {
|
||||||
|
const filter = /^~pluginNatives$/;
|
||||||
|
build.onResolve({ filter }, args => {
|
||||||
|
return {
|
||||||
|
namespace: "import-natives",
|
||||||
|
path: args.path
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
build.onLoad({ filter, namespace: "import-natives" }, async () => {
|
||||||
|
const pluginDirs = ["plugins", "userplugins"];
|
||||||
|
let code = "";
|
||||||
|
let natives = "\n";
|
||||||
|
let i = 0;
|
||||||
|
for (const dir of pluginDirs) {
|
||||||
|
const dirPath = join("src", dir);
|
||||||
|
if (!await existsAsync(dirPath)) continue;
|
||||||
|
const plugins = await readdir(dirPath);
|
||||||
|
for (const p of plugins) {
|
||||||
|
if (!await existsAsync(join(dirPath, p, "native.ts"))) continue;
|
||||||
|
|
||||||
|
const nameParts = p.split(".");
|
||||||
|
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
|
||||||
|
// pluginName.thing.desktop -> PluginName.thing
|
||||||
|
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
|
||||||
|
|
||||||
|
const mod = `p${i}`;
|
||||||
|
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
|
||||||
|
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code += `export default {${natives}};`;
|
||||||
|
return {
|
||||||
|
contents: code,
|
||||||
|
resolveDir: "./src"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Discord Desktop main & renderer & preload
|
// Discord Desktop main & renderer & preload
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
@ -62,7 +110,11 @@ await Promise.all([
|
|||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: true,
|
IS_DISCORD_DESKTOP: true,
|
||||||
IS_VESKTOP: false
|
IS_VESKTOP: false
|
||||||
}
|
},
|
||||||
|
plugins: [
|
||||||
|
...nodeCommonOpts.plugins,
|
||||||
|
globNativesPlugin
|
||||||
|
]
|
||||||
}),
|
}),
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
@ -107,7 +159,11 @@ await Promise.all([
|
|||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: false,
|
IS_DISCORD_DESKTOP: false,
|
||||||
IS_VESKTOP: true
|
IS_VESKTOP: true
|
||||||
}
|
},
|
||||||
|
plugins: [
|
||||||
|
...nodeCommonOpts.plugins,
|
||||||
|
globNativesPlugin
|
||||||
|
]
|
||||||
}),
|
}),
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
|
@ -20,8 +20,8 @@ import "../suppressExperimentalWarnings.js";
|
|||||||
import "../checkNodeVersion.js";
|
import "../checkNodeVersion.js";
|
||||||
|
|
||||||
import { exec, execSync } from "child_process";
|
import { exec, execSync } from "child_process";
|
||||||
import { existsSync, readFileSync } from "fs";
|
import { constants as FsConstants, readFileSync } from "fs";
|
||||||
import { readdir, readFile } from "fs/promises";
|
import { access, readdir, readFile } from "fs/promises";
|
||||||
import { join, relative } from "path";
|
import { join, relative } from "path";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
@ -47,6 +47,12 @@ export const banner = {
|
|||||||
|
|
||||||
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
|
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
|
||||||
|
|
||||||
|
export function existsAsync(path) {
|
||||||
|
return access(path, FsConstants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
||||||
/**
|
/**
|
||||||
* @type {import("esbuild").Plugin}
|
* @type {import("esbuild").Plugin}
|
||||||
@ -79,7 +85,7 @@ export const globPlugins = kind => ({
|
|||||||
let plugins = "\n";
|
let plugins = "\n";
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
if (!existsSync(`./src/${dir}`)) continue;
|
if (!await existsAsync(`./src/${dir}`)) continue;
|
||||||
const files = await readdir(`./src/${dir}`);
|
const files = await readdir(`./src/${dir}`);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
if (file.startsWith("_") || file.startsWith(".")) continue;
|
||||||
|
@ -34,7 +34,6 @@ import { patches, PMLogger, startAllPlugins } from "./plugins";
|
|||||||
import { localStorage } from "./utils/localStorage";
|
import { localStorage } from "./utils/localStorage";
|
||||||
import { relaunch } from "./utils/native";
|
import { relaunch } from "./utils/native";
|
||||||
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
|
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
|
||||||
import { sendTelemetry } from "./utils/telemetry";
|
|
||||||
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
|
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
|
||||||
import { onceReady } from "./webpack";
|
import { onceReady } from "./webpack";
|
||||||
import { SettingsRouter } from "./webpack/common";
|
import { SettingsRouter } from "./webpack/common";
|
||||||
@ -84,8 +83,6 @@ async function init() {
|
|||||||
|
|
||||||
syncSettings();
|
syncSettings();
|
||||||
|
|
||||||
sendTelemetry();
|
|
||||||
|
|
||||||
if (!IS_WEB) {
|
if (!IS_WEB) {
|
||||||
try {
|
try {
|
||||||
const isOutdated = await checkForUpdates();
|
const isOutdated = await checkForUpdates();
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
import { IpcRes } from "@utils/types";
|
import { IpcRes } from "@utils/types";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
|
import { PluginIpcMappings } from "main/ipcPlugins";
|
||||||
import type { UserThemeHeader } from "main/themes";
|
import type { UserThemeHeader } from "main/themes";
|
||||||
|
|
||||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||||
@ -17,6 +18,16 @@ export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
|||||||
return ipcRenderer.sendSync(event, ...args) as T;
|
return ipcRenderer.sendSync(event, ...args) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PluginHelpers = {} as Record<string, Record<string, (...args: any[]) => Promise<any>>>;
|
||||||
|
const pluginIpcMap = sendSync<PluginIpcMappings>(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP);
|
||||||
|
|
||||||
|
for (const [plugin, methods] of Object.entries(pluginIpcMap)) {
|
||||||
|
const map = PluginHelpers[plugin] = {};
|
||||||
|
for (const [methodName, method] of Object.entries(methods)) {
|
||||||
|
map[methodName] = (...args: any[]) => invoke(method as IpcEvents, ...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
themes: {
|
themes: {
|
||||||
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
|
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
|
||||||
@ -61,12 +72,5 @@ export default {
|
|||||||
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
||||||
},
|
},
|
||||||
|
|
||||||
pluginHelpers: {
|
pluginHelpers: PluginHelpers
|
||||||
OpenInApp: {
|
|
||||||
resolveRedirect: (url: string) => invoke<string>(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, url),
|
|
||||||
},
|
|
||||||
VoiceMessages: {
|
|
||||||
readRecording: (path: string) => invoke<Uint8Array | null>(IpcEvents.VOICE_MESSAGES_READ_RECORDING, path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -61,8 +61,6 @@ export interface Settings {
|
|||||||
settingsSync: boolean;
|
settingsSync: boolean;
|
||||||
settingsSyncVersion: number;
|
settingsSyncVersion: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
telemetry?: boolean; // tri-state, undefined = ask
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultSettings: Settings = {
|
const DefaultSettings: Settings = {
|
||||||
@ -93,9 +91,7 @@ const DefaultSettings: Settings = {
|
|||||||
url: "https://api.vencord.dev/",
|
url: "https://api.vencord.dev/",
|
||||||
settingsSync: false,
|
settingsSync: false,
|
||||||
settingsSyncVersion: 0
|
settingsSyncVersion: 0
|
||||||
},
|
}
|
||||||
|
|
||||||
telemetry: undefined
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -21,7 +21,6 @@ import { Settings, useSettings } from "@api/Settings";
|
|||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { isMac, isWindows } from "@utils/constants";
|
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
@ -47,6 +46,9 @@ function VencordSettings() {
|
|||||||
|
|
||||||
const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);
|
const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);
|
||||||
|
|
||||||
|
const isWindows = navigator.platform.toLowerCase().startsWith("win");
|
||||||
|
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||||
|
|
||||||
const Switches: Array<false | {
|
const Switches: Array<false | {
|
||||||
key: KeysOfType<typeof settings, boolean>;
|
key: KeysOfType<typeof settings, boolean>;
|
||||||
title: string;
|
title: string;
|
||||||
@ -91,11 +93,6 @@ function VencordSettings() {
|
|||||||
key: "macosTranslucency",
|
key: "macosTranslucency",
|
||||||
title: "Enable translucent window",
|
title: "Enable translucent window",
|
||||||
note: "Requires a full restart"
|
note: "Requires a full restart"
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "telemetry",
|
|
||||||
title: "Enable Telemetry",
|
|
||||||
note: "We only gather anonymous telemetry data. All data deleted after 3 days if you opt out."
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -17,73 +17,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
import { app, ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
import { readFile } from "fs/promises";
|
|
||||||
import { request } from "https";
|
|
||||||
import { basename, normalize } from "path";
|
|
||||||
|
|
||||||
import { getSettings } from "./ipcMain";
|
import PluginNatives from "~pluginNatives";
|
||||||
|
|
||||||
// FixSpotifyEmbeds
|
const PluginIpcMappings = {} as Record<string, Record<string, string>>;
|
||||||
app.on("browser-window-created", (_, win) => {
|
export type PluginIpcMappings = typeof PluginIpcMappings;
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
|
||||||
frame.once("dom-ready", () => {
|
|
||||||
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
|
||||||
const settings = getSettings().plugins?.FixSpotifyEmbeds;
|
|
||||||
if (!settings?.enabled) return;
|
|
||||||
|
|
||||||
frame.executeJavaScript(`
|
for (const [plugin, methods] of Object.entries(PluginNatives)) {
|
||||||
const original = Audio.prototype.play;
|
const entries = Object.entries(methods);
|
||||||
Audio.prototype.play = function() {
|
if (!entries.length) continue;
|
||||||
this.volume = ${(settings.volume / 100) || 0.1};
|
|
||||||
return original.apply(this, arguments);
|
const mappings = PluginIpcMappings[plugin] = {};
|
||||||
|
|
||||||
|
for (const [methodName, method] of entries) {
|
||||||
|
const key = `VencordPluginNative_${plugin}_${methodName}`;
|
||||||
|
ipcMain.handle(key, method);
|
||||||
|
mappings[methodName] = key;
|
||||||
}
|
}
|
||||||
`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// #region OpenInApp
|
|
||||||
// These links don't support CORS, so this has to be native
|
|
||||||
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
|
||||||
|
|
||||||
function getRedirect(url: string) {
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
const req = request(new URL(url), { method: "HEAD" }, res => {
|
|
||||||
resolve(
|
|
||||||
res.headers.location
|
|
||||||
? getRedirect(res.headers.location)
|
|
||||||
: url
|
|
||||||
);
|
|
||||||
});
|
|
||||||
req.on("error", reject);
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, async (_, url: string) => {
|
ipcMain.on(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP, e => {
|
||||||
if (!validRedirectUrls.test(url)) return url;
|
e.returnValue = PluginIpcMappings;
|
||||||
|
|
||||||
return getRedirect(url);
|
|
||||||
});
|
});
|
||||||
// #endregion
|
|
||||||
|
|
||||||
|
|
||||||
// #region VoiceMessages
|
|
||||||
ipcMain.handle(IpcEvents.VOICE_MESSAGES_READ_RECORDING, async (_, filePath: string) => {
|
|
||||||
filePath = normalize(filePath);
|
|
||||||
const filename = basename(filePath);
|
|
||||||
const discordBaseDirWithTrailingSlash = normalize(app.getPath("userData") + "/");
|
|
||||||
console.log(filename, discordBaseDirWithTrailingSlash, filePath);
|
|
||||||
if (filename !== "recording.ogg" || !filePath.startsWith(discordBaseDirWithTrailingSlash)) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const buf = await readFile(filePath);
|
|
||||||
return new Uint8Array(buf.buffer);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
5
src/modules.d.ts
vendored
5
src/modules.d.ts
vendored
@ -24,6 +24,11 @@ declare module "~plugins" {
|
|||||||
export default plugins;
|
export default plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "~pluginNatives" {
|
||||||
|
const pluginNatives: Record<string, Record<string, (event: Electron.IpcMainInvokeEvent, ...args: unknown[]) => unknown>>;
|
||||||
|
export default pluginNatives;
|
||||||
|
}
|
||||||
|
|
||||||
declare module "~git-hash" {
|
declare module "~git-hash" {
|
||||||
const hash: string;
|
const hash: string;
|
||||||
export default hash;
|
export default hash;
|
||||||
|
@ -27,8 +27,8 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: '"Message Username"',
|
find: '"Message Username"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/,
|
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
||||||
replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))"
|
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -21,16 +21,30 @@ import definePlugin from "@utils/types";
|
|||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "AlwaysAnimate",
|
name: "AlwaysAnimate",
|
||||||
description: "Animates anything that can be animated, besides status emojis.",
|
description: "Animates anything that can be animated",
|
||||||
authors: [Devs.FieryFlames],
|
authors: [Devs.FieryFlames],
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".canAnimate",
|
find: "canAnimate:",
|
||||||
all: true,
|
all: true,
|
||||||
|
// Some modules match the find but the replacement is returned untouched
|
||||||
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.canAnimate\b/g,
|
match: /canAnimate:.+?(?=([,}].*?\)))/g,
|
||||||
replace: ".canAnimate || true"
|
replace: (m, rest) => {
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) return "canAnimate:!0";
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Status emojis
|
||||||
|
find: ".Messages.GUILD_OWNER,",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
||||||
|
replace: "!0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -18,30 +18,40 @@
|
|||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { LazyComponent } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { find, findByPropsLazy } from "@webpack";
|
import { find, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { React, useStateFromStores } from "@webpack/common";
|
import { useStateFromStores } from "@webpack/common";
|
||||||
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
import { ExpandedGuildFolderStore, settings } from ".";
|
import { ExpandedGuildFolderStore, settings } from ".";
|
||||||
|
|
||||||
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
||||||
const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")')));
|
const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")')));
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(guildsBarProps => {
|
export default ErrorBoundary.wrap(guildsBarProps => {
|
||||||
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
||||||
|
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
|
||||||
|
|
||||||
const Sidebar = (
|
const Sidebar = (
|
||||||
<GuildsBar
|
<GuildsBar
|
||||||
{...guildsBarProps}
|
{...guildsBarProps}
|
||||||
isBetterFolders={true}
|
isBetterFolders={true}
|
||||||
|
betterFoldersExpandedIds={expandedFolders}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const visible = !!expandedFolders.size;
|
const visible = !!expandedFolders.size;
|
||||||
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
|
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
|
||||||
|
|
||||||
|
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.
|
||||||
|
// Also display flex otherwise to fix scrolling
|
||||||
|
const barStyle = {
|
||||||
|
display: isFullscreen ? "none" : "flex",
|
||||||
|
} as CSSProperties;
|
||||||
|
|
||||||
if (!guilds || !settings.store.sidebarAnim) {
|
if (!guilds || !settings.store.sidebarAnim) {
|
||||||
return visible
|
return visible
|
||||||
? <div style={{ display: "flex " }}>{Sidebar}</div>
|
? <div style={barStyle}>{Sidebar}</div>
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +63,9 @@ export default ErrorBoundary.wrap(guildsBarProps => {
|
|||||||
leave={{ width: 0 }}
|
leave={{ width: 0 }}
|
||||||
config={{ duration: 200 }}
|
config={{ duration: 200 }}
|
||||||
>
|
>
|
||||||
{(style, show) =>
|
{(animationStyle, show) =>
|
||||||
show && (
|
show && (
|
||||||
<Animations.animated.div style={{ ...style, display: "flex" }}>
|
<Animations.animated.div style={{ ...animationStyle, ...barStyle }}>
|
||||||
{Sidebar}
|
{Sidebar}
|
||||||
</Animations.animated.div>
|
</Animations.animated.div>
|
||||||
)
|
)
|
||||||
|
@ -18,13 +18,21 @@
|
|||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { FluxDispatcher, i18n } from "@webpack/common";
|
import { FluxDispatcher, i18n } from "@webpack/common";
|
||||||
|
|
||||||
import FolderSideBar from "./FolderSideBar";
|
import FolderSideBar from "./FolderSideBar";
|
||||||
|
|
||||||
const GuildFolderStore = findStoreLazy("SortedGuildStore");
|
enum FolderIconDisplay {
|
||||||
|
Never,
|
||||||
|
Always,
|
||||||
|
MoreThanOneFolderExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree);
|
||||||
|
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
||||||
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||||
|
|
||||||
@ -32,7 +40,7 @@ let lastGuildId = null as string | null;
|
|||||||
let dispatchingFoldersClose = false;
|
let dispatchingFoldersClose = false;
|
||||||
|
|
||||||
function getGuildFolder(id: string) {
|
function getGuildFolder(id: string) {
|
||||||
return GuildFolderStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
|
return SortedGuildStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeFolders() {
|
function closeFolders() {
|
||||||
@ -50,7 +58,6 @@ export const settings = definePluginSettings({
|
|||||||
sidebarAnim: {
|
sidebarAnim: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Animate opening the folder sidebar",
|
description: "Animate opening the folder sidebar",
|
||||||
restartNeeded: true,
|
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
closeAllFolders: {
|
closeAllFolders: {
|
||||||
@ -79,6 +86,16 @@ export const settings = definePluginSettings({
|
|||||||
description: "Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar",
|
description: "Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar",
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
showFolderIcon: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Show the folder icon above the folder guilds in the BetterFolders sidebar",
|
||||||
|
options: [
|
||||||
|
{ label: "Never", value: FolderIconDisplay.Never },
|
||||||
|
{ label: "Always", value: FolderIconDisplay.Always, default: true },
|
||||||
|
{ label: "When more than one folder is expanded", value: FolderIconDisplay.MoreThanOneFolderExpanded }
|
||||||
|
],
|
||||||
|
restartNeeded: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -99,25 +116,45 @@ export default definePlugin({
|
|||||||
match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
|
match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
|
||||||
replace: ",isBetterFolders"
|
replace: ",isBetterFolders"
|
||||||
},
|
},
|
||||||
|
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
||||||
|
{
|
||||||
|
match: /(useStateFromStoresArray\).{0,25}let \i)=(\i\.\i.getGuildsTree\(\))/,
|
||||||
|
replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`
|
||||||
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||||
{
|
{
|
||||||
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
||||||
replace: '$&.filter($self.makeGuildsBarGuildListFilter(typeof isBetterFolders!=="undefined"?isBetterFolders:false))'
|
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||||
{
|
{
|
||||||
match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/,
|
match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/,
|
||||||
replace: '$&.filter($self.makeGuildsBarTreeFilter(typeof isBetterFolders!=="undefined"?isBetterFolders:false))'
|
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
|
||||||
},
|
},
|
||||||
// Export the isBetterFolders variable to the folders component
|
// Export the isBetterFolders variable to the folders component
|
||||||
{
|
{
|
||||||
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
|
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
|
||||||
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
|
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
// Avoid rendering servers that are not in folders in the Better Folders sidebar
|
|
||||||
{
|
{
|
||||||
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?GUILD:)/,
|
// This is the parent folder component
|
||||||
replace: 'if((typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&$1.parentId==null)return null;'
|
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
|
||||||
|
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
// Modify the expanded state to instead return the list of expanded folders
|
||||||
|
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
|
||||||
|
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
|
||||||
|
// Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined
|
||||||
|
match: /(?<=folderNode:(\i),expanded:)\i(?=,)/,
|
||||||
|
replace: (isExpandedOrExpandedIds, folderNote) => ""
|
||||||
|
+ `typeof ${isExpandedOrExpandedIds}==="boolean"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),`
|
||||||
|
+ `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -125,33 +162,37 @@ export default definePlugin({
|
|||||||
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
|
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
|
||||||
predicate: () => settings.store.sidebar,
|
predicate: () => settings.store.sidebar,
|
||||||
replacement: [
|
replacement: [
|
||||||
// Create the isBetterFolders variable in the nested folders component (the parent exports all the props so we don't have to patch it)
|
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
|
||||||
{
|
|
||||||
match: /(?<=let{folderNode:\i,setNodeRef:\i,)/,
|
|
||||||
replace: "isBetterFolders,"
|
|
||||||
},
|
|
||||||
// If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions
|
// If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions
|
||||||
{
|
{
|
||||||
predicate: () => settings.store.keepIcons,
|
predicate: () => settings.store.keepIcons,
|
||||||
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i).+?;)(?=let)/,
|
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
|
||||||
replace: '$1=(typeof isBetterFolders!=="undefined"?isBetterFolders:false)?$1:false;'
|
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
|
||||||
},
|
|
||||||
// If we are rendering the Better Folders sidebar, we filter out folders that are not expanded
|
|
||||||
{
|
|
||||||
match: /(?=return\(0,\i.\i\)\("div")(?<=selected:\i,expanded:(\i),.+?)/,
|
|
||||||
replace: (_, expanded) => `if((typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&!${expanded})return null;`
|
|
||||||
},
|
},
|
||||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
|
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
|
||||||
replace: '(typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&'
|
replace: "!!arguments[0].isBetterFolders&&"
|
||||||
},
|
},
|
||||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
|
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
|
||||||
replace: (m, expanded) => `${m}((typeof isBetterFolders!=="undefined"?isBetterFolders:false)||!${expanded})&&`
|
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||||
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
|
match: /(?<=\.wrapper,children:\[)/,
|
||||||
|
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
||||||
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
|
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
||||||
|
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -212,6 +253,21 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set<any>) {
|
||||||
|
if (!isBetterFolders || expandedFolderIds == null) return oldTree;
|
||||||
|
|
||||||
|
const newTree = new GuildsTree();
|
||||||
|
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
|
||||||
|
newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
|
||||||
|
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
|
||||||
|
newTree.nodes = Object.fromEntries(
|
||||||
|
Object.entries(oldTree.nodes)
|
||||||
|
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
|
||||||
|
);
|
||||||
|
|
||||||
|
return newTree;
|
||||||
|
},
|
||||||
|
|
||||||
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
||||||
return child => {
|
return child => {
|
||||||
if (isBetterFolders) {
|
if (isBetterFolders) {
|
||||||
@ -230,6 +286,21 @@ export default definePlugin({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set<any>) {
|
||||||
|
if (!isBetterFolders) return true;
|
||||||
|
|
||||||
|
switch (settings.store.showFolderIcon) {
|
||||||
|
case FolderIconDisplay.Never:
|
||||||
|
return false;
|
||||||
|
case FolderIconDisplay.Always:
|
||||||
|
return true;
|
||||||
|
case FolderIconDisplay.MoreThanOneFolderExpanded:
|
||||||
|
return (expandedFolderIds?.size ?? 0) > 1;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
|
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
|
||||||
|
|
||||||
closeFolders
|
closeFolders
|
||||||
|
@ -30,6 +30,7 @@ const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
|||||||
const Colors = findByPropsLazy("profileColors");
|
const Colors = findByPropsLazy("profileColors");
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
|
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ async function embedDidMount(this: Component<Props>) {
|
|||||||
embed.rawTitle = titles[0].title;
|
embed.rawTitle = titles[0].title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thumbnails[0]?.votes >= 0) {
|
if (thumbnails[0]?.votes >= 0 && thumbnails[0].timestamp) {
|
||||||
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
|
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
|
||||||
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
|
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
|
||||||
}
|
}
|
||||||
|
@ -155,10 +155,15 @@ async function doClone(guildId: string, data: Sticker | Emoji) {
|
|||||||
type: Toasts.Type.SUCCESS,
|
type: Toasts.Type.SUCCESS,
|
||||||
id: Toasts.genId()
|
id: Toasts.genId()
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
|
let message = "Something went wrong (check console!)";
|
||||||
|
try {
|
||||||
|
message = JSON.parse(e.text).message;
|
||||||
|
} catch { }
|
||||||
|
|
||||||
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
|
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
message: "Oopsie something went wrong :( Check console!!!",
|
message: "Failed to clone: " + message,
|
||||||
type: Toasts.Type.FAILURE,
|
type: Toasts.Type.FAILURE,
|
||||||
id: Toasts.genId()
|
id: Toasts.genId()
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { Devs, isMac } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
@ -96,8 +96,9 @@ export default definePlugin({
|
|||||||
],
|
],
|
||||||
|
|
||||||
settingsAboutComponent: () => {
|
settingsAboutComponent: () => {
|
||||||
const modKey = isMac ? "cmd" : "ctrl";
|
const isMacOS = navigator.platform.includes("Mac");
|
||||||
const altKey = isMac ? "opt" : "alt";
|
const modKey = isMacOS ? "cmd" : "ctrl";
|
||||||
|
const altKey = isMacOS ? "opt" : "alt";
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
|
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
|
||||||
|
@ -201,15 +201,15 @@ export default definePlugin({
|
|||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
|
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
|
||||||
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention!=null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Allow stickers to be sent everywhere
|
// Allow stickers to be sent everywhere
|
||||||
{
|
{
|
||||||
find: "canUseStickersEverywhere:function",
|
find: "canUseCustomStickersEverywhere:function",
|
||||||
predicate: () => settings.store.enableStickerBypass,
|
predicate: () => settings.store.enableStickerBypass,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /canUseStickersEverywhere:function\(\i\){/,
|
match: /canUseCustomStickersEverywhere:function\(\i\){/,
|
||||||
replace: "$&return true;"
|
replace: "$&return true;"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -60,7 +60,7 @@ interface Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput");
|
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow");
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
searchOption: {
|
searchOption: {
|
||||||
@ -182,7 +182,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className={containerClasses.searchBar}
|
className={containerClasses.searchBar}
|
||||||
size={SearchBarComponent.Sizes.SMALL}
|
size={SearchBarComponent.Sizes.MEDIUM}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClear={() => {
|
onClear={() => {
|
||||||
setQuery("");
|
setQuery("");
|
||||||
|
27
src/plugins/fixSpotifyEmbeds.desktop/native.ts
Normal file
27
src/plugins/fixSpotifyEmbeds.desktop/native.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app } from "electron";
|
||||||
|
import { getSettings } from "main/ipcMain";
|
||||||
|
|
||||||
|
app.on("browser-window-created", (_, win) => {
|
||||||
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
|
frame.once("dom-ready", () => {
|
||||||
|
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
||||||
|
const settings = getSettings().plugins?.FixSpotifyEmbeds;
|
||||||
|
if (!settings?.enabled) return;
|
||||||
|
|
||||||
|
frame.executeJavaScript(`
|
||||||
|
const original = Audio.prototype.play;
|
||||||
|
Audio.prototype.play = function() {
|
||||||
|
this.volume = ${(settings.volume / 100) || 0.1};
|
||||||
|
return original.apply(this, arguments);
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -33,8 +33,8 @@ export default definePlugin({
|
|||||||
patches: [{
|
patches: [{
|
||||||
find: ".handleSelectGIF=",
|
find: ".handleSelectGIF=",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.handleSelectGIF=\i=>\{/,
|
match: /\.handleSelectGIF=(\i)=>\{/,
|
||||||
replace: ".handleSelectGIF=function(gif){return $self.handleSelect(gif);"
|
replace: ".handleSelectGIF=$1=>{if (!this.props.className) return $self.handleSelect($1);"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
@ -186,6 +186,13 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: ".carouselModal",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\.carouselModal.{0,100}onClick:)\i,/,
|
||||||
|
replace: "()=>{},"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
@ -16,18 +16,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vc-imgzoom-nearest-neighbor>img {
|
.vc-imgzoom-nearest-neighbor>img {
|
||||||
image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
image-rendering: pixelated;
|
||||||
|
|
||||||
|
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
||||||
[class|="carouselModal"] {
|
[class*="modalCarouselWrapper_"] {
|
||||||
height: fit-content;
|
top: 0 !important;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) {
|
[class*="carouselModal_"] {
|
||||||
position: absolute;
|
height: 0 !important;
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
}
|
||||||
|
@ -4,28 +4,58 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
import style from "./styles.css?managed";
|
import style from "./styles.css?managed";
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
inlineVideo: {
|
||||||
|
description: "Play videos without carousel modal",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
mediaLayoutType: {
|
||||||
|
description: "Choose media layout type",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
restartNeeded: true,
|
||||||
|
options: [
|
||||||
|
{ label: "STATIC, render loading image but image isn't resposive, no problem unless discord window width is too small", value: "STATIC", default: true },
|
||||||
|
{ label: "RESPONSIVE, image is responsive but not render loading image, cause messages shift when loaded", value: "RESPONSIVE" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoMosaic",
|
name: "NoMosaic",
|
||||||
authors: [Devs.AutumnVN],
|
authors: [Devs.AutumnVN],
|
||||||
description: "Removes Discord new image mosaic",
|
description: "Removes Discord new image mosaic",
|
||||||
tags: ["image", "mosaic", "media"],
|
tags: ["image", "mosaic", "media"],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".oneByTwoLayoutThreeGrid",
|
find: ".oneByTwoLayoutThreeGrid",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
||||||
replace: 'mediaLayoutType:"RESPONSIVE"'
|
replace: "mediaLayoutType:$self.mediaLayoutType()",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
||||||
replace: '"INVALID"',
|
replace: '"INVALID"',
|
||||||
},]
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "renderAttachments(",
|
||||||
|
predicate: () => settings.store.inlineVideo,
|
||||||
|
replacement: {
|
||||||
|
match: /url:(\i)\.url\}\);return /,
|
||||||
|
replace: "$&$1.content_type?.startsWith('image/')&&"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
|
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
|
||||||
@ -33,10 +63,17 @@ export default definePlugin({
|
|||||||
match: /\i===\i\.\i\.MOSAIC/,
|
match: /\i===\i\.\i\.MOSAIC/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
}
|
}
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
mediaLayoutType() {
|
||||||
|
return settings.store.mediaLayoutType;
|
||||||
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
enableStyle(style);
|
enableStyle(style);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
disableStyle(style);
|
disableStyle(style);
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,8 @@ export default definePlugin({
|
|||||||
}],
|
}],
|
||||||
isPrivateChannelRead(message: MessageJSON) {
|
isPrivateChannelRead(message: MessageJSON) {
|
||||||
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||||
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
|
(channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) ||
|
||||||
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
|
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
|
||||||
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
|
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
|
||||||
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
|
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType, PluginNative } from "@utils/types";
|
||||||
import { showToast, Toasts } from "@webpack/common";
|
import { showToast, Toasts } from "@webpack/common";
|
||||||
import type { MouseEvent } from "react";
|
import type { MouseEvent } from "react";
|
||||||
|
|
||||||
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||||
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user)\/(.+)(?:\?.+?)?$/;
|
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/;
|
||||||
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
||||||
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
||||||
|
|
||||||
@ -45,6 +45,8 @@ const settings = definePluginSettings({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "OpenInApp",
|
name: "OpenInApp",
|
||||||
description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser",
|
description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser",
|
||||||
@ -55,8 +57,8 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: "trackAnnouncementMessageLinkClicked({",
|
find: "trackAnnouncementMessageLinkClicked({",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)async function \1\(.+?\)\{/,
|
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/,
|
||||||
replace: "$& if(await $self.handleLink(...arguments)) return;"
|
replace: "async $& if(await $self.handleLink(...arguments)) return;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Make Spotify profile activity links open in app on web
|
// Make Spotify profile activity links open in app on web
|
||||||
@ -84,7 +86,7 @@ export default definePlugin({
|
|||||||
if (!IS_WEB && ShortUrlMatcher.test(url)) {
|
if (!IS_WEB && ShortUrlMatcher.test(url)) {
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
// CORS jumpscare
|
// CORS jumpscare
|
||||||
url = await VencordNative.pluginHelpers.OpenInApp.resolveRedirect(url);
|
url = await Native.resolveRedirect(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
spotify: {
|
spotify: {
|
||||||
|
31
src/plugins/openInApp/native.ts
Normal file
31
src/plugins/openInApp/native.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IpcMainInvokeEvent } from "electron";
|
||||||
|
import { request } from "https";
|
||||||
|
|
||||||
|
// These links don't support CORS, so this has to be native
|
||||||
|
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||||
|
|
||||||
|
function getRedirect(url: string) {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const req = request(new URL(url), { method: "HEAD" }, res => {
|
||||||
|
resolve(
|
||||||
|
res.headers.location
|
||||||
|
? getRedirect(res.headers.location)
|
||||||
|
: url
|
||||||
|
);
|
||||||
|
});
|
||||||
|
req.on("error", reject);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveRedirect(_: IpcMainInvokeEvent, url: string) {
|
||||||
|
if (!validRedirectUrls.test(url)) return url;
|
||||||
|
|
||||||
|
return getRedirect(url);
|
||||||
|
}
|
@ -4,6 +4,8 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
@ -28,8 +30,8 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: ".nonMediaAttachment]",
|
find: ".nonMediaAttachment]",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.nonMediaAttachment\].{0,10}children:\[(\S)/,
|
match: /\.nonMediaAttachment\]:!(\i).{0,10}children:\[(\S)/,
|
||||||
replace: "$&,$1&&$self.renderPiPButton(),"
|
replace: "$&,$1&&$2&&$self.renderPiPButton(),"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -40,6 +42,7 @@ export default definePlugin({
|
|||||||
{tooltipProps => (
|
{tooltipProps => (
|
||||||
<div
|
<div
|
||||||
{...tooltipProps}
|
{...tooltipProps}
|
||||||
|
className="vc-pip-button"
|
||||||
role="button"
|
role="button"
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
@ -70,7 +73,7 @@ export default definePlugin({
|
|||||||
>
|
>
|
||||||
<svg width="24px" height="24px" viewBox="0 0 24 24">
|
<svg width="24px" height="24px" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
fill="var(--interactive-normal)"
|
fill="currentColor"
|
||||||
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
|
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -137,5 +137,5 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
previewIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { Devs, isMac } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||||
@ -25,6 +25,7 @@ import { Message } from "discord-types/general";
|
|||||||
|
|
||||||
const Kangaroo = findByPropsLazy("jumpToMessage");
|
const Kangaroo = findByPropsLazy("jumpToMessage");
|
||||||
|
|
||||||
|
const isMac = navigator.platform.includes("Mac"); // bruh
|
||||||
let replyIdx = -1;
|
let replyIdx = -1;
|
||||||
let editIdx = -1;
|
let editIdx = -1;
|
||||||
|
|
||||||
|
@ -20,12 +20,12 @@ import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCal
|
|||||||
import { ReplyIcon } from "@components/Icons";
|
import { ReplyIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common";
|
import { ChannelStore, i18n, Menu, PermissionsBits, PermissionStore, SelectedChannelStore } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
const replyFn = findByCodeLazy("showMentionToggle", "TEXTAREA_FOCUS", "shiftKey");
|
const messageUtils = findByPropsLazy("replyToMessage");
|
||||||
|
|
||||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => {
|
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => {
|
||||||
// make sure the message is in the selected channel
|
// make sure the message is in the selected channel
|
||||||
@ -43,7 +43,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
|||||||
id="reply"
|
id="reply"
|
||||||
label={i18n.Messages.MESSAGE_ACTION_REPLY}
|
label={i18n.Messages.MESSAGE_ACTION_REPLY}
|
||||||
icon={ReplyIcon}
|
icon={ReplyIcon}
|
||||||
action={(e: React.MouseEvent) => replyFn(channel, message, e)}
|
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
|
|||||||
id="reply"
|
id="reply"
|
||||||
label={i18n.Messages.MESSAGE_ACTION_REPLY}
|
label={i18n.Messages.MESSAGE_ACTION_REPLY}
|
||||||
icon={ReplyIcon}
|
icon={ReplyIcon}
|
||||||
action={(e: React.MouseEvent) => replyFn(channel, message, e)}
|
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ export default definePlugin({
|
|||||||
authors: [Devs.Rini, Devs.TheKodeToad],
|
authors: [Devs.Rini, Devs.TheKodeToad],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"Message Username"',
|
find: ".useCanSeeRemixBadge)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=onContextMenu:\i,children:).*?\}/,
|
match: /(?<=onContextMenu:\i,children:).*?\}/,
|
||||||
replace: "$self.renderUsername(arguments[0])}"
|
replace: "$self.renderUsername(arguments[0])}"
|
||||||
|
11
src/plugins/superReactionTweaks/README.md
Normal file
11
src/plugins/superReactionTweaks/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Super Reaction Tweaks
|
||||||
|
|
||||||
|
This plugin applies configurable various tweaks to super reactions.
|
||||||
|
|
||||||
|
![Screenshot](https://user-images.githubusercontent.com/22851444/281598795-58f07116-9f95-4f64-940b-23a5499f2302.png)
|
||||||
|
|
||||||
|
## Features:
|
||||||
|
|
||||||
|
**Super React By Default** - The reaction picker will default to super reactions instead of normal reactions.
|
||||||
|
|
||||||
|
**Super Reaction Play Limit** - Allows you to decide how many super reaction animations can play at once, including removing the limit entirely.
|
63
src/plugins/superReactionTweaks/index.ts
Normal file
63
src/plugins/superReactionTweaks/index.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated, ant0n, FieryFlames and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
|
superReactByDefault: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Reaction picker will default to Super Reactions",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
unlimitedSuperReactionPlaying: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Remove the limit on Super Reactions playing at once",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
superReactionPlayingLimit: {
|
||||||
|
description: "Max Super Reactions to play at once",
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
default: 20,
|
||||||
|
markers: [5, 10, 20, 40, 60, 80, 100],
|
||||||
|
stickToMarkers: true,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
superReactionPlayingLimit: {
|
||||||
|
disabled() { return this.store.unlimitedSuperReactionPlaying; },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "SuperReactionTweaks",
|
||||||
|
description: "Customize the limit of Super Reactions playing at once, and super react by default",
|
||||||
|
authors: [Devs.FieryFlames, Devs.ant0n],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ",BURST_REACTION_EFFECT_PLAY",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/,
|
||||||
|
replace: "!$self.shouldPlayBurstReaction($1)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".hasAvailableBurstCurrency)",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\.useBurstReactionsExperiment.{0,20})useState\(!1\)(?=.+?(\i===\i\.EmojiIntention.REACTION))/,
|
||||||
|
replace: "useState($self.settings.store.superReactByDefault && $1)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
shouldPlayBurstReaction(playingCount: number) {
|
||||||
|
if (settings.store.unlimitedSuperReactionPlaying) return true;
|
||||||
|
if (playingCount <= settings.store.superReactionPlayingLimit) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
@ -112,7 +112,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: "getCooldownTextStyle",
|
find: "getCooldownTextStyle",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.length\?\i.\i\.Messages.THREE_USERS_TYPING\.format\({\i:(\i),\i:(\i),\i:\i}\):)\i\.\i\.Messages\.SEVERAL_USERS_TYPING/,
|
match: /(?<=(\i)\.length\?\i.\i\.Messages.THREE_USERS_TYPING\.format\({\i:(\i),(?:\i:)?(\i),\i:\i}\):)\i\.\i\.Messages\.SEVERAL_USERS_TYPING/,
|
||||||
replace: (_, users, a, b) => `$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`
|
replace: (_, users, a, b) => `$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`
|
||||||
},
|
},
|
||||||
predicate: () => settings.store.alternativeFormatting
|
predicate: () => settings.store.alternativeFormatting
|
||||||
|
@ -173,7 +173,7 @@ export default definePlugin({
|
|||||||
patches: [
|
patches: [
|
||||||
// Make pfps clickable
|
// Make pfps clickable
|
||||||
{
|
{
|
||||||
find: "onAddFriend:function",
|
find: "User Profile Modal - Context Menu",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\{src:(\i)(?=,avatarDecoration)/,
|
match: /\{src:(\i)(?=,avatarDecoration)/,
|
||||||
replace: "{src:$1,onClick:()=>$self.openImage($1)"
|
replace: "{src:$1,onClick:()=>$self.openImage($1)"
|
||||||
|
@ -16,11 +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 { PluginNative } from "@utils/types";
|
||||||
import { Button, showToast, Toasts, useState } from "@webpack/common";
|
import { Button, showToast, Toasts, useState } from "@webpack/common";
|
||||||
|
|
||||||
import type { VoiceRecorder } from ".";
|
import type { VoiceRecorder } from ".";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
|
|
||||||
|
const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => {
|
export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => {
|
||||||
const [recording, setRecording] = useState(false);
|
const [recording, setRecording] = useState(false);
|
||||||
|
|
||||||
@ -49,7 +52,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC
|
|||||||
} else {
|
} else {
|
||||||
discordVoice.stopLocalAudioRecording(async (filePath: string) => {
|
discordVoice.stopLocalAudioRecording(async (filePath: string) => {
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
const buf = await VencordNative.pluginHelpers.VoiceMessages.readRecording(filePath);
|
const buf = await Native.readRecording(filePath);
|
||||||
if (buf)
|
if (buf)
|
||||||
setAudioBlob(new Blob([buf], { type: "audio/ogg; codecs=opus" }));
|
setAudioBlob(new Blob([buf], { type: "audio/ogg; codecs=opus" }));
|
||||||
else
|
else
|
||||||
|
24
src/plugins/voiceMessages/native.ts
Normal file
24
src/plugins/voiceMessages/native.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app } from "electron";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import { basename, normalize } from "path";
|
||||||
|
|
||||||
|
export async function readRecording(_, filePath: string) {
|
||||||
|
filePath = normalize(filePath);
|
||||||
|
const filename = basename(filePath);
|
||||||
|
const discordBaseDirWithTrailingSlash = normalize(app.getPath("userData") + "/");
|
||||||
|
console.log(filename, discordBaseDirWithTrailingSlash, filePath);
|
||||||
|
if (filename !== "recording.ogg" || !filePath.startsWith(discordBaseDirWithTrailingSlash)) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buf = await readFile(filePath);
|
||||||
|
return new Uint8Array(buf.buffer);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -18,19 +18,14 @@
|
|||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findLazy, mapMangledModuleLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common";
|
import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common";
|
||||||
|
|
||||||
const GuildNavBinds = mapMangledModuleLazy("mod+alt+down", {
|
const KeyBinds = findByPropsLazy("JUMP_TO_GUILD", "SERVER_NEXT");
|
||||||
CtrlTab: m => m.binds?.at(-1) === "ctrl+tab",
|
|
||||||
CtrlShiftTab: m => m.binds?.at(-1) === "ctrl+shift+tab",
|
|
||||||
});
|
|
||||||
|
|
||||||
const DigitBinds = findLazy(m => m.binds?.[0] === "mod+1");
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "WebKeybinds",
|
name: "WebKeybinds",
|
||||||
description: "Re-adds keybinds missing in the web version of Discord: ctrl+t, ctrl+shift+t, ctrl+tab, ctrl+shift+tab, ctrl+1-9, ctrl+,",
|
description: "Re-adds keybinds missing in the web version of Discord: ctrl+t, ctrl+shift+t, ctrl+tab, ctrl+shift+tab, ctrl+1-9, ctrl+,. Only works fully on Vesktop/ArmCord, not inside your browser",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
enabledByDefault: true,
|
enabledByDefault: true,
|
||||||
|
|
||||||
@ -57,13 +52,13 @@ export default definePlugin({
|
|||||||
SettingsRouter.open("My Account");
|
SettingsRouter.open("My Account");
|
||||||
break;
|
break;
|
||||||
case "Tab":
|
case "Tab":
|
||||||
const handler = e.shiftKey ? GuildNavBinds.CtrlShiftTab : GuildNavBinds.CtrlTab;
|
const handler = e.shiftKey ? KeyBinds.SERVER_PREV : KeyBinds.SERVER_NEXT;
|
||||||
handler.action(e);
|
handler.action(e);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (e.key >= "1" && e.key <= "9") {
|
if (e.key >= "1" && e.key <= "9") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
DigitBinds.action(e, `mod+${e.key}`);
|
KeyBinds.JUMP_TO_GUILD.action(e, `mod+${e.key}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
@ -38,6 +38,8 @@ export const enum IpcEvents {
|
|||||||
BUILD = "VencordBuild",
|
BUILD = "VencordBuild",
|
||||||
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
|
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
|
||||||
|
|
||||||
|
GET_PLUGIN_IPC_METHOD_MAP = "VencordGetPluginIpcMethodMap",
|
||||||
|
|
||||||
OPEN_IN_APP__RESOLVE_REDIRECT = "VencordOIAResolveRedirect",
|
OPEN_IN_APP__RESOLVE_REDIRECT = "VencordOIAResolveRedirect",
|
||||||
VOICE_MESSAGES_READ_RECORDING = "VencordVMReadRecording",
|
VOICE_MESSAGES_READ_RECORDING = "VencordVMReadRecording",
|
||||||
}
|
}
|
||||||
|
@ -379,6 +379,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
name: "ProffDea",
|
name: "ProffDea",
|
||||||
id: 609329952180928513n
|
id: 609329952180928513n
|
||||||
},
|
},
|
||||||
|
ant0n: {
|
||||||
|
name: "ant0n",
|
||||||
|
id: 145224646868860928n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
@ -389,10 +393,3 @@ export const DevsById = /* #__PURE__*/ (() =>
|
|||||||
.map(([_, v]) => [v.id, v] as const)
|
.map(([_, v]) => [v.id, v] as const)
|
||||||
))
|
))
|
||||||
)() as Record<string, Dev>;
|
)() as Record<string, Dev>;
|
||||||
|
|
||||||
|
|
||||||
const { platform } = navigator;
|
|
||||||
|
|
||||||
export const isWindows = platform.startsWith("Win");
|
|
||||||
export const isMac = platform.startsWith("Mac");
|
|
||||||
export const isLinux = platform.startsWith("Linux");
|
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Settings } from "@api/Settings";
|
|
||||||
import { Alerts } from "@webpack/common";
|
|
||||||
|
|
||||||
import { isPluginEnabled } from "../plugins";
|
|
||||||
import { Plugins } from "../Vencord";
|
|
||||||
import { isLinux, isMac, isWindows } from "./constants";
|
|
||||||
|
|
||||||
export function sendTelemetry() {
|
|
||||||
// TODO: READ THIS CHECK BEFORE RELEASING!!
|
|
||||||
// if (IS_DEV) return; // don't send on devbuilds, usually contains incorrect data
|
|
||||||
|
|
||||||
// if we have not yet told the user about the telemetry's existence, or they haven't agreed at all, DON'T send a
|
|
||||||
// probe now, but tell them and then let them decide if they want to opt in or not.
|
|
||||||
if (Settings.telemetry === undefined) {
|
|
||||||
Alerts.show({
|
|
||||||
title: "Telemetry Notice",
|
|
||||||
body: <>
|
|
||||||
<p>
|
|
||||||
Vencord has a telemetry feature that sends anonymous data to us, which we use to improve the mod. We
|
|
||||||
gather your operating system, the version of Vencord you're using and a list of enabled plugins, and
|
|
||||||
we can use this data to help improve it for yourself and everyone else.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you don't want this, that's okay! We haven't sent anything yet. Please decide if you want to allow
|
|
||||||
us to gather a little bit of data. You can change this setting at any time in the future. If you
|
|
||||||
grant consent, we will start sending the data above the next time you reload or restart Discord.
|
|
||||||
</p>
|
|
||||||
</>,
|
|
||||||
confirmText: "Yes, that's fine",
|
|
||||||
cancelText: "No, I don't want that",
|
|
||||||
|
|
||||||
onConfirm() {
|
|
||||||
Settings.telemetry = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
onCancel() {
|
|
||||||
Settings.telemetry = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's disabled in settings, obviously don't do anything
|
|
||||||
if (!Settings.telemetry) return;
|
|
||||||
|
|
||||||
const activePluginsList = Object.keys(Plugins.plugins)
|
|
||||||
.filter(p => isPluginEnabled(p));
|
|
||||||
|
|
||||||
let operatingSystem = "Unknown";
|
|
||||||
|
|
||||||
if (isWindows) operatingSystem = "Windows";
|
|
||||||
else if (isMac) operatingSystem = "macOS";
|
|
||||||
else if (isLinux) operatingSystem = "Linux";
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
version: VERSION,
|
|
||||||
plugins: activePluginsList,
|
|
||||||
operatingSystem
|
|
||||||
};
|
|
||||||
|
|
||||||
navigator.sendBeacon("https://api.vencord.dev/v1/telemetry", JSON.stringify(data));
|
|
||||||
}
|
|
@ -307,3 +307,10 @@ export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon
|
|||||||
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
|
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
|
||||||
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
|
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
|
||||||
export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon;
|
export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon;
|
||||||
|
|
||||||
|
export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = {
|
||||||
|
[key in keyof PluginExports]:
|
||||||
|
PluginExports[key] extends (event: Electron.IpcMainInvokeEvent, ...args: infer Args) => infer Return
|
||||||
|
? (...args: Args) => Return extends Promise<any> ? Return : Promise<Return>
|
||||||
|
: never;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user