Compare commits
165 commits
immediate-
...
main
Author | SHA1 | Date | |
---|---|---|---|
bd1e45adc4 | |||
|
4f5ebec4bb | ||
|
7b9f0a36ba | ||
|
fc4e95806d | ||
|
1eff1a02bd | ||
|
414539f45e | ||
|
b2d5c00a23 | ||
|
a492f7657b | ||
|
e280ed2683 | ||
|
8fccda4a24 | ||
|
68662c9625 | ||
|
7d45862023 | ||
|
240195f9bf | ||
|
5ad35c36e4 | ||
|
a2213d4feb | ||
|
81dda2ce33 | ||
|
7415367d6c | ||
|
33d4f13a24 | ||
|
cdc756193e | ||
|
f43baddc55 | ||
|
21ded874a3 | ||
|
ea1e96185b | ||
|
ceba9776c4 | ||
|
c4f8221f75 | ||
|
3350922c09 | ||
|
f29662c5b3 | ||
|
cf28c65374 | ||
|
87cb1fd930 | ||
|
aac5242dc8 | ||
|
4036fbab92 | ||
|
79cbfe96c8 | ||
|
7ee70e831a | ||
|
e45b867ff0 | ||
|
78c2f0d61a | ||
|
72ec5e2023 | ||
|
43501bad07 | ||
|
e000a947a3 | ||
|
5c8ba6e542 | ||
|
317121fc08 | ||
|
30647b6bd9 | ||
|
ed99ae7f23 | ||
|
17f1ef275e | ||
|
ea0182a194 | ||
|
9e9d71d014 | ||
|
9bb983d40c | ||
|
5312514de6 | ||
|
8346dba324 | ||
|
47315b0eba | ||
|
a60af65b6d | ||
|
88e3bc037d | ||
|
19361ef790 | ||
|
c8f4ce9785 | ||
|
a53257634e | ||
|
3243120baa | ||
|
4ab297c9e3 | ||
|
154f371b14 | ||
|
263a96c310 | ||
|
5a77149b26 | ||
|
2707b10021 | ||
|
7be3a40b7c | ||
|
16a1c44947 | ||
|
3af06edb95 | ||
|
34629307dd | ||
|
20ed7dc96b | ||
|
0fd76ab15a | ||
|
0e813e78d0 | ||
|
79e2cb15f1 | ||
|
cca5d7dc09 | ||
|
9ccc74bde3 | ||
|
48a9aef2eb | ||
|
fc731bc6c8 | ||
|
0a0bd6a713 | ||
|
40a8cf1a85 | ||
|
5f1c5fa370 | ||
|
00c968473e | ||
|
2dc8c2bf76 | ||
|
464c4a9b61 | ||
|
dcfddcbc21 | ||
|
9d3c91e9df | ||
|
8d65bcf743 | ||
|
4a5f0838e2 | ||
|
99dc65fe4e | ||
|
3a339636d1 | ||
|
cea0a3c9d9 | ||
|
a3f5dc39a0 | ||
|
df44edd41b | ||
|
cdfc89b819 | ||
|
8711dd9a4b | ||
|
df454ca952 | ||
|
6628624082 | ||
|
dd87f360d7 | ||
|
3f61fe722d | ||
|
d70e0f27dc | ||
|
0ac80ce9d1 | ||
|
fcece61995 | ||
|
02f50b161b | ||
|
1150a50355 | ||
|
11321eb693 | ||
|
60b776669b | ||
|
d8df96d1e3 | ||
|
a9d44e3341 | ||
|
e7a54b0587 | ||
|
23c9e2ce22 | ||
|
5fb63246ca | ||
|
2bfeef88ca | ||
|
7ca4ea3d13 | ||
|
f8dfe217b1 | ||
|
f22d0e14a4 | ||
|
ac1b1d44f5 | ||
|
13993f3f69 | ||
|
a0308e03af | ||
|
cd61f4e744 | ||
|
5cf22113cf | ||
|
ea2772476d | ||
|
99458da3be | ||
|
c4f6f151e6 | ||
|
8558b1a589 | ||
|
76df29fba2 | ||
|
25ceff5ec2 | ||
|
e0d66ff071 | ||
|
66a75747f8 | ||
|
211569f7f5 | ||
|
af1edc88bf | ||
|
7ef536c6c6 | ||
|
69dc4fd594 | ||
|
1fe7912ec1 | ||
|
0cb84cee83 | ||
|
49c9fa1c8e | ||
|
fd1aba7bab | ||
|
3b295e1f6f | ||
|
152d4fdbb3 | ||
|
ce0740b885 | ||
|
64c3dd1c16 | ||
|
5f7a2c59c6 | ||
|
cd3a998c4b | ||
|
2270b88a98 | ||
|
9d4e859a0a | ||
|
439a4f8eb6 | ||
|
00f82e96bd | ||
|
5216bcca1e | ||
|
e7e298d2e7 | ||
|
d897dab054 | ||
|
88e8fa7e90 | ||
|
f5f59be1b6 | ||
|
534ab3eb5f | ||
|
a6ea03bacc | ||
|
e620431210 | ||
|
5afc24b41a | ||
|
7f8e241b9c | ||
|
553293ceee | ||
|
0af820c874 | ||
|
a11ccde40f | ||
|
58c3032bb2 | ||
|
4e89352758 | ||
|
e818905520 | ||
|
aa1b446c07 | ||
|
2dce060cf9 | ||
|
89bb3ee30a | ||
|
47db61d00e | ||
|
416d85dcf0 | ||
|
013c8d061d | ||
|
b5f626d1ff | ||
|
1e01f85217 | ||
|
91a32e22de | ||
|
43b3c137ce |
294 changed files with 7595 additions and 7820 deletions
|
@ -31,6 +31,7 @@ Before starting your plugin:
|
||||||
- No FakeDeafen or FakeMute
|
- No FakeDeafen or FakeMute
|
||||||
- No StereoMic
|
- No StereoMic
|
||||||
- No plugins that simply hide or redesign ui elements. This can be done with CSS
|
- No plugins that simply hide or redesign ui elements. This can be done with CSS
|
||||||
|
- No plugins that interact with specific Discord bots (official Discord apps like Youtube WatchTogether are okay)
|
||||||
- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc)
|
- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc)
|
||||||
- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones
|
- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones
|
||||||
- No plugins that require the user to enter their own API key
|
- No plugins that require the user to enter their own API key
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// @author Vendicated (https://github.com/Vendicated)
|
// @author Vendicated (https://github.com/Vendicated)
|
||||||
// @namespace https://github.com/Vendicated/Vencord
|
// @namespace https://github.com/Vendicated/Vencord
|
||||||
// @supportURL https://github.com/Vendicated/Vencord
|
// @supportURL https://github.com/Vendicated/Vencord
|
||||||
|
// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png
|
||||||
// @license GPL-3.0
|
// @license GPL-3.0
|
||||||
// @match *://*.discord.com/*
|
// @match *://*.discord.com/*
|
||||||
// @grant GM_xmlhttpRequest
|
// @grant GM_xmlhttpRequest
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
import stylistic from "@stylistic/eslint-plugin";
|
import stylistic from "@stylistic/eslint-plugin";
|
||||||
import pathAlias from "eslint-plugin-path-alias";
|
import pathAlias from "eslint-plugin-path-alias";
|
||||||
|
import react from "eslint-plugin-react";
|
||||||
import header from "eslint-plugin-simple-header";
|
import header from "eslint-plugin-simple-header";
|
||||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||||
import unusedImports from "eslint-plugin-unused-imports";
|
import unusedImports from "eslint-plugin-unused-imports";
|
||||||
|
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
||||||
|
{
|
||||||
|
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
rules: {
|
||||||
|
...react.configs.flat.recommended.rules,
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"react/display-name": "off",
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||||
plugins: {
|
plugins: {
|
||||||
|
@ -23,7 +38,7 @@ export default tseslint.config(
|
||||||
"@typescript-eslint": tseslint.plugin,
|
"@typescript-eslint": tseslint.plugin,
|
||||||
"simple-import-sort": simpleImportSort,
|
"simple-import-sort": simpleImportSort,
|
||||||
"unused-imports": unusedImports,
|
"unused-imports": unusedImports,
|
||||||
"path-alias": pathAlias,
|
"path-alias": pathAlias
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
|
|
54
package.json
54
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.10.3",
|
"version": "1.11.3",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -35,53 +35,55 @@
|
||||||
"testTsc": "tsc --noEmit"
|
"testTsc": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@intrnl/xxhash64": "^0.1.2",
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
||||||
"@vap/core": "0.0.12",
|
"@vap/core": "0.0.12",
|
||||||
"@vap/shiki": "0.10.5",
|
"@vap/shiki": "0.10.5",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||||
"monaco-editor": "^0.50.0",
|
"monaco-editor": "^0.52.2",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.9",
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "^2.6.1",
|
"@stylistic/eslint-plugin": "^2.12.1",
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.287",
|
||||||
"@types/diff": "^5.2.1",
|
"@types/diff": "^6.0.0",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^22.0.3",
|
"@types/node": "^22.10.5",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^19.0.2",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^19.0.2",
|
||||||
"@types/yazl": "^2.4.5",
|
"@types/yazl": "^2.4.5",
|
||||||
"diff": "^5.2.0",
|
"diff": "^7.0.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"esbuild": "^0.15.18",
|
"esbuild": "^0.15.18",
|
||||||
"eslint": "^9.8.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "2.1.0",
|
"eslint-plugin-path-alias": "2.1.0",
|
||||||
"eslint-plugin-simple-header": "^1.1.1",
|
"eslint-plugin-react": "^7.37.3",
|
||||||
|
"eslint-plugin-simple-header": "^1.2.1",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unused-imports": "^4.0.1",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"highlight.js": "10.7.3",
|
"highlight.js": "11.7.0",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.22.2",
|
||||||
"puppeteer-core": "^22.15.0",
|
"puppeteer-core": "^23.11.1",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
"stylelint": "^16.8.1",
|
"stylelint": "^16.12.0",
|
||||||
"stylelint-config-standard": "^36.0.1",
|
"stylelint-config-standard": "^36.0.1",
|
||||||
"ts-patch": "^3.2.1",
|
"ts-patch": "^3.3.0",
|
||||||
"ts-pattern": "^5.3.1",
|
"ts-pattern": "^5.6.0",
|
||||||
"tsx": "^4.16.5",
|
"tsx": "^4.19.2",
|
||||||
"type-fest": "^4.23.0",
|
"type-fest": "^4.31.0",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.0.0",
|
"typescript-eslint": "^8.19.0",
|
||||||
"typescript-transform-paths": "^3.4.7",
|
"typescript-transform-paths": "^3.5.3",
|
||||||
"zip-local": "^0.3.5"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@9.1.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
|
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
|
||||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||||
},
|
},
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
|
3267
pnpm-lock.yaml
generated
3267
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -312,7 +312,7 @@ export const commonOpts = {
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
bundle: true,
|
bundle: true,
|
||||||
watch,
|
watch,
|
||||||
minify: !watch && !IS_REPORTER,
|
minify: !watch,
|
||||||
sourcemap: watch ? "inline" : "",
|
sourcemap: watch ? "inline" : "",
|
||||||
legalComments: "linked",
|
legalComments: "linked",
|
||||||
banner,
|
banner,
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import pup, { JSHandle } from "puppeteer-core";
|
import pup, { JSHandle } from "puppeteer-core";
|
||||||
|
|
||||||
for (const variable of ["CHROMIUM_BIN"]) {
|
for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
|
||||||
if (!process.env[variable]) {
|
if (!process.env[variable]) {
|
||||||
console.error(`Missing environment variable ${variable}`);
|
console.error(`Missing environment variable ${variable}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -215,7 +215,7 @@ page.on("console", async e => {
|
||||||
|
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case "WebpackInterceptor:":
|
case "WebpackInterceptor:":
|
||||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module|took [\d.]+?ms) \(Module id is (.+?)\): (.+)/)!;
|
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
|
||||||
if (!patchFailMatch) break;
|
if (!patchFailMatch) break;
|
||||||
|
|
||||||
console.error(await getText());
|
console.error(await getText());
|
||||||
|
@ -226,7 +226,7 @@ page.on("console", async e => {
|
||||||
plugin,
|
plugin,
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
match: regex,
|
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
|
||||||
error: await maybeGetError(e.args()[3])
|
error: await maybeGetError(e.args()[3])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ page.on("error", e => console.error("[Error]", e.message));
|
||||||
page.on("pageerror", e => {
|
page.on("pageerror", e => {
|
||||||
if (e.message.includes("Sentry successfully disabled")) return;
|
if (e.message.includes("Sentry successfully disabled")) return;
|
||||||
|
|
||||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
|
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||||
console.error("[Page Error]", e.message);
|
console.error("[Page Error]", e.message);
|
||||||
report.otherErrors.push(e.message);
|
report.otherErrors.push(e.message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,9 +300,20 @@ page.on("pageerror", e => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function reporterRuntime(token: string) {
|
||||||
|
Vencord.Webpack.waitFor(
|
||||||
|
"loginToken",
|
||||||
|
m => {
|
||||||
|
console.log("[PUP_DEBUG]", "Logging in with token...");
|
||||||
|
m.loginToken(token);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await page.evaluateOnNewDocument(`
|
await page.evaluateOnNewDocument(`
|
||||||
if (location.host.endsWith("discord.com")) {
|
if (location.host.endsWith("discord.com")) {
|
||||||
${readFileSync("./dist/browser.js", "utf-8")};
|
${readFileSync("./dist/browser.js", "utf-8")};
|
||||||
|
(${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,10 @@ export * as Util from "./utils";
|
||||||
export * as QuickCss from "./utils/quickCss";
|
export * as QuickCss from "./utils/quickCss";
|
||||||
export * as Updater from "./utils/updater";
|
export * as Updater from "./utils/updater";
|
||||||
export * as Webpack from "./webpack";
|
export * as Webpack from "./webpack";
|
||||||
export * as WebpackPatcher from "./webpack/patchWebpack";
|
|
||||||
export { PlainSettings, Settings };
|
export { PlainSettings, Settings };
|
||||||
|
|
||||||
import "./utils/quickCss";
|
import "./utils/quickCss";
|
||||||
|
import "./webpack/patchWebpack";
|
||||||
|
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { StartAt } from "@utils/types";
|
import { StartAt } from "@utils/types";
|
||||||
|
@ -39,7 +39,7 @@ import { localStorage } from "./utils/localStorage";
|
||||||
import { relaunch } from "./utils/native";
|
import { relaunch } from "./utils/native";
|
||||||
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
|
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
|
||||||
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
|
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
|
||||||
import { onceDiscordLoaded } from "./webpack";
|
import { onceReady } from "./webpack";
|
||||||
import { SettingsRouter } from "./webpack/common";
|
import { SettingsRouter } from "./webpack/common";
|
||||||
|
|
||||||
if (IS_REPORTER) {
|
if (IS_REPORTER) {
|
||||||
|
@ -86,7 +86,7 @@ async function syncSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await onceDiscordLoaded;
|
await onceReady;
|
||||||
startAllPlugins(StartAt.WebpackReady);
|
startAllPlugins(StartAt.WebpackReady);
|
||||||
|
|
||||||
syncSettings();
|
syncSettings();
|
||||||
|
@ -125,7 +125,7 @@ async function init() {
|
||||||
const pendingPatches = patches.filter(p => !p.all && p.predicate?.() !== false);
|
const pendingPatches = patches.filter(p => !p.all && p.predicate?.() !== false);
|
||||||
if (pendingPatches.length)
|
if (pendingPatches.length)
|
||||||
PMLogger.warn(
|
PMLogger.warn(
|
||||||
"Webpack has finished initializing, but some patches haven't been applied yet.",
|
"Webpack has finished initialising, but some patches haven't been applied yet.",
|
||||||
"This might be expected since some Modules are lazy loaded, but please verify",
|
"This might be expected since some Modules are lazy loaded, but please verify",
|
||||||
"that all plugins are working as intended.",
|
"that all plugins are working as intended.",
|
||||||
"You are seeing this warning because this is a Development build of Vencord.",
|
"You are seeing this warning because this is a Development build of Vencord.",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { ComponentType, HTMLProps } from "react";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ export interface ProfileBadge {
|
||||||
/** The tooltip to show on hover. Required for image badges */
|
/** The tooltip to show on hover. Required for image badges */
|
||||||
description?: string;
|
description?: string;
|
||||||
/** Custom component for the badge (tooltip not included) */
|
/** Custom component for the badge (tooltip not included) */
|
||||||
component?: React.ComponentType<ProfileBadge & BadgeUserArgs>;
|
component?: ComponentType<ProfileBadge & BadgeUserArgs>;
|
||||||
/** The custom image to use */
|
/** The custom image to use */
|
||||||
image?: string;
|
image?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
@ -38,7 +39,7 @@ export interface ProfileBadge {
|
||||||
/** Should the user display this badge? */
|
/** Should the user display this badge? */
|
||||||
shouldShow?(userInfo: BadgeUserArgs): boolean;
|
shouldShow?(userInfo: BadgeUserArgs): boolean;
|
||||||
/** Optional props (e.g. style) for the badge, ignored for component badges */
|
/** Optional props (e.g. style) for the badge, ignored for component badges */
|
||||||
props?: React.ComponentPropsWithoutRef<"img">;
|
props?: HTMLProps<HTMLImageElement>;
|
||||||
/** Insert at start or end? */
|
/** Insert at start or end? */
|
||||||
position?: BadgePosition;
|
position?: BadgePosition;
|
||||||
/** The badge name to display, Discord uses this. Required for component badges */
|
/** The badge name to display, Discord uses this. Required for component badges */
|
||||||
|
@ -56,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
||||||
* Register a new badge with the Badges API
|
* Register a new badge with the Badges API
|
||||||
* @param badge The badge to register
|
* @param badge The badge to register
|
||||||
*/
|
*/
|
||||||
export function addBadge(badge: ProfileBadge) {
|
export function addProfileBadge(badge: ProfileBadge) {
|
||||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||||
Badges.add(badge);
|
Badges.add(badge);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
||||||
* Unregister a badge from the Badges API
|
* Unregister a badge from the Badges API
|
||||||
* @param badge The badge to remove
|
* @param badge The badge to remove
|
||||||
*/
|
*/
|
||||||
export function removeBadge(badge: ProfileBadge) {
|
export function removeProfileBadge(badge: ProfileBadge) {
|
||||||
return Badges.delete(badge);
|
return Badges.delete(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,20 +100,3 @@ export interface BadgeUserArgs {
|
||||||
userId: string;
|
userId: string;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectedAccount {
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
verified: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
connectedAccounts: ConnectedAccount[];
|
|
||||||
premiumType: number;
|
|
||||||
premiumSince: string;
|
|
||||||
premiumGuildSince?: any;
|
|
||||||
lastFetched: number;
|
|
||||||
profileFetchFailed: boolean;
|
|
||||||
application?: any;
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,12 +8,13 @@ import "./ChatButton.css";
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { findByProps } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
const ChannelTextAreaClasses = findByProps<Record<"button" | "buttonContainer", string>>("buttonContainer", "channelTextArea");
|
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||||
|
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||||
|
|
||||||
export interface ChatBarProps {
|
export interface ChatBarProps {
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
|
@ -73,9 +74,9 @@ export interface ChatBarProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||||
|
|
||||||
const buttonFactories = new Map<string, ChatBarButton>();
|
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||||
const logger = new Logger("ChatButtons");
|
const logger = new Logger("ChatButtons");
|
||||||
|
|
||||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
|
@ -90,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||||
|
|
||||||
export interface ChatBarButtonProps {
|
export interface ChatBarButtonProps {
|
||||||
|
@ -98,7 +99,8 @@ export interface ChatBarButtonProps {
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
|
onAuxClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu" | "onAuxClick">;
|
||||||
}
|
}
|
||||||
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
return (
|
return (
|
||||||
|
@ -108,12 +110,13 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
<Button
|
<Button
|
||||||
aria-label={props.tooltip}
|
aria-label={props.tooltip}
|
||||||
size=""
|
size=""
|
||||||
look={ButtonLooks.BLANK}
|
look={Button.Looks.BLANK}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
onContextMenu={props.onContextMenu}
|
onContextMenu={props.onContextMenu}
|
||||||
|
onAuxClick={props.onAuxClick}
|
||||||
{...props.buttonProps}
|
{...props.buttonProps}
|
||||||
>
|
>
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||||
import { findByCode } 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 createBotMessage = findByCode('username:"Clyde"');
|
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||||
|
|
||||||
export function generateId() {
|
export function generateId() {
|
||||||
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
||||||
|
@ -54,5 +54,5 @@ export function sendBotMessage(channelId: string, message: PartialDeep<Message>)
|
||||||
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
|
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
|
||||||
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
||||||
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
|
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
|
||||||
return (args.find(a => a.name === name)?.value || fallbackValue) as any;
|
return (args.find(a => a.name === name)?.value ?? fallbackValue) as any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,7 @@ function registerSubCommands(cmd: Command, plugin: string) {
|
||||||
const subCmd = {
|
const subCmd = {
|
||||||
...cmd,
|
...cmd,
|
||||||
...o,
|
...o,
|
||||||
|
options: o.options !== undefined ? o.options : undefined,
|
||||||
type: ApplicationCommandType.CHAT_INPUT,
|
type: ApplicationCommandType.CHAT_INPUT,
|
||||||
name: `${cmd.name} ${o.name}`,
|
name: `${cmd.name} ${o.name}`,
|
||||||
id: `${o.name}-${cmd.id}`,
|
id: `${o.name}-${cmd.id}`,
|
||||||
|
|
|
@ -24,13 +24,13 @@ import type { ReactElement } from "react";
|
||||||
* @param children The rendered context menu elements
|
* @param children The rendered context menu elements
|
||||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||||
*/
|
*/
|
||||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||||
/**
|
/**
|
||||||
* @param navId The navId of the context menu being patched
|
* @param navId The navId of the context menu being patched
|
||||||
* @param children The rendered context menu elements
|
* @param children The rendered context menu elements
|
||||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||||
*/
|
*/
|
||||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||||
|
|
||||||
const ContextMenuLogger = new Logger("ContextMenu");
|
const ContextMenuLogger = new Logger("ContextMenu");
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
|
||||||
* @returns Whether the patch was successfully removed from the context menu(s)
|
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||||
*/
|
*/
|
||||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
const navIds: string[] = Array.isArray(navId) ? navId : [navId];
|
||||||
|
|
||||||
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
||||||
* @param children The context menu children
|
* @param children The context menu children
|
||||||
* @param matchSubstring Whether to check if the id is a substring of the child id
|
* @param matchSubstring Whether to check if the id is a substring of the child id
|
||||||
*/
|
*/
|
||||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
|
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
contextMenuApiArguments?: Array<any>;
|
contextMenuAPIArguments?: Array<any>;
|
||||||
navId: string;
|
navId: string;
|
||||||
children: Array<ReactElement | null>;
|
children: Array<ReactElement<any> | null>;
|
||||||
"aria-label": string;
|
"aria-label": string;
|
||||||
onSelect: (() => void) | undefined;
|
onSelect: (() => void) | undefined;
|
||||||
onClose: (callback: (...args: Array<any>) => any) => void;
|
onClose: (callback: (...args: Array<any>) => any) => void;
|
||||||
|
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
children: cloneMenuChildren(props.children),
|
children: cloneMenuChildren(props.children),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuAPIArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||||
|
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
|
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map(cloneMenuChildren);
|
return obj.map(cloneMenuChildren);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
* 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 ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, User } from "discord-types/general/index.js";
|
import { Channel, User } from "discord-types/general/index.js";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
activities: any[];
|
activities: any[];
|
||||||
|
@ -38,27 +40,32 @@ interface DecoratorProps {
|
||||||
user: User;
|
user: User;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||||
type OnlyIn = "guilds" | "dms";
|
type OnlyIn = "guilds" | "dms";
|
||||||
|
|
||||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||||
|
|
||||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||||
decorators.set(identifier, { decorator, onlyIn });
|
decorators.set(identifier, { render, onlyIn });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecorator(identifier: string) {
|
export function removeMemberListDecorator(identifier: string) {
|
||||||
decorators.delete(identifier);
|
decorators.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return Array.from(decorators.values(), decoratorObj => {
|
return Array.from(
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
decorators.entries(),
|
||||||
// this can most likely be done cleaner
|
([key, { render: Decorator, onlyIn }]) => {
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||||
return decorator(props);
|
return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||||
|
<Decorator {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
);
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -16,26 +16,29 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
export type Accessory = {
|
import { JSX, ReactNode } from "react";
|
||||||
callback: AccessoryCallback;
|
|
||||||
|
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||||
|
export type MessageAccessory = {
|
||||||
|
render: MessageAccessoryFactory;
|
||||||
position?: number;
|
position?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessories = new Map<String, Accessory>();
|
export const accessories = new Map<string, MessageAccessory>();
|
||||||
|
|
||||||
export function addAccessory(
|
export function addMessageAccessory(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
callback: AccessoryCallback,
|
render: MessageAccessoryFactory,
|
||||||
position?: number
|
position?: number
|
||||||
) {
|
) {
|
||||||
accessories.set(identifier, {
|
accessories.set(identifier, {
|
||||||
callback,
|
render,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAccessory(identifier: string) {
|
export function removeMessageAccessory(identifier: string) {
|
||||||
accessories.delete(identifier);
|
accessories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,15 +46,12 @@ export function _modifyAccessories(
|
||||||
elements: JSX.Element[],
|
elements: JSX.Element[],
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
) {
|
) {
|
||||||
for (const accessory of accessories.values()) {
|
for (const [key, accessory] of accessories.entries()) {
|
||||||
let accessories = accessory.callback(props);
|
const res = (
|
||||||
if (accessories == null)
|
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||||
continue;
|
<accessory.render {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
if (!Array.isArray(accessories))
|
);
|
||||||
accessories = [accessories];
|
|
||||||
else if (accessories.length === 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
elements.splice(
|
elements.splice(
|
||||||
accessory.position != null
|
accessory.position != null
|
||||||
|
@ -60,7 +60,7 @@ export function _modifyAccessories(
|
||||||
: accessory.position
|
: accessory.position
|
||||||
: elements.length,
|
: elements.length,
|
||||||
0,
|
0,
|
||||||
...accessories.filter(e => e != null) as JSX.Element[]
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
* 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 ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, Message } from "discord-types/general/index.js";
|
import { Channel, Message } from "discord-types/general/index.js";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecorationProps {
|
export interface MessageDecorationProps {
|
||||||
author: {
|
author: {
|
||||||
/**
|
/**
|
||||||
* Will be username if the user has no nickname
|
* Will be username if the user has no nickname
|
||||||
|
@ -44,20 +46,25 @@ interface DecorationProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||||
|
|
||||||
export const decorations = new Map<string, Decoration>();
|
export const decorations = new Map<string, MessageDecorationFactory>();
|
||||||
|
|
||||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||||
decorations.set(identifier, decoration);
|
decorations.set(identifier, decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecoration(identifier: string) {
|
export function removeMessageDecoration(identifier: string) {
|
||||||
decorations.delete(identifier);
|
decorations.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
|
||||||
return [...decorations.values()].map(decoration => {
|
return Array.from(
|
||||||
return decoration(props);
|
decorations.entries(),
|
||||||
});
|
([key, Decoration]) => (
|
||||||
|
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||||
|
<Decoration {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
||||||
openWarningPopout: (props: any) => any;
|
openWarningPopout: (props: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||||
|
|
||||||
const sendListeners = new Set<SendListener>();
|
const sendListeners = new Set<MessageSendListener>();
|
||||||
const editListeners = new Set<EditListener>();
|
const editListeners = new Set<MessageEditListener>();
|
||||||
|
|
||||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||||
extra.replyOptions = replyOptions;
|
extra.replyOptions = replyOptions;
|
||||||
|
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreSendListener(listener: SendListener) {
|
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||||
sendListeners.add(listener);
|
sendListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreEditListener(listener: EditListener) {
|
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||||
editListeners.add(listener);
|
editListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
export function removePreSendListener(listener: SendListener) {
|
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||||
return sendListeners.delete(listener);
|
return sendListeners.delete(listener);
|
||||||
}
|
}
|
||||||
export function removePreEditListener(listener: EditListener) {
|
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||||
return editListeners.delete(listener);
|
return editListeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Message clicks
|
// Message clicks
|
||||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||||
|
|
||||||
const listeners = new Set<ClickListener>();
|
const listeners = new Set<MessageClickListener>();
|
||||||
|
|
||||||
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||||
// message object may be outdated, so (try to) fetch latest one
|
// message object may be outdated, so (try to) fetch latest one
|
||||||
|
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addClickListener(listener: ClickListener) {
|
export function addMessageClickListener(listener: MessageClickListener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeClickListener(listener: ClickListener) {
|
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||||
return listeners.delete(listener);
|
return listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,36 +19,37 @@
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
export interface ButtonItem {
|
export interface MessagePopoverButtonItem {
|
||||||
key?: string,
|
key?: string,
|
||||||
label: string,
|
label: string,
|
||||||
icon: React.ComponentType<AnyRecord>,
|
icon: ComponentType<any>,
|
||||||
message: Message,
|
message: Message,
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>,
|
onClick?: MouseEventHandler<HTMLButtonElement>,
|
||||||
onContextMenu?: React.MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||||
|
|
||||||
export const buttons = new Map<string, getButtonItem>();
|
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||||
|
|
||||||
export function addButton(
|
export function addMessagePopoverButton(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
item: getButtonItem,
|
item: MessagePopoverButtonFactory,
|
||||||
) {
|
) {
|
||||||
buttons.set(identifier, item);
|
buttons.set(identifier, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeButton(identifier: string) {
|
export function removeMessagePopoverButton(identifier: string) {
|
||||||
buttons.delete(identifier);
|
buttons.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _buildPopoverElements(
|
export function _buildPopoverElements(
|
||||||
Component: React.ComponentType<ButtonItem>,
|
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||||
message: Message
|
message: Message
|
||||||
) {
|
) {
|
||||||
const items: React.ReactNode[] = [];
|
const items: React.ReactNode[] = [];
|
||||||
|
|
|
@ -16,22 +16,23 @@
|
||||||
* 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 { find } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
|
|
||||||
const Notices = find(m => m.show && m.dismiss && !m.suppressAll);
|
let NoticesModule: any;
|
||||||
|
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
||||||
|
|
||||||
export const noticesQueue = [] as any[];
|
export const noticesQueue = [] as any[];
|
||||||
export let currentNotice: any = null;
|
export let currentNotice: any = null;
|
||||||
|
|
||||||
export function popNotice() {
|
export function popNotice() {
|
||||||
Notices.dismiss();
|
NoticesModule.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nextNotice() {
|
export function nextNotice() {
|
||||||
currentNotice = noticesQueue.shift();
|
currentNotice = noticesQueue.shift();
|
||||||
|
|
||||||
if (currentNotice) {
|
if (currentNotice) {
|
||||||
Notices.show(...currentNotice, "VencordNotice");
|
NoticesModule.show(...currentNotice, "VencordNotice");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,40 +16,36 @@
|
||||||
* 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 { Logger } from "@utils/Logger";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { ComponentType } from "react";
|
||||||
const logger = new Logger("ServerListAPI");
|
|
||||||
|
|
||||||
export const enum ServerListRenderPosition {
|
export const enum ServerListRenderPosition {
|
||||||
Above,
|
Above,
|
||||||
In,
|
In,
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderFunctionsAbove = new Set<Function>();
|
const componentsAbove = new Set<ComponentType>();
|
||||||
const renderFunctionsIn = new Set<Function>();
|
const componentsBelow = new Set<ComponentType>();
|
||||||
|
|
||||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).add(renderFunction);
|
getRenderFunctions(position).add(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).delete(renderFunction);
|
getRenderFunctions(position).delete(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderAll = (position: ServerListRenderPosition) => {
|
export const renderAll = (position: ServerListRenderPosition) => {
|
||||||
const ret: Array<JSX.Element> = [];
|
return Array.from(
|
||||||
|
getRenderFunctions(position),
|
||||||
for (const renderFunction of getRenderFunctions(position)) {
|
(Component, i) => (
|
||||||
try {
|
<ErrorBoundary noop key={i}>
|
||||||
ret.unshift(renderFunction());
|
<Component />
|
||||||
} catch (e) {
|
</ErrorBoundary>
|
||||||
logger.error("Failed to render server list element:", e);
|
)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
};
|
|
@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
|
||||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||||
import { putCloudSettings } from "@utils/settingsSync";
|
import { putCloudSettings } from "@utils/settingsSync";
|
||||||
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
||||||
import { React } from "@webpack/common";
|
import { React, useEffect } from "@webpack/common";
|
||||||
|
|
||||||
import plugins from "~plugins";
|
import plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -32,10 +32,9 @@ export interface Settings {
|
||||||
autoUpdate: boolean;
|
autoUpdate: boolean;
|
||||||
autoUpdateNotification: boolean,
|
autoUpdateNotification: boolean,
|
||||||
useQuickCss: boolean;
|
useQuickCss: boolean;
|
||||||
themeLinks: string[];
|
|
||||||
eagerPatches: boolean;
|
|
||||||
enabledThemes: string[];
|
|
||||||
enableReactDevtools: boolean;
|
enableReactDevtools: boolean;
|
||||||
|
themeLinks: string[];
|
||||||
|
enabledThemes: string[];
|
||||||
frameless: boolean;
|
frameless: boolean;
|
||||||
transparent: boolean;
|
transparent: boolean;
|
||||||
winCtrlQ: boolean;
|
winCtrlQ: boolean;
|
||||||
|
@ -82,7 +81,6 @@ const DefaultSettings: Settings = {
|
||||||
autoUpdateNotification: true,
|
autoUpdateNotification: true,
|
||||||
useQuickCss: true,
|
useQuickCss: true,
|
||||||
themeLinks: [],
|
themeLinks: [],
|
||||||
eagerPatches: IS_REPORTER,
|
|
||||||
enabledThemes: [],
|
enabledThemes: [],
|
||||||
enableReactDevtools: false,
|
enableReactDevtools: false,
|
||||||
frameless: false,
|
frameless: false,
|
||||||
|
@ -118,6 +116,7 @@ const saveSettingsOnFrequentAction = debounce(async () => {
|
||||||
}
|
}
|
||||||
}, 60_000);
|
}, 60_000);
|
||||||
|
|
||||||
|
|
||||||
export const SettingsStore = new SettingsStoreClass(settings, {
|
export const SettingsStore = new SettingsStoreClass(settings, {
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
getDefaultValue({
|
getDefaultValue({
|
||||||
|
@ -193,7 +192,7 @@ export const Settings = SettingsStore.store;
|
||||||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (paths) {
|
if (paths) {
|
||||||
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||||
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||||
|
@ -201,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
SettingsStore.addGlobalChangeListener(forceUpdate);
|
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||||
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [paths]);
|
||||||
|
|
||||||
return SettingsStore.store;
|
return SettingsStore.store;
|
||||||
}
|
}
|
||||||
|
@ -221,6 +220,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
|
||||||
|
const settings = SettingsStore.plain.plugins[pluginName];
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
|
||||||
|
|
||||||
|
settings[newSetting] = settings[oldSetting];
|
||||||
|
delete settings[oldSetting];
|
||||||
|
SettingsStore.markAsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
export function definePluginSettings<
|
export function definePluginSettings<
|
||||||
Def extends SettingsDefinition,
|
Def extends SettingsDefinition,
|
||||||
Checks extends SettingsChecks<Def>,
|
Checks extends SettingsChecks<Def>,
|
||||||
|
|
|
@ -89,8 +89,8 @@ export const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnec
|
||||||
* // -- plugin.ts --
|
* // -- plugin.ts --
|
||||||
* import pluginStyle from "./plugin.css?managed";
|
* import pluginStyle from "./plugin.css?managed";
|
||||||
* import { setStyleVars } from "@api/Styles";
|
* import { setStyleVars } from "@api/Styles";
|
||||||
* import { findByProps } from "@webpack";
|
* import { findByPropsLazy } from "@webpack";
|
||||||
* const classNames = findByProps("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... }
|
* const classNames = findByPropsLazy("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... }
|
||||||
*
|
*
|
||||||
* // Inside some plugin method like "start()"
|
* // Inside some plugin method like "start()"
|
||||||
* setStyleClassNames(pluginStyle, classNames);
|
* setStyleClassNames(pluginStyle, classNames);
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { findByFactoryCode } from "@webpack";
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||||
|
|
||||||
interface UserSettingDefinition<T> {
|
interface UserSettingDefinition<T> {
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +43,12 @@ interface UserSettingDefinition<T> {
|
||||||
userSettingsAPIName: string;
|
userSettingsAPIName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserSettings = findByFactoryCode<Record<PropertyKey, UserSettingDefinition<any>>>('"textAndImages","renderSpoilers"');
|
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.
|
* Get the setting with the given setting group and name.
|
||||||
|
@ -50,7 +56,7 @@ export const UserSettings = findByFactoryCode<Record<PropertyKey, UserSettingDef
|
||||||
* @param group The setting group
|
* @param group The setting group
|
||||||
* @param name The name of the setting
|
* @param name The name of the setting
|
||||||
*/
|
*/
|
||||||
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> {
|
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.");
|
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency.");
|
||||||
|
|
||||||
for (const key in UserSettings) {
|
for (const key in UserSettings) {
|
||||||
|
@ -60,12 +66,10 @@ export function getUserSetting<T = any>(group: string, name: string): UserSettin
|
||||||
return userSetting;
|
return userSetting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`UserSettingsAPI: Setting ${group}.${name} not found.`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazy version of {@link getUserSetting}
|
* {@link getUserSettingDefinition}, lazy.
|
||||||
*
|
*
|
||||||
* Get the setting with the given setting group and name.
|
* Get the setting with the given setting group and name.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Badge({ text, color }): JSX.Element {
|
export function Badge({ text, color }) {
|
||||||
return (
|
return (
|
||||||
<div className="vc-plugins-badge" style={{
|
<div className="vc-plugins-badge" style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { findByProps } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Parser } from "@webpack/common";
|
import { Parser } from "@webpack/common";
|
||||||
|
|
||||||
const CodeContainerClasses = findByProps("markup", "codeContainer");
|
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders code in a Discord codeblock
|
* Renders code in a Discord codeblock
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { LazyComponent, LazyComponentType } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { React } from "@webpack/common";
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
import { ErrorCard } from "./ErrorCard";
|
import { ErrorCard } from "./ErrorCard";
|
||||||
|
@ -27,7 +27,7 @@ interface Props<T = any> {
|
||||||
/** Render nothing if an error occurs */
|
/** Render nothing if an error occurs */
|
||||||
noop?: boolean;
|
noop?: boolean;
|
||||||
/** Fallback component to render if an error occurs */
|
/** Fallback component to render if an error occurs */
|
||||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
|
||||||
/** called when an error occurs. The props property is only available if using .wrap */
|
/** called when an error occurs. The props property is only available if using .wrap */
|
||||||
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
||||||
/** Custom error message */
|
/** Custom error message */
|
||||||
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -80,10 +79,14 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
if (this.props.noop) return null;
|
if (this.props.noop) return null;
|
||||||
|
|
||||||
if (this.props.fallback)
|
if (this.props.fallback)
|
||||||
return <this.props.fallback
|
return (
|
||||||
children={this.props.children}
|
<this.props.fallback
|
||||||
{...this.state}
|
wrappedProps={this.props.wrappedProps}
|
||||||
/>;
|
{...this.state}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</this.props.fallback>
|
||||||
|
);
|
||||||
|
|
||||||
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
||||||
|
|
||||||
|
@ -104,8 +107,8 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}) as
|
}) as
|
||||||
LazyComponentType<React.PropsWithChildren<Props>> & {
|
React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||||
wrap<T extends AnyRecord>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.FunctionComponent<T>;
|
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.FunctionComponent<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
.vc-expandableheader-center-flex {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-expandableheader-btn {
|
|
||||||
all: unset;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "./ExpandableHeader.css";
|
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import { Text, Tooltip, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-expandableheader-");
|
|
||||||
|
|
||||||
export interface ExpandableHeaderProps {
|
|
||||||
onMoreClick?: () => void;
|
|
||||||
moreTooltipText?: string;
|
|
||||||
onDropDownClick?: (state: boolean) => void;
|
|
||||||
defaultState?: boolean;
|
|
||||||
headerText: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
buttons?: React.ReactNode[];
|
|
||||||
forceOpen?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ExpandableHeader({
|
|
||||||
children,
|
|
||||||
onMoreClick,
|
|
||||||
buttons,
|
|
||||||
moreTooltipText,
|
|
||||||
onDropDownClick,
|
|
||||||
headerText,
|
|
||||||
defaultState = false,
|
|
||||||
forceOpen = false,
|
|
||||||
}: ExpandableHeaderProps) {
|
|
||||||
const [showContent, setShowContent] = useState(defaultState || forceOpen);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
marginBottom: "8px"
|
|
||||||
}}>
|
|
||||||
<Text
|
|
||||||
tag="h2"
|
|
||||||
variant="eyebrow"
|
|
||||||
style={{
|
|
||||||
color: "var(--header-primary)",
|
|
||||||
display: "inline"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{headerText}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<div className={cl("center-flex")}>
|
|
||||||
{
|
|
||||||
buttons ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
onMoreClick && // only show more button if callback is provided
|
|
||||||
<Tooltip text={moreTooltipText}>
|
|
||||||
{tooltipProps => (
|
|
||||||
<button
|
|
||||||
{...tooltipProps}
|
|
||||||
className={cl("btn")}
|
|
||||||
onClick={onMoreClick}>
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path fill="var(--text-normal)" d="M7 12.001C7 10.8964 6.10457 10.001 5 10.001C3.89543 10.001 3 10.8964 3 12.001C3 13.1055 3.89543 14.001 5 14.001C6.10457 14.001 7 13.1055 7 12.001ZM14 12.001C14 10.8964 13.1046 10.001 12 10.001C10.8954 10.001 10 10.8964 10 12.001C10 13.1055 10.8954 14.001 12 14.001C13.1046 14.001 14 13.1055 14 12.001ZM19 10.001C20.1046 10.001 21 10.8964 21 12.001C21 13.1055 20.1046 14.001 19 14.001C17.8954 14.001 17 13.1055 17 12.001C17 10.8964 17.8954 10.001 19 10.001Z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<Tooltip text={showContent ? "Hide " + headerText : "Show " + headerText}>
|
|
||||||
{tooltipProps => (
|
|
||||||
<button
|
|
||||||
{...tooltipProps}
|
|
||||||
className={cl("btn")}
|
|
||||||
onClick={() => {
|
|
||||||
setShowContent(v => !v);
|
|
||||||
onDropDownClick?.(showContent);
|
|
||||||
}}
|
|
||||||
disabled={forceOpen}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
transform={showContent ? "scale(1 -1)" : "scale(1 1)"}
|
|
||||||
>
|
|
||||||
<path fill="var(--text-normal)" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{showContent && children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CSSProperties } from "react";
|
import { CSSProperties, JSX } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
columns: number;
|
columns: number;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function Heart() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#db61a2"
|
fill="#db61a2"
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
|
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -18,10 +18,9 @@
|
||||||
|
|
||||||
import "./iconStyles.css";
|
import "./iconStyles.css";
|
||||||
|
|
||||||
import { getTheme, Theme } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { i18n } from "@webpack/common";
|
import type { JSX, PropsWithChildren } from "react";
|
||||||
import type { PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
|
@ -56,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
||||||
className={classes(className, "vc-link-icon")}
|
className={classes(className, "vc-link-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g fill="none" fill-rule="evenodd">
|
<g fill="none" fillRule="evenodd">
|
||||||
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
|
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
|
||||||
<rect width={width} height={height} />
|
<rect width={width} height={height} />
|
||||||
</g>
|
</g>
|
||||||
|
@ -123,8 +122,8 @@ export function InfoIcon(props: IconProps) {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
transform="translate(2 2)"
|
fillRule="evenodd"
|
||||||
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
|
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
|
||||||
export function OwnerCrownIcon(props: IconProps) {
|
export function OwnerCrownIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
aria-label={i18n.Messages.GUILD_OWNER}
|
aria-label={getIntlMessage("GUILD_OWNER")}
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-owner-crown-icon")}
|
className={classes(props.className, "vc-owner-crown-icon")}
|
||||||
role="img"
|
role="img"
|
||||||
|
@ -212,9 +211,10 @@ export function CogWheel(props: IconProps) {
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
clipRule="evenodd"
|
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
|
fillRule="evenodd"
|
||||||
|
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
|
||||||
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
>
|
>
|
||||||
<polygon
|
<polygon
|
||||||
fill-rule="nonzero"
|
fillRule="nonzero"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
||||||
/>
|
/>
|
||||||
|
@ -407,23 +407,30 @@ export function PencilIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
export function GithubIcon(props: IconProps) {
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
return (
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
<Icon
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
{...props}
|
||||||
|
viewBox="-3 -3 30 30"
|
||||||
export function GithubIcon(props: ImageProps) {
|
>
|
||||||
const src = getTheme() === Theme.Light
|
<path
|
||||||
? GithubIconLight
|
fill={props.fill || "currentColor"}
|
||||||
: GithubIconDark;
|
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.745.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.998.108-.775.42-1.305.763-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.398 3-.403 1.02.005 2.043.137 3 .403 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.873.118 3.176.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.625-5.475 5.92.43.37.823 1.102.823 2.222v3.293c0 .32.218.694.825.577C20.565 21.797 24 17.298 24 12c0-6.63-5.37-12-12-12z"
|
||||||
|
/>
|
||||||
return <img {...props} src={src} />;
|
</Icon>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteIcon(props: ImageProps) {
|
export function WebsiteIcon(props: IconProps) {
|
||||||
const src = getTheme() === Theme.Light
|
return (
|
||||||
? WebsiteIconLight
|
<Icon
|
||||||
: WebsiteIconDark;
|
{...props}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
return <img {...props} src={src} />;
|
>
|
||||||
|
<path
|
||||||
|
fill={props.fill || "currentColor"}
|
||||||
|
d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zM4 12c0-.899.156-1.762.431-2.569L6 11l2 2v2l2 2 1 1v1.931C7.061 19.436 4 16.072 4 12zm14.33 4.873C17.677 16.347 16.687 16 16 16v-1a2 2 0 0 0-2-2h-4v-3a2 2 0 0 0 2-2V7h1a2 2 0 0 0 2-2v-.411C17.928 5.778 20 8.65 20 12a7.947 7.947 0 0 1-1.67 4.873z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!profile && !user.bot && user.id)
|
if (!profile && !user.bot && user.id)
|
||||||
fetchUserProfile(user.id);
|
fetchUserProfile(user.id);
|
||||||
}, [user.id]);
|
}, [user.id, user.bot, profile]);
|
||||||
|
|
||||||
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||||
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||||
|
|
|
@ -6,16 +6,19 @@
|
||||||
|
|
||||||
import "./LinkIconButton.css";
|
import "./LinkIconButton.css";
|
||||||
|
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { GithubIcon, WebsiteIcon } from "..";
|
import { GithubIcon, WebsiteIcon } from "..";
|
||||||
|
|
||||||
export function GithubLinkIcon() {
|
export function GithubLinkIcon() {
|
||||||
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
|
||||||
|
return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteLinkIcon() {
|
export function WebsiteLinkIcon() {
|
||||||
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
|
||||||
|
return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
@ -24,18 +24,20 @@ 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 { gitRemote } from "@shared/vencordUserAgent";
|
||||||
|
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";
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { find, findByProps, findComponentByCode } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserUtils } from "@webpack/common";
|
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||||
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 { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ISettingCustomElementProps,
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
SettingCustomComponent,
|
SettingCustomComponent,
|
||||||
|
@ -49,9 +51,9 @@ import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-plugin-modal-");
|
const cl = classNameFactory("vc-plugin-modal-");
|
||||||
|
|
||||||
const UserSummaryItem = findComponentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||||
const AvatarStyles = findByProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||||
const UserRecord = find<Constructor<Partial<User>>>(m => m?.prototype?.getAvatarURL && m?.prototype?.hasHadPremium);
|
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
|
||||||
|
|
||||||
interface PluginModalProps extends ModalProps {
|
interface PluginModalProps extends ModalProps {
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
|
@ -73,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
|
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
|
||||||
[OptionType.STRING]: SettingTextComponent,
|
[OptionType.STRING]: SettingTextComponent,
|
||||||
[OptionType.NUMBER]: SettingNumericComponent,
|
[OptionType.NUMBER]: SettingNumericComponent,
|
||||||
[OptionType.BIGINT]: SettingNumericComponent,
|
[OptionType.BIGINT]: SettingNumericComponent,
|
||||||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
[OptionType.SELECT]: SettingSelectComponent,
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
[OptionType.SLIDER]: SettingSliderComponent,
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
[OptionType.COMPONENT]: SettingCustomComponent
|
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||||
|
[OptionType.CUSTOM]: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
@ -108,7 +111,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
setAuthors(a => [...a, author]);
|
setAuthors(a => [...a, author]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, [plugin.authors]);
|
||||||
|
|
||||||
async function saveAndClose() {
|
async function saveAndClose() {
|
||||||
if (!plugin.options) {
|
if (!plugin.options) {
|
||||||
|
@ -128,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
for (const [key, value] of Object.entries(tempSettings)) {
|
for (const [key, value] of Object.entries(tempSettings)) {
|
||||||
const option = plugin.options[key];
|
const option = plugin.options[key];
|
||||||
pluginSettings[key] = value;
|
pluginSettings[key] = value;
|
||||||
option?.onChange?.(value);
|
|
||||||
|
if (option.type === OptionType.CUSTOM) continue;
|
||||||
if (option?.restartNeeded) restartNeeded = true;
|
if (option?.restartNeeded) restartNeeded = true;
|
||||||
}
|
}
|
||||||
if (restartNeeded) onRestartNeeded();
|
if (restartNeeded) onRestartNeeded();
|
||||||
|
@ -140,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
} else {
|
} else {
|
||||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
if (setting.hidden) return null;
|
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||||
|
|
||||||
function onChange(newValue: any) {
|
function onChange(newValue: any) {
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
import { PluginOptionComponent } from "@utils/types";
|
import { PluginOptionComponent } from "@utils/types";
|
||||||
|
|
||||||
import { ISettingElementProps } from ".";
|
import { ISettingCustomElementProps } from ".";
|
||||||
|
|
||||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
|
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
|
||||||
return option.component({ setValue: onChange, setError: onError, option });
|
return option.component({ setValue: onChange, setError: onError, option });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||||
|
|
||||||
export interface ISettingElementProps<T extends PluginOptionBase> {
|
interface ISettingElementPropsBase<T> {
|
||||||
option: T;
|
option: T;
|
||||||
onChange(newValue: any): void;
|
onChange(newValue: any): void;
|
||||||
pluginSettings: {
|
pluginSettings: {
|
||||||
|
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
definedSettings?: DefinedSettings;
|
definedSettings?: DefinedSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
|
||||||
|
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
|
||||||
|
|
||||||
export * from "../../Badge";
|
export * from "../../Badge";
|
||||||
export * from "./SettingBooleanComponent";
|
export * from "./SettingBooleanComponent";
|
||||||
export * from "./SettingCustomComponent";
|
export * from "./SettingCustomComponent";
|
||||||
|
|
|
@ -33,19 +33,20 @@ import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByProps } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
import Plugins, { ExcludedPlugins } from "~plugins";
|
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||||
|
|
||||||
// Avoid circular dependency
|
// Avoid circular dependency
|
||||||
const PluginManager = proxyLazy(() => require("../../plugins")) as typeof import("../../plugins");
|
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
||||||
|
|
||||||
const cl = classNameFactory("vc-plugins-");
|
const cl = classNameFactory("vc-plugins-");
|
||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByProps("inputWrapper", "inputDefault", "error");
|
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
||||||
const ButtonClasses = findByProps("button", "disabled", "enabled");
|
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||||
|
|
||||||
|
|
||||||
function showErrorToast(message: string) {
|
function showErrorToast(message: string) {
|
||||||
|
@ -100,7 +101,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
|
|
||||||
// If we're enabling a plugin, make sure all deps are enabled recursively.
|
// If we're enabling a plugin, make sure all deps are enabled recursively.
|
||||||
if (!wasEnabled) {
|
if (!wasEnabled) {
|
||||||
const { restartNeeded, failures } = PluginManager.startDependenciesRecursive(plugin);
|
const { restartNeeded, failures } = startDependenciesRecursive(plugin);
|
||||||
if (failures.length) {
|
if (failures.length) {
|
||||||
logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`);
|
logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`);
|
||||||
showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null);
|
showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null);
|
||||||
|
@ -126,7 +127,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = wasEnabled ? PluginManager.stopPlugin(plugin) : PluginManager.startPlugin(plugin);
|
const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
settings.enabled = false;
|
settings.enabled = false;
|
||||||
|
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||||
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import "./Switch.css";
|
import "./Switch.css";
|
||||||
|
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { findByProps } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
interface SwitchProps {
|
interface SwitchProps {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
|
@ -29,7 +29,7 @@ interface SwitchProps {
|
||||||
|
|
||||||
const SWITCH_ON = "var(--green-360)";
|
const SWITCH_ON = "var(--green-360)";
|
||||||
const SWITCH_OFF = "var(--primary-400)";
|
const SWITCH_OFF = "var(--primary-400)";
|
||||||
const SwitchClasses = findByProps("slider", "input", "container");
|
const SwitchClasses = findByPropsLazy("slider", "input", "container");
|
||||||
|
|
||||||
export function Switch({ checked, onChange, disabled }: SwitchProps) {
|
export function Switch({ checked, onChange, disabled }: SwitchProps) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { Margins } from "@utils/margins";
|
||||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
import { Patch, ReplaceFn } from "@utils/types";
|
import { Patch, ReplaceFn } from "@utils/types";
|
||||||
import { searchFactories } from "@webpack";
|
import { search } from "@webpack";
|
||||||
import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
@ -33,7 +33,7 @@ if (IS_DEV) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const findCandidates = debounce(function ({ find, setModule, setError }) {
|
const findCandidates = debounce(function ({ find, setModule, setError }) {
|
||||||
const candidates = searchFactories(find);
|
const candidates = search(find);
|
||||||
const keys = Object.keys(candidates);
|
const keys = Object.keys(candidates);
|
||||||
const len = keys.length;
|
const len = keys.length;
|
||||||
if (len === 0)
|
if (len === 0)
|
||||||
|
@ -56,7 +56,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
const [compileResult, setCompileResult] = React.useState<[boolean, string]>();
|
const [compileResult, setCompileResult] = React.useState<[boolean, string]>();
|
||||||
|
|
||||||
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
||||||
const src = String(fact).replaceAll("\n", "");
|
const src: string = fact.toString().replaceAll("\n", "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new RegExp(match);
|
new RegExp(match);
|
||||||
|
@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDiff() {
|
function renderDiff() {
|
||||||
return diff?.map(p => {
|
return diff?.map((p, idx) => {
|
||||||
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
||||||
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
|
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
|
||||||
|
|
||||||
if (!parsed.find) throw new Error("No 'find' field");
|
if (!parsed.find) throw new Error("No 'find' field");
|
||||||
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
||||||
|
@ -27,9 +27,9 @@ import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findComponentByFields } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import type { Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -37,14 +37,14 @@ import { AddonCard } from "./AddonCard";
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
|
||||||
type FileInputProps = {
|
type FileInput = ComponentType<{
|
||||||
ref: Ref<HTMLInputElement>;
|
ref: Ref<HTMLInputElement>;
|
||||||
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
|
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
filters?: { name?: string; extensions: string[]; }[];
|
filters?: { name?: string; extensions: string[]; }[];
|
||||||
};
|
}>;
|
||||||
|
|
||||||
const FileInput = findComponentByFields<FileInputProps>("activateUploadDialogue", "setRef");
|
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
const cl = classNameFactory("vc-settings-theme-");
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ function ThemesTab() {
|
||||||
Icon={PaintbrushIcon}
|
Icon={PaintbrushIcon}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{Vencord.Plugins.isPluginEnabled("ClientTheme") && (
|
{Settings.plugins.ClientTheme.enabled && (
|
||||||
<QuickAction
|
<QuickAction
|
||||||
text="Edit ClientTheme"
|
text="Edit ClientTheme"
|
||||||
action={() => openPluginModal(Plugins.ClientTheme)}
|
action={() => openPluginModal(Plugins.ClientTheme)}
|
||||||
|
|
|
@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
||||||
title: "Oops!",
|
title: "Oops!",
|
||||||
body: (
|
body: (
|
||||||
<ErrorCard>
|
<ErrorCard>
|
||||||
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
|
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
|
||||||
return (
|
return (
|
||||||
<Card style={{ padding: "0 0.5em" }}>
|
<Card style={{ padding: "0 0.5em" }}>
|
||||||
{updates.map(({ hash, author, message }) => (
|
{updates.map(({ hash, author, message }) => (
|
||||||
<div style={{
|
<div key={hash} style={{
|
||||||
marginTop: "0.5em",
|
marginTop: "0.5em",
|
||||||
marginBottom: "0.5em"
|
marginBottom: "0.5em"
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -8,12 +8,13 @@ import "./quickActions.css";
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Card } from "@webpack/common";
|
import { Card } from "@webpack/common";
|
||||||
|
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-quickActions-");
|
const cl = classNameFactory("vc-settings-quickActions-");
|
||||||
|
|
||||||
export interface QuickActionProps {
|
export interface QuickActionProps {
|
||||||
Icon: React.ComponentType<{ className?: string; }>;
|
Icon: ComponentType<{ className?: string; }>;
|
||||||
text: React.ReactNode;
|
text: ReactNode;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +30,7 @@ export function QuickAction(props: QuickActionProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function QuickActionCard(props: React.PropsWithChildren) {
|
export function QuickActionCard(props: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<Card className={cl("card")}>
|
<Card className={cl("card")}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -24,8 +24,9 @@ import { handleComponentFailed } from "@components/handleComponentFailed";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { onlyOnce } from "@utils/onlyOnce";
|
import { onlyOnce } from "@utils/onlyOnce";
|
||||||
import { Forms, Text } from "@webpack/common";
|
import { Forms, Text } from "@webpack/common";
|
||||||
|
import type { ComponentType, PropsWithChildren } from "react";
|
||||||
|
|
||||||
export function SettingsTab({ title, children }: React.PropsWithChildren<{ title: string; }>) {
|
export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) {
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Text
|
<Text
|
||||||
|
@ -43,7 +44,7 @@ export function SettingsTab({ title, children }: React.PropsWithChildren<{ title
|
||||||
|
|
||||||
export const handleSettingsTabError = onlyOnce(handleComponentFailed);
|
export const handleSettingsTabError = onlyOnce(handleComponentFailed);
|
||||||
|
|
||||||
export function wrapTab<P extends AnyRecord>(component: React.ComponentType<P>, tab: string) {
|
export function wrapTab(component: ComponentType<any>, tab: string) {
|
||||||
return ErrorBoundary.wrap(component, {
|
return ErrorBoundary.wrap(component, {
|
||||||
message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`,
|
message: `Failed to render the ${tab} tab. If this issue persists, try using the installer to reinstall!`,
|
||||||
onError: handleSettingsTabError,
|
onError: handleSettingsTabError,
|
||||||
|
|
|
@ -10,7 +10,6 @@ export * from "./CodeBlock";
|
||||||
export * from "./DonateButton";
|
export * from "./DonateButton";
|
||||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||||
export * from "./ErrorCard";
|
export * from "./ErrorCard";
|
||||||
export * from "./ExpandableHeader";
|
|
||||||
export * from "./Flex";
|
export * from "./Flex";
|
||||||
export * from "./Heart";
|
export * from "./Heart";
|
||||||
export * from "./Icons";
|
export * from "./Icons";
|
||||||
|
|
|
@ -23,61 +23,35 @@ if (IS_DEV || IS_REPORTER) {
|
||||||
var logger = new Logger("Tracer", "#FFD166");
|
var logger = new Logger("Tracer", "#FFD166");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const beginTrace = !(IS_DEV || IS_REPORTER) ? () => { } :
|
const noop = function () { };
|
||||||
|
|
||||||
|
export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop :
|
||||||
function beginTrace(name: string, ...args: any[]) {
|
function beginTrace(name: string, ...args: any[]) {
|
||||||
if (name in traces) {
|
if (name in traces)
|
||||||
throw new Error(`Trace ${name} already exists!`);
|
throw new Error(`Trace ${name} already exists!`);
|
||||||
}
|
|
||||||
|
|
||||||
traces[name] = [performance.now(), args];
|
traces[name] = [performance.now(), args];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const finishTrace = !(IS_DEV || IS_REPORTER) ? () => 0 :
|
export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) {
|
||||||
function finishTrace(name: string) {
|
const end = performance.now();
|
||||||
const end = performance.now();
|
|
||||||
|
|
||||||
const [start, args] = traces[name];
|
const [start, args] = traces[name];
|
||||||
delete traces[name];
|
delete traces[name];
|
||||||
|
|
||||||
const totalTime = end - start;
|
logger.debug(`${name} took ${end - start}ms`, args);
|
||||||
logger.debug(`${name} took ${totalTime}ms`, args);
|
};
|
||||||
|
|
||||||
return totalTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Func = (...args: any[]) => any;
|
type Func = (...args: any[]) => any;
|
||||||
type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
|
type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
|
||||||
|
|
||||||
function noopTracerWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
|
const noopTracer =
|
||||||
return function (this: unknown, ...args: Parameters<F>): [ReturnType<F>, number] {
|
<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) => f;
|
||||||
return [f.apply(this, args), 0];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function noopTracer<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const traceFunctionWithResults = !(IS_DEV || IS_REPORTER)
|
|
||||||
? noopTracerWithResults
|
|
||||||
: function traceFunctionWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): (this: unknown, ...args: Parameters<F>) => [ReturnType<F>, number] {
|
|
||||||
return function (this: unknown, ...args: Parameters<F>) {
|
|
||||||
const traceName = mapper?.(...args) ?? name;
|
|
||||||
|
|
||||||
beginTrace(traceName, ...arguments);
|
|
||||||
try {
|
|
||||||
return [f.apply(this, args), finishTrace(traceName)];
|
|
||||||
} catch (e) {
|
|
||||||
finishTrace(traceName);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const traceFunction = !(IS_DEV || IS_REPORTER)
|
export const traceFunction = !(IS_DEV || IS_REPORTER)
|
||||||
? noopTracer
|
? noopTracer
|
||||||
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F {
|
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F {
|
||||||
return function (this: unknown, ...args: Parameters<F>) {
|
return function (this: any, ...args: Parameters<F>) {
|
||||||
const traceName = mapper?.(...args) ?? name;
|
const traceName = mapper?.(...args) ?? name;
|
||||||
|
|
||||||
beginTrace(traceName, ...arguments);
|
beginTrace(traceName, ...arguments);
|
||||||
|
|
|
@ -8,11 +8,10 @@ import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { wreq } from "@webpack";
|
import { wreq } from "@webpack";
|
||||||
import { AnyModuleFactory, ModuleFactory } from "webpack";
|
|
||||||
|
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||||
|
|
||||||
export async function loadLazyChunks() {
|
export async function loadLazyChunks() {
|
||||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||||
|
|
||||||
|
@ -26,12 +25,19 @@ export async function loadLazyChunks() {
|
||||||
// True if resolved, false otherwise
|
// True if resolved, false otherwise
|
||||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||||
|
|
||||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i(?:\.\i)?\.bind\(\i,"?([^)]+?)"?(?:,[^)]+?)?\)\)/g);
|
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||||
|
|
||||||
|
let foundCssDebuggingLoad = false;
|
||||||
|
|
||||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||||
|
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
|
||||||
|
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
|
||||||
|
|
||||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||||
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
|
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
|
||||||
|
|
||||||
|
const shouldForceDefer = false;
|
||||||
|
|
||||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
|
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
|
||||||
|
|
||||||
|
@ -42,6 +48,16 @@ export async function loadLazyChunks() {
|
||||||
let invalidChunkGroup = false;
|
let invalidChunkGroup = false;
|
||||||
|
|
||||||
for (const id of chunkIds) {
|
for (const id of chunkIds) {
|
||||||
|
if (hasCssDebuggingLoad) {
|
||||||
|
if (chunkIds.length > 1) {
|
||||||
|
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidChunks.add(id);
|
||||||
|
invalidChunkGroup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||||
|
|
||||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
|
@ -66,14 +82,19 @@ export async function loadLazyChunks() {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from(validChunkGroups)
|
Array.from(validChunkGroups)
|
||||||
.map(([chunkIds]) =>
|
.map(([chunkIds]) =>
|
||||||
Promise.all(chunkIds.map(id => wreq.e(id)))
|
Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Requires the entry points for all valid chunk groups
|
// Requires the entry points for all valid chunk groups
|
||||||
for (const [, entryPoint] of validChunkGroups) {
|
for (const [, entryPoint] of validChunkGroups) {
|
||||||
try {
|
try {
|
||||||
if (wreq.m[entryPoint]) wreq(entryPoint);
|
if (shouldForceDefer) {
|
||||||
|
deferredRequires.add(entryPoint);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
@ -101,33 +122,32 @@ export async function loadLazyChunks() {
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function factoryListener(factory: AnyModuleFactory | ModuleFactory) {
|
Webpack.factoryListeners.add(factory => {
|
||||||
let isResolved = false;
|
let isResolved = false;
|
||||||
searchAndLoadLazyChunks(String(factory))
|
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
|
||||||
.then(() => isResolved = true)
|
|
||||||
.catch(() => 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);
|
chunksSearchPromises.push(() => isResolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
Webpack.factoryListeners.add(factoryListener);
|
|
||||||
for (const factoryId in wreq.m) {
|
|
||||||
factoryListener(wreq.m[factoryId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await chunksSearchingDone;
|
await chunksSearchingDone;
|
||||||
Webpack.factoryListeners.delete(factoryListener);
|
|
||||||
|
|
||||||
// Require deferred entry points
|
// Require deferred entry points
|
||||||
for (const deferredRequire of deferredRequires) {
|
for (const deferredRequire of deferredRequires) {
|
||||||
wreq(deferredRequire);
|
wreq!(deferredRequire as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||||
const allChunks = [] as number[];
|
const allChunks = [] as number[];
|
||||||
|
|
||||||
// Matches "id" or id:
|
// Matches "id" or id:
|
||||||
for (const currentMatch of String(wreq.u).matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
||||||
const id = currentMatch[1] ?? currentMatch[2];
|
const id = currentMatch[1] ?? currentMatch[2];
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
|
|
||||||
|
@ -136,8 +156,7 @@ export async function loadLazyChunks() {
|
||||||
|
|
||||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||||
|
|
||||||
// Chunks which our regex could not catch to load
|
// Chunks that are not loaded (not used) by Discord code anymore
|
||||||
// It will always contain WebWorker assets, and also currently contains some language packs which are loaded differently
|
|
||||||
const chunksLeft = allChunks.filter(id => {
|
const chunksLeft = allChunks.filter(id => {
|
||||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||||
});
|
});
|
||||||
|
@ -147,9 +166,12 @@ export async function loadLazyChunks() {
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
.then(t => t.includes("importScripts("));
|
.then(t => t.includes("importScripts("));
|
||||||
|
|
||||||
// Loads the chunk. Currently this only happens with the language packs which are loaded differently
|
// Loads and requires a chunk
|
||||||
if (!isWorkerAsset) {
|
if (!isWorkerAsset) {
|
||||||
await wreq.e(id);
|
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);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -4,39 +4,22 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SYM_LAZY_COMPONENT_INNER } from "@utils/lazyReact";
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { SYM_PROXY_INNER_GET, SYM_PROXY_INNER_VALUE } from "@utils/proxyInner";
|
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { addPatch, patches } from "plugins";
|
import { patches } from "plugins";
|
||||||
|
|
||||||
import { loadLazyChunks } from "./loadLazyChunks";
|
import { loadLazyChunks } from "./loadLazyChunks";
|
||||||
|
|
||||||
async function runReporter() {
|
const ReporterLogger = new Logger("Reporter");
|
||||||
const ReporterLogger = new Logger("Reporter");
|
|
||||||
|
|
||||||
|
async function runReporter() {
|
||||||
try {
|
try {
|
||||||
ReporterLogger.log("Starting test...");
|
ReporterLogger.log("Starting test...");
|
||||||
|
|
||||||
let loadLazyChunksResolve: (value: void) => void;
|
let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void;
|
||||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||||
|
|
||||||
// The main patch for starting the reporter chunk loading
|
Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
|
||||||
addPatch({
|
|
||||||
find: '"Could not find app-mount"',
|
|
||||||
replacement: {
|
|
||||||
match: /(?<="use strict";)/,
|
|
||||||
replace: "Vencord.Webpack._initReporter();"
|
|
||||||
}
|
|
||||||
}, "Vencord Reporter");
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
Vencord.Webpack._initReporter = function () {
|
|
||||||
// initReporter is called in the patched entry point of Discord
|
|
||||||
// setImmediate to only start searching for lazy chunks after Discord initialized the app
|
|
||||||
setTimeout(() => loadLazyChunks().then(loadLazyChunksResolve), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
await loadLazyChunksDone;
|
await loadLazyChunksDone;
|
||||||
|
|
||||||
for (const patch of patches) {
|
for (const patch of patches) {
|
||||||
|
@ -45,158 +28,59 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) {
|
for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
|
||||||
if (totalTime > 3) {
|
let method = searchType;
|
||||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${totalTime}ms (Module id is ${String(moduleId)}): ${match}`);
|
|
||||||
|
if (searchType === "findComponent") method = "find";
|
||||||
|
if (searchType === "findExportedComponent") method = "findByProps";
|
||||||
|
if (searchType === "waitFor" || searchType === "waitForComponent") {
|
||||||
|
if (typeof args[0] === "string") method = "findByProps";
|
||||||
|
else method = "find";
|
||||||
}
|
}
|
||||||
}
|
if (searchType === "waitForStore") method = "findStore";
|
||||||
|
|
||||||
await Promise.all(Webpack.webpackSearchHistory.map(async ([searchType, args]) => {
|
let result: any;
|
||||||
args = [...args];
|
|
||||||
|
|
||||||
let result = null as any;
|
|
||||||
try {
|
try {
|
||||||
switch (searchType) {
|
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
case "webpackDependantLazy":
|
const [factory] = args;
|
||||||
case "webpackDependantLazyComponent": {
|
result = factory();
|
||||||
const [factory] = args;
|
} else if (method === "extractAndLoadChunks") {
|
||||||
result = factory();
|
const [code, matcher] = args;
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "extractAndLoadChunks": {
|
|
||||||
const extractAndLoadChunks = args.shift();
|
|
||||||
|
|
||||||
result = await extractAndLoadChunks();
|
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||||
if (result === false) {
|
if (result === false) result = null;
|
||||||
result = null;
|
} else if (method === "mapMangledModule") {
|
||||||
}
|
const [code, mapper] = args;
|
||||||
|
|
||||||
break;
|
result = Webpack.mapMangledModule(code, mapper);
|
||||||
}
|
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
||||||
default: {
|
} else {
|
||||||
const findResult = args.shift();
|
// @ts-ignore
|
||||||
|
result = Webpack[method](...args);
|
||||||
if (findResult != null) {
|
|
||||||
if (findResult.$$vencordCallbackCalled != null && findResult.$$vencordCallbackCalled()) {
|
|
||||||
result = findResult;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (findResult[SYM_PROXY_INNER_GET] != null) {
|
|
||||||
result = findResult[SYM_PROXY_INNER_VALUE];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (findResult[SYM_LAZY_COMPONENT_INNER] != null) {
|
|
||||||
result = findResult[SYM_LAZY_COMPONENT_INNER]();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchType === "mapMangledModule") {
|
|
||||||
result = findResult;
|
|
||||||
|
|
||||||
for (const innerMap in result) {
|
|
||||||
if (
|
|
||||||
(result[innerMap][SYM_PROXY_INNER_GET] != null && result[innerMap][SYM_PROXY_INNER_VALUE] == null) ||
|
|
||||||
(result[innerMap][SYM_LAZY_COMPONENT_INNER] != null && result[innerMap][SYM_LAZY_COMPONENT_INNER]() == null)
|
|
||||||
) {
|
|
||||||
throw new Error("Webpack Find Fail");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This can happen if a `find` was immediately found
|
|
||||||
result = findResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||||
throw new Error("Webpack Find Fail");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
let filterName = "";
|
if (args[0].$$vencordProps != null) {
|
||||||
let parsedArgs = args;
|
logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
|
|
||||||
if (args[0].$$vencordProps != null) {
|
|
||||||
if (["find", "findComponent", "waitFor"].includes(searchType)) {
|
|
||||||
filterName = args[0].$$vencordProps[0];
|
|
||||||
parsedArgs = args[0].$$vencordProps.slice(1);
|
|
||||||
} else {
|
} else {
|
||||||
parsedArgs = args[0].$$vencordProps;
|
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||||
}
|
}
|
||||||
}
|
} 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);
|
||||||
|
|
||||||
function stringifyFilter(code: Webpack.CodeFilterWithSingle) {
|
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||||
if (Array.isArray(code)) {
|
|
||||||
return `[${code.map(arg => arg instanceof RegExp ? String(arg) : JSON.stringify(arg)).join(", ")}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return code instanceof RegExp ? String(code) : JSON.stringify(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if parsedArgs is the same as args, it means vencordProps of the filter was not available (like in normal filter functions),
|
|
||||||
// so log the filter function instead
|
|
||||||
if (
|
|
||||||
parsedArgs === args &&
|
|
||||||
["waitFor", "find", "findComponent", "webpackDependantLazy", "webpackDependantLazyComponent"].includes(searchType)
|
|
||||||
) {
|
|
||||||
let filter = String(parsedArgs[0]);
|
|
||||||
if (filter.length > 150) {
|
|
||||||
filter = filter.slice(0, 147) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
logMessage += `(${filter})`;
|
|
||||||
} else if (searchType === "extractAndLoadChunks") {
|
|
||||||
const [code, matcher] = parsedArgs;
|
|
||||||
|
|
||||||
let regexStr: string;
|
|
||||||
if (matcher === Webpack.DefaultExtractAndLoadChunksRegex) {
|
|
||||||
regexStr = "DefaultExtractAndLoadChunksRegex";
|
|
||||||
} else {
|
|
||||||
regexStr = String(matcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
logMessage += `(${stringifyFilter(code)}, ${regexStr})`;
|
|
||||||
} else if (searchType === "mapMangledModule") {
|
|
||||||
const [code, mappers] = parsedArgs;
|
|
||||||
|
|
||||||
const parsedFailedMappers = Object.entries<any>(mappers)
|
|
||||||
.filter(([key]) =>
|
|
||||||
result == null ||
|
|
||||||
(result[key]?.[SYM_PROXY_INNER_GET] != null && result[key][SYM_PROXY_INNER_VALUE] == null) ||
|
|
||||||
(result[key]?.[SYM_LAZY_COMPONENT_INNER] != null && result[key][SYM_LAZY_COMPONENT_INNER]() == null)
|
|
||||||
)
|
|
||||||
.map(([key, filter]) => {
|
|
||||||
let parsedFilter: string;
|
|
||||||
|
|
||||||
if (filter.$$vencordProps != null) {
|
|
||||||
const filterName = filter.$$vencordProps[0];
|
|
||||||
parsedFilter = `${filterName}(${filter.$$vencordProps.slice(1).map((arg: any) => arg instanceof RegExp ? String(arg) : JSON.stringify(arg)).join(", ")})`;
|
|
||||||
} else {
|
|
||||||
parsedFilter = String(filter);
|
|
||||||
if (parsedFilter.length > 150) {
|
|
||||||
parsedFilter = parsedFilter.slice(0, 147) + "...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [key, parsedFilter];
|
|
||||||
});
|
|
||||||
|
|
||||||
logMessage += `(${stringifyFilter(code)}, {\n${parsedFailedMappers.map(([key, parsedFilter]) => `\t${key}: ${parsedFilter}`).join(",\n")}\n})`;
|
|
||||||
} else {
|
} else {
|
||||||
logMessage += `(${filterName.length ? `${filterName}(` : ""}${parsedArgs.map(stringifyFilter).join(", ")})${filterName.length ? ")" : ""}`;
|
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
}
|
}
|
||||||
}));
|
}
|
||||||
|
|
||||||
ReporterLogger.log("Finished test");
|
ReporterLogger.log("Finished test");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -204,5 +88,4 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run after the Vencord object has been created
|
runReporter();
|
||||||
setTimeout(runReporter, 0);
|
|
||||||
|
|
11
src/globals.d.ts
vendored
11
src/globals.d.ts
vendored
|
@ -19,10 +19,6 @@
|
||||||
import { LoDashStatic } from "lodash";
|
import { LoDashStatic } from "lodash";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
type AnyRecord = Record<PropertyKey, any>;
|
|
||||||
type AnyComponentType<P extends AnyRecord = AnyRecord> = React.ComponentType<P & AnyRecord> & AnyRecord;
|
|
||||||
type AnyComponentTypeWithChildren<P extends AnyRecord = AnyRecord> = AnyComponentType<React.PropsWithChildren<P>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exists only at build time, so references to it in patches should insert it
|
* This exists only at build time, so references to it in patches should insert it
|
||||||
* via String interpolation OR use different replacement code based on this
|
* via String interpolation OR use different replacement code based on this
|
||||||
|
@ -68,8 +64,13 @@ declare global {
|
||||||
export var Vesktop: any;
|
export var Vesktop: any;
|
||||||
export var VesktopNative: any;
|
export var VesktopNative: any;
|
||||||
|
|
||||||
interface Window extends AnyRecord {
|
interface Window {
|
||||||
|
webpackChunkdiscord_app: {
|
||||||
|
push(chunk: any): any;
|
||||||
|
pop(): any;
|
||||||
|
};
|
||||||
_: LoDashStatic;
|
_: LoDashStatic;
|
||||||
|
[k: string]: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,13 +71,16 @@ export async function installExt(id: string) {
|
||||||
// React Devtools v4.25
|
// React Devtools v4.25
|
||||||
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
|
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
|
||||||
// Unfortunately, Google does not serve old versions, so this is the only way
|
// Unfortunately, Google does not serve old versions, so this is the only way
|
||||||
|
// This zip file is pinned to long commit hash so it cannot be changed remotely
|
||||||
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
|
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
|
||||||
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`;
|
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
|
||||||
|
|
||||||
const buf = await get(url, {
|
const buf = await get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)"
|
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await extract(crxToZip(buf), extDir).catch(console.error);
|
await extract(crxToZip(buf), extDir).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
src/plugins/_api/badges/fixDiscordBadgePadding.css
Normal file
5
src/plugins/_api/badges/fixDiscordBadgePadding.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* the profile popout badge container(s) */
|
||||||
|
[class*="biteSize_"] [class*="tags_"] [class*="container_"] {
|
||||||
|
/* Discord has padding set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
* 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 "./fixDiscordBadgePadding.css";
|
||||||
|
|
||||||
import { _getBadges, 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";
|
||||||
|
@ -26,7 +28,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
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, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
@ -77,7 +79,7 @@ export default definePlugin({
|
||||||
replace: "...$1.props,$& $1.image??"
|
replace: "...$1.props,$& $1.image??"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=text:(\i)\.description,.{0,200})children:/,
|
match: /(?<="aria-label":(\i)\.description,.{0,200})children:/,
|
||||||
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||||
},
|
},
|
||||||
// conditionally override their onClick with badge.onClick if it exists
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
|
@ -100,8 +102,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userProfileBadge: ContributorBadge,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
|
||||||
await loadBadges();
|
await loadBadges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -141,8 +144,8 @@ export default definePlugin({
|
||||||
closeModal(modalKey);
|
closeModal(modalKey);
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
||||||
}}>
|
}}>
|
||||||
<Modals.ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
<Modals.ModalHeader>
|
<ModalHeader>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<Forms.FormTitle
|
<Forms.FormTitle
|
||||||
tag="h2"
|
tag="h2"
|
||||||
|
@ -156,8 +159,8 @@ export default definePlugin({
|
||||||
Vencord Donor
|
Vencord Donor
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalHeader>
|
</ModalHeader>
|
||||||
<Modals.ModalContent>
|
<ModalContent>
|
||||||
<Flex>
|
<Flex>
|
||||||
<img
|
<img
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
@ -180,13 +183,13 @@ export default definePlugin({
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
</Modals.ModalContent>
|
</ModalContent>
|
||||||
<Modals.ModalFooter>
|
<ModalFooter>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<DonateButton />
|
<DonateButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalFooter>
|
</ModalFooter>
|
||||||
</Modals.ModalRoot>
|
</ModalRoot>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,16 @@ export default definePlugin({
|
||||||
description: "API to add buttons to the chat input",
|
description: "API to add buttons to the chat input",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
patches: [{
|
patches: [
|
||||||
find: '"sticker")',
|
{
|
||||||
replacement: {
|
find: '"sticker")',
|
||||||
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
replacement: {
|
||||||
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
|
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
|
||||||
|
replace: (m, not, children) => not
|
||||||
|
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
|
||||||
|
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,12 +34,22 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Menu,{",
|
find: "navId:",
|
||||||
all: true,
|
all: true,
|
||||||
replacement: {
|
noWarn: true,
|
||||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
replacement: [
|
||||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
{
|
||||||
}
|
match: /navId:(?=.+?([,}].*?\)))/g,
|
||||||
|
replace: (m, rest) => {
|
||||||
|
// Check if this navId: match is a destructuring statement, ignore it if it is
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) {
|
||||||
|
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
24
src/plugins/_api/dynamicImageModalApi.ts
Normal file
24
src/plugins/_api/dynamicImageModalApi.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "DynamicImageModalAPI",
|
||||||
|
authors: [Devs.sadan, Devs.Nuckyz],
|
||||||
|
description: "Allows you to omit either width or height when opening an image modal",
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "SCALE_DOWN:",
|
||||||
|
replacement: {
|
||||||
|
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
|
||||||
|
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
||||||
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
||||||
replace: "$&vencordProps=$1,"
|
replace: "$&vencordProps=$1,"
|
||||||
}, {
|
}, {
|
||||||
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
||||||
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
68
src/plugins/_api/menuItemDemangler.ts
Normal file
68
src/plugins/_api/menuItemDemangler.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
// duplicate values have multiple branches with different types. Just include all to be safe
|
||||||
|
const nameMap = {
|
||||||
|
radio: "MenuRadioItem",
|
||||||
|
separator: "MenuSeparator",
|
||||||
|
checkbox: "MenuCheckboxItem",
|
||||||
|
groupstart: "MenuGroup",
|
||||||
|
|
||||||
|
control: "MenuControlItem",
|
||||||
|
compositecontrol: "MenuControlItem",
|
||||||
|
|
||||||
|
item: "MenuItem",
|
||||||
|
customitem: "MenuItem",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MenuItemDemanglerAPI",
|
||||||
|
description: "Demangles Discord's Menu Item module",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
required: true,
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '"Menu API',
|
||||||
|
replacement: {
|
||||||
|
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
|
||||||
|
replace: (m, mod) => {
|
||||||
|
const nameAssignments = [] as string[];
|
||||||
|
|
||||||
|
// if (t.type === m.MenuItem)
|
||||||
|
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
|
||||||
|
// push({type:"item"})
|
||||||
|
const pushTypeRe = /type:"(\w+)"/g;
|
||||||
|
|
||||||
|
let typeMatch: RegExpExecArray | null;
|
||||||
|
// for each if (t.type === ...)
|
||||||
|
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
|
||||||
|
// extract the current menu item
|
||||||
|
const item = typeMatch[1];
|
||||||
|
// Set the starting index of the second regex to that of the first to start
|
||||||
|
// matching from after the if
|
||||||
|
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
|
||||||
|
// extract the first type: "..."
|
||||||
|
const type = pushTypeRe.exec(m)?.[1];
|
||||||
|
if (type && type in nameMap) {
|
||||||
|
const name = nameMap[type];
|
||||||
|
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nameAssignments.length < 6) {
|
||||||
|
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all our redefines with the actual module
|
||||||
|
return `${nameAssignments.join(";")};${m}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Cyn],
|
authors: [Devs.Cyn],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
||||||
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"Message Username"',
|
find: '"Message Username"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
|
||||||
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,26 +25,23 @@ export default definePlugin({
|
||||||
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.EDIT_TEXTAREA_HELP",
|
find: "#{intl::EDIT_TEXTAREA_HELP}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
||||||
replace: (match, args) => "" +
|
replace: (match, args) => "" +
|
||||||
`async ${match}` +
|
`async ${match}` +
|
||||||
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
||||||
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
|
"return Promise.resolve({shouldClear:false,shouldRefocus:true});"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".handleSendMessage,onResize",
|
find: ".handleSendMessage,onResize",
|
||||||
replacement: {
|
replacement: {
|
||||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
// https://regex101.com/r/hBlXpl/1
|
||||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
|
||||||
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
|
||||||
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
|
||||||
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
|
||||||
`${rest1}async ${rest2}` +
|
|
||||||
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
||||||
"return{shoudClear:true,shouldRefocus:true};"
|
"return{shouldClear:false,shouldRefocus:true};"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,8 +49,7 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||||
replace: (m, message, channel, event) =>
|
replace: (m, message, channel, event) =>
|
||||||
// the message param is shadowed by the event param, so need to alias them
|
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
|
||||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -23,11 +23,14 @@ export default definePlugin({
|
||||||
name: "MessagePopoverAPI",
|
name: "MessagePopoverAPI",
|
||||||
description: "API to add buttons to message popovers.",
|
description: "API to add buttons to message popovers.",
|
||||||
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
||||||
patches: [{
|
patches: [
|
||||||
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
{
|
||||||
replacement: {
|
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||||
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.Messages\.MESSAGE_ACTION_REPLY.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
|
replacement: {
|
||||||
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
|
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||||
|
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||||
|
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}],
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,8 +33,8 @@ export default definePlugin({
|
||||||
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
|
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.*NOTICE_DISMISS:\1)/,
|
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||||
replace: "if($2.id==\"VencordNotice\")return($2=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,16 +25,16 @@ export default definePlugin({
|
||||||
description: "Api required for plugins that modify the server list",
|
description: "Api required for plugins that modify the server list",
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "Messages.DISCODO_DISABLED",
|
find: "#{intl::DISCODO_DISABLED}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||||
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.SERVERS,children",
|
find: "#{intl::SERVERS}),children",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/,
|
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
|
||||||
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { WebpackRequire } from "webpack";
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
disableAnalytics: {
|
disableAnalytics: {
|
||||||
|
@ -62,13 +61,13 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".installedLogHooks)",
|
find: ".BetterDiscord||null!=",
|
||||||
replacement: {
|
replacement: {
|
||||||
// if getDebugLogging() returns false, the hooks don't get installed.
|
// Make hasClientMods return false
|
||||||
match: "getDebugLogging(){",
|
match: /(?=let \i=window;)/,
|
||||||
replace: "getDebugLogging(){return false;"
|
replace: "return false;"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
startAt: StartAt.Init,
|
startAt: StartAt.Init,
|
||||||
|
@ -82,9 +81,9 @@ export default definePlugin({
|
||||||
Object.defineProperty(Function.prototype, "g", {
|
Object.defineProperty(Function.prototype, "g", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
||||||
set(this: WebpackRequire, globalObj: WebpackRequire["g"]) {
|
set(v: any) {
|
||||||
Object.defineProperty(this, "g", {
|
Object.defineProperty(this, "g", {
|
||||||
value: globalObj,
|
value: v,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
writable: true
|
writable: true
|
||||||
|
@ -93,11 +92,11 @@ export default definePlugin({
|
||||||
// Ensure this is most likely the Sentry WebpackInstance.
|
// Ensure this is most likely the Sentry WebpackInstance.
|
||||||
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
||||||
const { stack } = new Error();
|
const { stack } = new Error();
|
||||||
if (this.c != null || !stack?.includes("http") || !String(this).includes("exports:{}")) {
|
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assetPath = stack.match(/http.+?(?=:\d+?:\d+?$)/m)?.[0];
|
const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0];
|
||||||
if (!assetPath) {
|
if (!assetPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -107,8 +106,7 @@ export default definePlugin({
|
||||||
srcRequest.send();
|
srcRequest.send();
|
||||||
|
|
||||||
// Final condition to see if this is the Sentry WebpackInstance
|
// Final condition to see if this is the Sentry WebpackInstance
|
||||||
// This is matching window.DiscordSentry=, but without `window` to avoid issues on some proxies
|
if (!srcRequest.responseText.includes("window.DiscordSentry=")) {
|
||||||
if (!srcRequest.responseText.includes(".DiscordSentry=")) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import BackupAndRestoreTab from "@components/VencordSettings/BackupAndRestoreTab";
|
import BackupAndRestoreTab from "@components/VencordSettings/BackupAndRestoreTab";
|
||||||
import CloudTab from "@components/VencordSettings/CloudTab";
|
import CloudTab from "@components/VencordSettings/CloudTab";
|
||||||
import PatchHelperTab from "@components/VencordSettings/PatchHelperTab";
|
import PatchHelperTab from "@components/VencordSettings/PatchHelperTab";
|
||||||
|
@ -25,35 +25,20 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
|
||||||
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
||||||
import VencordTab from "@components/VencordSettings/VencordTab";
|
import VencordTab from "@components/VencordSettings/VencordTab";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { i18n, React } from "@webpack/common";
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
|
||||||
type SectionType = "HEADER" | "DIVIDER" | "CUSTOM";
|
type SectionType = "HEADER" | "DIVIDER" | "CUSTOM";
|
||||||
type SectionTypes = Record<SectionType, SectionType>;
|
type SectionTypes = Record<SectionType, SectionType>;
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
settingsLocation: {
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
description: "Where to put the Vencord settings section",
|
|
||||||
options: [
|
|
||||||
{ label: "At the very top", value: "top" },
|
|
||||||
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
|
|
||||||
{ label: "Below the Nitro section", value: "belowNitro" },
|
|
||||||
{ label: "Above Activity Settings", value: "aboveActivity" },
|
|
||||||
{ label: "Below Activity Settings", value: "belowActivity" },
|
|
||||||
{ label: "At the very bottom", value: "bottom" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
description: "Adds Settings UI and debug info",
|
description: "Adds Settings UI and debug info",
|
||||||
authors: [Devs.Ven, Devs.Megu],
|
authors: [Devs.Ven, Devs.Megu],
|
||||||
required: true,
|
required: true,
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
@ -73,20 +58,21 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: ".SEARCH_NO_RESULTS&&0===",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
|
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
|
||||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
||||||
replace: "$2.open($1);return;"
|
replace: "$2.open($1);return;"
|
||||||
|
@ -152,25 +138,30 @@ export default definePlugin({
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
},
|
},
|
||||||
|
|
||||||
isRightSpot({ header, settingsChilds }: { header?: string; settingsChilds?: string[]; }) {
|
isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) {
|
||||||
const firstChild = settingsChilds?.[0];
|
const firstChild = settings?.[0];
|
||||||
// lowest two elements... sanity backup
|
// lowest two elements... sanity backup
|
||||||
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
|
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
|
||||||
|
|
||||||
const { settingsLocation } = settings.store;
|
const { settingsLocation } = Settings.plugins.Settings;
|
||||||
|
|
||||||
if (settingsLocation === "bottom") return firstChild === "LOGOUT";
|
if (settingsLocation === "bottom") return firstChild === "LOGOUT";
|
||||||
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
|
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
|
||||||
|
|
||||||
if (!header) return;
|
if (!header) return;
|
||||||
|
|
||||||
const names = {
|
try {
|
||||||
top: i18n.Messages.USER_SETTINGS,
|
const names = {
|
||||||
aboveNitro: i18n.Messages.BILLING_SETTINGS,
|
top: getIntlMessage("USER_SETTINGS"),
|
||||||
belowNitro: i18n.Messages.APP_SETTINGS,
|
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
|
||||||
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
|
belowNitro: getIntlMessage("APP_SETTINGS"),
|
||||||
};
|
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
||||||
return header === names[settingsLocation];
|
};
|
||||||
|
|
||||||
|
return header === names[settingsLocation];
|
||||||
|
} catch {
|
||||||
|
return firstChild === "PREMIUM";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
patchedSettings: new WeakSet(),
|
patchedSettings: new WeakSet(),
|
||||||
|
@ -197,8 +188,23 @@ export default definePlugin({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
options: {
|
||||||
|
settingsLocation: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Where to put the Vencord settings section",
|
||||||
|
options: [
|
||||||
|
{ label: "At the very top", value: "top" },
|
||||||
|
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
|
||||||
|
{ label: "Below the Nitro section", value: "belowNitro" },
|
||||||
|
{ label: "Above Activity Settings", value: "aboveActivity" },
|
||||||
|
{ label: "Below Activity Settings", value: "belowActivity" },
|
||||||
|
{ label: "At the very bottom", value: "bottom" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
get electronVersion() {
|
get electronVersion() {
|
||||||
return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
|
return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
get chromiumVersion() {
|
get chromiumVersion() {
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
* 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 { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -34,6 +33,7 @@ import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import plugins, { PluginMeta } from "~plugins";
|
import plugins, { PluginMeta } from "~plugins";
|
||||||
|
@ -59,7 +59,7 @@ const TrustedRolesIds = [
|
||||||
|
|
||||||
const AsyncFunction = async function () { }.constructor;
|
const AsyncFunction = async function () { }.constructor;
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame");
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
async function forceUpdate() {
|
async function forceUpdate() {
|
||||||
const outdated = await checkForUpdates();
|
const outdated = await checkForUpdates();
|
||||||
|
@ -77,7 +77,7 @@ async function generateDebugInfoMessage() {
|
||||||
const client = (() => {
|
const client = (() => {
|
||||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||||
|
@ -142,15 +142,15 @@ export default definePlugin({
|
||||||
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: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".BEGINNING_DM.format",
|
find: "#{intl::BEGINNING_DM}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
|
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
|
||||||
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
|
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
@ -235,7 +235,87 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
renderMessageAccessory(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;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||||
|
const userId = channel.getRecipientId();
|
||||||
if (!isPluginDev(userId)) return null;
|
if (!isPluginDev(userId)) return null;
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
|
@ -248,85 +328,4 @@ export default definePlugin({
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}, { noop: true }),
|
}, { noop: true }),
|
||||||
|
|
||||||
start() {
|
|
||||||
addAccessory("vencord-debug", props => {
|
|
||||||
const buttons = [] as JSX.Element[];
|
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
|
||||||
!IS_UPDATER_DISABLED
|
|
||||||
&& (
|
|
||||||
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
|
||||||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
|
||||||
)
|
|
||||||
&& props.message.content?.includes("update");
|
|
||||||
|
|
||||||
if (shouldAddUpdateButton) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
key="vc-update"
|
|
||||||
color={Button.Colors.GREEN}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
if (await forceUpdate())
|
|
||||||
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
|
||||||
else
|
|
||||||
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
|
||||||
} catch (e) {
|
|
||||||
new Logger(this.name).error("Error while updating:", e);
|
|
||||||
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Update Now
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
|
||||||
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
key="vc-dbg"
|
|
||||||
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
|
||||||
>
|
|
||||||
Run /vencord-debug
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
key="vc-plg-list"
|
|
||||||
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
|
||||||
>
|
|
||||||
Run /vencord-plugins
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.message.author.id === VENBOT_USER_ID) {
|
|
||||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
|
||||||
if (match) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
key="vc-run-snippet"
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await AsyncFunction(match[1])();
|
|
||||||
showToast("Success!", Toasts.Type.SUCCESS);
|
|
||||||
} catch (e) {
|
|
||||||
new Logger(this.name).error("Error while running snippet:", e);
|
|
||||||
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Run Snippet
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttons.length
|
|
||||||
? <Flex>{buttons}</Flex>
|
|
||||||
: null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,21 +9,21 @@ 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 definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByProps, findComponentByCode } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
|
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
popoutProps: Record<string, any>;
|
popoutProps: Record<string, any>;
|
||||||
currentUser: User;
|
currentUser: User;
|
||||||
originalPopout: () => React.ReactNode;
|
originalRenderPopout: () => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserProfile = findComponentByCode("UserProfilePopoutWrapper: user cannot be undefined");
|
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
|
||||||
const styles = findByProps("accountProfilePopoutWrapper");
|
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||||
|
|
||||||
let openAlternatePopout = false;
|
let openAlternatePopout = false;
|
||||||
let accountPanelRef: React.MutableRefObject<AnyRecord | null> = { current: null };
|
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||||
|
|
||||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
||||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||||
|
@ -69,23 +69,23 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
|
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
||||||
group: true,
|
group: true,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=\.SIZE_32\)}\);)/,
|
match: /(?<=\.AVATAR_SIZE\);)/,
|
||||||
replace: "$self.useAccountPanelRef();"
|
replace: "$self.useAccountPanelRef();"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
||||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
|
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
||||||
replace: "$&onRequestClose:$self.onPopoutClose,"
|
replace: "$&onRequestClose:$self.onPopoutClose,"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=.avatarWrapper,)/,
|
match: /(?<=\.avatarWrapper,)/,
|
||||||
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -112,17 +112,17 @@ export default definePlugin({
|
||||||
openAlternatePopout = false;
|
openAlternatePopout = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
|
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalRenderPopout }: UserProfileProps) => {
|
||||||
if (
|
if (
|
||||||
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
||||||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
||||||
) {
|
) {
|
||||||
return originalPopout();
|
return originalRenderPopout();
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentChannel = getCurrentChannel();
|
const currentChannel = getCurrentChannel();
|
||||||
if (currentChannel?.getGuildId() == null) {
|
if (currentChannel?.getGuildId() == null) {
|
||||||
return originalPopout();
|
return originalRenderPopout();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -41,10 +41,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Status emojis
|
// Status emojis
|
||||||
find: ".Messages.GUILD_OWNER,",
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
|
||||||
replace: "!0"
|
replace: (_, rest) => `${rest}!0`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,10 +28,18 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: 'action:"EXPAND_ROLES"',
|
find: 'action:"EXPAND_ROLES"',
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
|
{
|
||||||
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
|
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
|
||||||
}
|
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
|
||||||
|
// which makes the collapse button never show up and calculation never occur
|
||||||
|
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
|
||||||
|
replace: isExpanded => "false"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "bitbucket.org",
|
find: "bitbucket.org",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
|
match: /function \i\(\i\){(?=.{0,30}pathname:\i)/,
|
||||||
replace: "$&return null;"
|
replace: "$&return null;"
|
||||||
},
|
},
|
||||||
predicate: () => settings.store.file
|
predicate: () => settings.store.file
|
||||||
|
|
|
@ -21,12 +21,12 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByProps, findComponentByCode } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
type AnonUpload = Upload & { anonymise?: boolean; };
|
type AnonUpload = Upload & { anonymise?: boolean; };
|
||||||
|
|
||||||
const ActionBarIcon = findComponentByCode(".actionBarIcon)");
|
const ActionBarIcon = findByCodeLazy(".actionBarIcon)");
|
||||||
const UploadDraft = findByProps("popFirstFile", "update");
|
const UploadDraft = findByPropsLazy("popFirstFile", "update");
|
||||||
|
|
||||||
const enum Methods {
|
const enum Methods {
|
||||||
Random,
|
Random,
|
||||||
|
@ -86,9 +86,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
|
find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
|
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
|
||||||
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
|
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
179
src/plugins/antiTessie/index.ts
Normal file
179
src/plugins/antiTessie/index.ts
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MessageExtra, MessageObject } from "@api/MessageEvents";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { findLazy } from "@webpack";
|
||||||
|
|
||||||
|
const TesseractLogger = new Logger("Tesseract", "#ff9e64");
|
||||||
|
|
||||||
|
let worker!: Tesseract.Worker;
|
||||||
|
function reduceBboxGreatest(acc: Tesseract.Bbox, cur: Tesseract.Bbox): Tesseract.Bbox {
|
||||||
|
return {
|
||||||
|
x0: Math.min(acc.x0, cur.x0),
|
||||||
|
x1: Math.max(acc.x1, cur.x1),
|
||||||
|
y0: Math.min(acc.y0, cur.y0),
|
||||||
|
y1: Math.max(acc.y1, cur.y1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function findWordLocation(text: Tesseract.Block[], regex: RegExp): Tesseract.Bbox[] {
|
||||||
|
const locs: Tesseract.Bbox[] = [];
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const block = text[i];
|
||||||
|
if (block.text.match(regex)) {
|
||||||
|
const bl = locs.length;
|
||||||
|
for (let j = 0; j < block.paragraphs.length; j++) {
|
||||||
|
const paragraph = block.paragraphs[j];
|
||||||
|
if (paragraph.text.match(regex)) {
|
||||||
|
const bl = locs.length;
|
||||||
|
for (let k = 0; k < paragraph.lines.length; k++) {
|
||||||
|
const line = paragraph.lines[k];
|
||||||
|
if (line.text.match(regex)) {
|
||||||
|
const bl = locs.length;
|
||||||
|
for (let l = 0; l < line.words.length; l++) {
|
||||||
|
const word = line.words[l];
|
||||||
|
let matches: RegExpExecArray[];
|
||||||
|
if ((matches = [...word.text.matchAll(new RegExp(regex, `${regex.flags.replace("g", "")}g`))]).length) {
|
||||||
|
for (const match of matches) {
|
||||||
|
const syms = word.symbols
|
||||||
|
.slice(match.index, match.index + match[0].length)
|
||||||
|
.map(x => x.bbox)
|
||||||
|
.reduce(reduceBboxGreatest);
|
||||||
|
locs.push(syms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (locs.length === bl) {
|
||||||
|
locs.push(line.bbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (locs.length === bl) {
|
||||||
|
locs.push(paragraph.bbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (locs.length === bl) {
|
||||||
|
locs.push(block.bbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return locs;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CloudUpload {
|
||||||
|
new(file: { file: File; isThumbnail: boolean; platform: number; }, channelId: string, showDiaglog: boolean, numCurAttachments: number): CloudUpload;
|
||||||
|
upload(): void;
|
||||||
|
}
|
||||||
|
const CloudUpload: CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
|
||||||
|
|
||||||
|
function getImage(file: File): Promise<HTMLImageElement> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = URL.createObjectURL(file);
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
|
||||||
|
return new Promise<Blob>(resolve => {
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
if (blob) {
|
||||||
|
resolve(blob);
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to create Blob");
|
||||||
|
}
|
||||||
|
}, "image/png");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const badRegex = /nix(?:os)?|This ?content ?is|blocked ?by ?this ?server/i;
|
||||||
|
export default definePlugin({
|
||||||
|
name: "AntiTessie",
|
||||||
|
authors: [Devs.sadan],
|
||||||
|
description: "Scans your messages with ocr for anything that matches the selected regex, and if found, blurs it",
|
||||||
|
|
||||||
|
start() {
|
||||||
|
if (!window?.Tesseract) {
|
||||||
|
fetch(
|
||||||
|
"https://unpkg.com/tesseract.js@6.0.0/dist/tesseract.min.js"
|
||||||
|
)
|
||||||
|
.then(async r => void (0, eval)(await r.text()))
|
||||||
|
.then(async () => {
|
||||||
|
worker = await Tesseract.createWorker("eng", Tesseract.OEM.TESSERACT_LSTM_COMBINED, {
|
||||||
|
corePath: "https://unpkg.com/tesseract.js-core@6.0.0/tesseract-core-simd-lstm.wasm.js",
|
||||||
|
workerPath: "https://unpkg.com/tesseract.js@6.0.0/dist/worker.min.js",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
worker.setParameters({
|
||||||
|
tessedit_pageseg_mode: Tesseract.PSM.AUTO
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
worker.terminate();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async onBeforeMessageSend(channelId: string, message: MessageObject, extra: MessageExtra): Promise<void | { cancel: boolean; }> {
|
||||||
|
if (extra.channel.guild_id !== "1015060230222131221" && extra.channel.guild_id !== "1041012073603289109") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploads = extra?.uploads ?? [];
|
||||||
|
for (let i = 0; i < uploads.length; i++) {
|
||||||
|
async function convertToFile(canvas: HTMLCanvasElement): Promise<File> {
|
||||||
|
const blob = await canvasToBlob(canvas);
|
||||||
|
return new File([blob], `${upload.filename.substring(0, upload.filename.lastIndexOf("."))}.png`, {
|
||||||
|
type: "image/png"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const upload = uploads[i];
|
||||||
|
|
||||||
|
if (!upload.isImage) continue;
|
||||||
|
|
||||||
|
console.log(upload);
|
||||||
|
|
||||||
|
const ret = await worker.recognize(upload.item.file, {
|
||||||
|
}, {
|
||||||
|
text: true,
|
||||||
|
blocks: true,
|
||||||
|
});
|
||||||
|
if (ret.data.text.match(badRegex)) {
|
||||||
|
const toBlur = findWordLocation(ret.data.blocks!, badRegex);
|
||||||
|
|
||||||
|
const sourceImage = await getImage(upload.item.file);
|
||||||
|
const width = sourceImage.naturalWidth;
|
||||||
|
const height = sourceImage.naturalHeight;
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.canvas.width = width;
|
||||||
|
ctx.canvas.height = height;
|
||||||
|
|
||||||
|
ctx.drawImage(sourceImage, 0, 0, width, height);
|
||||||
|
for (const { x0, x1, y0, y1 } of toBlur) {
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
|
||||||
|
}
|
||||||
|
const newFile = await convertToFile(canvas);
|
||||||
|
const attachment = new CloudUpload({
|
||||||
|
file: newFile,
|
||||||
|
isThumbnail: false,
|
||||||
|
platform: 1
|
||||||
|
}, channelId, false, uploads.length);
|
||||||
|
attachment.upload();
|
||||||
|
extra.uploads![i] = attachment as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -4,6 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import { execFile } from "child_process";
|
import { execFile } from "child_process";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ interface RemoteData {
|
||||||
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
||||||
|
|
||||||
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
|
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
|
||||||
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
|
const APPLE_MUSIC_TOKEN_REGEX = canonicalizeMatch(/Promise.allSettled\(\i\)\}const \i="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)"/);
|
||||||
|
|
||||||
let cachedToken: string | undefined = undefined;
|
let cachedToken: string | undefined = undefined;
|
||||||
|
|
||||||
|
|
|
@ -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 { findByCode } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
const fetchApplicationsRPC = findByCode("APPLICATION_RPC(", "Client ID");
|
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];
|
||||||
|
@ -73,8 +73,8 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
|
// Legcord comes with its own arRPC implementation, so this plugin just confuses users
|
||||||
if ("armcord" in window) return;
|
if ("legcord" in window) return;
|
||||||
|
|
||||||
if (ws) ws.close();
|
if (ws) ws.close();
|
||||||
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "BAN_CONFIRM_TITLE.",
|
find: "#{intl::BAN_CONFIRM_TITLE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /src:\i\("?\d+"?\)/g,
|
match: /src:\i\("?\d+"?\)/g,
|
||||||
replace: "src:$self.source"
|
replace: "src:$self.source"
|
||||||
|
|
|
@ -17,15 +17,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { findByProps, findComponentByCode, findStore } from "@webpack";
|
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { useStateFromStores } from "@webpack/common";
|
import { Animations, useStateFromStores } from "@webpack/common";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
import { ExpandedGuildFolderStore, settings } from ".";
|
import { ExpandedGuildFolderStore, settings } from ".";
|
||||||
|
|
||||||
const ChannelRTCStore = findStore("ChannelRTCStore");
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
const Animations = findByProps("a", "animated", "useTransition");
|
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
||||||
const GuildsBar = findComponentByCode('("guildsnav")');
|
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(guildsBarProps => {
|
export default ErrorBoundary.wrap(guildsBarProps => {
|
||||||
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
||||||
|
|
|
@ -18,9 +18,10 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { find, findByProps, findStore } from "@webpack";
|
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||||
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
|
import { FluxDispatcher, useMemo } from "@webpack/common";
|
||||||
|
|
||||||
import FolderSideBar from "./FolderSideBar";
|
import FolderSideBar from "./FolderSideBar";
|
||||||
|
|
||||||
|
@ -30,10 +31,10 @@ enum FolderIconDisplay {
|
||||||
MoreThanOneFolderExpanded
|
MoreThanOneFolderExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExpandedGuildFolderStore = findStore("ExpandedGuildFolderStore");
|
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
const SortedGuildStore = findStore("SortedGuildStore");
|
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
||||||
const GuildsTree = find(m => m.prototype?.moveNextTo);
|
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
|
||||||
const FolderUtils = findByProps("move", "toggleGuildFolderExpand");
|
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||||
|
|
||||||
let lastGuildId = null as string | null;
|
let lastGuildId = null as string | null;
|
||||||
let dispatchingFoldersClose = false;
|
let dispatchingFoldersClose = false;
|
||||||
|
@ -122,7 +123,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||||
{
|
{
|
||||||
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
match: /lastTargetNode:\i\[\i\.length-1\].+?}\)(?::null)?\](?=}\))/,
|
||||||
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
|
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||||
|
@ -158,7 +159,7 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
|
find: ".expandedFolderBackground,",
|
||||||
predicate: () => settings.store.sidebar,
|
predicate: () => settings.store.sidebar,
|
||||||
replacement: [
|
replacement: [
|
||||||
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
|
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
|
||||||
|
@ -172,8 +173,8 @@ export default definePlugin({
|
||||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
|
match: /(?=,\{from:\{height)/,
|
||||||
replace: "$self.shouldShowTransition(arguments[0])&&"
|
replace: "&&$self.shouldShowTransition(arguments[0])"
|
||||||
},
|
},
|
||||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
{
|
{
|
||||||
|
@ -184,7 +185,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
match: /(?<=\.wrapper,children:\[)/,
|
match: /(?<=\.isExpanded\),children:\[)/,
|
||||||
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -205,7 +206,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.DISCODO_DISABLED",
|
find: "#{intl::DISCODO_DISABLED}",
|
||||||
predicate: () => settings.store.closeAllHomeButton,
|
predicate: () => settings.store.closeAllHomeButton,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Close all folders when clicking the home button
|
// Close all folders when clicking the home button
|
||||||
|
@ -275,19 +276,29 @@ export default definePlugin({
|
||||||
|
|
||||||
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
||||||
return child => {
|
return child => {
|
||||||
if (isBetterFolders) {
|
if (!isBetterFolders) return true;
|
||||||
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
|
|
||||||
|
try {
|
||||||
|
return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
|
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
|
||||||
return child => {
|
return child => {
|
||||||
if (isBetterFolders) {
|
if (!isBetterFolders) return true;
|
||||||
return child?.props?.onScroll != null;
|
|
||||||
|
if (child?.props?.className?.includes("itemsContainer") && child.props.children != null) {
|
||||||
|
// Filter out everything but the scroller for the guild list
|
||||||
|
child.props.children = child.props.children.filter(child => child?.props?.onScroll != null);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,9 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.GIF,",
|
find: "#{intl::GIF}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
|
match: /alt:(\i)=(\i\.\i\.string\(\i\.\i#{intl::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",
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -31,7 +31,7 @@ const settings = definePluginSettings({
|
||||||
noSpellCheck: {
|
noSpellCheck: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Disable spellcheck in notes",
|
description: "Disable spellcheck in notes",
|
||||||
disabled: () => settings.store.hide,
|
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -63,9 +63,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "Messages.NOTE_PLACEHOLDER",
|
find: "#{intl::NOTE_PLACEHOLDER}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.NOTE_PLACEHOLDER,/,
|
match: /#{intl::NOTE_PLACEHOLDER}\),/,
|
||||||
replace: "$&spellCheck:!$self.noSpellCheck,"
|
replace: "$&spellCheck:!$self.noSpellCheck,"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByProps } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
|
import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
|
||||||
|
|
||||||
const GuildSettingsActions = findByProps("open", "selectRole", "updateGuild");
|
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
||||||
|
|
||||||
const DeveloperMode = getUserSettingLazy("appearance", "developerMode");
|
const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!;
|
||||||
|
|
||||||
function PencilIcon() {
|
function PencilIcon() {
|
||||||
return (
|
return (
|
||||||
|
@ -83,7 +83,7 @@ export default definePlugin({
|
||||||
if (!role) return;
|
if (!role) return;
|
||||||
|
|
||||||
if (role.colorString) {
|
if (role.colorString) {
|
||||||
children.push(
|
children.unshift(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-copy-role-color"
|
id="vc-copy-role-color"
|
||||||
label="Copy Role Color"
|
label="Copy Role Color"
|
||||||
|
@ -93,22 +93,8 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role.icon) {
|
|
||||||
children.push(
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="vc-view-role-icon"
|
|
||||||
label="View Role Icon"
|
|
||||||
action={() => {
|
|
||||||
openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
|
|
||||||
}}
|
|
||||||
icon={ImageIcon}
|
|
||||||
/>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
||||||
children.push(
|
children.unshift(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-edit-role"
|
id="vc-edit-role"
|
||||||
label="Edit Role"
|
label="Edit Role"
|
||||||
|
@ -120,6 +106,24 @@ export default definePlugin({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role.icon) {
|
||||||
|
children.push(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-view-role-icon"
|
||||||
|
label="View Role Icon"
|
||||||
|
action={() => {
|
||||||
|
openImageModal({
|
||||||
|
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
|
||||||
|
height: 128,
|
||||||
|
width: 128
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
icon={ImageIcon}
|
||||||
|
/>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,32 +16,16 @@
|
||||||
* 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 { definePluginSettings } from "@api/Settings";
|
import { Settings } 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 { Clipboard, Toasts } from "@webpack/common";
|
import { Clipboard, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
bothStyles: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Show both role dot and coloured names",
|
|
||||||
restartNeeded: true,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
copyRoleColorInProfilePopout: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Allow click on role dot in profile popout to copy role color",
|
|
||||||
restartNeeded: true,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterRoleDot",
|
name: "BetterRoleDot",
|
||||||
authors: [Devs.Ven, Devs.AutumnVN],
|
authors: [Devs.Ven, Devs.AutumnVN],
|
||||||
description:
|
description:
|
||||||
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
|
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
@ -55,7 +39,7 @@ export default definePlugin({
|
||||||
find: '"dot"===',
|
find: '"dot"===',
|
||||||
all: true,
|
all: true,
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
predicate: () => settings.store.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
||||||
replace: "true",
|
replace: "true",
|
||||||
|
@ -63,9 +47,9 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
find: ".ADD_ROLE_A11Y_LABEL",
|
find: "#{intl::ADD_ROLE_A11Y_LABEL}",
|
||||||
all: true,
|
all: true,
|
||||||
predicate: () => settings.store.copyRoleColorInProfilePopout && !settings.store.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"dot"===\i/,
|
match: /"dot"===\i/,
|
||||||
|
@ -75,7 +59,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".roleVerifiedIcon",
|
find: ".roleVerifiedIcon",
|
||||||
all: true,
|
all: true,
|
||||||
predicate: () => settings.store.copyRoleColorInProfilePopout && !settings.store.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"dot"===\i/,
|
match: /"dot"===\i/,
|
||||||
|
@ -84,6 +68,21 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
bothStyles: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show both role dot and coloured names",
|
||||||
|
restartNeeded: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
copyRoleColorInProfilePopout: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Allow click on role dot in profile popout to copy role color",
|
||||||
|
restartNeeded: true,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
copyToClipBoard(color: string) {
|
copyToClipBoard(color: string) {
|
||||||
Clipboard.copy(color);
|
Clipboard.copy(color);
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
|
|
|
@ -99,7 +99,7 @@ export const UnknownIcon = (props: React.PropsWithChildren<SVGProps<SVGSVGElemen
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
>
|
>
|
||||||
<path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
|
<path fillRule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -21,20 +21,20 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByProps, findExportedComponent, findStore } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { RenameButton } from "./components/RenameButton";
|
import { RenameButton } from "./components/RenameButton";
|
||||||
import { Session, SessionInfo } from "./types";
|
import { Session, SessionInfo } from "./types";
|
||||||
import { fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, savedSessionsCache, saveSessionsToDataStore } from "./utils";
|
import { fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, savedSessionsCache, saveSessionsToDataStore } from "./utils";
|
||||||
|
|
||||||
const AuthSessionsStore = findStore("AuthSessionsStore");
|
const AuthSessionsStore = findStoreLazy("AuthSessionsStore");
|
||||||
const UserSettingsModal = findByProps("saveAccountChanges", "open");
|
const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
|
||||||
|
|
||||||
const TimestampClasses = findByProps("timestampTooltip", "blockquoteContainer");
|
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
||||||
const SessionIconClasses = findByProps("sessionIcon");
|
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
||||||
|
|
||||||
const BlobMask = findExportedComponent("BlobMask");
|
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
backgroundCheck: {
|
backgroundCheck: {
|
||||||
|
@ -60,7 +60,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT",
|
find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
|
||||||
replacement: [
|
replacement: [
|
||||||
// Replace children with a single label with state
|
// Replace children with a single label with state
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { isObjectEmpty } from "@utils/misc";
|
import { isObjectEmpty } from "@utils/misc";
|
||||||
import { Alerts, i18n, Menu, useMemo, useState } from "@webpack/common";
|
import { Alerts, Menu, useMemo, useState } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ export default function PluginsSubmenu() {
|
||||||
query={query}
|
query={query}
|
||||||
onChange={setQuery}
|
onChange={setQuery}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
placeholder={i18n.Messages.SEARCH}
|
placeholder={getIntlMessage("SEARCH")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -7,12 +7,11 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { SYM_PROXY_INNER_VALUE } from "@utils/proxyInner";
|
|
||||||
import { NoopComponent } from "@utils/react";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByProps } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
|
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
|
||||||
import type { HTMLAttributes, ReactElement } from "react";
|
import type { HTMLAttributes, ReactElement } from "react";
|
||||||
|
|
||||||
import PluginsSubmenu from "./PluginsSubmenu";
|
import PluginsSubmenu from "./PluginsSubmenu";
|
||||||
|
@ -20,7 +19,8 @@ import PluginsSubmenu from "./PluginsSubmenu";
|
||||||
type SettingsEntry = { section: string, label: string; };
|
type SettingsEntry = { section: string, label: string; };
|
||||||
|
|
||||||
const cl = classNameFactory("");
|
const cl = classNameFactory("");
|
||||||
const Classes = findByProps("animating", "baseLayer", "bg", "layer", "layers");
|
let Classes: Record<string, string>;
|
||||||
|
waitFor(["animating", "baseLayer", "bg", "layer", "layers"], m => Classes = m);
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
disableFade: {
|
disableFade: {
|
||||||
|
@ -101,8 +101,8 @@ export default definePlugin({
|
||||||
find: 'minimal:"contentColumnMinimal"',
|
find: 'minimal:"contentColumnMinimal"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\(0,\i\.useTransition\)\((\i)/,
|
match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
|
||||||
replace: "(_cb=>_cb(void 0,$1))||$&"
|
replace: "(_cb=>_cb(void 0,$1))||"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\i\.animated\.div/,
|
match: /\i\.animated\.div/,
|
||||||
|
@ -112,15 +112,15 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.disableFade
|
predicate: () => settings.store.disableFade
|
||||||
},
|
},
|
||||||
{ // Load menu TOC eagerly
|
{ // Load menu TOC eagerly
|
||||||
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await [^};]*?\)\)).*?,(?=\1\(this)/,
|
||||||
replace: "$&(async ()=>$2)(),"
|
replace: "$&(async ()=>$2)(),"
|
||||||
},
|
},
|
||||||
predicate: () => settings.store.eagerLoad
|
predicate: () => settings.store.eagerLoad
|
||||||
},
|
},
|
||||||
{ // Settings cog context menu
|
{ // Settings cog context menu
|
||||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||||
|
@ -143,7 +143,7 @@ export default definePlugin({
|
||||||
// try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but
|
// try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but
|
||||||
// not in children
|
// not in children
|
||||||
Layer(props: LayerProps) {
|
Layer(props: LayerProps) {
|
||||||
if (FocusLock === NoopComponent || ComponentDispatch[SYM_PROXY_INNER_VALUE] == null || Classes[SYM_PROXY_INNER_VALUE] == null) {
|
if (!FocusLock || !ComponentDispatch || !Classes) {
|
||||||
new Logger("BetterSettings").error("Failed to find some components");
|
new Logger("BetterSettings").error("Failed to find some components");
|
||||||
return props.children;
|
return props.children;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ export default definePlugin({
|
||||||
if (item.section === "HEADER") {
|
if (item.section === "HEADER") {
|
||||||
items.push({ label: item.label, items: [] });
|
items.push({ label: item.label, items: [] });
|
||||||
} else if (item.section === "DIVIDER") {
|
} else if (item.section === "DIVIDER") {
|
||||||
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
|
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
|
||||||
} else {
|
} else {
|
||||||
items.at(-1)!.items.push(item);
|
items.at(-1)!.items.push(item);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
map(render: (item: SettingsEntry) => ReactElement) {
|
map(render: (item: SettingsEntry) => ReactElement<any>) {
|
||||||
return items
|
return items
|
||||||
.filter(a => a.items.length > 0)
|
.filter(a => a.items.length > 0)
|
||||||
.map(({ label, items }) => {
|
.map(({ label, items }) => {
|
||||||
|
@ -181,11 +181,14 @@ export default definePlugin({
|
||||||
if (label) {
|
if (label) {
|
||||||
return (
|
return (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
|
key={label}
|
||||||
id={label.replace(/\W/, "_")}
|
id={label.replace(/\W/, "_")}
|
||||||
label={label}
|
label={label}
|
||||||
children={children}
|
|
||||||
action={children[0].props.action}
|
action={children[0].props.action}
|
||||||
/>);
|
>
|
||||||
|
{children}
|
||||||
|
</Menu.MenuItem>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"ChannelAttachButton"',
|
find: '"ChannelAttachButton"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
||||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,7 +57,11 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
|
||||||
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
||||||
if (!previewUrl) return;
|
if (!previewUrl) return;
|
||||||
|
|
||||||
openImageModal(previewUrl);
|
openImageModal({
|
||||||
|
url: previewUrl,
|
||||||
|
height: 720,
|
||||||
|
width: 1280
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
* 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 { findStore } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
|
|
||||||
import * as t from "./types/stores";
|
import * as t from "./types/stores";
|
||||||
|
|
||||||
export const ApplicationStreamPreviewStore: t.ApplicationStreamPreviewStore = findStore("ApplicationStreamPreviewStore");
|
export const ApplicationStreamPreviewStore: t.ApplicationStreamPreviewStore = findStoreLazy("ApplicationStreamPreviewStore");
|
||||||
export const ApplicationStreamingStore: t.ApplicationStreamingStore = findStore("ApplicationStreamingStore");
|
export const ApplicationStreamingStore: t.ApplicationStreamingStore = findStoreLazy("ApplicationStreamingStore");
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { Settings } 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";
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ function setCss() {
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
.vc-nsfw-img [class^=imageWrapper] img,
|
.vc-nsfw-img [class^=imageWrapper] img,
|
||||||
.vc-nsfw-img [class^=wrapperPaused] video {
|
.vc-nsfw-img [class^=wrapperPaused] video {
|
||||||
filter: blur(${settings.store.blurAmount}px);
|
filter: blur(${Settings.plugins.BlurNSFW.blurAmount}px);
|
||||||
transition: filter 0.2s;
|
transition: filter 0.2s;
|
||||||
}
|
}
|
||||||
.vc-nsfw-img [class^=imageWrapper]:hover img,
|
.vc-nsfw-img [class^=imageWrapper]:hover img,
|
||||||
|
@ -36,31 +36,30 @@ function setCss() {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
blurAmount: {
|
|
||||||
type: OptionType.NUMBER,
|
|
||||||
description: "Blur Amount",
|
|
||||||
default: 10,
|
|
||||||
onChange: setCss
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BlurNSFW",
|
name: "BlurNSFW",
|
||||||
description: "Blur attachments in NSFW channels until hovered",
|
description: "Blur attachments in NSFW channels until hovered",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".embedWrapper,embed",
|
find: ".embedWrapper,embed",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /\.embedWrapper(?=.+?channel_id:(\i)\.id)/g,
|
match: /\.container/,
|
||||||
replace: "$&+($1.nsfw?' vc-nsfw-img':'')"
|
replace: "$&+(this.props.channel.nsfw? ' vc-nsfw-img': '')"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
blurAmount: {
|
||||||
|
type: OptionType.NUMBER,
|
||||||
|
description: "Blur Amount",
|
||||||
|
default: 10,
|
||||||
|
onChange: setCss
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
style = document.createElement("style");
|
style = document.createElement("style");
|
||||||
style.id = "VcBlurNsfw";
|
style.id = "VcBlurNsfw";
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { useTimer } from "@utils/react";
|
import { useTimer } from "@utils/react";
|
||||||
|
@ -25,7 +25,7 @@ import { React } from "@webpack/common";
|
||||||
|
|
||||||
function formatDuration(ms: number) {
|
function formatDuration(ms: number) {
|
||||||
// here be dragons (moment fucking sucks)
|
// here be dragons (moment fucking sucks)
|
||||||
const human = settings.store.format === "human";
|
const human = Settings.plugins.CallTimer.format === "human";
|
||||||
|
|
||||||
const format = (n: number) => human ? n : n.toString().padStart(2, "0");
|
const format = (n: number) => human ? n : n.toString().padStart(2, "0");
|
||||||
const unit = (s: string) => human ? s : "";
|
const unit = (s: string) => human ? s : "";
|
||||||
|
@ -46,40 +46,40 @@ function formatDuration(ms: number) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
format: {
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
description: "The timer format. This can be any valid moment.js format",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "30d 23:00:42",
|
|
||||||
value: "stopwatch",
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "30d 23h 00m 42s",
|
|
||||||
value: "human"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "CallTimer",
|
name: "CallTimer",
|
||||||
description: "Adds a timer to vcs",
|
description: "Adds a timer to vcs",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
settings,
|
|
||||||
|
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
interval: void 0 as NodeJS.Timeout | undefined,
|
interval: void 0 as NodeJS.Timeout | undefined,
|
||||||
|
|
||||||
|
options: {
|
||||||
|
format: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "The timer format. This can be any valid moment.js format",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "30d 23:00:42",
|
||||||
|
value: "stopwatch",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "30d 23h 00m 42s",
|
||||||
|
value: "human"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "renderConnectionStatus(){",
|
find: "renderConnectionStatus(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i/,
|
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
|
||||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
renderTimer(channelId: string) {
|
renderTimer(channelId: string) {
|
||||||
return <ErrorBoundary noop>
|
return <ErrorBoundary noop>
|
||||||
<this.Timer channelId={channelId} />
|
<this.Timer channelId={channelId} />
|
||||||
|
|
|
@ -17,11 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addPreEditListener,
|
MessageObject
|
||||||
addPreSendListener,
|
|
||||||
MessageObject,
|
|
||||||
removePreEditListener,
|
|
||||||
removePreSendListener
|
|
||||||
} from "@api/MessageEvents";
|
} from "@api/MessageEvents";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -36,7 +32,18 @@ export default definePlugin({
|
||||||
name: "ClearURLs",
|
name: "ClearURLs",
|
||||||
description: "Removes tracking garbage from URLs",
|
description: "Removes tracking garbage from URLs",
|
||||||
authors: [Devs.adryd],
|
authors: [Devs.adryd],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
start() {
|
||||||
|
this.createRules();
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageSend(_, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
escapeRegExp(str: string) {
|
escapeRegExp(str: string) {
|
||||||
return (str && reHasRegExpChar.test(str))
|
return (str && reHasRegExpChar.test(str))
|
||||||
|
@ -133,17 +140,4 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
|
||||||
this.createRules();
|
|
||||||
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
|
||||||
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
|
||||||
this.onSend(msg)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removePreSendListener(this.preSend);
|
|
||||||
removePreEditListener(this.preEdit);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,10 +11,10 @@ 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 { findByCode, findComponentByCode, findStore } from "@webpack";
|
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
|
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
const ColorPicker = findComponentByCode(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
||||||
|
|
||||||
const colorPresets = [
|
const colorPresets = [
|
||||||
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
||||||
|
@ -30,20 +30,20 @@ function onPickColor(color: number) {
|
||||||
updateColorVars(hexColor);
|
updateColorVars(hexColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveClientTheme = findByCode('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"===');
|
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"===');
|
||||||
|
|
||||||
function setTheme(theme: string) {
|
function setTheme(theme: string) {
|
||||||
saveClientTheme({ theme });
|
saveClientTheme({ theme });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClientThemesBackgroundStore = findStore("ClientThemesBackgroundStore");
|
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
|
||||||
|
|
||||||
function ThemeSettings() {
|
function ThemeSettings() {
|
||||||
const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme);
|
const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme);
|
||||||
const isLightTheme = theme === "light";
|
const isLightTheme = theme === "light";
|
||||||
const oppositeTheme = isLightTheme ? "dark" : "light";
|
const oppositeTheme = isLightTheme ? "dark" : "light";
|
||||||
|
|
||||||
const nitroTheme = useStateFromStores([ClientThemesBackgroundStore], () => ClientThemesBackgroundStore.gradientPreset);
|
const nitroTheme = useStateFromStores([NitroThemeStore], () => NitroThemeStore.gradientPreset);
|
||||||
const nitroThemeEnabled = nitroTheme !== undefined;
|
const nitroThemeEnabled = nitroTheme !== undefined;
|
||||||
|
|
||||||
const selectedLuminance = relativeLuminance(settings.store.color);
|
const selectedLuminance = relativeLuminance(settings.store.color);
|
||||||
|
@ -91,15 +91,12 @@ function ThemeSettings() {
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
color: {
|
color: {
|
||||||
description: "Color your Discord client theme will be based around. Light mode isn't supported",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
default: "313338",
|
||||||
component: () => <ThemeSettings />
|
component: ThemeSettings
|
||||||
},
|
},
|
||||||
resetColor: {
|
resetColor: {
|
||||||
description: "Reset Theme Color",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
|
||||||
component: () => (
|
component: () => (
|
||||||
<Button onClick={() => onPickColor(0x313338)}>
|
<Button onClick={() => onPickColor(0x313338)}>
|
||||||
Reset Theme Color
|
Reset Theme Color
|
||||||
|
@ -110,7 +107,7 @@ const settings = definePluginSettings({
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ClientTheme",
|
name: "ClientTheme",
|
||||||
authors: [Devs.F53, Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
|
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,20 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
{
|
||||||
|
find: "https://github.com/highlightjs/highlight.js/issues/2277",
|
||||||
|
replacement: {
|
||||||
|
match: /\(console.log\(`Deprecated.+?`\),/,
|
||||||
|
replace: "("
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: 'The "interpolate" function is deprecated in v10 (use "to" instead)',
|
||||||
|
replacement: {
|
||||||
|
match: /,console.warn\(\i\+'The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: 'console.warn("Window state not initialized"',
|
find: 'console.warn("Window state not initialized"',
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -81,10 +95,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||||
all: true,
|
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -140,5 +153,5 @@ export default definePlugin({
|
||||||
replace: "$self.NoopLogger()"
|
replace: "$self.NoopLogger()"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,44 +18,43 @@
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
|
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
|
||||||
|
import { runtimeHashMessageKey } from "@utils/intlHash";
|
||||||
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
|
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
|
||||||
import { SYM_LAZY_COMPONENT_INNER } from "@utils/lazyReact";
|
import { ModalAPI } from "@utils/modal";
|
||||||
import { relaunch } from "@utils/native";
|
import { relaunch } from "@utils/native";
|
||||||
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
|
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
|
||||||
import { SYM_PROXY_INNER_GET, SYM_PROXY_INNER_VALUE } from "@utils/proxyInner";
|
|
||||||
import definePlugin, { PluginNative, StartAt } from "@utils/types";
|
import definePlugin, { PluginNative, StartAt } from "@utils/types";
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { cacheFindAll, cacheFindModuleId, extract, filters, searchFactories } 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 { loadLazyChunks } from "debug/loadLazyChunks";
|
||||||
|
import type { ComponentType } from "react";
|
||||||
|
|
||||||
const DESKTOP_ONLY = (f: string) => () => {
|
const DESKTOP_ONLY = (f: string) => () => {
|
||||||
throw new Error(`'${f}' is Discord Desktop only.`);
|
throw new Error(`'${f}' is Discord Desktop only.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const define: typeof Object.defineProperty =
|
||||||
|
(obj, prop, desc) => {
|
||||||
|
if (Object.hasOwn(desc, "value"))
|
||||||
|
desc.writable = true;
|
||||||
|
|
||||||
type Define = typeof Object.defineProperty;
|
return Object.defineProperty(obj, prop, {
|
||||||
const define: Define = (target, p, attributes) => {
|
configurable: true,
|
||||||
if (Object.hasOwn(attributes, "value")) {
|
enumerable: true,
|
||||||
attributes.writable = true;
|
...desc
|
||||||
}
|
});
|
||||||
|
};
|
||||||
return Object.defineProperty(target, p, {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
...attributes
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function makeShortcuts() {
|
function makeShortcuts() {
|
||||||
function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn, shouldReturnFactory = false) {
|
function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) {
|
||||||
const cache = new Map<string, unknown>();
|
const cache = new Map<string, unknown>();
|
||||||
|
|
||||||
return function (...filterProps: unknown[]) {
|
return function (...filterProps: unknown[]) {
|
||||||
const cacheKey = String(filterProps);
|
const cacheKey = String(filterProps);
|
||||||
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
if (cache.has(cacheKey)) return cache.get(cacheKey);
|
||||||
|
|
||||||
const matches = cacheFindAll(filterFactory(...filterProps), shouldReturnFactory);
|
const matches = findAll(filterFactory(...filterProps));
|
||||||
|
|
||||||
const result = (() => {
|
const result = (() => {
|
||||||
switch (matches.length) {
|
switch (matches.length) {
|
||||||
|
@ -63,72 +62,52 @@ function makeShortcuts() {
|
||||||
case 1: return matches[0];
|
case 1: return matches[0];
|
||||||
default:
|
default:
|
||||||
const uniqueMatches = [...new Set(matches)];
|
const uniqueMatches = [...new Set(matches)];
|
||||||
if (uniqueMatches.length > 1) {
|
if (uniqueMatches.length > 1)
|
||||||
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
|
console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
|
||||||
}
|
|
||||||
|
|
||||||
return uniqueMatches[0];
|
return matches[0];
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (result && cacheKey) cache.set(cacheKey, result);
|
if (result && cacheKey) cache.set(cacheKey, result);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let fakeRenderWin: WeakRef<Window> | undefined;
|
let fakeRenderWin: WeakRef<Window> | undefined;
|
||||||
|
|
||||||
const find = newFindWrapper(f => f);
|
const find = newFindWrapper(f => f);
|
||||||
const findByProps = newFindWrapper(filters.byProps);
|
const findByProps = newFindWrapper(filters.byProps);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])),
|
||||||
wp: Webpack,
|
wp: Webpack,
|
||||||
wpc: { getter: () => Webpack.cache },
|
wpc: { getter: () => Webpack.cache },
|
||||||
wreq: { getter: () => Webpack.wreq },
|
wreq: { getter: () => Webpack.wreq },
|
||||||
|
wpsearch: search,
|
||||||
WebpackInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances },
|
|
||||||
loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
|
|
||||||
|
|
||||||
wpsearch: searchFactories,
|
|
||||||
wpex: extract,
|
wpex: extract,
|
||||||
wpexs: (...code: Webpack.CodeFilter) => extract(cacheFindModuleId(...code)!),
|
wpexs: (code: string) => extract(findModuleId(code)!),
|
||||||
|
loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
|
||||||
filters,
|
|
||||||
find,
|
find,
|
||||||
findAll: cacheFindAll,
|
findAll: findAll,
|
||||||
findComponent: find,
|
|
||||||
findAllComponents: cacheFindAll,
|
|
||||||
findExportedComponent: (...props: Webpack.PropsFilter) => findByProps(...props)[props[0]],
|
|
||||||
findComponentByCode: newFindWrapper(filters.componentByCode),
|
|
||||||
findAllComponentsByCode: (...code: Webpack.PropsFilter) => cacheFindAll(filters.componentByCode(...code)),
|
|
||||||
findComponentByFields: newFindWrapper(filters.componentByFields),
|
|
||||||
findAllComponentsByFields: (...fields: Webpack.PropsFilter) => cacheFindAll(filters.componentByFields(...fields)),
|
|
||||||
findByProps,
|
findByProps,
|
||||||
findAllByProps: (...props: Webpack.PropsFilter) => cacheFindAll(filters.byProps(...props)),
|
findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
|
||||||
findProp: (...props: Webpack.PropsFilter) => findByProps(...props)[props[0]],
|
|
||||||
findByCode: newFindWrapper(filters.byCode),
|
findByCode: newFindWrapper(filters.byCode),
|
||||||
findAllByCode: (code: Webpack.CodeFilter) => cacheFindAll(filters.byCode(...code)),
|
findAllByCode: (code: string) => findAll(filters.byCode(code)),
|
||||||
|
findComponentByCode: newFindWrapper(filters.componentByCode),
|
||||||
|
findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)),
|
||||||
|
findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]],
|
||||||
findStore: newFindWrapper(filters.byStoreName),
|
findStore: newFindWrapper(filters.byStoreName),
|
||||||
findByFactoryCode: newFindWrapper(filters.byFactoryCode),
|
|
||||||
findAllByFactoryCode: (...code: Webpack.CodeFilter) => cacheFindAll(filters.byFactoryCode(...code)),
|
|
||||||
findModuleFactory: newFindWrapper(filters.byFactoryCode, true),
|
|
||||||
findAllModuleFactories: (...code: Webpack.CodeFilter) => cacheFindAll(filters.byFactoryCode(...code), true),
|
|
||||||
|
|
||||||
plugins: { getter: () => Vencord.Plugins.plugins },
|
|
||||||
PluginsApi: { getter: () => Vencord.Plugins },
|
PluginsApi: { getter: () => Vencord.Plugins },
|
||||||
|
plugins: { getter: () => Vencord.Plugins.plugins },
|
||||||
Settings: { getter: () => Vencord.Settings },
|
Settings: { getter: () => Vencord.Settings },
|
||||||
Api: { getter: () => Vencord.Api },
|
Api: { getter: () => Vencord.Api },
|
||||||
Util: { getter: () => Vencord.Util },
|
Util: { getter: () => Vencord.Util },
|
||||||
|
|
||||||
reload: () => location.reload(),
|
reload: () => location.reload(),
|
||||||
restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch,
|
restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch,
|
||||||
|
|
||||||
canonicalizeMatch,
|
canonicalizeMatch,
|
||||||
canonicalizeReplace,
|
canonicalizeReplace,
|
||||||
canonicalizeReplacement,
|
canonicalizeReplacement,
|
||||||
|
runtimeHashMessageKey,
|
||||||
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
|
fakeRender: (component: ComponentType, props: any) => {
|
||||||
fakeRender: (component: React.ComponentType<AnyRecord>, props: any) => {
|
|
||||||
const prevWin = fakeRenderWin?.deref();
|
const prevWin = fakeRenderWin?.deref();
|
||||||
const win = prevWin?.closed === false
|
const win = prevWin?.closed === false
|
||||||
? prevWin
|
? prevWin
|
||||||
|
@ -154,9 +133,14 @@ function makeShortcuts() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
|
const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div")));
|
||||||
|
root.render(Common.React.createElement(component, props));
|
||||||
|
|
||||||
|
doc.addEventListener("close", () => root.unmount(), { once: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
|
||||||
|
|
||||||
channel: { getter: () => getCurrentChannel(), preload: false },
|
channel: { getter: () => getCurrentChannel(), preload: false },
|
||||||
channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false },
|
channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false },
|
||||||
guild: { getter: () => getCurrentGuild(), preload: false },
|
guild: { getter: () => getCurrentGuild(), preload: false },
|
||||||
|
@ -164,8 +148,9 @@ function makeShortcuts() {
|
||||||
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 },
|
||||||
|
openModal: { getter: () => ModalAPI.openModal },
|
||||||
|
openModalLazy: { getter: () => ModalAPI.openModalLazy },
|
||||||
|
|
||||||
...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])),
|
|
||||||
Stores: {
|
Stores: {
|
||||||
getter: () => Object.fromEntries(
|
getter: () => Object.fromEntries(
|
||||||
Common.Flux.Store.getAll()
|
Common.Flux.Store.getAll()
|
||||||
|
@ -182,18 +167,16 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
|
||||||
|
|
||||||
function unwrapProxy(value: any) {
|
function unwrapProxy(value: any) {
|
||||||
if (value[SYM_LAZY_GET]) {
|
if (value[SYM_LAZY_GET]) {
|
||||||
return forceLoad ? value[SYM_LAZY_GET]() : value[SYM_LAZY_CACHED];
|
forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
|
||||||
} else if (value[SYM_PROXY_INNER_GET]) {
|
} else if (value.$$vencordInternal) {
|
||||||
return forceLoad ? value[SYM_PROXY_INNER_GET]() : value[SYM_PROXY_INNER_VALUE];
|
return forceLoad ? value.$$vencordInternal() : value;
|
||||||
} else if (value[SYM_LAZY_COMPONENT_INNER]) {
|
|
||||||
return value[SYM_LAZY_COMPONENT_INNER]() != null ? value[SYM_LAZY_COMPONENT_INNER]() : value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = unwrapProxy(currentVal);
|
const value = unwrapProxy(currentVal);
|
||||||
if (value != null && typeof value === "object") {
|
if (typeof value === "object" && value !== null) {
|
||||||
const descriptors = Object.getOwnPropertyDescriptors(value);
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
||||||
|
|
||||||
for (const propKey in descriptors) {
|
for (const propKey in descriptors) {
|
||||||
|
@ -212,6 +195,7 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
define(window.shortcutList, key, { value });
|
define(window.shortcutList, key, { value });
|
||||||
|
define(window, key, { value });
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -222,15 +206,23 @@ export default definePlugin({
|
||||||
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: 'this,"_changeCallbacks",',
|
||||||
|
replacement: {
|
||||||
|
match: /\i\(this,"_changeCallbacks",/,
|
||||||
|
replace: "Reflect.defineProperty(this,Symbol.toStringTag,{value:this.getName(),configurable:!0,writable:!0,enumerable:!1}),$&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
startAt: StartAt.Init,
|
startAt: StartAt.Init,
|
||||||
start() {
|
start() {
|
||||||
const shortcuts = makeShortcuts();
|
const shortcuts = makeShortcuts();
|
||||||
window.shortcutList = {};
|
window.shortcutList = {};
|
||||||
|
|
||||||
for (const key in shortcuts) {
|
for (const [key, val] of Object.entries(shortcuts)) {
|
||||||
const val = shortcuts[key];
|
if ("getter" in val) {
|
||||||
|
|
||||||
if (Object.hasOwn(val, "getter")) {
|
|
||||||
define(window.shortcutList, key, {
|
define(window.shortcutList, key, {
|
||||||
get: () => loadAndCacheShortcut(key, val, true)
|
get: () => loadAndCacheShortcut(key, val, true)
|
||||||
});
|
});
|
||||||
|
@ -244,8 +236,8 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unproxy loaded modules
|
// unproxy loaded modules
|
||||||
Webpack.onceDiscordLoaded.then(() => {
|
Webpack.onceReady.then(() => {
|
||||||
setTimeout(() => this.eagerLoad(false), 1000);
|
setTimeout(() => this.eagerLoad(false), 1000);
|
||||||
|
|
||||||
if (!IS_WEB) {
|
if (!IS_WEB) {
|
||||||
|
@ -256,13 +248,13 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async eagerLoad(forceLoad: boolean) {
|
async eagerLoad(forceLoad: boolean) {
|
||||||
await Webpack.onceDiscordLoaded;
|
await Webpack.onceReady;
|
||||||
|
|
||||||
const shortcuts = makeShortcuts();
|
const shortcuts = makeShortcuts();
|
||||||
for (const key in shortcuts) {
|
|
||||||
const val = shortcuts[key];
|
|
||||||
|
|
||||||
if (!Object.hasOwn(val, "getter") || val.preload === false) continue;
|
for (const [key, val] of Object.entries(shortcuts)) {
|
||||||
|
if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loadAndCacheShortcut(key, val, forceLoad);
|
loadAndCacheShortcut(key, val, forceLoad);
|
||||||
} catch { } // swallow not found errors in DEV
|
} catch { } // swallow not found errors in DEV
|
||||||
|
|
|
@ -8,10 +8,10 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { copyWithToast } from "@utils/misc";
|
import { copyWithToast } from "@utils/misc";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findProp } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Menu } from "@webpack/common";
|
import { Menu } from "@webpack/common";
|
||||||
|
|
||||||
const convertNameToSurrogate = findProp("convertNameToSurrogate");
|
const { convertNameToSurrogate } = findByPropsLazy("convertNameToSurrogate");
|
||||||
|
|
||||||
interface Emoji {
|
interface Emoji {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -55,7 +55,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"expression-picker"(children, { target }: { target: Target; }) {
|
"expression-picker"(children, { target }: { target: Target }) {
|
||||||
if (target.dataset.type !== "emoji") return;
|
if (target.dataset.type !== "emoji") return;
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Obsidian, Devs.Nuckyz],
|
authors: [Devs.Obsidian, Devs.Nuckyz],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.PREVIEW_BYTES_LEFT.format(",
|
find: "#{intl::PREVIEW_BYTES_LEFT}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.footerGap.+?url:\i,fileName:\i,fileSize:\i}\),(?<=fileContents:(\i),bytesLeft:(\i).+?)/g,
|
match: /\.footerGap.+?url:\i,fileName:\i,fileSize:\i}\),(?<=fileContents:(\i),bytesLeft:(\i).+?)/g,
|
||||||
replace: "$&$self.addCopyButton({fileContents:$1,bytesLeft:$2}),"
|
replace: "$&$self.addCopyButton({fileContents:$1,bytesLeft:$2}),"
|
||||||
|
|
|
@ -23,12 +23,22 @@ import { Logger } from "@utils/Logger";
|
||||||
import { closeAllModals } from "@utils/modal";
|
import { closeAllModals } from "@utils/modal";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { maybePromptToUpdate } from "@utils/updater";
|
import { maybePromptToUpdate } from "@utils/updater";
|
||||||
import { findByProps } from "@webpack";
|
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||||
import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||||
|
|
||||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||||
const ModalStack = findByProps("pushLazy", "popAll");
|
|
||||||
const DraftManager = findByProps("clearDraft", "saveDraft");
|
const { ModalStack, DraftManager } = proxyLazyWebpack(() => {
|
||||||
|
const [ModalStack, DraftManager] = findBulk(
|
||||||
|
filters.byProps("pushLazy", "popAll"),
|
||||||
|
filters.byProps("clearDraft", "saveDraft"),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ModalStack,
|
||||||
|
DraftManager
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
attemptToPreventCrashes: {
|
attemptToPreventCrashes: {
|
||||||
|
@ -57,7 +67,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
find: "#{intl::ERRORS_UNEXPECTED_CRASH}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /this\.setState\((.+?)\)/,
|
match: /this\.setState\((.+?)\)/,
|
||||||
replace: "$self.handleCrash(this,$1);"
|
replace: "$self.handleCrash(this,$1);"
|
||||||
|
|
|
@ -42,10 +42,11 @@ export default definePlugin({
|
||||||
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
||||||
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
||||||
{
|
{
|
||||||
find: ".ENTER&&(!",
|
find: ".selectPreviousCommandOption(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "$self.shouldSubmit($1, $2)"
|
match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
|
||||||
|
replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,9 +16,10 @@
|
||||||
* 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 { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { isTruthy } from "@utils/guards";
|
import { isTruthy } from "@utils/guards";
|
||||||
|
@ -26,16 +27,15 @@ import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCode, findComponentByCode } from "@webpack";
|
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const useProfileThemeStyle = findByCode("profileThemeStyle:", "--profile-gradient-primary-color");
|
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
|
||||||
const ActivityComponent = findComponentByCode("onOpenGameProfile");
|
const ActivityView = findComponentByCodeLazy(".party?(0", ".card");
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame");
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ const settings = definePluginSettings({
|
||||||
value: TimestampMode.NOW
|
value: TimestampMode.NOW
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Same as your current time",
|
label: "Same as your current time (not reset after 24h)",
|
||||||
value: TimestampMode.TIME
|
value: TimestampMode.TIME
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -260,7 +260,7 @@ const settings = definePluginSettings({
|
||||||
|
|
||||||
function onChange() {
|
function onChange() {
|
||||||
setRpc(true);
|
setRpc(true);
|
||||||
if (Vencord.Plugins.isPluginEnabled("CustomRPC")) setRpc();
|
if (Settings.plugins.CustomRPC.enabled) setRpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStreamLinkDisabled() {
|
function isStreamLinkDisabled() {
|
||||||
|
@ -269,6 +269,7 @@ function isStreamLinkDisabled() {
|
||||||
|
|
||||||
function isStreamLinkValid(value: string) {
|
function isStreamLinkValid(value: string) {
|
||||||
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
|
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
|
||||||
|
if (value && value.length > 512) return "Streaming link must be not longer than 512 characters.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,8 +278,9 @@ function isTimestampDisabled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isImageKeyValid(value: string) {
|
function isImageKeyValid(value: string) {
|
||||||
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image. (e.g. https://i.imgur.com/...)";
|
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead.";
|
||||||
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image. (e.g. https://media.tenor.com/...)";
|
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'";
|
||||||
|
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image (e.g. https://media.tenor.com/...). Right click the GIF and click 'Copy image address'";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,13 +392,24 @@ async function setRpc(disable?: boolean) {
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "CustomRPC",
|
name: "CustomRPC",
|
||||||
description: "Allows you to set a custom rich presence.",
|
description: "Add a fully customisable Rich Presence (Game status) to your Discord profile",
|
||||||
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||||
dependencies: ["UserSettingsAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
start: setRpc,
|
start: setRpc,
|
||||||
stop: () => setRpc(true),
|
stop: () => setRpc(true),
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".party?(0",
|
||||||
|
all: true,
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.id===\i\.id\?null:/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
settingsAboutComponent: () => {
|
settingsAboutComponent: () => {
|
||||||
const activity = useAwaiter(createActivity);
|
const activity = useAwaiter(createActivity);
|
||||||
const gameActivityEnabled = ShowCurrentGame.useSetting();
|
const gameActivityEnabled = ShowCurrentGame.useSetting();
|
||||||
|
@ -410,7 +423,7 @@ export default definePlugin({
|
||||||
style={{ padding: "1em" }}
|
style={{ padding: "1em" }}
|
||||||
>
|
>
|
||||||
<Forms.FormTitle>Notice</Forms.FormTitle>
|
<Forms.FormTitle>Notice</Forms.FormTitle>
|
||||||
<Forms.FormText>Game activity isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
|
<Forms.FormText>Activity Sharing isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color={Button.Colors.TRANSPARENT}
|
color={Button.Colors.TRANSPARENT}
|
||||||
|
@ -422,24 +435,33 @@ export default definePlugin({
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Forms.FormText>
|
<Flex flexDirection="column" style={{ gap: ".5em" }} className={Margins.top16}>
|
||||||
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
<Forms.FormText>
|
||||||
get the application ID.
|
Go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||||
</Forms.FormText>
|
get the application ID.
|
||||||
<Forms.FormText>
|
</Forms.FormText>
|
||||||
Upload images in the Rich Presence tab to get the image keys.
|
<Forms.FormText>
|
||||||
</Forms.FormText>
|
Upload images in the Rich Presence tab to get the image keys.
|
||||||
<Forms.FormText>
|
</Forms.FormText>
|
||||||
If you want to use image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and select "Copy image address".
|
<Forms.FormText>
|
||||||
</Forms.FormText>
|
If you want to use an image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and selecting "Copy image address".
|
||||||
|
</Forms.FormText>
|
||||||
|
<Forms.FormText>
|
||||||
|
You can't see your own buttons on your profile, but everyone else can see it fine.
|
||||||
|
</Forms.FormText>
|
||||||
|
<Forms.FormText>
|
||||||
|
Some weird unicode text ("fonts" 𝖑𝖎𝖐𝖊 𝖙𝖍𝖎𝖘) may cause the rich presence to not show up, try using normal letters instead.
|
||||||
|
</Forms.FormText>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top8} />
|
<Forms.FormDivider className={Margins.top8} />
|
||||||
|
|
||||||
<div style={{ width: "284px", ...profileThemeStyle, padding: 8, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
|
<div style={{ width: "284px", ...profileThemeStyle, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
|
||||||
{activity[0] && <ActivityComponent activity={activity[0]} channelId={SelectedChannelStore.getChannelId()}
|
{activity[0] && <ActivityView
|
||||||
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
|
activity={activity[0]}
|
||||||
application={{ id: settings.store.appID }}
|
user={UserStore.getCurrentUser()}
|
||||||
user={UserStore.getCurrentUser()} />}
|
currentUser={UserStore.getCurrentUser()}
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "DefaultCustomizationSections",
|
find: "DefaultCustomizationSections",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/,
|
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
|
||||||
replace: "$self.DecorSection(),"
|
replace: "$self.DecorSection(),"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue