mirror of
https://github.com/Vendicated/Vencord
synced 2024-09-13 04:29:24 -04:00
Merge branch 'main' into main
This commit is contained in:
commit
2c553a3914
151 changed files with 2439 additions and 1293 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -14,6 +14,8 @@
|
||||||
"typescript.preferences.quoteStyle": "double",
|
"typescript.preferences.quoteStyle": "double",
|
||||||
"javascript.preferences.quoteStyle": "double",
|
"javascript.preferences.quoteStyle": "double",
|
||||||
|
|
||||||
|
"eslint.experimental.useFlatConfig": false,
|
||||||
|
|
||||||
"gitlens.remotes": [
|
"gitlens.remotes": [
|
||||||
{
|
{
|
||||||
"domain": "codeberg.org",
|
"domain": "codeberg.org",
|
||||||
|
|
10
README.md
10
README.md
|
@ -5,8 +5,8 @@
|
||||||
The cutest Discord client mod
|
The cutest Discord client mod
|
||||||
|
|
||||||
| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) |
|
| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) |
|
||||||
|:--:|
|
| :--------------------------------------------------------------------------------------------------: |
|
||||||
| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
|
| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
@ -32,9 +32,9 @@ https://discord.gg/D9uwnFnqmd
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|
||||||
|:--:|
|
| :------------------------------------------------------------------------------------------: |
|
||||||
| [![](https://meow.vendicated.dev/sponsors.png)](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)* |
|
| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.8.8",
|
"version": "1.9.1",
|
||||||
"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": {
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"eslint-plugin-simple-header": "^1.0.2",
|
"eslint-plugin-simple-header": "^1.0.2",
|
||||||
"fflate": "^0.7.4",
|
"fflate": "^0.7.4",
|
||||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||||
"monaco-editor": "^0.43.0",
|
"monaco-editor": "^0.50.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,8 +35,8 @@ importers:
|
||||||
specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3
|
specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3
|
||||||
version: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3
|
version: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3
|
||||||
monaco-editor:
|
monaco-editor:
|
||||||
specifier: ^0.43.0
|
specifier: ^0.50.0
|
||||||
version: 0.43.0
|
version: 0.50.0
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^4.0.2
|
specifier: ^4.0.2
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
|
@ -1531,6 +1531,10 @@ packages:
|
||||||
is-core-module@2.13.1:
|
is-core-module@2.13.1:
|
||||||
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
|
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
|
||||||
|
|
||||||
|
is-core-module@2.14.0:
|
||||||
|
resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-data-descriptor@0.1.4:
|
is-data-descriptor@0.1.4:
|
||||||
resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
|
resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -1791,8 +1795,8 @@ packages:
|
||||||
moment@2.29.4:
|
moment@2.29.4:
|
||||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||||
|
|
||||||
monaco-editor@0.43.0:
|
monaco-editor@0.50.0:
|
||||||
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
|
resolution: {integrity: sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA==}
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
|
@ -3463,7 +3467,7 @@ snapshots:
|
||||||
eslint-import-resolver-node@0.3.9:
|
eslint-import-resolver-node@0.3.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
is-core-module: 2.13.1
|
is-core-module: 2.14.0
|
||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -3490,7 +3494,7 @@ snapshots:
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.59.1(eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
|
eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.59.1(eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.13.1
|
is-core-module: 2.14.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
object.fromentries: 2.0.8
|
object.fromentries: 2.0.8
|
||||||
|
@ -3934,6 +3938,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
is-core-module@2.14.0:
|
||||||
|
dependencies:
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
is-data-descriptor@0.1.4:
|
is-data-descriptor@0.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
kind-of: 3.2.2
|
kind-of: 3.2.2
|
||||||
|
@ -4169,7 +4177,7 @@ snapshots:
|
||||||
|
|
||||||
moment@2.29.4: {}
|
moment@2.29.4: {}
|
||||||
|
|
||||||
monaco-editor@0.43.0: {}
|
monaco-editor@0.50.0: {}
|
||||||
|
|
||||||
ms@2.0.0: {}
|
ms@2.0.0: {}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,8 @@ await page.setBypassCSP(true);
|
||||||
|
|
||||||
async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
|
async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
|
||||||
return await (handle as JSHandle<Error>)?.getProperty("message")
|
return await (handle as JSHandle<Error>)?.getProperty("message")
|
||||||
.then(m => m?.jsonValue());
|
.then(m => m?.jsonValue())
|
||||||
|
.catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const report = {
|
const report = {
|
||||||
|
@ -75,9 +76,11 @@ const IGNORED_DISCORD_ERRORS = [
|
||||||
"Attempting to set fast connect zstd when unsupported"
|
"Attempting to set fast connect zstd when unsupported"
|
||||||
] as Array<string | RegExp>;
|
] as Array<string | RegExp>;
|
||||||
|
|
||||||
function toCodeBlock(s: string) {
|
function toCodeBlock(s: string, indentation = 0, isDiscord = false) {
|
||||||
s = s.replace(/```/g, "`\u200B`\u200B`");
|
s = s.replace(/```/g, "`\u200B`\u200B`");
|
||||||
return "```" + s + " ```";
|
|
||||||
|
const indentationStr = Array(!isDiscord ? indentation : 0).fill(" ").join("");
|
||||||
|
return `\`\`\`\n${s.split("\n").map(s => indentationStr + s).join("\n")}\n${indentationStr}\`\`\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function printReport() {
|
async function printReport() {
|
||||||
|
@ -91,35 +94,35 @@ async function printReport() {
|
||||||
report.badPatches.forEach(p => {
|
report.badPatches.forEach(p => {
|
||||||
console.log(`- ${p.plugin} (${p.type})`);
|
console.log(`- ${p.plugin} (${p.type})`);
|
||||||
console.log(` - ID: \`${p.id}\``);
|
console.log(` - ID: \`${p.id}\``);
|
||||||
console.log(` - Match: ${toCodeBlock(p.match)}`);
|
console.log(` - Match: ${toCodeBlock(p.match, " - Match: ".length)}`);
|
||||||
if (p.error) console.log(` - Error: ${toCodeBlock(p.error)}`);
|
if (p.error) console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
console.log("## Bad Webpack Finds");
|
console.log("## Bad Webpack Finds");
|
||||||
report.badWebpackFinds.forEach(p => console.log("- " + p));
|
report.badWebpackFinds.forEach(p => console.log("- " + toCodeBlock(p, "- ".length)));
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
console.log("## Bad Starts");
|
console.log("## Bad Starts");
|
||||||
report.badStarts.forEach(p => {
|
report.badStarts.forEach(p => {
|
||||||
console.log(`- ${p.plugin}`);
|
console.log(`- ${p.plugin}`);
|
||||||
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
console.log(` - Error: ${toCodeBlock(p.error, " - Error: ".length)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
console.log("## Discord Errors");
|
console.log("## Discord Errors");
|
||||||
report.otherErrors.forEach(e => {
|
report.otherErrors.forEach(e => {
|
||||||
console.log(`- ${toCodeBlock(e)}`);
|
console.log(`- ${toCodeBlock(e, "- ".length)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
console.log("## Ignored Discord Errors");
|
console.log("## Ignored Discord Errors");
|
||||||
report.ignoredErrors.forEach(e => {
|
report.ignoredErrors.forEach(e => {
|
||||||
console.log(`- ${toCodeBlock(e)}`);
|
console.log(`- ${toCodeBlock(e, "- ".length)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
@ -141,16 +144,16 @@ async function printReport() {
|
||||||
const lines = [
|
const lines = [
|
||||||
`**__${p.plugin} (${p.type}):__**`,
|
`**__${p.plugin} (${p.type}):__**`,
|
||||||
`ID: \`${p.id}\``,
|
`ID: \`${p.id}\``,
|
||||||
`Match: ${toCodeBlock(p.match)}`
|
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
||||||
];
|
];
|
||||||
if (p.error) lines.push(`Error: ${toCodeBlock(p.error)}`);
|
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}).join("\n\n") || "None",
|
}).join("\n\n") || "None",
|
||||||
color: report.badPatches.length ? 0xff0000 : 0x00ff00
|
color: report.badPatches.length ? 0xff0000 : 0x00ff00
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Bad Webpack Finds",
|
title: "Bad Webpack Finds",
|
||||||
description: report.badWebpackFinds.map(toCodeBlock).join("\n") || "None",
|
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
||||||
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
|
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -158,7 +161,7 @@ async function printReport() {
|
||||||
description: report.badStarts.map(p => {
|
description: report.badStarts.map(p => {
|
||||||
const lines = [
|
const lines = [
|
||||||
`**__${p.plugin}:__**`,
|
`**__${p.plugin}:__**`,
|
||||||
toCodeBlock(p.error)
|
toCodeBlock(p.error, 0, true)
|
||||||
];
|
];
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
@ -167,7 +170,7 @@ async function printReport() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Discord Errors",
|
title: "Discord Errors",
|
||||||
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None",
|
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
|
||||||
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
|
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -241,17 +244,26 @@ page.on("console", async e => {
|
||||||
error: await maybeGetError(e.args()[3]) ?? "Unknown error"
|
error: await maybeGetError(e.args()[3]) ?? "Unknown error"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "LazyChunkLoader:":
|
||||||
|
console.error(await getText());
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
|
case "A fatal error occurred:":
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "Reporter:":
|
case "Reporter:":
|
||||||
console.error(await getText());
|
console.error(await getText());
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
case "A fatal error occurred:":
|
||||||
|
process.exit(1);
|
||||||
case "Webpack Find Fail:":
|
case "Webpack Find Fail:":
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
report.badWebpackFinds.push(otherMessage);
|
report.badWebpackFinds.push(otherMessage);
|
||||||
break;
|
break;
|
||||||
case "A fatal error occurred:":
|
|
||||||
process.exit(1);
|
|
||||||
case "Finished test":
|
case "Finished test":
|
||||||
await browser.close();
|
await browser.close();
|
||||||
await printReport();
|
await printReport();
|
||||||
|
@ -277,7 +289,14 @@ page.on("console", async e => {
|
||||||
});
|
});
|
||||||
|
|
||||||
page.on("error", e => console.error("[Error]", e.message));
|
page.on("error", e => console.error("[Error]", e.message));
|
||||||
page.on("pageerror", e => console.error("[Page Error]", e.message));
|
page.on("pageerror", e => {
|
||||||
|
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||||
|
console.error("[Page Error]", e.message);
|
||||||
|
report.otherErrors.push(e.message);
|
||||||
|
} else {
|
||||||
|
report.ignoredErrors.push(e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function reporterRuntime(token: string) {
|
async function reporterRuntime(token: string) {
|
||||||
Vencord.Webpack.waitFor(
|
Vencord.Webpack.waitFor(
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { User } from "discord-types/general";
|
|
||||||
import { ComponentType, HTMLProps } from "react";
|
import { ComponentType, HTMLProps } from "react";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
@ -79,14 +78,14 @@ export function _getBadges(args: BadgeUserArgs) {
|
||||||
: badges.push({ ...badge, ...args });
|
: badges.push({ ...badge, ...args });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.user.id);
|
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
|
||||||
if (donorBadges) badges.unshift(...donorBadges);
|
if (donorBadges) badges.unshift(...donorBadges);
|
||||||
|
|
||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BadgeUserArgs {
|
export interface BadgeUserArgs {
|
||||||
user: User;
|
userId: string;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { MessageActions, SnowflakeUtils } from "@webpack/common";
|
import { MessageActions, SnowflakeUtils } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
|
|
||||||
import { Argument } from "./types";
|
import { Argument } from "./types";
|
||||||
|
|
||||||
const MessageCreator = findByPropsLazy("createBotMessage");
|
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||||
|
|
||||||
export function generateId() {
|
export function generateId() {
|
||||||
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
||||||
|
@ -37,7 +37,7 @@ export function generateId() {
|
||||||
* @returns {Message}
|
* @returns {Message}
|
||||||
*/
|
*/
|
||||||
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
|
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
|
||||||
const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] });
|
const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
|
||||||
|
|
||||||
MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage));
|
MessageActions.receiveMessage(channelId, mergeDefaults(message, botMessage));
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { Message } from "discord-types/general";
|
||||||
* @param messageId The message id
|
* @param messageId The message id
|
||||||
* @param fields The fields of the message to change. Leave empty if you just want to re-render
|
* @param fields The fields of the message to change. Leave empty if you just want to re-render
|
||||||
*/
|
*/
|
||||||
export function updateMessage(channelId: string, messageId: string, fields?: Partial<Message>) {
|
export function updateMessage(channelId: string, messageId: string, fields?: Partial<Message & Record<string, any>>) {
|
||||||
const channelMessageCache = MessageCache.getOrCreate(channelId);
|
const channelMessageCache = MessageCache.getOrCreate(channelId);
|
||||||
if (!channelMessageCache.has(messageId)) return;
|
if (!channelMessageCache.has(messageId)) return;
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
||||||
{timeout !== 0 && !permanent && (
|
{timeout !== 0 && !permanent && (
|
||||||
<div
|
<div
|
||||||
className="vc-notification-progressbar"
|
className="vc-notification-progressbar"
|
||||||
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-experiment)" }}
|
style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-500)" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
||||||
|
|
||||||
if (path === "plugins" && key in plugins)
|
if (path === "plugins" && key in plugins)
|
||||||
return target[key] = {
|
return target[key] = {
|
||||||
enabled: plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
enabled: IS_REPORTER ?? plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
|
|
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));
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import * as $Notifications from "./Notifications";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
import * as $Settings from "./Settings";
|
import * as $Settings from "./Settings";
|
||||||
import * as $Styles from "./Styles";
|
import * as $Styles from "./Styles";
|
||||||
|
import * as $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
|
||||||
|
@ -116,3 +117,8 @@ export const ChatButtons = $ChatButtons;
|
||||||
* An API allowing you to update and re-render messages
|
* An API allowing you to update and re-render messages
|
||||||
*/
|
*/
|
||||||
export const MessageUpdater = $MessageUpdater;
|
export const MessageUpdater = $MessageUpdater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An API allowing you to get 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"));
|
||||||
|
@ -69,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
|
||||||
<Forms.FormText className={cl("dep-text")}>
|
<Forms.FormText className={cl("dep-text")}>
|
||||||
Restart now to apply new plugins and their settings
|
Restart now to apply new plugins and their settings
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Button color={Button.Colors.YELLOW} onClick={() => location.reload()}>
|
<Button onClick={() => location.reload()}>
|
||||||
Restart
|
Restart
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
@ -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,53 +287,48 @@ 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 = [];
|
|
||||||
|
|
||||||
for (const p of sortedPlugins) {
|
const showApi = searchValue.value.includes("API");
|
||||||
if (!p.options && p.name.endsWith("API") && searchValue.value !== "API")
|
for (const p of sortedPlugins) {
|
||||||
continue;
|
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!pluginFilter(p)) continue;
|
if (!pluginFilter(p)) continue;
|
||||||
|
|
||||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||||
|
|
||||||
if (isRequired) {
|
if (isRequired) {
|
||||||
const tooltipText = p.required
|
const tooltipText = p.required
|
||||||
? "This plugin is required for Vencord to function."
|
? "This plugin is required for Vencord to function."
|
||||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||||
|
|
||||||
requiredPlugins.push(
|
|
||||||
<Tooltip text={tooltipText} key={p.name}>
|
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
|
||||||
<PluginCard
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onRestartNeeded={name => changes.handleChange(name)}
|
|
||||||
disabled={true}
|
|
||||||
plugin={p}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
plugins.push(
|
|
||||||
<PluginCard
|
|
||||||
onRestartNeeded={name => changes.handleChange(name)}
|
|
||||||
disabled={false}
|
|
||||||
plugin={p}
|
|
||||||
isNew={newPlugins?.includes(p.name)}
|
|
||||||
key={p.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
requiredPlugins.push(
|
||||||
|
<Tooltip text={tooltipText} key={p.name}>
|
||||||
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
|
<PluginCard
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onRestartNeeded={name => changes.handleChange(name)}
|
||||||
|
disabled={true}
|
||||||
|
plugin={p}
|
||||||
|
key={p.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
plugins.push(
|
||||||
|
<PluginCard
|
||||||
|
onRestartNeeded={name => changes.handleChange(name)}
|
||||||
|
disabled={false}
|
||||||
|
plugin={p}
|
||||||
|
isNew={newPlugins?.includes(p.name)}
|
||||||
|
key={p.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -332,9 +359,18 @@ export default function PluginSettings() {
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("grid")}>
|
{plugins.length || requiredPlugins.length
|
||||||
{plugins}
|
? (
|
||||||
</div>
|
<div className={cl("grid")}>
|
||||||
|
{plugins.length
|
||||||
|
? plugins
|
||||||
|
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: <ExcludedPluginsList search={search} />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top20} />
|
<Forms.FormDivider className={Margins.top20} />
|
||||||
|
|
||||||
|
@ -342,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 >
|
||||||
);
|
);
|
||||||
|
|
|
@ -78,6 +78,7 @@
|
||||||
|
|
||||||
.vc-plugins-restart-card button {
|
.vc-plugins-restart-card button {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
background: var(--info-warning-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-info-button svg:not(:hover, :focus) {
|
.vc-plugins-info-button svg:not(:hover, :focus) {
|
||||||
|
|
169
src/debug/loadLazyChunks.ts
Normal file
169
src/debug/loadLazyChunks.ts
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
import * as Webpack from "@webpack";
|
||||||
|
import { wreq } from "@webpack";
|
||||||
|
|
||||||
|
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||||
|
|
||||||
|
export async function loadLazyChunks() {
|
||||||
|
try {
|
||||||
|
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||||
|
|
||||||
|
const validChunks = new Set<string>();
|
||||||
|
const invalidChunks = new Set<string>();
|
||||||
|
const deferredRequires = new Set<string>();
|
||||||
|
|
||||||
|
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
||||||
|
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||||
|
|
||||||
|
// True if resolved, false otherwise
|
||||||
|
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||||
|
|
||||||
|
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||||
|
|
||||||
|
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||||
|
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||||
|
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
|
||||||
|
|
||||||
|
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
|
||||||
|
// the chunk containing the component
|
||||||
|
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
|
||||||
|
|
||||||
|
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||||
|
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
|
||||||
|
|
||||||
|
if (chunkIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invalidChunkGroup = false;
|
||||||
|
|
||||||
|
for (const id of chunkIds) {
|
||||||
|
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||||
|
|
||||||
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(t => t.includes("importScripts("));
|
||||||
|
|
||||||
|
if (isWorkerAsset) {
|
||||||
|
invalidChunks.add(id);
|
||||||
|
invalidChunkGroup = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
validChunks.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!invalidChunkGroup) {
|
||||||
|
validChunkGroups.add([chunkIds, entryPoint]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Loads all found valid chunk groups
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(validChunkGroups)
|
||||||
|
.map(([chunkIds]) =>
|
||||||
|
Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Requires the entry points for all valid chunk groups
|
||||||
|
for (const [, entryPoint] of validChunkGroups) {
|
||||||
|
try {
|
||||||
|
if (shouldForceDefer) {
|
||||||
|
deferredRequires.add(entryPoint);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setImmediate to only check if all chunks were loaded after this function resolves
|
||||||
|
// We check if all chunks were loaded every time a factory is loaded
|
||||||
|
// If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
|
||||||
|
// But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
|
||||||
|
setTimeout(() => {
|
||||||
|
let allResolved = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < chunksSearchPromises.length; i++) {
|
||||||
|
const isResolved = chunksSearchPromises[i]();
|
||||||
|
|
||||||
|
if (isResolved) {
|
||||||
|
// Remove finished promises to avoid having to iterate through a huge array everytime
|
||||||
|
chunksSearchPromises.splice(i--, 1);
|
||||||
|
} else {
|
||||||
|
allResolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allResolved) chunksSearchingResolve();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Webpack.factoryListeners.add(factory => {
|
||||||
|
let isResolved = false;
|
||||||
|
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
|
||||||
|
|
||||||
|
chunksSearchPromises.push(() => isResolved);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const factoryId in wreq.m) {
|
||||||
|
let isResolved = false;
|
||||||
|
searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
|
||||||
|
|
||||||
|
chunksSearchPromises.push(() => isResolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
await chunksSearchingDone;
|
||||||
|
|
||||||
|
// Require deferred entry points
|
||||||
|
for (const deferredRequire of deferredRequires) {
|
||||||
|
wreq!(deferredRequire as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||||
|
const allChunks = [] as string[];
|
||||||
|
|
||||||
|
// Matches "id" or id:
|
||||||
|
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
|
||||||
|
const id = currentMatch[1] ?? currentMatch[2];
|
||||||
|
if (id == null) continue;
|
||||||
|
|
||||||
|
allChunks.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||||
|
|
||||||
|
// Chunks that are not loaded (not used) by Discord code anymore
|
||||||
|
const chunksLeft = allChunks.filter(id => {
|
||||||
|
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(chunksLeft.map(async id => {
|
||||||
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(t => t.includes("importScripts("));
|
||||||
|
|
||||||
|
// Loads and requires a chunk
|
||||||
|
if (!isWorkerAsset) {
|
||||||
|
await wreq.e(id as any);
|
||||||
|
// Technically, the id of the chunk does not match the entry point
|
||||||
|
// But, still try it because we have no way to get the actual entry point
|
||||||
|
if (wreq.m[id]) wreq(id as any);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
LazyChunkLoaderLogger.log("Finished loading all chunks!");
|
||||||
|
} catch (e) {
|
||||||
|
LazyChunkLoaderLogger.log("A fatal error occurred:", e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,171 +5,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { wreq } from "@webpack";
|
|
||||||
import { patches } from "plugins";
|
import { patches } from "plugins";
|
||||||
|
|
||||||
|
import { loadLazyChunks } from "./loadLazyChunks";
|
||||||
|
|
||||||
const ReporterLogger = new Logger("Reporter");
|
const ReporterLogger = new Logger("Reporter");
|
||||||
|
|
||||||
async function runReporter() {
|
async function runReporter() {
|
||||||
ReporterLogger.log("Starting test...");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const validChunks = new Set<string>();
|
ReporterLogger.log("Starting test...");
|
||||||
const invalidChunks = new Set<string>();
|
|
||||||
const deferredRequires = new Set<string>();
|
|
||||||
|
|
||||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void;
|
||||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||||
|
|
||||||
// True if resolved, false otherwise
|
Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
|
||||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
await loadLazyChunksDone;
|
||||||
|
|
||||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
|
|
||||||
|
|
||||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
|
||||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
|
||||||
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
|
|
||||||
|
|
||||||
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
|
|
||||||
// the chunk containing the component
|
|
||||||
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
|
|
||||||
|
|
||||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
|
||||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
|
|
||||||
|
|
||||||
if (chunkIds.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let invalidChunkGroup = false;
|
|
||||||
|
|
||||||
for (const id of chunkIds) {
|
|
||||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
|
||||||
|
|
||||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
|
|
||||||
|
|
||||||
if (isWasm && IS_WEB) {
|
|
||||||
invalidChunks.add(id);
|
|
||||||
invalidChunkGroup = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
validChunks.add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!invalidChunkGroup) {
|
|
||||||
validChunkGroups.add([chunkIds, entryPoint]);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Loads all found valid chunk groups
|
|
||||||
await Promise.all(
|
|
||||||
Array.from(validChunkGroups)
|
|
||||||
.map(([chunkIds]) =>
|
|
||||||
Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Requires the entry points for all valid chunk groups
|
|
||||||
for (const [, entryPoint] of validChunkGroups) {
|
|
||||||
try {
|
|
||||||
if (shouldForceDefer) {
|
|
||||||
deferredRequires.add(entryPoint);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setImmediate to only check if all chunks were loaded after this function resolves
|
|
||||||
// We check if all chunks were loaded every time a factory is loaded
|
|
||||||
// If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
|
|
||||||
// But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
|
|
||||||
setTimeout(() => {
|
|
||||||
let allResolved = true;
|
|
||||||
|
|
||||||
for (let i = 0; i < chunksSearchPromises.length; i++) {
|
|
||||||
const isResolved = chunksSearchPromises[i]();
|
|
||||||
|
|
||||||
if (isResolved) {
|
|
||||||
// Remove finished promises to avoid having to iterate through a huge array everytime
|
|
||||||
chunksSearchPromises.splice(i--, 1);
|
|
||||||
} else {
|
|
||||||
allResolved = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allResolved) chunksSearchingResolve();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Webpack.beforeInitListeners.add(async () => {
|
|
||||||
ReporterLogger.log("Loading all chunks...");
|
|
||||||
|
|
||||||
Webpack.factoryListeners.add(factory => {
|
|
||||||
let isResolved = false;
|
|
||||||
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
|
|
||||||
|
|
||||||
chunksSearchPromises.push(() => isResolved);
|
|
||||||
});
|
|
||||||
|
|
||||||
// setImmediate to only search the initial factories after Discord initialized the app
|
|
||||||
// our beforeInitListeners are called before Discord initializes the app
|
|
||||||
setTimeout(() => {
|
|
||||||
for (const factoryId in wreq.m) {
|
|
||||||
let isResolved = false;
|
|
||||||
searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
|
|
||||||
|
|
||||||
chunksSearchPromises.push(() => isResolved);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
await chunksSearchingDone;
|
|
||||||
|
|
||||||
// Require deferred entry points
|
|
||||||
for (const deferredRequire of deferredRequires) {
|
|
||||||
wreq!(deferredRequire as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
|
||||||
const allChunks = [] as string[];
|
|
||||||
|
|
||||||
// Matches "id" or id:
|
|
||||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
|
|
||||||
const id = currentMatch[1] ?? currentMatch[2];
|
|
||||||
if (id == null) continue;
|
|
||||||
|
|
||||||
allChunks.push(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
|
||||||
|
|
||||||
// Chunks that are not loaded (not used) by Discord code anymore
|
|
||||||
const chunksLeft = allChunks.filter(id => {
|
|
||||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(chunksLeft.map(async id => {
|
|
||||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
|
|
||||||
|
|
||||||
// Loads and requires a chunk
|
|
||||||
if (!isWasm) {
|
|
||||||
await wreq.e(id as any);
|
|
||||||
if (wreq.m[id]) wreq(id as any);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
ReporterLogger.log("Finished loading all chunks!");
|
|
||||||
|
|
||||||
for (const patch of patches) {
|
for (const patch of patches) {
|
||||||
if (!patch.all) {
|
if (!patch.all) {
|
||||||
|
@ -188,9 +39,8 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
if (searchType === "waitForStore") method = "findStore";
|
if (searchType === "waitForStore") method = "findStore";
|
||||||
|
|
||||||
|
let result: any;
|
||||||
try {
|
try {
|
||||||
let result: any;
|
|
||||||
|
|
||||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
const [factory] = args;
|
const [factory] = args;
|
||||||
result = factory();
|
result = factory();
|
||||||
|
@ -199,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,8 +5,8 @@
|
||||||
<title>Vencord QuickCSS Editor</title>
|
<title>Vencord QuickCSS Editor</title>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/editor/editor.main.min.css"
|
href="https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs/editor/editor.main.css"
|
||||||
integrity="sha512-MOoQ02h80hklccfLrXFYkCzG+WVjORflOp9Zp8dltiaRP+35LYnO4LKOklR64oMGfGgJDLO8WJpkM1o5gZXYZQ=="
|
integrity="sha256-tiJPQ2O04z/pZ/AwdyIghrOMzewf+PIvEl1YKbQvsZk="
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
<script
|
<script
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/loader.min.js"
|
src="https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs/loader.js"
|
||||||
integrity="sha512-QzMpXeCPciAHP4wbYlV2PYgrQcaEkDQUjzkPU4xnjyVSD9T36/udamxtNBqb4qK4/bMQMPZ8ayrBe9hrGdBFjQ=="
|
integrity="sha256-KcU48TGr84r7unF7J5IgBo95aeVrEbrGe04S7TcFUjs="
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
></script>
|
></script>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
<script>
|
<script>
|
||||||
require.config({
|
require.config({
|
||||||
paths: {
|
paths: {
|
||||||
vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs",
|
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -131,11 +131,16 @@ if (!IS_VANILLA) {
|
||||||
|
|
||||||
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||||
|
|
||||||
// Monkey patch commandLine to disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
// Monkey patch commandLine to:
|
||||||
|
// - disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||||
|
// - disable UseEcoQoSForBackgroundProcess: Work around Discord unloading when in background
|
||||||
const originalAppend = app.commandLine.appendSwitch;
|
const originalAppend = app.commandLine.appendSwitch;
|
||||||
app.commandLine.appendSwitch = function (...args) {
|
app.commandLine.appendSwitch = function (...args) {
|
||||||
if (args[0] === "disable-features" && !args[1]?.includes("WidgetLayering")) {
|
if (args[0] === "disable-features") {
|
||||||
args[1] += ",WidgetLayering";
|
const disabledFeatures = new Set((args[1] ?? "").split(","));
|
||||||
|
disabledFeatures.add("WidgetLayering");
|
||||||
|
disabledFeatures.add("UseEcoQoSForBackgroundProcess");
|
||||||
|
args[1] += [...disabledFeatures].join(",");
|
||||||
}
|
}
|
||||||
return originalAppend.apply(this, args);
|
return originalAppend.apply(this, args);
|
||||||
};
|
};
|
||||||
|
|
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" {
|
||||||
|
|
|
@ -18,18 +18,20 @@
|
||||||
|
|
||||||
import "./fixBadgeOverflow.css";
|
import "./fixBadgeOverflow.css";
|
||||||
|
|
||||||
import { BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Heart } from "@components/Heart";
|
import { Heart } from "@components/Heart";
|
||||||
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, Modals, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Toasts } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png";
|
const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png";
|
||||||
|
|
||||||
|
@ -37,8 +39,8 @@ const ContributorBadge: ProfileBadge = {
|
||||||
description: "Vencord Contributor",
|
description: "Vencord Contributor",
|
||||||
image: CONTRIBUTOR_BADGE,
|
image: CONTRIBUTOR_BADGE,
|
||||||
position: BadgePosition.START,
|
position: BadgePosition.START,
|
||||||
shouldShow: ({ user }) => isPluginDev(user.id),
|
shouldShow: ({ userId }) => isPluginDev(userId),
|
||||||
onClick: (_, { user }) => openContributorModal(user)
|
onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId))
|
||||||
};
|
};
|
||||||
|
|
||||||
let DonorBadges = {} as Record<string, Array<Record<"tooltip" | "badge", string>>>;
|
let DonorBadges = {} as Record<string, Array<Record<"tooltip" | "badge", string>>>;
|
||||||
|
@ -66,7 +68,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
|
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
|
||||||
replace: "$&$1.unshift(...Vencord.Api.Badges._getBadges(arguments[0]));",
|
replace: "$&$1.unshift(...$self.getBadges(arguments[0]));",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// alt: "", aria-hidden: false, src: originalSrc
|
// alt: "", aria-hidden: false, src: originalSrc
|
||||||
|
@ -82,7 +84,36 @@ export default definePlugin({
|
||||||
// conditionally override their onClick with badge.onClick if it exists
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
{
|
{
|
||||||
match: /href:(\i)\.link/,
|
match: /href:(\i)\.link/,
|
||||||
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, arguments[0]) }),$&"
|
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* new profiles */
|
||||||
|
{
|
||||||
|
find: ".PANEL]:14",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/,
|
||||||
|
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".description,delay:",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
// alt: "", aria-hidden: false, src: originalSrc
|
||||||
|
match: /alt:" ","aria-hidden":!0,src:(?=.{0,20}(\i)\.icon)/,
|
||||||
|
// ...badge.props, ..., src: badge.image ?? ...
|
||||||
|
replace: "...$1.props,$& $1.image??"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<=text:(\i)\.description,.{0,50})children:/,
|
||||||
|
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||||
|
},
|
||||||
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
|
{
|
||||||
|
match: /href:(\i)\.link/,
|
||||||
|
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -104,6 +135,17 @@ export default definePlugin({
|
||||||
await loadBadges();
|
await loadBadges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getBadges(props: { userId: string; user?: User; guildId: string; }) {
|
||||||
|
try {
|
||||||
|
props.userId ??= props.user?.id!;
|
||||||
|
|
||||||
|
return _getBadges(props);
|
||||||
|
} catch (e) {
|
||||||
|
new Logger("BadgeAPI#hasBadges").error(e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {
|
renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {
|
||||||
const Component = badge.component!;
|
const Component = badge.component!;
|
||||||
return <Component {...badge} />;
|
return <Component {...badge} />;
|
||||||
|
|
|
@ -15,8 +15,8 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: '"sticker")',
|
find: '"sticker")',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /!\i\.isMobile(?=.+?(\i)\.push\(.{0,50}"gift")/,
|
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
||||||
replace: "$& &&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)"
|
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".handleSendMessage",
|
find: ".handleSendMessage,onResize",
|
||||||
replacement: {
|
replacement: {
|
||||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
||||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
||||||
|
|
50
src/plugins/_api/userSettings.ts
Normal file
50
src/plugins/_api/userSettings.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "UserSettingsAPI",
|
||||||
|
description: "Patches Discord's UserSettings to expose their group and name.",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ",updateSetting:",
|
||||||
|
replacement: [
|
||||||
|
// Main setting definition
|
||||||
|
{
|
||||||
|
match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/,
|
||||||
|
replace: "userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1],$&"
|
||||||
|
},
|
||||||
|
// Selective wrapper
|
||||||
|
{
|
||||||
|
match: /updateSetting:.{0,100}SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE/,
|
||||||
|
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,45 +56,24 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Discord Stable
|
|
||||||
// FIXME: remove once change merged to stable
|
|
||||||
{
|
{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
replacement: {
|
replacement: [
|
||||||
get match() {
|
{
|
||||||
switch (Settings.plugins.Settings.settingsLocation) {
|
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||||
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
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),$&"
|
{
|
||||||
}
|
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.ACTIVITY_SETTINGS",
|
]
|
||||||
replacement: {
|
|
||||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "useDefaultUserSettingsSections:function",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=useDefaultUserSettingsSections:function\(\){return )(\i)\}/,
|
|
||||||
replace: "$self.wrapSettingsHook($1)}"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/,
|
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
||||||
replace: "$2.default.open($1);return;"
|
replace: "$2.open($1);return;"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -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,12 +56,88 @@ const TrustedRolesIds = [
|
||||||
"1042507929485586532", // donor
|
"1042507929485586532", // donor
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const AsyncFunction = async function () { }.constructor;
|
||||||
|
|
||||||
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
|
async function forceUpdate() {
|
||||||
|
const outdated = await checkForUpdates();
|
||||||
|
if (outdated) {
|
||||||
|
await update();
|
||||||
|
relaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
return outdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateDebugInfoMessage() {
|
||||||
|
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||||
|
|
||||||
|
const client = (() => {
|
||||||
|
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||||
|
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||||
|
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||||
|
return `${name} (${navigator.userAgent})`;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
Vencord:
|
||||||
|
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
||||||
|
`${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
||||||
|
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
||||||
|
Platform: window.navigator.platform
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonIssues = {
|
||||||
|
"NoRPC enabled": Vencord.Plugins.isPluginEnabled("NoRPC"),
|
||||||
|
"Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false),
|
||||||
|
"Vencord DevBuild": !IS_STANDALONE,
|
||||||
|
"Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin),
|
||||||
|
"More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5,
|
||||||
|
};
|
||||||
|
|
||||||
|
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({
|
export default definePlugin({
|
||||||
name: "SupportHelper",
|
name: "SupportHelper",
|
||||||
required: true,
|
required: true,
|
||||||
description: "Helps us provide support to you",
|
description: "Helps us provide support to you",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["CommandsAPI"],
|
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".BEGINNING_DM.format",
|
find: ".BEGINNING_DM.format",
|
||||||
|
@ -62,51 +147,20 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
commands: [{
|
commands: [
|
||||||
name: "vencord-debug",
|
{
|
||||||
description: "Send Vencord Debug info",
|
name: "vencord-debug",
|
||||||
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
description: "Send Vencord debug info",
|
||||||
async execute() {
|
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
execute: async () => ({ content: await generateDebugInfoMessage() })
|
||||||
|
},
|
||||||
const client = (() => {
|
{
|
||||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
name: "vencord-plugins",
|
||||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
description: "Send Vencord plugin list",
|
||||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||||
|
execute: () => ({ content: generatePluginList() })
|
||||||
// @ts-expect-error
|
|
||||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
|
||||||
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 = {
|
|
||||||
Vencord:
|
|
||||||
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
|
||||||
`${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
|
||||||
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
|
||||||
Platform: window.navigator.platform
|
|
||||||
};
|
|
||||||
|
|
||||||
if (IS_DISCORD_DESKTOP) {
|
|
||||||
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
|
|
||||||
}
|
|
||||||
|
|
||||||
const debugInfo = `
|
|
||||||
>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}
|
|
||||||
|
|
||||||
Enabled Plugins (${enabledPlugins.length}):
|
|
||||||
${makeCodeblock(enabledPlugins.join(", "))}
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: debugInfo.trim().replaceAll("```\n", "```")
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}],
|
],
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
async CHANNEL_SELECT({ channelId }) {
|
async CHANNEL_SELECT({ channelId }) {
|
||||||
|
@ -115,24 +169,25 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
const selfId = UserStore.getCurrentUser()?.id;
|
const selfId = UserStore.getCurrentUser()?.id;
|
||||||
if (!selfId || isPluginDev(selfId)) return;
|
if (!selfId || isPluginDev(selfId)) return;
|
||||||
|
|
||||||
if (isOutdated) {
|
if (!IS_UPDATER_DISABLED) {
|
||||||
return Alerts.show({
|
await checkForUpdatesOnce().catch(() => { });
|
||||||
title: "Hold on!",
|
|
||||||
body: <div>
|
if (isOutdated) {
|
||||||
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
|
return Alerts.show({
|
||||||
<Forms.FormText className={Margins.top8}>
|
title: "Hold on!",
|
||||||
Please first update before asking for support!
|
body: <div>
|
||||||
</Forms.FormText>
|
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
|
||||||
</div>,
|
<Forms.FormText className={Margins.top8}>
|
||||||
onCancel: () => openUpdaterModal!(),
|
Please first update before asking for support!
|
||||||
cancelText: "View Updates",
|
</Forms.FormText>
|
||||||
confirmText: "Update & Restart Now",
|
</div>,
|
||||||
async onConfirm() {
|
onCancel: () => openUpdaterModal!(),
|
||||||
await update();
|
cancelText: "View Updates",
|
||||||
relaunch();
|
confirmText: "Update & Restart Now",
|
||||||
},
|
onConfirm: forceUpdate,
|
||||||
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
|
||||||
|
@ -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;
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.domain
|
predicate: () => settings.store.domain
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "isSuspiciousDownload:",
|
find: "bitbucket.org",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
|
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
|
||||||
replace: "$&return null;"
|
replace: "$&return null;"
|
||||||
|
|
9
src/plugins/appleMusic.desktop/README.md
Normal file
9
src/plugins/appleMusic.desktop/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# AppleMusicRichPresence
|
||||||
|
|
||||||
|
This plugin enables Discord rich presence for your Apple Music! (This only works on macOS with the Music app.)
|
||||||
|
|
||||||
|
![Screenshot of the activity in Discord](https://github.com/Vendicated/Vencord/assets/70191398/1f811090-ab5f-4060-a9ee-d0ac44a1d3c0)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
For the customizable activity format strings, you can use several special strings to include track data in activities! `{name}` is replaced with the track name; `{artist}` is replaced with the artist(s)' name(s); and `{album}` is replaced with the album name.
|
262
src/plugins/appleMusic.desktop/index.tsx
Normal file
262
src/plugins/appleMusic.desktop/index.tsx
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||||
|
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||||
|
|
||||||
|
const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
|
interface ActivityAssets {
|
||||||
|
large_image?: string;
|
||||||
|
large_text?: string;
|
||||||
|
small_image?: string;
|
||||||
|
small_text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActivityButton {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Activity {
|
||||||
|
state: string;
|
||||||
|
details?: string;
|
||||||
|
timestamps?: {
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
};
|
||||||
|
assets?: ActivityAssets;
|
||||||
|
buttons?: Array<string>;
|
||||||
|
name: string;
|
||||||
|
application_id: string;
|
||||||
|
metadata?: {
|
||||||
|
button_urls?: Array<string>;
|
||||||
|
};
|
||||||
|
type: number;
|
||||||
|
flags: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum ActivityType {
|
||||||
|
PLAYING = 0,
|
||||||
|
LISTENING = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum ActivityFlag {
|
||||||
|
INSTANCE = 1 << 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrackData {
|
||||||
|
name: string;
|
||||||
|
album: string;
|
||||||
|
artist: string;
|
||||||
|
|
||||||
|
appleMusicLink?: string;
|
||||||
|
songLink?: string;
|
||||||
|
|
||||||
|
albumArtwork?: string;
|
||||||
|
artistArtwork?: string;
|
||||||
|
|
||||||
|
playerPosition: number;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum AssetImageType {
|
||||||
|
Album = "Album",
|
||||||
|
Artist = "Artist",
|
||||||
|
Disabled = "Disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
const applicationId = "1239490006054207550";
|
||||||
|
|
||||||
|
function setActivity(activity: Activity | null) {
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "LOCAL_ACTIVITY_UPDATE",
|
||||||
|
activity,
|
||||||
|
socketId: "AppleMusic",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
activityType: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Which type of activity",
|
||||||
|
options: [
|
||||||
|
{ label: "Playing", value: ActivityType.PLAYING, default: true },
|
||||||
|
{ label: "Listening", value: ActivityType.LISTENING }
|
||||||
|
],
|
||||||
|
},
|
||||||
|
refreshInterval: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "The interval between activity refreshes (seconds)",
|
||||||
|
markers: [1, 2, 2.5, 3, 5, 10, 15],
|
||||||
|
default: 5,
|
||||||
|
restartNeeded: true,
|
||||||
|
},
|
||||||
|
enableTimestamps: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether or not to enable timestamps",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
enableButtons: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether or not to enable buttons",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
nameString: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Activity name format string",
|
||||||
|
default: "Apple Music"
|
||||||
|
},
|
||||||
|
detailsString: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Activity details format string",
|
||||||
|
default: "{name}"
|
||||||
|
},
|
||||||
|
stateString: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Activity state format string",
|
||||||
|
default: "{artist}"
|
||||||
|
},
|
||||||
|
largeImageType: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Activity assets large image type",
|
||||||
|
options: [
|
||||||
|
{ label: "Album artwork", value: AssetImageType.Album, default: true },
|
||||||
|
{ label: "Artist artwork", value: AssetImageType.Artist },
|
||||||
|
{ label: "Disabled", value: AssetImageType.Disabled }
|
||||||
|
],
|
||||||
|
},
|
||||||
|
largeTextString: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Activity assets large text format string",
|
||||||
|
default: "{album}"
|
||||||
|
},
|
||||||
|
smallImageType: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Activity assets small image type",
|
||||||
|
options: [
|
||||||
|
{ label: "Album artwork", value: AssetImageType.Album },
|
||||||
|
{ label: "Artist artwork", value: AssetImageType.Artist, default: true },
|
||||||
|
{ label: "Disabled", value: AssetImageType.Disabled }
|
||||||
|
],
|
||||||
|
},
|
||||||
|
smallTextString: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "Activity assets small text format string",
|
||||||
|
default: "{artist}"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function customFormat(formatStr: string, data: TrackData) {
|
||||||
|
return formatStr
|
||||||
|
.replaceAll("{name}", data.name)
|
||||||
|
.replaceAll("{album}", data.album)
|
||||||
|
.replaceAll("{artist}", data.artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImageAsset(type: AssetImageType, data: TrackData) {
|
||||||
|
const source = type === AssetImageType.Album
|
||||||
|
? data.albumArtwork
|
||||||
|
: data.artistArtwork;
|
||||||
|
|
||||||
|
if (!source) return undefined;
|
||||||
|
|
||||||
|
return ApplicationAssetUtils.fetchAssetIds(applicationId, [source]).then(ids => ids[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "AppleMusicRichPresence",
|
||||||
|
description: "Discord rich presence for your Apple Music!",
|
||||||
|
authors: [Devs.RyanCaoDev],
|
||||||
|
hidden: !navigator.platform.startsWith("Mac"),
|
||||||
|
reporterTestable: ReporterTestable.None,
|
||||||
|
|
||||||
|
settingsAboutComponent() {
|
||||||
|
return <>
|
||||||
|
<Forms.FormText>
|
||||||
|
For the customizable activity format strings, you can use several special strings to include track data in activities!{" "}
|
||||||
|
<code>{"{name}"}</code> is replaced with the track name; <code>{"{artist}"}</code> is replaced with the artist(s)' name(s); and <code>{"{album}"}</code> is replaced with the album name.
|
||||||
|
</Forms.FormText>
|
||||||
|
</>;
|
||||||
|
},
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.updatePresence();
|
||||||
|
this.updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null });
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePresence() {
|
||||||
|
this.getActivity().then(activity => { setActivity(activity); });
|
||||||
|
},
|
||||||
|
|
||||||
|
async getActivity(): Promise<Activity | null> {
|
||||||
|
const trackData = await Native.fetchTrackData();
|
||||||
|
if (!trackData) return null;
|
||||||
|
|
||||||
|
const [largeImageAsset, smallImageAsset] = await Promise.all([
|
||||||
|
getImageAsset(settings.store.largeImageType, trackData),
|
||||||
|
getImageAsset(settings.store.smallImageType, trackData)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const assets: ActivityAssets = {};
|
||||||
|
|
||||||
|
if (settings.store.largeImageType !== AssetImageType.Disabled) {
|
||||||
|
assets.large_image = largeImageAsset;
|
||||||
|
assets.large_text = customFormat(settings.store.largeTextString, trackData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.smallImageType !== AssetImageType.Disabled) {
|
||||||
|
assets.small_image = smallImageAsset;
|
||||||
|
assets.small_text = customFormat(settings.store.smallTextString, trackData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons: ActivityButton[] = [];
|
||||||
|
|
||||||
|
if (settings.store.enableButtons) {
|
||||||
|
if (trackData.appleMusicLink)
|
||||||
|
buttons.push({
|
||||||
|
label: "Listen on Apple Music",
|
||||||
|
url: trackData.appleMusicLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (trackData.songLink)
|
||||||
|
buttons.push({
|
||||||
|
label: "View on SongLink",
|
||||||
|
url: trackData.songLink,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
application_id: applicationId,
|
||||||
|
|
||||||
|
name: customFormat(settings.store.nameString, trackData),
|
||||||
|
details: customFormat(settings.store.detailsString, trackData),
|
||||||
|
state: customFormat(settings.store.stateString, trackData),
|
||||||
|
|
||||||
|
timestamps: (settings.store.enableTimestamps ? {
|
||||||
|
start: Date.now() - (trackData.playerPosition * 1000),
|
||||||
|
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
|
||||||
|
} : undefined),
|
||||||
|
|
||||||
|
assets,
|
||||||
|
|
||||||
|
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
|
||||||
|
metadata: { button_urls: buttons.map(v => v.url) || undefined, },
|
||||||
|
|
||||||
|
type: settings.store.activityType,
|
||||||
|
flags: ActivityFlag.INSTANCE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
120
src/plugins/appleMusic.desktop/native.ts
Normal file
120
src/plugins/appleMusic.desktop/native.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execFile } from "child_process";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
import type { TrackData } from ".";
|
||||||
|
|
||||||
|
const exec = promisify(execFile);
|
||||||
|
|
||||||
|
// function exec(file: string, args: string[] = []) {
|
||||||
|
// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => {
|
||||||
|
// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] });
|
||||||
|
|
||||||
|
// let stdout: string | null = null;
|
||||||
|
// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; });
|
||||||
|
// let stderr: string | null = null;
|
||||||
|
// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; });
|
||||||
|
|
||||||
|
// process.on("exit", code => { resolve({ code, stdout, stderr }); });
|
||||||
|
// process.on("error", err => reject(err));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function applescript(cmds: string[]) {
|
||||||
|
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
|
||||||
|
return stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSearchUrl(type: string, query: string) {
|
||||||
|
const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json");
|
||||||
|
url.searchParams.set("types", type);
|
||||||
|
url.searchParams.set("limit", "1");
|
||||||
|
url.searchParams.set("term", query);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestOptions: RequestInit = {
|
||||||
|
headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" },
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RemoteData {
|
||||||
|
appleMusicLink?: string,
|
||||||
|
songLink?: string,
|
||||||
|
albumArtwork?: string,
|
||||||
|
artistArtwork?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
||||||
|
|
||||||
|
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
|
||||||
|
if (id === cachedRemoteData?.id) {
|
||||||
|
if ("data" in cachedRemoteData) return cachedRemoteData.data;
|
||||||
|
if ("failures" in cachedRemoteData && cachedRemoteData.failures >= 5) return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [songData, artistData] = await Promise.all([
|
||||||
|
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()),
|
||||||
|
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json())
|
||||||
|
]);
|
||||||
|
|
||||||
|
const appleMusicLink = songData?.songs?.data[0]?.attributes.url;
|
||||||
|
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;
|
||||||
|
|
||||||
|
const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
|
||||||
|
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
|
||||||
|
|
||||||
|
cachedRemoteData = {
|
||||||
|
id,
|
||||||
|
data: { appleMusicLink, songLink, albumArtwork, artistArtwork }
|
||||||
|
};
|
||||||
|
return cachedRemoteData.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);
|
||||||
|
cachedRemoteData = {
|
||||||
|
id,
|
||||||
|
failures: (id === cachedRemoteData?.id && "failures" in cachedRemoteData ? cachedRemoteData.failures : 0) + 1
|
||||||
|
};
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTrackData(): Promise<TrackData | null> {
|
||||||
|
try {
|
||||||
|
await exec("pgrep", ["^Music$"]);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerState = await applescript(['tell application "Music"', "get player state", "end tell"])
|
||||||
|
.then(out => out.trim());
|
||||||
|
if (playerState !== "playing") return null;
|
||||||
|
|
||||||
|
const playerPosition = await applescript(['tell application "Music"', "get player position", "end tell"])
|
||||||
|
.then(text => Number.parseFloat(text.trim()));
|
||||||
|
|
||||||
|
const stdout = await applescript([
|
||||||
|
'set output to ""',
|
||||||
|
'tell application "Music"',
|
||||||
|
"set t_id to database id of current track",
|
||||||
|
"set t_name to name of current track",
|
||||||
|
"set t_album to album of current track",
|
||||||
|
"set t_artist to artist of current track",
|
||||||
|
"set t_duration to duration of current track",
|
||||||
|
'set output to "" & t_id & "\\n" & t_name & "\\n" & t_album & "\\n" & t_artist & "\\n" & t_duration',
|
||||||
|
"end tell",
|
||||||
|
"return output"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [id, name, album, artist, durationStr] = stdout.split("\n").filter(k => !!k);
|
||||||
|
const duration = Number.parseFloat(durationStr);
|
||||||
|
|
||||||
|
const remoteData = await fetchRemoteData({ id, name, artist, album });
|
||||||
|
|
||||||
|
return { name, album, artist, playerPosition, duration, ...remoteData };
|
||||||
|
}
|
|
@ -20,10 +20,10 @@ import { popNotice, showNotice } from "@api/Notices";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { ReporterTestable } from "@utils/types";
|
import definePlugin, { ReporterTestable } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL");
|
const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID");
|
||||||
|
|
||||||
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||||
|
@ -32,7 +32,7 @@ async function lookupAsset(applicationId: string, key: string): Promise<string>
|
||||||
const apps: any = {};
|
const apps: any = {};
|
||||||
async function lookupApp(applicationId: string): Promise<string> {
|
async function lookupApp(applicationId: string): Promise<string> {
|
||||||
const socket: any = {};
|
const socket: any = {};
|
||||||
await RpcUtils.fetchApplicationsRPC(socket, applicationId);
|
await fetchApplicationsRPC(socket, applicationId);
|
||||||
return socket.application;
|
return socket.application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "BAN_CONFIRM_TITLE.",
|
find: "BAN_CONFIRM_TITLE.",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /src:\i\("\d+"\)/g,
|
match: /src:\i\("?\d+"?\)/g,
|
||||||
replace: "src: Vencord.Settings.plugins.BANger.source"
|
replace: "src: Vencord.Settings.plugins.BANger.source"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||||
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
|
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
|
||||||
|
|
||||||
import FolderSideBar from "./FolderSideBar";
|
import FolderSideBar from "./FolderSideBar";
|
||||||
|
@ -30,7 +30,7 @@ enum FolderIconDisplay {
|
||||||
MoreThanOneFolderExpanded
|
MoreThanOneFolderExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
const { GuildsTree } = findByPropsLazy("GuildsTree");
|
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
|
||||||
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
||||||
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||||
|
@ -117,7 +117,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
||||||
{
|
{
|
||||||
match: /\[(\i)\]=(\(0,\i\.useStateFromStoresArray\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
|
match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
|
||||||
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
|
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||||
|
@ -139,13 +139,13 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// This is the parent folder component
|
// This is the parent folder component
|
||||||
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
|
find: ".toggleGuildFolderExpand(",
|
||||||
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Modify the expanded state to instead return the list of expanded folders
|
// Modify the expanded state to instead return the list of expanded folders
|
||||||
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
|
match: /(\],\(\)=>)(\i\.\i)\.isFolderExpanded\(\i\)\)/,
|
||||||
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
|
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders())`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
|
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
|
||||||
|
@ -196,7 +196,7 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "APPLICATION_LIBRARY,render",
|
find: "APPLICATION_LIBRARY,render:",
|
||||||
predicate: () => settings.store.sidebar,
|
predicate: () => settings.store.sidebar,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Render the Better Folders sidebar
|
// Render the Better Folders sidebar
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.GIF,",
|
find: ".Messages.GIF,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
|
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
|
||||||
replace:
|
replace:
|
||||||
// rename prop so we can always use default value
|
// rename prop so we can always use default value
|
||||||
"alt_$$:$1=$self.altify($3)||$2",
|
"alt_$$:$1=$self.altify($3)||$2",
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Samwich],
|
authors: [Devs.Samwich],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".GIFPickerResultTypes.SEARCH",
|
find: '"state",{resultType:',
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /(?<="state",{resultType:)null/,
|
match: /(?<="state",{resultType:)null/,
|
||||||
replace: '"Favorites"'
|
replace: '"Favorites"'
|
||||||
|
|
|
@ -5,15 +5,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
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";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common";
|
import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
|
||||||
|
|
||||||
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
||||||
|
|
||||||
|
const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!;
|
||||||
|
|
||||||
function PencilIcon() {
|
function PencilIcon() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
@ -62,12 +65,13 @@ export default definePlugin({
|
||||||
name: "BetterRoleContext",
|
name: "BetterRoleContext",
|
||||||
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
||||||
authors: [Devs.Ven, Devs.goodbee],
|
authors: [Devs.Ven, Devs.goodbee],
|
||||||
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
// DeveloperMode needs to be enabled for the context menu to be shown
|
// DeveloperMode needs to be enabled for the context menu to be shown
|
||||||
TextAndImagesSettingsStores.DeveloperMode.updateSetting(true);
|
DeveloperMode.updateSetting(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
|
|
|
@ -48,6 +48,7 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
find: ".ADD_ROLE_A11Y_LABEL",
|
find: ".ADD_ROLE_A11Y_LABEL",
|
||||||
|
all: true,
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -57,6 +58,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".roleVerifiedIcon",
|
find: ".roleVerifiedIcon",
|
||||||
|
all: true,
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
|
@ -77,15 +77,6 @@ export default definePlugin({
|
||||||
replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&"
|
replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
// Add the ability to change BlobMask's lower badge height
|
|
||||||
// (it allows changing width so we can mirror that logic)
|
|
||||||
find: "this.getBadgePositionInterpolation(",
|
|
||||||
replacement: {
|
|
||||||
match: /(\i\.animated\.rect,{id:\i,x:48-\(\i\+8\)\+4,y:)28(,width:\i\+8,height:)24,/,
|
|
||||||
replace: (_, leftPart, rightPart) => `${leftPart} 48 - ((this.props.lowerBadgeHeight ?? 16) + 8) + 4 ${rightPart} (this.props.lowerBadgeHeight ?? 16) + 8,`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -153,14 +144,16 @@ export default definePlugin({
|
||||||
<PlatformIcon width={14} height={14} />
|
<PlatformIcon width={14} height={14} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
lowerBadgeWidth={20}
|
lowerBadgeSize={{
|
||||||
lowerBadgeHeight={20}
|
width: 20,
|
||||||
|
height: 20
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={SessionIconClasses.sessionIcon}
|
className={SessionIconClasses.sessionIcon}
|
||||||
style={{ backgroundColor: GetOsColor(session.client_info.os) }}
|
style={{ backgroundColor: GetOsColor(session.client_info.os) }}
|
||||||
>
|
>
|
||||||
<DeviceIcon width={28} height={28} />
|
<DeviceIcon width={28} height={28} color="currentColor" />
|
||||||
</div>
|
</div>
|
||||||
</BlobMask>
|
</BlobMask>
|
||||||
);
|
);
|
||||||
|
|
|
@ -83,19 +83,19 @@ export default definePlugin({
|
||||||
find: "this.renderArtisanalHack()",
|
find: "this.renderArtisanalHack()",
|
||||||
replacement: [
|
replacement: [
|
||||||
{ // Fade in on layer
|
{ // Fade in on layer
|
||||||
match: /(?<=\((\i),"contextType",\i\.AccessibilityPreferencesContext\);)/,
|
match: /(?<=\((\i),"contextType",\i\.\i\);)/,
|
||||||
replace: "$1=$self.Layer;",
|
replace: "$1=$self.Layer;",
|
||||||
predicate: () => settings.store.disableFade
|
predicate: () => settings.store.disableFade
|
||||||
},
|
},
|
||||||
{ // Lazy-load contents
|
{ // Lazy-load contents
|
||||||
match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g,
|
match: /createPromise:\(\)=>([^:}]*?),webpackId:"?\d+"?,name:(?!="CollectiblesShop")"[^"]+"/g,
|
||||||
replace: "$&,_:$1",
|
replace: "$&,_:$1",
|
||||||
predicate: () => settings.store.eagerLoad
|
predicate: () => settings.store.eagerLoad
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ // For some reason standardSidebarView also has a small fade-in
|
{ // For some reason standardSidebarView also has a small fade-in
|
||||||
find: "DefaultCustomContentScroller:function()",
|
find: 'minimal:"contentColumnMinimal"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\(0,\i\.useTransition\)\((\i)/,
|
match: /\(0,\i\.useTransition\)\((\i)/,
|
||||||
|
@ -111,7 +111,7 @@ export default definePlugin({
|
||||||
{ // Load menu TOC eagerly
|
{ // Load menu TOC eagerly
|
||||||
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
||||||
replace: "$&(async ()=>$2)(),"
|
replace: "$&(async ()=>$2)(),"
|
||||||
},
|
},
|
||||||
predicate: () => settings.store.eagerLoad
|
predicate: () => settings.store.eagerLoad
|
||||||
|
@ -119,8 +119,8 @@ export default definePlugin({
|
||||||
{ // Settings cog context menu
|
{ // Settings cog context menu
|
||||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(0,\i.useDefaultUserSettingsSections\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||||
replace: "$self.wrapMenu($&)"
|
replace: "$1$self.wrapMenu($2)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Button, Forms, useStateFromStores } from "@webpack/common";
|
import { Button, Forms, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||||
|
@ -30,7 +30,7 @@ function onPickColor(color: number) {
|
||||||
updateColorVars(hexColor);
|
updateColorVars(hexColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { saveClientTheme } = findByPropsLazy("saveClientTheme");
|
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE",settings:{useSystemTheme:"system"===');
|
||||||
|
|
||||||
function setTheme(theme: string) {
|
function setTheme(theme: string) {
|
||||||
saveClientTheme({ theme });
|
saveClientTheme({ theme });
|
||||||
|
|
|
@ -25,6 +25,7 @@ import definePlugin, { PluginNative, StartAt } from "@utils/types";
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { extract, filters, findAll, findModuleId, search } from "@webpack";
|
import { extract, filters, findAll, findModuleId, search } from "@webpack";
|
||||||
import * as Common from "@webpack/common";
|
import * as Common from "@webpack/common";
|
||||||
|
import { loadLazyChunks } from "debug/loadLazyChunks";
|
||||||
import type { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
|
|
||||||
const DESKTOP_ONLY = (f: string) => () => {
|
const DESKTOP_ONLY = (f: string) => () => {
|
||||||
|
@ -82,6 +83,7 @@ function makeShortcuts() {
|
||||||
wpsearch: search,
|
wpsearch: search,
|
||||||
wpex: extract,
|
wpex: extract,
|
||||||
wpexs: (code: string) => extract(findModuleId(code)!),
|
wpexs: (code: string) => extract(findModuleId(code)!),
|
||||||
|
loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
|
||||||
find,
|
find,
|
||||||
findAll: findAll,
|
findAll: findAll,
|
||||||
findByProps,
|
findByProps,
|
||||||
|
@ -139,7 +141,15 @@ function makeShortcuts() {
|
||||||
guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },
|
guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },
|
||||||
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
|
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
|
||||||
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
|
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
|
||||||
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }
|
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
|
||||||
|
|
||||||
|
Stores: {
|
||||||
|
getter: () => Object.fromEntries(
|
||||||
|
Common.Flux.Store.getAll()
|
||||||
|
.map(store => [store.getName(), store] as const)
|
||||||
|
.filter(([name]) => name.length > 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
src/plugins/copyEmojiMarkdown/README.md
Normal file
5
src/plugins/copyEmojiMarkdown/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# CopyEmojiMarkdown
|
||||||
|
|
||||||
|
Allows you to copy emojis as formatted string. Custom emojis will be copied as `<:trolley:1024751352028602449>`, default emojis as `🛒`
|
||||||
|
|
||||||
|
![](https://github.com/Vendicated/Vencord/assets/45497981/417f345a-7031-4fe7-8e42-e238870cd547)
|
75
src/plugins/copyEmojiMarkdown/index.tsx
Normal file
75
src/plugins/copyEmojiMarkdown/index.tsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { copyWithToast } from "@utils/misc";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Menu } from "@webpack/common";
|
||||||
|
|
||||||
|
const { convertNameToSurrogate } = findByPropsLazy("convertNameToSurrogate");
|
||||||
|
|
||||||
|
interface Emoji {
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Target {
|
||||||
|
dataset: Emoji;
|
||||||
|
firstChild: HTMLImageElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmojiMarkdown(target: Target, copyUnicode: boolean): string {
|
||||||
|
const { id: emojiId, name: emojiName } = target.dataset;
|
||||||
|
|
||||||
|
if (!emojiId) {
|
||||||
|
return copyUnicode
|
||||||
|
? convertNameToSurrogate(emojiName)
|
||||||
|
: `:${emojiName}:`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = target?.firstChild.src.match(
|
||||||
|
/https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(\w+)/
|
||||||
|
)?.[1];
|
||||||
|
|
||||||
|
return `<${extension === "gif" ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
copyUnicode: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Copy the raw unicode character instead of :name: for default emojis (👽)",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "CopyEmojiMarkdown",
|
||||||
|
description: "Allows you to copy emojis as formatted string (<:blobcatcozy:1026533070955872337>)",
|
||||||
|
authors: [Devs.HappyEnderman, Devs.Vishnya],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
contextMenus: {
|
||||||
|
"expression-picker"(children, { target }: { target: Target }) {
|
||||||
|
if (target.dataset.type !== "emoji") return;
|
||||||
|
|
||||||
|
children.push(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-copy-emoji-markdown"
|
||||||
|
label="Copy Emoji Markdown"
|
||||||
|
action={() => {
|
||||||
|
copyWithToast(
|
||||||
|
getEmojiMarkdown(target, settings.store.copyUnicode),
|
||||||
|
"Success! Copied emoji markdown."
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -24,20 +24,19 @@ import { closeAllModals } from "@utils/modal";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { maybePromptToUpdate } from "@utils/updater";
|
import { maybePromptToUpdate } from "@utils/updater";
|
||||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||||
import { DraftType, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||||
|
|
||||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||||
|
|
||||||
const { ModalStack, DraftManager, closeExpressionPicker } = proxyLazyWebpack(() => {
|
const { ModalStack, DraftManager } = proxyLazyWebpack(() => {
|
||||||
const [ModalStack, DraftManager, ExpressionManager] = findBulk(
|
const [ModalStack, DraftManager] = findBulk(
|
||||||
filters.byProps("pushLazy", "popAll"),
|
filters.byProps("pushLazy", "popAll"),
|
||||||
filters.byProps("clearDraft", "saveDraft"),
|
filters.byProps("clearDraft", "saveDraft"),
|
||||||
filters.byProps("closeExpressionPicker", "openExpressionPicker"),);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ModalStack,
|
ModalStack,
|
||||||
DraftManager,
|
DraftManager
|
||||||
closeExpressionPicker: ExpressionManager?.closeExpressionPicker,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -144,7 +143,7 @@ export default definePlugin({
|
||||||
CrashHandlerLogger.debug("Failed to clear drafts.", err);
|
CrashHandlerLogger.debug("Failed to clear drafts.", err);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
closeExpressionPicker();
|
ExpressionPickerStore.closeExpressionPicker();
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
CrashHandlerLogger.debug("Failed to close expression picker.", err);
|
CrashHandlerLogger.debug("Failed to close expression picker.", err);
|
||||||
|
|
|
@ -40,9 +40,9 @@ export default definePlugin({
|
||||||
}),
|
}),
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "KeyboardKeys.ENTER&&(!",
|
find: ".ENTER&&(!",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.which===\i\.KeyboardKeys.ENTER&&).{0,100}(\(0,\i\.hasOpenPlainTextCodeBlock\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
||||||
replace: "$self.shouldSubmit($1, $2)"
|
replace: "$self.shouldSubmit($1, $2)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
|
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";
|
||||||
|
@ -26,12 +27,14 @@ import { classes } from "@utils/misc";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common";
|
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
|
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
|
||||||
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
||||||
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
||||||
|
|
||||||
|
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)\//, "");
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||||
|
@ -178,7 +181,7 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
startTime: {
|
startTime: {
|
||||||
type: OptionType.NUMBER,
|
type: OptionType.NUMBER,
|
||||||
description: "Start timestamp in milisecond (only for custom timestamp mode)",
|
description: "Start timestamp in milliseconds (only for custom timestamp mode)",
|
||||||
onChange: onChange,
|
onChange: onChange,
|
||||||
disabled: isTimestampDisabled,
|
disabled: isTimestampDisabled,
|
||||||
isValid: (value: number) => {
|
isValid: (value: number) => {
|
||||||
|
@ -188,7 +191,7 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
endTime: {
|
endTime: {
|
||||||
type: OptionType.NUMBER,
|
type: OptionType.NUMBER,
|
||||||
description: "End timestamp in milisecond (only for custom timestamp mode)",
|
description: "End timestamp in milliseconds (only for custom timestamp mode)",
|
||||||
onChange: onChange,
|
onChange: onChange,
|
||||||
disabled: isTimestampDisabled,
|
disabled: isTimestampDisabled,
|
||||||
isValid: (value: number) => {
|
isValid: (value: number) => {
|
||||||
|
@ -390,13 +393,14 @@ export default definePlugin({
|
||||||
name: "CustomRPC",
|
name: "CustomRPC",
|
||||||
description: "Allows you to set a custom rich presence.",
|
description: "Allows you to set a custom rich presence.",
|
||||||
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||||
|
dependencies: ["UserSettingsAPI"],
|
||||||
start: setRpc,
|
start: setRpc,
|
||||||
stop: () => setRpc(true),
|
stop: () => setRpc(true),
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
settingsAboutComponent: () => {
|
settingsAboutComponent: () => {
|
||||||
const activity = useAwaiter(createActivity);
|
const activity = useAwaiter(createActivity);
|
||||||
const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting();
|
const gameActivityEnabled = ShowCurrentGame.useSetting();
|
||||||
const { profileThemeStyle } = useProfileThemeStyle({});
|
const { profileThemeStyle } = useProfileThemeStyle({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -412,7 +416,7 @@ export default definePlugin({
|
||||||
<Button
|
<Button
|
||||||
color={Button.Colors.TRANSPARENT}
|
color={Button.Colors.TRANSPARENT}
|
||||||
className={Margins.top8}
|
className={Margins.top8}
|
||||||
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(true)}
|
onClick={() => ShowCurrentGame.updateSetting(true)}
|
||||||
>
|
>
|
||||||
Enable
|
Enable
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -33,26 +33,23 @@ export default definePlugin({
|
||||||
authors: [Devs.newwares],
|
authors: [Devs.newwares],
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
|
||||||
find: "IDLE_DURATION:function(){return",
|
|
||||||
replacement: {
|
|
||||||
match: /(IDLE_DURATION:function\(\){return )\i/,
|
|
||||||
replace: "$1$self.getIdleTimeout()"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: 'type:"IDLE",idle:',
|
find: 'type:"IDLE",idle:',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /Math\.min\((\i\.AfkTimeout\.getSetting\(\)\*\i\.default\.Millis\.SECOND),\i\.IDLE_DURATION\)/,
|
match: /(?<=Date\.now\(\)-\i>)\i\.\i/,
|
||||||
|
replace: "$self.getIdleTimeout()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,
|
||||||
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
|
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\i\.default\.dispatch\({type:"IDLE",idle:!1}\)/,
|
match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/,
|
||||||
replace: "$self.handleOnline()"
|
replace: "$self.handleOnline()"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(setInterval\(\i,\.25\*)\i\.IDLE_DURATION/,
|
match: /(setInterval\(\i,\.25\*)\i\.\i/,
|
||||||
replace: "$1$self.getIntervalDelay()" // For web installs
|
replace: "$1$self.getIntervalDelay()" // For web installs
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -69,7 +69,7 @@ async function embedDidMount(this: Component<Props>) {
|
||||||
|
|
||||||
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
|
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
|
||||||
embed.dearrow.oldTitle = embed.rawTitle;
|
embed.dearrow.oldTitle = embed.rawTitle;
|
||||||
embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1");
|
embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
|
if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import "./ui/styles.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { UserStore } from "@webpack/common";
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants";
|
import { CDN_URL, RAW_SKU_ID, SKU_ID } from "./lib/constants";
|
||||||
|
@ -20,7 +19,6 @@ import { settings } from "./settings";
|
||||||
import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components";
|
import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components";
|
||||||
import DecorSection from "./ui/components/DecorSection";
|
import DecorSection from "./ui/components/DecorSection";
|
||||||
|
|
||||||
const { isAnimatedAvatarDecoration } = findByPropsLazy("isAnimatedAvatarDecoration");
|
|
||||||
export interface AvatarDecoration {
|
export interface AvatarDecoration {
|
||||||
asset: string;
|
asset: string;
|
||||||
skuId: string;
|
skuId: string;
|
||||||
|
@ -61,7 +59,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Remove NEW label from decor avatar decorations
|
// Remove NEW label from decor avatar decorations
|
||||||
{
|
{
|
||||||
match: /(?<=\.Section\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/,
|
match: /(?<=\.\i\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/,
|
||||||
replace: "||$1.skuId===$self.SKU_ID"
|
replace: "||$1.skuId===$self.SKU_ID"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -93,7 +91,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
// Use Decor avatar decoration hook
|
// Use Decor avatar decoration hook
|
||||||
{
|
{
|
||||||
match: /(?<=getAvatarDecorationURL\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/,
|
match: /(?<=\i\)\({avatarDecoration:)(\i).avatarDecoration(?=,)/,
|
||||||
replace: "$self.useUserDecorAvatarDecoration($1)??$&"
|
replace: "$self.useUserDecorAvatarDecoration($1)??$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -133,7 +131,7 @@ export default definePlugin({
|
||||||
if (avatarDecoration?.skuId === SKU_ID) {
|
if (avatarDecoration?.skuId === SKU_ID) {
|
||||||
const parts = avatarDecoration.asset.split("_");
|
const parts = avatarDecoration.asset.split("_");
|
||||||
// Remove a_ prefix if it's animated and animation is disabled
|
// Remove a_ prefix if it's animated and animation is disabled
|
||||||
if (isAnimatedAvatarDecoration(avatarDecoration.asset) && !canAnimate) parts.shift();
|
if (avatarDecoration.asset.startsWith("a_") && !canAnimate) parts.shift();
|
||||||
return `${CDN_URL}/${parts.join("_")}.png`;
|
return `${CDN_URL}/${parts.join("_")}.png`;
|
||||||
} else if (avatarDecoration?.skuId === RAW_SKU_ID) {
|
} else if (avatarDecoration?.skuId === RAW_SKU_ID) {
|
||||||
return avatarDecoration.asset;
|
return avatarDecoration.asset;
|
||||||
|
|
|
@ -10,5 +10,5 @@ import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
|
||||||
export const cl = classNameFactory("vc-decor-");
|
export const cl = classNameFactory("vc-decor-");
|
||||||
export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
|
export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
|
||||||
|
|
||||||
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]);
|
export const requireAvatarDecorationModal = extractAndLoadChunksLazy([".COLLECTIBLES_SHOP_FULLSCREEN&&"]);
|
||||||
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);
|
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Link } from "@components/Link";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { filters, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common";
|
import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Text, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
|
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
|
||||||
|
@ -19,7 +19,10 @@ import { AvatarDecorationModalPreview } from "../components";
|
||||||
|
|
||||||
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
|
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
|
||||||
|
|
||||||
const { default: HelpMessage, HelpMessageTypes } = findByPropsLazy("HelpMessageTypes");
|
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', {
|
||||||
|
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"),
|
||||||
|
HelpMessage: filters.byCode(".iconDiv")
|
||||||
|
});
|
||||||
|
|
||||||
function useObjectURL(object: Blob | MediaSource | null) {
|
function useObjectURL(object: Blob | MediaSource | null) {
|
||||||
const [url, setUrl] = useState<string | null>(null);
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,?(?=\i\(this,"idleTimeout",new \i\.Timeout\))/,
|
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
|
||||||
replace: ";return;"
|
replace: ";return;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||||
import { Promisable } from "type-fest";
|
import { Promisable } from "type-fest";
|
||||||
|
|
||||||
const StickersStore = findStoreLazy("StickersStore");
|
const StickersStore = findStoreLazy("StickersStore");
|
||||||
const EmojiManager = findByPropsLazy("fetchEmoji", "uploadEmoji", "deleteEmoji");
|
const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
|
||||||
|
|
||||||
interface Sticker {
|
interface Sticker {
|
||||||
t: "Sticker";
|
t: "Sticker";
|
||||||
|
@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) {
|
||||||
reader.readAsDataURL(data);
|
reader.readAsDataURL(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
return EmojiManager.uploadEmoji({
|
return uploadEmoji({
|
||||||
guildId,
|
guildId,
|
||||||
name: emoji.name.split("~")[0],
|
name: emoji.name.split("~")[0],
|
||||||
image: dataUrl
|
image: dataUrl
|
||||||
|
|
3
src/plugins/experiments/hideBugReport.css
Normal file
3
src/plugins/experiments/hideBugReport.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#staff-help-popout-staff-help-bug-reporter {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -17,22 +17,23 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
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";
|
||||||
import { Forms, React, UserStore } from "@webpack/common";
|
import { Forms, React } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
|
||||||
|
|
||||||
const KbdStyles = findByPropsLazy("key", "removeBuildOverride");
|
import hideBugReport from "./hideBugReport.css?managed";
|
||||||
|
|
||||||
|
const KbdStyles = findByPropsLazy("key", "combo");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
enableIsStaff: {
|
toolbarDevMenu: {
|
||||||
description: "Enable isStaff",
|
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Change the Help (?) toolbar button (top right in chat) to Discord's developer menu",
|
||||||
default: false,
|
default: false,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ const settings = definePluginSettings({
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Experiments",
|
name: "Experiments",
|
||||||
description: "Enable Access to Experiments in Discord!",
|
description: "Enable Access to Experiments & other dev-only features in Discord!",
|
||||||
authors: [
|
authors: [
|
||||||
Devs.Megu,
|
Devs.Megu,
|
||||||
Devs.Ven,
|
Devs.Ven,
|
||||||
|
@ -48,6 +49,7 @@ export default definePlugin({
|
||||||
Devs.BanTheNons,
|
Devs.BanTheNons,
|
||||||
Devs.Nuckyz
|
Devs.Nuckyz
|
||||||
],
|
],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
@ -65,37 +67,35 @@ export default definePlugin({
|
||||||
replace: "$1=!0;"
|
replace: "$1=!0;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: '"isStaff",',
|
|
||||||
predicate: () => settings.store.enableIsStaff,
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /(?<=>)(\i)\.hasFlag\((\i\.\i)\.STAFF\)(?=})/,
|
|
||||||
replace: (_, user, flags) => `$self.isStaff(${user},${flags})`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /hasFreePremium\(\){return this.isStaff\(\)\s*?\|\|/,
|
|
||||||
replace: "hasFreePremium(){return ",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: 'H1,title:"Experiments"',
|
find: 'H1,title:"Experiments"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: 'title:"Experiments",children:[',
|
match: 'title:"Experiments",children:[',
|
||||||
replace: "$&$self.WarningCard(),"
|
replace: "$&$self.WarningCard(),"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// change top right chat toolbar button from the help one to the dev one
|
||||||
|
{
|
||||||
|
find: "toolbar:function",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.isStaff\(\)/,
|
||||||
|
replace: "true"
|
||||||
|
},
|
||||||
|
predicate: () => settings.store.toolbarDevMenu
|
||||||
|
},
|
||||||
|
|
||||||
|
// makes the Favourites Server experiment allow favouriting DMs and threads
|
||||||
|
{
|
||||||
|
find: "useCanFavoriteChannel",
|
||||||
|
replacement: {
|
||||||
|
match: /!\(\i\.isDM\(\)\|\|\i\.isThread\(\)\)/,
|
||||||
|
replace: "true",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
isStaff(user: User, flags: any) {
|
start: () => enableStyle(hideBugReport),
|
||||||
try {
|
stop: () => disableStyle(hideBugReport),
|
||||||
return UserStore.getCurrentUser()?.id === user.id || user.hasFlag(flags.STAFF);
|
|
||||||
} catch (err) {
|
|
||||||
new Logger("Experiments").error(err);
|
|
||||||
return user.hasFlag(flags.STAFF);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsAboutComponent: () => {
|
settingsAboutComponent: () => {
|
||||||
const isMacOS = navigator.platform.includes("Mac");
|
const isMacOS = navigator.platform.includes("Mac");
|
||||||
|
@ -105,14 +105,12 @@ export default definePlugin({
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
|
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
|
||||||
<Forms.FormText variant="text-md/normal">
|
<Forms.FormText variant="text-md/normal">
|
||||||
You can enable client DevTools{" "}
|
You can open Discord's DevTools via {" "}
|
||||||
<kbd className={KbdStyles.key}>{modKey}</kbd> +{" "}
|
<div className={KbdStyles.combo} style={{ display: "inline-flex" }}>
|
||||||
<kbd className={KbdStyles.key}>{altKey}</kbd> +{" "}
|
<kbd className={KbdStyles.key}>{modKey}</kbd> +{" "}
|
||||||
<kbd className={KbdStyles.key}>O</kbd>{" "}
|
<kbd className={KbdStyles.key}>{altKey}</kbd> +{" "}
|
||||||
after enabling <code>isStaff</code> below
|
<kbd className={KbdStyles.key}>O</kbd>{" "}
|
||||||
</Forms.FormText>
|
</div>
|
||||||
<Forms.FormText>
|
|
||||||
and then toggling <code>Enable DevTools</code> in the <code>Developer Options</code> tab in settings.
|
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
@ -128,6 +126,12 @@ export default definePlugin({
|
||||||
|
|
||||||
<Forms.FormText className={Margins.top8}>
|
<Forms.FormText className={Margins.top8}>
|
||||||
Only use experiments if you know what you're doing. Vencord is not responsible for any damage caused by enabling experiments.
|
Only use experiments if you know what you're doing. Vencord is not responsible for any damage caused by enabling experiments.
|
||||||
|
|
||||||
|
If you don't know what an experiment does, ignore it. Do not ask us what experiments do either, we probably don't know.
|
||||||
|
</Forms.FormText>
|
||||||
|
|
||||||
|
<Forms.FormText className={Margins.top8}>
|
||||||
|
No, you cannot use server-side features like checking the "Send to Client" box.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
), { noop: true })
|
), { noop: true })
|
||||||
|
|
|
@ -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";
|
||||||
|
@ -37,8 +37,8 @@ const StickerStore = findStoreLazy("StickersStore") as {
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||||
const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS");
|
|
||||||
const RoleSubscriptionEmojiUtils = findByPropsLazy("isUnusableRoleSubscriptionEmoji");
|
const BINARY_READ_OPTIONS = findByPropsLazy("readerFactory");
|
||||||
|
|
||||||
function searchProtoClassField(localName: string, protoClass: any) {
|
function searchProtoClassField(localName: string, protoClass: any) {
|
||||||
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
|
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
|
||||||
|
@ -52,6 +52,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,
|
||||||
|
@ -236,11 +237,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Allows the usage of subscription-locked emojis
|
// Allows the usage of subscription-locked emojis
|
||||||
{
|
{
|
||||||
find: "isUnusableRoleSubscriptionEmoji:function",
|
find: ".getUserIsAdmin(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /isUnusableRoleSubscriptionEmoji:function/,
|
match: /(function \i\(\i,\i)\){(.{0,250}.getUserIsAdmin\(.+?return!1})/,
|
||||||
// Replace the original export with a func that always returns false and alias the original
|
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
|
||||||
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Allow stickers to be sent everywhere
|
// Allow stickers to be sent everywhere
|
||||||
|
@ -338,7 +338,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Call our function to decide whether the embed should be ignored or not
|
// Call our function to decide whether the embed should be ignored or not
|
||||||
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
||||||
match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/,
|
match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\(\((\i),\i\)?=>{)/,
|
||||||
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
|
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -361,7 +361,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Export the renderable sticker to be used in the fake nitro sticker notice
|
// Export the renderable sticker to be used in the fake nitro sticker notice
|
||||||
match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/,
|
match: /let{renderableSticker:(\i).{0,270}sticker:\i,channel:\i,/,
|
||||||
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
|
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -399,7 +399,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Separate patch for allowing using custom app icons
|
// Separate patch for allowing using custom app icons
|
||||||
{
|
{
|
||||||
find: ".FreemiumAppIconIds.DEFAULT&&(",
|
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
|
@ -472,12 +472,12 @@ export default definePlugin({
|
||||||
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
|
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
|
||||||
if (premiumType === 2 || backgroundGradientPresetId == null) return original();
|
if (premiumType === 2 || backgroundGradientPresetId == null) return original();
|
||||||
|
|
||||||
if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !ProtoUtils) return;
|
if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !BINARY_READ_OPTIONS) return;
|
||||||
|
|
||||||
const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance;
|
const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance;
|
||||||
|
|
||||||
const newAppearanceProto = currentAppearanceSettings != null
|
const newAppearanceProto = currentAppearanceSettings != null
|
||||||
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS)
|
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), BINARY_READ_OPTIONS)
|
||||||
: AppearanceSettingsActionCreators.create();
|
: AppearanceSettingsActionCreators.create();
|
||||||
|
|
||||||
newAppearanceProto.theme = theme;
|
newAppearanceProto.theme = theme;
|
||||||
|
@ -816,8 +816,7 @@ export default definePlugin({
|
||||||
if (e.type === 0) return true;
|
if (e.type === 0) return true;
|
||||||
if (e.available === false) return false;
|
if (e.available === false) return false;
|
||||||
|
|
||||||
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
|
if (isUnusableRoleSubscriptionEmoji(e, this.guildId, true)) return false;
|
||||||
if (isUnusableRoleSubEmoji(e, this.guildId)) return false;
|
|
||||||
|
|
||||||
if (this.canUseEmotes)
|
if (this.canUseEmotes)
|
||||||
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
|
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
|
||||||
|
|
|
@ -111,7 +111,7 @@ interface ProfileModalProps {
|
||||||
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||||
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>('"ProfileCustomizationPreview"');
|
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>('"ProfileCustomizationPreview"');
|
||||||
|
|
||||||
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("(.+?)"\).then\(\i\.bind\(\i,"(.+?)"\)\)/);
|
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "FakeProfileThemes",
|
name: "FakeProfileThemes",
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
find: "MAX_AUTOCOMPLETE_RESULTS+",
|
find: "numLockedEmojiResults:",
|
||||||
replacement: [
|
replacement: [
|
||||||
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
|
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
|
||||||
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
|
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Grzesiek11],
|
authors: [Devs.Grzesiek11],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".default.Messages.DELETED_ROLE_PLACEHOLDER",
|
find: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
|
match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
|
||||||
replace: "$&\\n?",
|
replace: "$&\\n?",
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
authors: [Devs.D3SOX, Devs.Nickyux],
|
authors: [Devs.D3SOX, Devs.Nickyux],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "AVATAR_DECORATION_PADDING:",
|
find: ".PREMIUM_GUILD_SUBSCRIPTION_TOOLTIP",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,isOwner:(\i),/,
|
match: /,isOwner:(\i),/,
|
||||||
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
||||||
|
|
|
@ -16,14 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Constants, RestAPI, UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
const FriendInvites = findByPropsLazy("createFriendInvite");
|
const FriendInvites = findByPropsLazy("createFriendInvite");
|
||||||
const { uuid4 } = findByPropsLazy("uuid4");
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "FriendInvites",
|
name: "FriendInvites",
|
||||||
|
@ -35,47 +33,9 @@ export default definePlugin({
|
||||||
name: "create friend invite",
|
name: "create friend invite",
|
||||||
description: "Generates a friend invite link.",
|
description: "Generates a friend invite link.",
|
||||||
inputType: ApplicationCommandInputType.BOT,
|
inputType: ApplicationCommandInputType.BOT,
|
||||||
options: [{
|
|
||||||
name: "Uses",
|
|
||||||
description: "How many uses?",
|
|
||||||
choices: [
|
|
||||||
{ label: "1", name: "1", value: "1" },
|
|
||||||
{ label: "5", name: "5", value: "5" }
|
|
||||||
],
|
|
||||||
required: false,
|
|
||||||
type: ApplicationCommandOptionType.INTEGER
|
|
||||||
}],
|
|
||||||
|
|
||||||
execute: async (args, ctx) => {
|
execute: async (args, ctx) => {
|
||||||
const uses = findOption<number>(args, "Uses", 5);
|
const invite = await FriendInvites.createFriendInvite();
|
||||||
|
|
||||||
if (uses === 1 && !UserStore.getCurrentUser().phone)
|
|
||||||
return sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "You need to have a phone number connected to your account to create a friend invite with 1 use!"
|
|
||||||
});
|
|
||||||
|
|
||||||
let invite: any;
|
|
||||||
if (uses === 1) {
|
|
||||||
const random = uuid4();
|
|
||||||
const { body: { invite_suggestions } } = await RestAPI.post({
|
|
||||||
url: Constants.Endpoints.FRIEND_FINDER,
|
|
||||||
body: {
|
|
||||||
modified_contacts: {
|
|
||||||
[random]: [1, "", ""]
|
|
||||||
},
|
|
||||||
phone_contact_methods_count: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
invite = await FriendInvites.createFriendInvite({
|
|
||||||
code: invite_suggestions[0][3],
|
|
||||||
recipient_phone_number_or_email: random,
|
|
||||||
contact_visibility: 1,
|
|
||||||
filter_visibilities: [],
|
|
||||||
filtered_invite_suggestions_index: 1
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
invite = await FriendInvites.createFriendInvite();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `
|
content: `
|
||||||
|
|
|
@ -4,21 +4,23 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
|
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
|
||||||
|
|
||||||
const container = findByPropsLazy("memberSinceWrapper");
|
const container = findByPropsLazy("memberSinceWrapper");
|
||||||
const { getCreatedAtDate } = findByPropsLazy("getCreatedAtDate");
|
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
|
||||||
const clydeMoreInfo = findByPropsLazy("clydeMoreInfo");
|
|
||||||
const locale = findByPropsLazy("getLocale");
|
const locale = findByPropsLazy("getLocale");
|
||||||
const lastSection = findByPropsLazy("lastSection");
|
const lastSection = findByPropsLazy("lastSection");
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-friendssince-");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "FriendsSince",
|
name: "FriendsSince",
|
||||||
description: "Shows when you became friends with someone in the user popout",
|
description: "Shows when you became friends with someone in the user popout",
|
||||||
|
@ -26,17 +28,17 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// User popup
|
// User popup
|
||||||
{
|
{
|
||||||
find: ".AnalyticsSections.USER_PROFILE}",
|
find: ".USER_PROFILE}};return",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i.default,\{userId:(\i.id).{0,30}}\)/,
|
match: /,{userId:(\i.id).{0,30}}\)/,
|
||||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// User DMs "User Profile" popup in the right
|
// User DMs "User Profile" popup in the right
|
||||||
{
|
{
|
||||||
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
find: ".PROFILE_PANEL,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i.default,\{userId:([^,]+?)}\)/,
|
match: /,{userId:([^,]+?)}\)/,
|
||||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -69,7 +71,7 @@ export default definePlugin({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={lastSection.section}>
|
<div className={lastSection.section}>
|
||||||
<Heading variant="eyebrow" className={clydeMoreInfo.title}>
|
<Heading variant="eyebrow" className={cl("title")}>
|
||||||
Friends Since
|
Friends Since
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
|
@ -86,7 +88,7 @@ export default definePlugin({
|
||||||
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<Text variant="text-sm/normal" className={classes(clydeMoreInfo.body, textClassName)}>
|
<Text variant="text-sm/normal" className={classes(cl("body"), textClassName)}>
|
||||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
12
src/plugins/friendsSince/styles.css
Normal file
12
src/plugins/friendsSince/styles.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/* copy pasted from discord */
|
||||||
|
|
||||||
|
.vc-friendssince-title {
|
||||||
|
display: flex;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-friendssince-body {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px
|
||||||
|
}
|
|
@ -18,16 +18,18 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
|
import { 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";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { StatusSettingsStores } from "@webpack/common";
|
|
||||||
|
|
||||||
import style from "./style.css?managed";
|
import style from "./style.css?managed";
|
||||||
|
|
||||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||||
|
|
||||||
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
function makeIcon(showCurrentGame?: boolean) {
|
function makeIcon(showCurrentGame?: boolean) {
|
||||||
const { oldIcon } = settings.use(["oldIcon"]);
|
const { oldIcon } = settings.use(["oldIcon"]);
|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ function makeIcon(showCurrentGame?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function GameActivityToggleButton() {
|
function GameActivityToggleButton() {
|
||||||
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting();
|
const showCurrentGame = ShowCurrentGame.useSetting();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
@ -68,7 +70,7 @@ function GameActivityToggleButton() {
|
||||||
icon={makeIcon(showCurrentGame)}
|
icon={makeIcon(showCurrentGame)}
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={!showCurrentGame}
|
aria-checked={!showCurrentGame}
|
||||||
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)}
|
onClick={() => ShowCurrentGame.updateSetting(old => !old)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,6 +87,7 @@ export default definePlugin({
|
||||||
name: "GameActivityToggle",
|
name: "GameActivityToggle",
|
||||||
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||||
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
||||||
|
dependencies: ["UserSettingsAPI"],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[class*="withTagAsButton"] {
|
[class*="panels"] [class*="avatarWrapper"] {
|
||||||
min-width: 88px !important;
|
min-width: 88px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,7 @@
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { insertTextIntoChatInputBox } from "@utils/discord";
|
import { insertTextIntoChatInputBox } from "@utils/discord";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { ExpressionPickerStore } from "@webpack/common";
|
||||||
|
|
||||||
const { closeExpressionPicker } = findByPropsLazy("closeExpressionPicker");
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "GifPaste",
|
name: "GifPaste",
|
||||||
|
@ -39,7 +37,7 @@ export default definePlugin({
|
||||||
handleSelect(gif?: { url: string; }) {
|
handleSelect(gif?: { url: string; }) {
|
||||||
if (gif) {
|
if (gif) {
|
||||||
insertTextIntoChatInputBox(gif.url + " ");
|
insertTextIntoChatInputBox(gif.url + " ");
|
||||||
closeExpressionPicker();
|
ExpressionPickerStore.closeExpressionPicker();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common";
|
import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ const settings = definePluginSettings({
|
||||||
unholyMultiGreetEnabled?: boolean;
|
unholyMultiGreetEnabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { WELCOME_STICKERS } = findByPropsLazy("WELCOME_STICKERS");
|
const WELCOME_STICKERS = findLazy(m => Array.isArray(m) && m[0]?.name === "Wave");
|
||||||
|
|
||||||
function greet(channel: Channel, message: Message, stickers: string[]) {
|
function greet(channel: Channel, message: Message, stickers: string[]) {
|
||||||
const options = MessageActions.getSendMessageOptionsForReply({
|
const options = MessageActions.getSendMessageOptionsForReply({
|
||||||
|
|
|
@ -6,13 +6,14 @@
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
|
import { 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";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { Button, Forms, showToast, StatusSettingsStores, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
|
import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
|
||||||
|
|
||||||
const enum ActivitiesTypes {
|
const enum ActivitiesTypes {
|
||||||
Game,
|
Game,
|
||||||
|
@ -27,6 +28,8 @@ interface IgnoredActivity {
|
||||||
|
|
||||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||||
|
|
||||||
|
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 (
|
||||||
<Tooltip text={tooltipText}>
|
<Tooltip text={tooltipText}>
|
||||||
|
@ -68,7 +71,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
||||||
|
|
||||||
// Trigger activities recalculation
|
// Trigger activities recalculation
|
||||||
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
|
ShowCurrentGame.updateSetting(old => old);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ImportCustomRPCComponent() {
|
function ImportCustomRPCComponent() {
|
||||||
|
@ -205,6 +208,7 @@ export default definePlugin({
|
||||||
name: "IgnoreActivities",
|
name: "IgnoreActivities",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
|
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
|
||||||
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "unknownUserMentionPlaceholder:",
|
find: "unknownUserMentionPlaceholder:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(0,\i\.isEmbedInline\)\(\i\)/,
|
// SimpleEmbedTypes.has(embed.type) && isEmbedInline(embed)
|
||||||
|
match: /\i\.has\(\i\.type\)&&\(0,\i\.\i\)\(\i\)/,
|
||||||
replace: "false",
|
replace: "false",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,17 +19,11 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { ChannelStore, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common";
|
import { ChannelStore, Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common";
|
||||||
import { Settings } from "Vencord";
|
import { Settings } from "Vencord";
|
||||||
|
|
||||||
const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore");
|
const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore");
|
||||||
const { FriendsSections } = findByPropsLazy("FriendsSections");
|
|
||||||
|
|
||||||
interface UserAffinity {
|
|
||||||
user_id: string;
|
|
||||||
affinity: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ImplicitRelationships",
|
name: "ImplicitRelationships",
|
||||||
|
@ -70,7 +64,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Piggyback relationship fetch
|
// Piggyback relationship fetch
|
||||||
{
|
{
|
||||||
find: ".fetchRelationships()",
|
find: '"FriendsStore',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i\.\i)\.fetchRelationships\(\)/,
|
match: /(\i\.\i)\.fetchRelationships\(\)/,
|
||||||
// This relationship fetch is actually completely useless, but whatevs
|
// This relationship fetch is actually completely useless, but whatevs
|
||||||
|
@ -182,6 +176,6 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
FriendsSections.IMPLICIT = "IMPLICIT";
|
Constants.FriendsSections.IMPLICIT = "IMPLICIT";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,7 +44,6 @@ const settings = Settings.plugins;
|
||||||
|
|
||||||
export function isPluginEnabled(p: string) {
|
export function isPluginEnabled(p: string) {
|
||||||
return (
|
return (
|
||||||
IS_REPORTER ||
|
|
||||||
Plugins[p]?.required ||
|
Plugins[p]?.required ||
|
||||||
Plugins[p]?.isDependency ||
|
Plugins[p]?.isDependency ||
|
||||||
settings[p]?.enabled
|
settings[p]?.enabled
|
||||||
|
@ -170,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,7 +206,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||||
const { name, commands, flux, contextMenus } = p;
|
const { name, commands, contextMenus } = p;
|
||||||
|
|
||||||
if (p.start) {
|
if (p.start) {
|
||||||
logger.info("Starting plugin", name);
|
logger.info("Starting plugin", name);
|
||||||
|
@ -242,7 +252,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
||||||
}, p => `startPlugin ${p.name}`);
|
}, p => `startPlugin ${p.name}`);
|
||||||
|
|
||||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||||
const { name, commands, flux, contextMenus } = p;
|
const { name, commands, contextMenus } = p;
|
||||||
|
|
||||||
if (p.stop) {
|
if (p.stop) {
|
||||||
logger.info("Stopping plugin", name);
|
logger.info("Stopping plugin", name);
|
||||||
|
|
|
@ -10,19 +10,21 @@ import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
const linkRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
const linkRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
||||||
|
|
||||||
const { SlateTransforms } = findByPropsLazy("SlateTransforms");
|
const SlateTransforms = findByPropsLazy("insertText", "selectCommandOption");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MaskedLinkPaste",
|
name: "MaskedLinkPaste",
|
||||||
authors: [Devs.TheSun],
|
authors: [Devs.TheSun],
|
||||||
description: "Pasting a link while having text selected will paste a hyperlink",
|
description: "Pasting a link while having text selected will paste a hyperlink",
|
||||||
patches: [{
|
patches: [
|
||||||
find: ".selection,preventEmojiSurrogates:",
|
{
|
||||||
replacement: {
|
find: ".selection,preventEmojiSurrogates:",
|
||||||
match: /(?<=SlateTransforms.delete.{0,50})(\i)\.insertText\((\i)\)/,
|
replacement: {
|
||||||
replace: "$self.handlePaste($1, $2, () => $&)"
|
match: /(?<=\i.delete.{0,50})(\i)\.insertText\((\i)\)/,
|
||||||
|
replace: "$self.handlePaste($1, $2, () => $&)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}],
|
],
|
||||||
|
|
||||||
handlePaste(editor, content: string, originalBehavior: () => void) {
|
handlePaste(editor, content: string, originalBehavior: () => void) {
|
||||||
if (content && linkRegex.test(content) && editor.operations?.[0]?.type === "remove_text") {
|
if (content && linkRegex.test(content) && editor.operations?.[0]?.type === "remove_text") {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { 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";
|
||||||
|
@ -37,7 +38,6 @@ import {
|
||||||
PermissionStore,
|
PermissionStore,
|
||||||
RestAPI,
|
RestAPI,
|
||||||
Text,
|
Text,
|
||||||
TextAndImagesSettingsStores,
|
|
||||||
UserStore
|
UserStore
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
@ -49,11 +49,13 @@ const messageCache = new Map<string, {
|
||||||
|
|
||||||
const Embed = findComponentByCodeLazy(".inlineMediaEmbed");
|
const Embed = findComponentByCodeLazy(".inlineMediaEmbed");
|
||||||
const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:");
|
const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageContent:");
|
||||||
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
|
const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hideAccessories");
|
||||||
|
|
||||||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||||
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
||||||
|
|
||||||
|
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\//;
|
||||||
|
|
||||||
|
@ -281,6 +283,8 @@ function getChannelLabelAndIconUrl(channel: Channel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
|
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
|
||||||
|
const compact = MessageDisplayCompact.useSetting();
|
||||||
|
|
||||||
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
||||||
|
|
||||||
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
||||||
|
@ -305,6 +309,7 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
|
||||||
message={message}
|
message={message}
|
||||||
channel={channel}
|
channel={channel}
|
||||||
subscribeToComponentDispatch={false}
|
subscribeToComponentDispatch={false}
|
||||||
|
compact={compact}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -314,7 +319,7 @@ function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps):
|
||||||
|
|
||||||
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
const { message, channel } = props;
|
const { message, channel } = props;
|
||||||
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
const compact = MessageDisplayCompact.useSetting();
|
||||||
const images = getImages(message);
|
const images = getImages(message);
|
||||||
const { parse } = Parser;
|
const { parse } = Parser;
|
||||||
|
|
||||||
|
@ -361,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"],
|
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
|
|
||||||
import "./messageLogger.css";
|
import "./messageLogger.css";
|
||||||
|
|
||||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -26,11 +27,17 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
|
||||||
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
||||||
import textStyle from "./deleteStyleText.css?managed";
|
import textStyle from "./deleteStyleText.css?managed";
|
||||||
|
|
||||||
|
interface MLMessage extends Message {
|
||||||
|
deleted?: boolean;
|
||||||
|
editHistory?: { timestamp: Date; content: string; }[];
|
||||||
|
}
|
||||||
|
|
||||||
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
|
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
|
||||||
|
|
||||||
function addDeleteStyle() {
|
function addDeleteStyle() {
|
||||||
|
@ -89,35 +96,77 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const patchChannelContextMenu: NavContextMenuPatchCallback = (children, { channel }) => {
|
||||||
|
const messages = MessageStore.getMessages(channel?.id) as MLMessage[];
|
||||||
|
if (!messages?.some(msg => msg.deleted || msg.editHistory?.length)) return;
|
||||||
|
|
||||||
|
const group = findGroupChildrenByChildId("mark-channel-read", children) ?? children;
|
||||||
|
group.push(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-ml-clear-channel"
|
||||||
|
label="Clear Message Log"
|
||||||
|
color="danger"
|
||||||
|
action={() => {
|
||||||
|
messages.forEach(msg => {
|
||||||
|
if (msg.deleted)
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "MESSAGE_DELETE",
|
||||||
|
channelId: channel.id,
|
||||||
|
id: msg.id,
|
||||||
|
mlDeleted: true
|
||||||
|
});
|
||||||
|
else
|
||||||
|
updateMessage(channel.id, msg.id, {
|
||||||
|
editHistory: []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageLogger",
|
name: "MessageLogger",
|
||||||
description: "Temporarily logs deleted and edited messages.",
|
description: "Temporarily logs deleted and edited messages.",
|
||||||
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN],
|
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux],
|
||||||
|
dependencies: ["MessageUpdaterAPI"],
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"message": patchMessageContextMenu
|
"message": patchMessageContextMenu,
|
||||||
|
"channel-context": patchChannelContextMenu,
|
||||||
|
"user-context": patchChannelContextMenu,
|
||||||
|
"gdm-context": patchChannelContextMenu
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addDeleteStyle();
|
addDeleteStyle();
|
||||||
},
|
},
|
||||||
|
|
||||||
renderEdit(edit: { timestamp: any, content: string; }) {
|
renderEdits: ErrorBoundary.wrap(({ message: { id: messageId, channel_id: channelId } }: { message: Message; }) => {
|
||||||
return (
|
const message = useStateFromStores(
|
||||||
<ErrorBoundary noop>
|
[MessageStore],
|
||||||
<div className="messagelogger-edited">
|
() => MessageStore.getMessage(channelId, messageId) as MLMessage,
|
||||||
{Parser.parse(edit.content)}
|
null,
|
||||||
<Timestamp
|
(oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory
|
||||||
timestamp={edit.timestamp}
|
|
||||||
isEdited={true}
|
|
||||||
isInline={false}
|
|
||||||
>
|
|
||||||
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
|
|
||||||
</Timestamp>
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
);
|
||||||
},
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{message.editHistory?.map(edit => (
|
||||||
|
<div className="messagelogger-edited">
|
||||||
|
{Parser.parse(edit.content)}
|
||||||
|
<Timestamp
|
||||||
|
timestamp={edit.timestamp}
|
||||||
|
isEdited={true}
|
||||||
|
isInline={false}
|
||||||
|
>
|
||||||
|
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
|
||||||
|
</Timestamp>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, { noop: true }),
|
||||||
|
|
||||||
makeEdit(newMessage: any, oldMessage: any): any {
|
makeEdit(newMessage: any, oldMessage: any): any {
|
||||||
return {
|
return {
|
||||||
|
@ -222,11 +271,9 @@ export default definePlugin({
|
||||||
(message.channel_id === "1026515880080842772" && message.author?.id === "1017176847865352332");
|
(message.channel_id === "1026515880080842772" && message.author?.id === "1017176847865352332");
|
||||||
},
|
},
|
||||||
|
|
||||||
// Based on canary 63b8f1b4f2025213c5cf62f0966625bee3d53136
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// MessageStore
|
// MessageStore
|
||||||
// Module 171447
|
|
||||||
find: '"MessageStore"',
|
find: '"MessageStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
@ -255,7 +302,7 @@ export default definePlugin({
|
||||||
replace: "$1" +
|
replace: "$1" +
|
||||||
".update($3,m =>" +
|
".update($3,m =>" +
|
||||||
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" +
|
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" +
|
||||||
" $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" +
|
" $2.message.edited_timestamp && $2.message.content !== m.content ?" +
|
||||||
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
|
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
|
||||||
" m" +
|
" m" +
|
||||||
")" +
|
")" +
|
||||||
|
@ -271,7 +318,6 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Message domain model
|
// Message domain model
|
||||||
// Module 451
|
|
||||||
find: "}addReaction(",
|
find: "}addReaction(",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
@ -285,14 +331,8 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Updated message transformer(?)
|
// Updated message transformer(?)
|
||||||
// Module 819525
|
|
||||||
find: "THREAD_STARTER_MESSAGE?null===",
|
find: "THREAD_STARTER_MESSAGE?null===",
|
||||||
replacement: [
|
replacement: [
|
||||||
// {
|
|
||||||
// // DEBUG: Log the params of the target function to the patch below
|
|
||||||
// match: /function N\(e,t\){/,
|
|
||||||
// replace: "function L(e,t){console.log('pre-transform', e, t);"
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
||||||
match: /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/,
|
match: /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/,
|
||||||
|
@ -300,11 +340,6 @@ export default definePlugin({
|
||||||
"Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, attachments:$1.attachments })"
|
"Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, attachments:$1.attachments })"
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
|
||||||
// // DEBUG: Log the params of the target function to the patch below
|
|
||||||
// match: /function R\(e\){/,
|
|
||||||
// replace: "function R(e){console.log('after-edit-transform', arguments);"
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
// Construct new edited message and add editHistory & deleted (ref above)
|
// Construct new edited message and add editHistory & deleted (ref above)
|
||||||
// Pass in custom data to attachment parser to mark attachments deleted as well
|
// Pass in custom data to attachment parser to mark attachments deleted as well
|
||||||
|
@ -335,7 +370,6 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Attachment renderer
|
// Attachment renderer
|
||||||
// Module 96063
|
|
||||||
find: ".removeMosaicItemHoverButton",
|
find: ".removeMosaicItemHoverButton",
|
||||||
group: true,
|
group: true,
|
||||||
replacement: [
|
replacement: [
|
||||||
|
@ -352,7 +386,6 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Base message component renderer
|
// Base message component renderer
|
||||||
// Module 748241
|
|
||||||
find: "Message must not be a thread starter message",
|
find: "Message must not be a thread starter message",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
@ -365,20 +398,18 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Message content renderer
|
// Message content renderer
|
||||||
// Module 43016
|
|
||||||
find: "Messages.MESSAGE_EDITED,\")\"",
|
find: "Messages.MESSAGE_EDITED,\")\"",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Render editHistory in the deepest div for message content
|
// Render editHistory in the deepest div for message content
|
||||||
match: /(\)\("div",\{id:.+?children:\[)/,
|
match: /(\)\("div",\{id:.+?children:\[)/,
|
||||||
replace: "$1 (arguments[0].message.editHistory?.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
|
replace: "$1 (!!arguments[0].message.editHistory?.length && $self.renderEdits(arguments[0])),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
// ReferencedMessageStore
|
// ReferencedMessageStore
|
||||||
// Module 778667
|
|
||||||
find: '"ReferencedMessageStore"',
|
find: '"ReferencedMessageStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
@ -394,7 +425,6 @@ export default definePlugin({
|
||||||
|
|
||||||
{
|
{
|
||||||
// Message context base menu
|
// Message context base menu
|
||||||
// Module 600300
|
|
||||||
find: "useMessageMenu:",
|
find: "useMessageMenu:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
@ -404,18 +434,5 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
|
||||||
// // MessageStore caching internals
|
|
||||||
// // Module 819525
|
|
||||||
// find: "e.getOrCreate=function(t)",
|
|
||||||
// replacement: [
|
|
||||||
// // {
|
|
||||||
// // // DEBUG: log getOrCreate return values from MessageStore caching internals
|
|
||||||
// // match: /getOrCreate=function(.+?)return/,
|
|
||||||
// // replace: "getOrCreate=function$1console.log('getOrCreate',n);return"
|
|
||||||
// // }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,12 +21,10 @@ import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByCodeLazy, findLazy } from "@webpack";
|
||||||
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
||||||
import { RC } from "@webpack/types";
|
import type { Permissions, RC } from "@webpack/types";
|
||||||
import { Channel, Message, User } from "discord-types/general";
|
import type { Channel, Guild, Message, User } from "discord-types/general";
|
||||||
|
|
||||||
type PermissionName = "CREATE_INSTANT_INVITE" | "KICK_MEMBERS" | "BAN_MEMBERS" | "ADMINISTRATOR" | "MANAGE_CHANNELS" | "MANAGE_GUILD" | "CHANGE_NICKNAME" | "MANAGE_NICKNAMES" | "MANAGE_ROLES" | "MANAGE_WEBHOOKS" | "MANAGE_GUILD_EXPRESSIONS" | "CREATE_GUILD_EXPRESSIONS" | "VIEW_AUDIT_LOG" | "VIEW_CHANNEL" | "VIEW_GUILD_ANALYTICS" | "VIEW_CREATOR_MONETIZATION_ANALYTICS" | "MODERATE_MEMBERS" | "SEND_MESSAGES" | "SEND_TTS_MESSAGES" | "MANAGE_MESSAGES" | "EMBED_LINKS" | "ATTACH_FILES" | "READ_MESSAGE_HISTORY" | "MENTION_EVERYONE" | "USE_EXTERNAL_EMOJIS" | "ADD_REACTIONS" | "USE_APPLICATION_COMMANDS" | "MANAGE_THREADS" | "CREATE_PUBLIC_THREADS" | "CREATE_PRIVATE_THREADS" | "USE_EXTERNAL_STICKERS" | "SEND_MESSAGES_IN_THREADS" | "CONNECT" | "SPEAK" | "MUTE_MEMBERS" | "DEAFEN_MEMBERS" | "MOVE_MEMBERS" | "USE_VAD" | "PRIORITY_SPEAKER" | "STREAM" | "USE_EMBEDDED_ACTIVITIES" | "USE_SOUNDBOARD" | "USE_EXTERNAL_SOUNDS" | "REQUEST_TO_SPEAK" | "MANAGE_EVENTS" | "CREATE_EVENTS";
|
|
||||||
|
|
||||||
interface Tag {
|
interface Tag {
|
||||||
// name used for identifying, must be alphanumeric + underscores
|
// name used for identifying, must be alphanumeric + underscores
|
||||||
|
@ -34,7 +32,7 @@ interface Tag {
|
||||||
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
||||||
displayName: string;
|
displayName: string;
|
||||||
description: string;
|
description: string;
|
||||||
permissions?: PermissionName[];
|
permissions?: Permissions[];
|
||||||
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +52,14 @@ interface TagSettings {
|
||||||
[k: string]: TagSetting;
|
[k: string]: TagSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PermissionStore.computePermissions is not the same function and doesn't work here
|
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
|
||||||
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
const computePermissions: (options: {
|
||||||
computePermissions({ ...args }): bigint;
|
user?: { id: string; } | string | null;
|
||||||
};
|
context?: Guild | Channel | null;
|
||||||
|
overwrites?: Channel["permissionOverwrites"] | null;
|
||||||
|
checkElevated?: boolean /* = true */;
|
||||||
|
excludeGuildPermissions?: boolean /* = false */;
|
||||||
|
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
|
||||||
|
|
||||||
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
||||||
|
|
||||||
|
@ -193,14 +195,14 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// add tags to the tag list
|
// add tags to the tag list
|
||||||
{
|
{
|
||||||
find: "BotTagTypes:",
|
find: ".ORIGINAL_POSTER=",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
|
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
|
||||||
replace: "($1=$self.getTagTypes()))[$2.BOT"
|
replace: "($1=$self.getTagTypes()))[$2.BOT"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP,",
|
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL,",
|
||||||
replacement: [
|
replacement: [
|
||||||
// make the tag show the right text
|
// make the tag show the right text
|
||||||
{
|
{
|
||||||
|
@ -222,7 +224,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// in messages
|
// in messages
|
||||||
{
|
{
|
||||||
find: "renderSystemTag:",
|
find: ".Types.ORIGINAL_POSTER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
||||||
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
||||||
|
@ -283,7 +285,7 @@ export default definePlugin({
|
||||||
const guild = GuildStore.getGuild(channel?.guild_id);
|
const guild = GuildStore.getGuild(channel?.guild_id);
|
||||||
if (!guild) return [];
|
if (!guild) return [];
|
||||||
|
|
||||||
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||||
return Object.entries(PermissionsBits)
|
return Object.entries(PermissionsBits)
|
||||||
.map(([perm, permInt]) =>
|
.map(([perm, permInt]) =>
|
||||||
permissions & permInt ? perm : ""
|
permissions & permInt ? perm : ""
|
||||||
|
@ -330,7 +332,7 @@ export default definePlugin({
|
||||||
}: {
|
}: {
|
||||||
message?: Message,
|
message?: Message,
|
||||||
user: User & { isClyde(): boolean; },
|
user: User & { isClyde(): boolean; },
|
||||||
channel?: Channel & { isForumPost(): boolean; },
|
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
origType?: number;
|
origType?: number;
|
||||||
location: "chat" | "not-chat";
|
location: "chat" | "not-chat";
|
||||||
|
@ -367,7 +369,7 @@ export default definePlugin({
|
||||||
tag.permissions?.some(perm => perms.includes(perm)) ||
|
tag.permissions?.some(perm => perms.includes(perm)) ||
|
||||||
(tag.condition?.(message!, user, channel))
|
(tag.condition?.(message!, user, channel))
|
||||||
) {
|
) {
|
||||||
if (channel.isForumPost() && channel.ownerId === user.id)
|
if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id)
|
||||||
type = Tag.Types[`${tag.name}-OP`];
|
type = Tag.Types[`${tag.name}-OP`];
|
||||||
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
|
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
|
||||||
type = Tag.Types[`${tag.name}-BOT`];
|
type = Tag.Types[`${tag.name}-BOT`];
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".UserProfileSections.USER_INFO_CONNECTIONS:",
|
find: ".USER_INFO_CONNECTIONS:case",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
||||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"
|
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"
|
||||||
|
|
|
@ -19,11 +19,16 @@
|
||||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
|
|
||||||
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
|
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
|
||||||
const { toggleShowAllChannels } = findByPropsLazy("toggleShowAllChannels");
|
const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", {
|
||||||
const { isOptInEnabledForGuild } = findByPropsLazy("isOptInEnabledForGuild");
|
toggleShowAllChannels: m => {
|
||||||
|
const s = String(m);
|
||||||
|
return s.length < 100 && !s.includes("onboardExistingMember") && !s.includes("getOptedInChannels");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const isOptInEnabledForGuild = findByCodeLazy(".COMMUNITY)||", ".isOptInEnabled(");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
guild: {
|
guild: {
|
||||||
|
|
|
@ -18,8 +18,10 @@
|
||||||
|
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked");
|
const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked");
|
||||||
|
|
||||||
|
@ -59,6 +61,12 @@ export default definePlugin({
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
isBlocked: message =>
|
|
||||||
RelationshipStore.isBlocked(message.author.id)
|
isBlocked(message: Message) {
|
||||||
|
try {
|
||||||
|
return RelationshipStore.isBlocked(message.author.id);
|
||||||
|
} catch (e) {
|
||||||
|
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "HangStatusTypes.CHILLING)",
|
find: ".CHILLING)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /{enableHangStatus:(\i),/,
|
match: /{enableHangStatus:(\i),/,
|
||||||
replace: "{_enableHangStatus:$1=false,"
|
replace: "{_enableHangStatus:$1=false,"
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "isGroupableMedia:function()",
|
find: '=>"IMAGE"===',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/,
|
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/,
|
||||||
replace: "=>false;"
|
replace: "=>false;"
|
||||||
|
|
35
src/plugins/noOnboardingDelay/index.ts
Normal file
35
src/plugins/noOnboardingDelay/index.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NoOnboardingDelay",
|
||||||
|
description: "Skips the slow and annoying onboarding delay",
|
||||||
|
authors: [Devs.nekohaxx],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "Messages.ONBOARDING_COVER_WELCOME_SUBTITLE",
|
||||||
|
replacement: {
|
||||||
|
match: "3e3",
|
||||||
|
replace: "0"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -62,6 +62,16 @@ export default definePlugin({
|
||||||
replace: "return 0;"
|
replace: "return 0;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// New message requests hook
|
||||||
|
{
|
||||||
|
find: 'location:"use-message-requests-count"',
|
||||||
|
predicate: () => settings.store.hideMessageRequestsCount,
|
||||||
|
replacement: {
|
||||||
|
match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /,
|
||||||
|
replace: "$&0;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Old message requests hook
|
||||||
{
|
{
|
||||||
find: "getMessageRequestsCount(){",
|
find: "getMessageRequestsCount(){",
|
||||||
predicate: () => settings.store.hideMessageRequestsCount,
|
predicate: () => settings.store.hideMessageRequestsCount,
|
||||||
|
|
|
@ -63,8 +63,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "trackAnnouncementMessageLinkClicked({",
|
find: "trackAnnouncementMessageLinkClicked({",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/,
|
match: /function (\i\(\i,\i\)\{)(?=.{0,100}trusted:)/,
|
||||||
replace: "async $& if(await $self.handleLink(...arguments)) return;"
|
replace: "async function $1 if(await $self.handleLink(...arguments)) return;"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Make Spotify profile activity links open in app on web
|
// Make Spotify profile activity links open in app on web
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||||
import { FluxDispatcher } from "@webpack/common";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
|
|
||||||
const enum Intensity {
|
const enum Intensity {
|
||||||
|
@ -46,6 +46,7 @@ export default definePlugin({
|
||||||
name: "PartyMode",
|
name: "PartyMode",
|
||||||
description: "Allows you to use party mode cause the party never ends ✨",
|
description: "Allows you to use party mode cause the party never ends ✨",
|
||||||
authors: [Devs.UwUDev],
|
authors: [Devs.UwUDev],
|
||||||
|
reporterTestable: ReporterTestable.None,
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
|
|
@ -19,10 +19,10 @@
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common";
|
import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common";
|
||||||
|
|
||||||
const { InvitesDisabledExperiment } = findByPropsLazy("InvitesDisabledExperiment");
|
const InvitesDisabledExperiment = findLazy(m => m.definition?.id === "2022-07_invites_disabled");
|
||||||
|
|
||||||
function showDisableInvites(guildId: string) {
|
function showDisableInvites(guildId: string) {
|
||||||
// Once the experiment is removed, this should keep working
|
// Once the experiment is removed, this should keep working
|
||||||
|
@ -56,8 +56,8 @@ export default definePlugin({
|
||||||
replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})",
|
replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/,
|
match: /\.INVITES_DISABLED\)(?=.+?\.Messages\.INVITES_PERMANENTLY_DISABLED_TIP.+?checked:(\i)).+?\[\1,(\i)\]=\i.useState\(\i\)/,
|
||||||
replace: "$1,setChecked=$2"
|
replace: "$&,setChecked=$2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
|
@ -31,7 +32,7 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// Permission lockout, just set the check to true
|
// Permission lockout, just set the check to true
|
||||||
{
|
{
|
||||||
find: ".showPermissionLockoutModal(",
|
find: ".STAGE_CHANNEL_CANNOT_OVERWRITE_PERMISSION",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
|
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
|
||||||
|
@ -45,9 +46,8 @@ export default definePlugin({
|
||||||
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
|
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// are we java yet?
|
match: /{(\i:function\(\){return \i},?){2}}/,
|
||||||
match: /(?<=(?:isDefaultChannelThresholdMetAfterDelete|checkDefaultChannelThresholdMetAfterChannelPermissionDeny):function\(\)\{)return \i(?=\})/g,
|
replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)")
|
||||||
replace: "return () => true"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.onboarding
|
predicate: () => settings.store.onboarding
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
|
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
|
||||||
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
|
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack";
|
||||||
import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common";
|
import { Button, Forms, Text, TextInput, Toasts, useEffect, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { DEFAULT_COLOR, SWATCHES } from "../constants";
|
import { DEFAULT_COLOR, SWATCHES } from "../constants";
|
||||||
|
@ -31,9 +31,9 @@ interface ColorPickerWithSwatchesProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||||
const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>("presets,", "customColor:");
|
const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker");
|
||||||
|
|
||||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\).{0,50}"UserSettings"/);
|
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\("?.+?"?\).+?\])\).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
||||||
|
|
||||||
const cl = classNameFactory("vc-pindms-modal-");
|
const cl = classNameFactory("vc-pindms-modal-");
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ export default definePlugin({
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
{
|
{
|
||||||
match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.default),\{channel:.+?)/,
|
match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.\i),\{channel:.+?)/,
|
||||||
replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();"
|
replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -131,7 +131,7 @@ export default definePlugin({
|
||||||
|
|
||||||
// Fix Alt Up/Down navigation
|
// Fix Alt Up/Down navigation
|
||||||
{
|
{
|
||||||
find: ".Routes.APPLICATION_STORE&&",
|
find: ".APPLICATION_STORE&&",
|
||||||
replacement: {
|
replacement: {
|
||||||
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
|
// channelIds = __OVERLAY__ ? stuff : [...getStaticPaths(),...channelIds)]
|
||||||
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,
|
match: /(?<=\i=__OVERLAY__\?\i:\[\.\.\.\i\(\),\.\.\.)\i/,
|
||||||
|
|
|
@ -51,14 +51,17 @@ const Icons = {
|
||||||
desktop: Icon("M4 2.5c-1.103 0-2 .897-2 2v11c0 1.104.897 2 2 2h7v2H7v2h10v-2h-4v-2h7c1.103 0 2-.896 2-2v-11c0-1.103-.897-2-2-2H4Zm16 2v9H4v-9h16Z"),
|
desktop: Icon("M4 2.5c-1.103 0-2 .897-2 2v11c0 1.104.897 2 2 2h7v2H7v2h10v-2h-4v-2h7c1.103 0 2-.896 2-2v-11c0-1.103-.897-2-2-2H4Zm16 2v9H4v-9h16Z"),
|
||||||
web: Icon("M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93Zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39Z"),
|
web: Icon("M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93Zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39Z"),
|
||||||
mobile: Icon("M 187 0 L 813 0 C 916.277 0 1000 83.723 1000 187 L 1000 1313 C 1000 1416.277 916.277 1500 813 1500 L 187 1500 C 83.723 1500 0 1416.277 0 1313 L 0 187 C 0 83.723 83.723 0 187 0 Z M 125 1000 L 875 1000 L 875 250 L 125 250 Z M 500 1125 C 430.964 1125 375 1180.964 375 1250 C 375 1319.036 430.964 1375 500 1375 C 569.036 1375 625 1319.036 625 1250 C 625 1180.964 569.036 1125 500 1125 Z", { viewBox: "0 0 1000 1500", height: 17, width: 17 }),
|
mobile: Icon("M 187 0 L 813 0 C 916.277 0 1000 83.723 1000 187 L 1000 1313 C 1000 1416.277 916.277 1500 813 1500 L 187 1500 C 83.723 1500 0 1416.277 0 1313 L 0 187 C 0 83.723 83.723 0 187 0 Z M 125 1000 L 875 1000 L 875 250 L 125 250 Z M 500 1125 C 430.964 1125 375 1180.964 375 1250 C 375 1319.036 430.964 1375 500 1375 C 569.036 1375 625 1319.036 625 1250 C 625 1180.964 569.036 1125 500 1125 Z", { viewBox: "0 0 1000 1500", height: 17, width: 17 }),
|
||||||
console: Icon("M14.8 2.7 9 3.1V47h3.3c1.7 0 6.2.3 10 .7l6.7.6V2l-4.2.2c-2.4.1-6.9.3-10 .5zm1.8 6.4c1 1.7-1.3 3.6-2.7 2.2C12.7 10.1 13.5 8 15 8c.5 0 1.2.5 1.6 1.1zM16 33c0 6-.4 10-1 10s-1-4-1-10 .4-10 1-10 1 4 1 10zm15-8v23.3l3.8-.7c2-.3 4.7-.6 6-.6H43V3h-2.2c-1.3 0-4-.3-6-.6L31 1.7V25z", { viewBox: "0 0 50 50" }),
|
embedded: Icon("M14.8 2.7 9 3.1V47h3.3c1.7 0 6.2.3 10 .7l6.7.6V2l-4.2.2c-2.4.1-6.9.3-10 .5zm1.8 6.4c1 1.7-1.3 3.6-2.7 2.2C12.7 10.1 13.5 8 15 8c.5 0 1.2.5 1.6 1.1zM16 33c0 6-.4 10-1 10s-1-4-1-10 .4-10 1-10 1 4 1 10zm15-8v23.3l3.8-.7c2-.3 4.7-.6 6-.6H43V3h-2.2c-1.3 0-4-.3-6-.6L31 1.7V25z", { viewBox: "0 0 50 50" }),
|
||||||
};
|
};
|
||||||
type Platform = keyof typeof Icons;
|
type Platform = keyof typeof Icons;
|
||||||
|
|
||||||
const StatusUtils = findByPropsLazy("useStatusFillColor", "StatusTypes");
|
const StatusUtils = findByPropsLazy("useStatusFillColor", "StatusTypes");
|
||||||
|
|
||||||
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
||||||
const tooltip = platform[0].toUpperCase() + platform.slice(1);
|
const tooltip = platform === "embedded"
|
||||||
|
? "Console"
|
||||||
|
: platform[0].toUpperCase() + platform.slice(1);
|
||||||
|
|
||||||
const Icon = Icons[platform] ?? Icons.desktop;
|
const Icon = Icons[platform] ?? Icons.desktop;
|
||||||
|
|
||||||
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />;
|
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />;
|
||||||
|
@ -127,9 +130,9 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
|
||||||
};
|
};
|
||||||
|
|
||||||
const badge: ProfileBadge = {
|
const badge: ProfileBadge = {
|
||||||
component: p => <PlatformIndicator {...p} wantMargin={false} />,
|
component: p => <PlatformIndicator {...p} user={UserStore.getUser(p.userId)} wantMargin={false} />,
|
||||||
position: BadgePosition.START,
|
position: BadgePosition.START,
|
||||||
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length,
|
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.userId) ?? {}).length,
|
||||||
key: "indicator"
|
key: "indicator"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { UserUtils } from "@webpack/common";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types";
|
import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types";
|
||||||
import { deleteGroup, deleteGuild, getGroup, getGuild, notify } from "./utils";
|
import { deleteGroup, deleteGuild, getGroup, getGuild, GuildAvailabilityStore, notify } from "./utils";
|
||||||
|
|
||||||
let manuallyRemovedFriend: string | undefined;
|
let manuallyRemovedFriend: string | undefined;
|
||||||
let manuallyRemovedGuild: string | undefined;
|
let manuallyRemovedGuild: string | undefined;
|
||||||
|
@ -63,7 +63,7 @@ export async function onRelationshipRemove({ relationship: { type, id } }: Relat
|
||||||
|
|
||||||
export function onGuildDelete({ guild: { id, unavailable } }: GuildDelete) {
|
export function onGuildDelete({ guild: { id, unavailable } }: GuildDelete) {
|
||||||
if (!settings.store.servers) return;
|
if (!settings.store.servers) return;
|
||||||
if (unavailable) return;
|
if (unavailable || GuildAvailabilityStore.isUnavailable(id)) return;
|
||||||
|
|
||||||
if (manuallyRemovedGuild === id) {
|
if (manuallyRemovedGuild === id) {
|
||||||
deleteGuild(id);
|
deleteGuild(id);
|
||||||
|
|
|
@ -19,11 +19,20 @@
|
||||||
import { DataStore, Notices } from "@api/index";
|
import { DataStore, Notices } from "@api/index";
|
||||||
import { showNotification } from "@api/Notifications";
|
import { showNotification } from "@api/Notifications";
|
||||||
import { getUniqueUsername, openUserProfile } from "@utils/discord";
|
import { getUniqueUsername, openUserProfile } from "@utils/discord";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
|
import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
|
||||||
|
import { FluxStore } from "@webpack/types";
|
||||||
|
|
||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
|
import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
|
||||||
|
|
||||||
|
export const GuildAvailabilityStore = findStoreLazy("GuildAvailabilityStore") as FluxStore & {
|
||||||
|
totalGuilds: number;
|
||||||
|
totalUnavailableGuilds: number;
|
||||||
|
unavailableGuilds: string[];
|
||||||
|
isUnavailable(guildId: string): boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const guilds = new Map<string, SimpleGuild>();
|
const guilds = new Map<string, SimpleGuild>();
|
||||||
const groups = new Map<string, SimpleGroupChannel>();
|
const groups = new Map<string, SimpleGroupChannel>();
|
||||||
const friends = {
|
const friends = {
|
||||||
|
@ -59,7 +68,7 @@ export async function syncAndRunChecks() {
|
||||||
|
|
||||||
if (settings.store.servers && oldGuilds?.size) {
|
if (settings.store.servers && oldGuilds?.size) {
|
||||||
for (const [id, guild] of oldGuilds) {
|
for (const [id, guild] of oldGuilds) {
|
||||||
if (!guilds.has(id))
|
if (!guilds.has(id) && !GuildAvailabilityStore.isUnavailable(id))
|
||||||
notify(`You are no longer in the server ${guild.name}.`, guild.iconURL);
|
notify(`You are no longer in the server ${guild.name}.`, guild.iconURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,16 @@ import "./style.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
import { Timestamp } from "@webpack/common";
|
import { Timestamp } from "@webpack/common";
|
||||||
import type { Message } from "discord-types/general";
|
import type { Message } from "discord-types/general";
|
||||||
import type { HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
|
|
||||||
const { getMessageTimestampId } = findByPropsLazy("getMessageTimestampId");
|
const { calendarFormat, dateFormat, isSameDay } = mapMangledModuleLazy("millisecondsInUnit:", {
|
||||||
const { calendarFormat, dateFormat, isSameDay } = findByPropsLazy("calendarFormat", "dateFormat", "isSameDay", "accessibilityLabelCalendarFormat");
|
calendarFormat: filters.byCode("sameElse"),
|
||||||
|
dateFormat: filters.byCode('":'),
|
||||||
|
isSameDay: filters.byCode("Math.abs(+"),
|
||||||
|
});
|
||||||
const MessageClasses = findByPropsLazy("separator", "latin24CompactTimeStamp");
|
const MessageClasses = findByPropsLazy("separator", "latin24CompactTimeStamp");
|
||||||
|
|
||||||
function Sep(props: HTMLAttributes<HTMLElement>) {
|
function Sep(props: HTMLAttributes<HTMLElement>) {
|
||||||
|
@ -42,7 +45,6 @@ function ReplyTimestamp({
|
||||||
const baseTimestamp = baseMessage.timestamp as any;
|
const baseTimestamp = baseMessage.timestamp as any;
|
||||||
return (
|
return (
|
||||||
<Timestamp
|
<Timestamp
|
||||||
id={getMessageTimestampId(referencedMessage.message)}
|
|
||||||
className="vc-reply-timestamp"
|
className="vc-reply-timestamp"
|
||||||
compact={isSameDay(refTimestamp, baseTimestamp)}
|
compact={isSameDay(refTimestamp, baseTimestamp)}
|
||||||
timestamp={refTimestamp}
|
timestamp={refTimestamp}
|
||||||
|
@ -65,7 +67,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "renderSingleLineMessage:function()",
|
find: ".REPLY_QUOTE_MESSAGE_BLOCKED",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<="aria-label":\i,children:\[)(?=\i,\i,\i\])/,
|
match: /(?<="aria-label":\i,children:\[)(?=\i,\i,\i\])/,
|
||||||
replace: "$self.ReplyTimestamp(arguments[0]),"
|
replace: "$self.ReplyTimestamp(arguments[0]),"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue