From 47de9fab2efc4edd29aca46eca87c931607c13dc Mon Sep 17 00:00:00 2001
From: Manti <67705577+mantikafasi@users.noreply.github.com>
Date: Sun, 18 Dec 2022 01:30:29 +0300
Subject: [PATCH 01/24] Make some changes to reviewdb ui and add badges to it
(#245)
---
.../reviewDB/components/ReviewBadge.tsx | 45 ++++++++++++
.../reviewDB/components/ReviewComponent.tsx | 21 ++++--
.../reviewDB/components/ReviewsView.tsx | 70 +++++++++----------
src/plugins/reviewDB/entities/Badge.ts | 26 +++++++
src/plugins/reviewDB/entities/Review.ts | 3 +
src/webpack/common.tsx | 4 ++
6 files changed, 126 insertions(+), 43 deletions(-)
create mode 100644 src/plugins/reviewDB/components/ReviewBadge.tsx
create mode 100644 src/plugins/reviewDB/entities/Badge.ts
diff --git a/src/plugins/reviewDB/components/ReviewBadge.tsx b/src/plugins/reviewDB/components/ReviewBadge.tsx
new file mode 100644
index 000000000..4a3c0c4c1
--- /dev/null
+++ b/src/plugins/reviewDB/components/ReviewBadge.tsx
@@ -0,0 +1,45 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { MaskedLinkStore, Tooltip } from "@webpack/common";
+
+import { Badge } from "../entities/Badge";
+
+export default function ReviewBadge(badge: Badge) {
+ return (
+
+ {({ onMouseEnter, onMouseLeave }) => (
+
+ MaskedLinkStore.openUntrustedLink({
+ href: badge.redirect_url,
+ })
+ }
+ />
+ )}
+
+ );
+}
diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx
index 8808ccd5f..ddb49223d 100644
--- a/src/plugins/reviewDB/components/ReviewComponent.tsx
+++ b/src/plugins/reviewDB/components/ReviewComponent.tsx
@@ -24,6 +24,7 @@ import { Review } from "../entities/Review";
import { deleteReview, reportReview } from "../Utils/ReviewDBAPI";
import { canDeleteReview, openUserProfileModal, showToast } from "../Utils/Utils";
import MessageButton from "./MessageButton";
+import ReviewBadge from "./ReviewBadge";
export default LazyComponent(() => {
// this is terrible, blame mantika
@@ -78,24 +79,32 @@ export default LazyComponent(() => {
}
return (
-
- <>
-
- User Reviews
-
- {reviews?.map(review =>
-
- )}
- {reviews?.length === 0 && (
-
- Looks like nobody reviewed this user yet. You could be the first!
-
- )}
-
);
}
diff --git a/src/plugins/reviewDB/entities/Badge.ts b/src/plugins/reviewDB/entities/Badge.ts
new file mode 100644
index 000000000..4c3bd1eb5
--- /dev/null
+++ b/src/plugins/reviewDB/entities/Badge.ts
@@ -0,0 +1,26 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+
+export interface Badge {
+ badge_name: string;
+ badge_description: string;
+ badge_icon: string;
+ redirect_url: string;
+ badge_type: number;
+}
diff --git a/src/plugins/reviewDB/entities/Review.ts b/src/plugins/reviewDB/entities/Review.ts
index 662c91a47..12c3d5066 100644
--- a/src/plugins/reviewDB/entities/Review.ts
+++ b/src/plugins/reviewDB/entities/Review.ts
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import { Badge } from "./Badge";
+
export interface Review {
comment: string,
id: number,
@@ -24,4 +26,5 @@ export interface Review {
star: number,
username: string,
profile_photo: string;
+ badges: Badge[];
}
diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx
index 42db7589f..31175f95b 100644
--- a/src/webpack/common.tsx
+++ b/src/webpack/common.tsx
@@ -290,3 +290,7 @@ export const ContextMenu = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', {
options?: { enableSpellCheck?: boolean; }
): void;
};
+
+export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', {
+ openUntrustedLink: filters.byCode(".apply(this,arguments)")
+});
From 4974c53f9cc3a3adccfa11f4af68ac4f190b0fc8 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 18 Dec 2022 01:13:34 -0300
Subject: [PATCH 02/24] Improve PronounDB patch (#348)
---
src/plugins/pronoundb/index.ts | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts
index 49b25f754..c8481105d 100644
--- a/src/plugins/pronoundb/index.ts
+++ b/src/plugins/pronoundb/index.ts
@@ -43,17 +43,10 @@ export default definePlugin({
},
// Hijack the discord pronouns section (hidden without experiment) and add a wrapper around the text section
{
- find: "currentPronouns:",
- all: true,
- noWarn: true,
+ find: ".Messages.BOT_PROFILE_SLASH_COMMANDS",
replacement: {
- match: /\(0,.{1,3}\.jsxs?\)\((.{1,10}),(\{[^[}]*currentPronouns:[^}]*(\w)\.pronouns[^}]*\})\)/,
- replace: (original, PronounComponent, pronounProps, fullProps) => {
- // UserSettings
- if (pronounProps.includes("onPronounsChange")) return original;
-
- return `${fullProps}&&Vencord.Plugins.plugins.PronounDB.PronounsProfileWrapper(${PronounComponent}, ${pronounProps}, ${fullProps})`;
- }
+ match: /\(0,.\.jsx\)\((?.{1,2}\..),(?{currentPronouns.+?:(?.{1,2})\.pronouns.+?})\)/,
+ replace: "$&&Vencord.Plugins.plugins.PronounDB.PronounsProfileWrapper($,$,$)"
}
},
// Make pronouns experiment be enabled by default
From 989bd36eeb6dd6c4b391900765847cdcf87484d9 Mon Sep 17 00:00:00 2001
From: Justice Almanzar
Date: Mon, 19 Dec 2022 17:59:54 -0500
Subject: [PATCH 03/24] refactor: identifier escapes + "self" group (#339)
Co-authored-by: Ven
---
.eslintrc.json | 4 +-
package.json | 3 +-
patches/eslint@8.28.0.patch | 45 +++++++++++++++
pnpm-lock.yaml | 26 +++++----
src/components/PatchHelper.tsx | 23 ++++++--
src/plugins/shikiCodeblocks/index.ts | 6 +-
.../shikiCodeblocks/{style.css => shiki.css} | 9 ++-
src/utils/patches.ts | 55 +++++++++++++++++++
src/utils/types.ts | 4 +-
src/webpack/patchWebpack.ts | 12 ++--
10 files changed, 157 insertions(+), 30 deletions(-)
create mode 100644 patches/eslint@8.28.0.patch
rename src/plugins/shikiCodeblocks/{style.css => shiki.css} (97%)
create mode 100644 src/utils/patches.ts
diff --git a/.eslintrc.json b/.eslintrc.json
index 9bbe4f5c5..4cb86e0e8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -84,7 +84,9 @@
"no-extra-semi": "error",
"consistent-return": ["warn", { "treatUndefinedAsUnspecified": true }],
"dot-notation": "error",
- "no-useless-escape": "error",
+ "no-useless-escape": ["error", {
+ "extra": "i"
+ }],
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
diff --git a/package.json b/package.json
index f0c31035f..fe83c6a56 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,8 @@
"packageManager": "pnpm@7.13.4",
"pnpm": {
"patchedDependencies": {
- "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch"
+ "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
+ "eslint@8.28.0": "patches/eslint@8.28.0.patch"
}
},
"webExt": {
diff --git a/patches/eslint@8.28.0.patch b/patches/eslint@8.28.0.patch
new file mode 100644
index 000000000..994481b91
--- /dev/null
+++ b/patches/eslint@8.28.0.patch
@@ -0,0 +1,45 @@
+diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
+index 2046a148a17fd1d5f3a4bbc9f45f7700259d11fa..f4898c6b57355a4fd72c43a9f32bf1a36a6ccf4a 100644
+--- a/lib/rules/no-useless-escape.js
++++ b/lib/rules/no-useless-escape.js
+@@ -97,12 +97,30 @@ module.exports = {
+ escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
+ },
+
+- schema: []
++ schema: [{
++ type: "object",
++ properties: {
++ extra: {
++ type: "string",
++ default: ""
++ },
++ extraCharClass: {
++ type: "string",
++ default: ""
++ },
++ },
++ additionalProperties: false
++ }]
+ },
+
+ create(context) {
++ const options = context.options[0] || {};
++ const { extra, extraCharClass } = options || ''
+ const sourceCode = context.getSourceCode();
+
++ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra))
++ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass))
++
+ /**
+ * Reports a node
+ * @param {ASTNode} node The node to report
+@@ -238,7 +256,7 @@ module.exports = {
+ .filter(charInfo => charInfo.escaped)
+
+ // Filter out characters that are valid to escape, based on their position in the regular expression.
+- .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text))
++ .filter(charInfo => !(charInfo.inCharClass ? CHARCLASS_ESCAPES : NON_CHARCLASS_ESCAPES).has(charInfo.text))
+
+ // Report all the remaining characters.
+ .forEach(charInfo => report(node, charInfo.index, charInfo.text));
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6f76ff3fe..bca41baab 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,6 +4,9 @@ patchedDependencies:
eslint-plugin-path-alias@1.0.0:
hash: m6sma4g6bh67km3q6igf6uxaja
path: patches/eslint-plugin-path-alias@1.0.0.patch
+ eslint@8.28.0:
+ hash: 7wc6icvgtg3uswirb5tpsbjnbe
+ path: patches/eslint@8.28.0.patch
specifiers:
'@types/diff': ^5.0.2
@@ -50,7 +53,7 @@ devDependencies:
diff: 5.1.0
discord-types: 1.3.26
esbuild: 0.15.16
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
eslint-import-resolver-alias: 1.1.2
eslint-plugin-header: 3.1.1_eslint@8.28.0
eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0
@@ -216,7 +219,7 @@ packages:
'@typescript-eslint/type-utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
'@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
debug: 4.3.4
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
ignore: 5.2.0
natural-compare-lite: 1.4.0
regexpp: 3.2.0
@@ -241,7 +244,7 @@ packages:
'@typescript-eslint/types': 5.45.0
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
debug: 4.3.4
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
typescript: 4.9.3
transitivePeerDependencies:
- supports-color
@@ -268,7 +271,7 @@ packages:
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
'@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
debug: 4.3.4
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
tsutils: 3.21.0_typescript@4.9.3
typescript: 4.9.3
transitivePeerDependencies:
@@ -312,7 +315,7 @@ packages:
'@typescript-eslint/scope-manager': 5.45.0
'@typescript-eslint/types': 5.45.0
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.28.0
semver: 7.3.7
@@ -894,7 +897,7 @@ packages:
peerDependencies:
eslint: '>=7.7.0'
dependencies:
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
dev: true
/eslint-plugin-path-alias/1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0:
@@ -902,7 +905,7 @@ packages:
peerDependencies:
eslint: ^7
dependencies:
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
nanomatch: 1.2.13
transitivePeerDependencies:
- supports-color
@@ -914,7 +917,7 @@ packages:
peerDependencies:
eslint: '>=5.0.0'
dependencies:
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
dev: true
/eslint-plugin-unused-imports/2.0.0_5am2datodjm2qi4eijrjrnoz54:
@@ -928,7 +931,7 @@ packages:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
eslint-rule-composer: 0.3.0
dev: true
@@ -959,7 +962,7 @@ packages:
peerDependencies:
eslint: '>=5'
dependencies:
- eslint: 8.28.0
+ eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe
eslint-visitor-keys: 2.1.0
dev: true
@@ -973,7 +976,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
- /eslint/8.28.0:
+ /eslint/8.28.0_7wc6icvgtg3uswirb5tpsbjnbe:
resolution: {integrity: sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
@@ -1020,6 +1023,7 @@ packages:
transitivePeerDependencies:
- supports-color
dev: true
+ patched: true
/espree/9.4.0:
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
diff --git a/src/components/PatchHelper.tsx b/src/components/PatchHelper.tsx
index 22c2b4dc0..cb6098028 100644
--- a/src/components/PatchHelper.tsx
+++ b/src/components/PatchHelper.tsx
@@ -18,6 +18,7 @@
import { debounce } from "@utils/debounce";
import { makeCodeblock } from "@utils/misc";
+import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches";
import { search } from "@webpack";
import { Button, Clipboard, Forms, Margins, Parser, React, Switch, Text, TextInput } from "@webpack/common";
@@ -41,20 +42,29 @@ const findCandidates = debounce(function ({ find, setModule, setError }) {
setModule([keys[0], candidates[keys[0]]]);
});
-function ReplacementComponent({ module, match, replacement, setReplacementError }) {
+interface ReplacementComponentProps {
+ module: [id: number, factory: Function];
+ match: string | RegExp;
+ replacement: string | ReplaceFn;
+ setReplacementError(error: any): void;
+}
+
+function ReplacementComponent({ module, match, replacement, setReplacementError }: ReplacementComponentProps) {
const [id, fact] = module;
const [compileResult, setCompileResult] = React.useState<[boolean, string]>();
const [patchedCode, matchResult, diff] = React.useMemo(() => {
const src: string = fact.toString().replaceAll("\n", "");
+ const canonicalMatch = canonicalizeMatch(match);
try {
- var patched = src.replace(match, replacement);
+ const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
+ var patched = src.replace(canonicalMatch, canonicalReplace as string);
setReplacementError(void 0);
} catch (e) {
setReplacementError((e as Error).message);
return ["", [], []];
}
- const m = src.match(match);
+ const m = src.match(canonicalMatch);
return [patched, m, makeDiff(src, patched, m)];
}, [id, match, replacement]);
@@ -179,9 +189,10 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
{Object.entries({
"$$": "Insert a $",
"$&": "Insert the entire match",
- "$`": "Insert the substring before the match",
+ "$`\u200b": "Insert the substring before the match",
"$'": "Insert the substring after the match",
- "$n": "Insert the nth capturing group ($1, $2...)"
+ "$n": "Insert the nth capturing group ($1, $2...)",
+ "$self": "Insert the plugin instance",
}).map(([placeholder, desc]) => (
{Parser.parse("`" + placeholder + "`")}: {desc}
@@ -206,7 +217,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
function PatchHelper() {
const [find, setFind] = React.useState("");
const [match, setMatch] = React.useState("");
- const [replacement, setReplacement] = React.useState("");
+ const [replacement, setReplacement] = React.useState("");
const [replacementError, setReplacementError] = React.useState();
diff --git a/src/plugins/shikiCodeblocks/index.ts b/src/plugins/shikiCodeblocks/index.ts
index fd6b04bf7..58e0048bf 100644
--- a/src/plugins/shikiCodeblocks/index.ts
+++ b/src/plugins/shikiCodeblocks/index.ts
@@ -22,7 +22,7 @@ import { wordsFromPascal, wordsToTitle } from "@utils/text";
import definePlugin, { OptionType } from "@utils/types";
import previewExampleText from "~fileContent/previewExample.tsx";
-import cssText from "~fileContent/style.css";
+import cssText from "~fileContent/shiki.css";
import { Settings } from "../../Vencord";
import { shiki } from "./api/shiki";
@@ -44,8 +44,8 @@ export default definePlugin({
{
find: "codeBlock:{react:function",
replacement: {
- match: /codeBlock:\{react:function\((.),(.),(.)\)\{/,
- replace: "$&return Vencord.Plugins.plugins.ShikiCodeblocks.renderHighlighter($1,$2,$3);",
+ match: /codeBlock:\{react:function\((\i),(\i),(\i)\)\{/,
+ replace: "$&return $self.renderHighlighter($1,$2,$3);",
},
},
],
diff --git a/src/plugins/shikiCodeblocks/style.css b/src/plugins/shikiCodeblocks/shiki.css
similarity index 97%
rename from src/plugins/shikiCodeblocks/style.css
rename to src/plugins/shikiCodeblocks/shiki.css
index b246db4c9..b871d9957 100644
--- a/src/plugins/shikiCodeblocks/style.css
+++ b/src/plugins/shikiCodeblocks/shiki.css
@@ -1,10 +1,13 @@
-.shiki-root {
- border-radius: 4px;
-
+.shiki-container {
+ border: 4px;
/* fallback background */
background-color: var(--background-secondary);
}
+.shiki-root {
+ border-radius: 4px;
+}
+
.shiki-root code {
display: block;
overflow-x: auto;
diff --git a/src/utils/patches.ts b/src/utils/patches.ts
new file mode 100644
index 000000000..8ecd68e80
--- /dev/null
+++ b/src/utils/patches.ts
@@ -0,0 +1,55 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { PatchReplacement } from "./types";
+
+export type ReplaceFn = (match: string, ...groups: string[]) => string;
+
+export function canonicalizeMatch(match: RegExp | string) {
+ if (typeof match === "string") return match;
+ const canonSource = match.source
+ .replaceAll("\\i", "[A-Za-z_$][\\w$]*");
+ return new RegExp(canonSource, match.flags);
+}
+
+export function canonicalizeReplace(replace: string | ReplaceFn, pluginName: string) {
+ if (typeof replace === "function") return replace;
+ return replace.replaceAll("$self", `Vencord.Plugins.plugins.${pluginName}`);
+}
+
+export function canonicalizeDescriptor(descriptor: TypedPropertyDescriptor, canonicalize: (value: T) => T) {
+ if (descriptor.get) {
+ const original = descriptor.get;
+ descriptor.get = function () {
+ return canonicalize(original.call(this));
+ };
+ } else if (descriptor.value) {
+ descriptor.value = canonicalize(descriptor.value);
+ }
+ return descriptor;
+}
+
+export function canonicalizeReplacement(replacement: Pick, plugin: string) {
+ const descriptors = Object.getOwnPropertyDescriptors(replacement);
+ descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch);
+ descriptors.replace = canonicalizeDescriptor(
+ descriptors.replace,
+ replace => canonicalizeReplace(replace, plugin),
+ );
+ Object.defineProperties(replacement, descriptors);
+}
diff --git a/src/utils/types.ts b/src/utils/types.ts
index fd8f02baa..d3083fcab 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -19,6 +19,8 @@
import { Command } from "@api/Commands";
import { Promisable } from "type-fest";
+import type { ReplaceFn } from "./patches";
+
// exists to export default definePlugin({...})
export default function definePlugin
(p: P & Record) {
return p;
@@ -26,7 +28,7 @@ export default function definePlugin
(p: P & Record string);
+ replace: string | ReplaceFn;
predicate?(): boolean;
}
diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts
index 8f11b6348..7b318b218 100644
--- a/src/webpack/patchWebpack.ts
+++ b/src/webpack/patchWebpack.ts
@@ -18,6 +18,8 @@
import { WEBPACK_CHUNK } from "@utils/constants";
import Logger from "@utils/Logger";
+import { canonicalizeReplacement } from "@utils/patches";
+import { PatchReplacement } from "@utils/types";
import { _initWebpack } from ".";
@@ -135,15 +137,17 @@ function patchPush() {
if (code.includes(patch.find)) {
patchedBy.add(patch.plugin);
- // @ts-ignore we change all patch.replacement to array in plugins/index
- for (const replacement of patch.replacement) {
+ // we change all patch.replacement to array in plugins/index
+ for (const replacement of patch.replacement as PatchReplacement[]) {
if (replacement.predicate && !replacement.predicate()) continue;
const lastMod = mod;
const lastCode = code;
+ canonicalizeReplacement(replacement, patch.plugin);
+
try {
- const newCode = code.replace(replacement.match, replacement.replace);
- if (newCode === code && !replacement.noWarn) {
+ const newCode = code.replace(replacement.match, replacement.replace as string);
+ if (newCode === code && !patch.noWarn) {
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`);
if (IS_DEV) {
logger.debug("Function Source:\n", code);
From 94ad8e8f61abb3c6256f9caa39878150b326790f Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 20 Dec 2022 00:33:52 +0100
Subject: [PATCH 04/24] Add useEffect/useState/useMemo to webpack commons
---
src/plugins/spotifyControls/PlayerComponent.tsx | 10 +++++-----
src/utils/misc.tsx | 8 ++++----
src/utils/react.ts | 4 ++--
src/webpack/common.tsx | 12 ++++++++++--
4 files changed, 21 insertions(+), 13 deletions(-)
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index 24394c6f8..af53f59c9 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -22,7 +22,7 @@ import { Link } from "@components/Link";
import { debounce } from "@utils/debounce";
import { classes, LazyComponent } from "@utils/misc";
import { filters, find, findByCodeLazy } from "@webpack";
-import { ContextMenu, FluxDispatcher, Forms, Menu, React } from "@webpack/common";
+import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState } from "@webpack/common";
import { SpotifyStore, Track } from "./SpotifyStore";
@@ -142,10 +142,10 @@ function SeekBar() {
() => [SpotifyStore.mPosition, SpotifyStore.isSettingPosition, SpotifyStore.isPlaying]
);
- const [position, setPosition] = React.useState(storePosition);
+ const [position, setPosition] = useState(storePosition);
// eslint-disable-next-line consistent-return
- React.useEffect(() => {
+ useEffect(() => {
if (isPlaying && !isSettingPosition) {
setPosition(SpotifyStore.position);
const interval = setInterval(() => {
@@ -232,7 +232,7 @@ function AlbumContextMenu({ track }: { track: Track; }) {
function Info({ track }: { track: Track; }) {
const img = track?.album?.image;
- const [coverExpanded, setCoverExpanded] = React.useState(false);
+ const [coverExpanded, setCoverExpanded] = useState(false);
const i = (
<>
@@ -327,7 +327,7 @@ export function Player() {
);
const isPlaying = useStateFromStores([SpotifyStore], () => SpotifyStore.isPlaying);
- const [shouldHide, setShouldHide] = React.useState(false);
+ const [shouldHide, setShouldHide] = useState(false);
// Hide player after 5 minutes of inactivity
// eslint-disable-next-line consistent-return
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index 8b7cea2da..6710523a7 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { Clipboard, React, Toasts } from "@webpack/common";
+import { Clipboard, React, Toasts, useEffect, useState } from "@webpack/common";
/**
* Makes a lazy function. On first call, the value is computed.
@@ -48,13 +48,13 @@ export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterO
deps: [],
onError: null,
}, providedOpts);
- const [state, setState] = React.useState({
+ const [state, setState] = useState({
value: opts.fallbackValue,
error: null,
pending: true
});
- React.useEffect(() => {
+ useEffect(() => {
let isAlive = true;
if (!state.pending) setState({ ...state, pending: true });
@@ -72,7 +72,7 @@ export function useAwaiter(factory: () => Promise, providedOpts?: AwaiterO
* Returns a function that can be used to force rerender react components
*/
export function useForceUpdater() {
- const [, set] = React.useState(0);
+ const [, set] = useState(0);
return () => set(s => s + 1);
}
diff --git a/src/utils/react.ts b/src/utils/react.ts
index 8585846c4..e5e1f677d 100644
--- a/src/utils/react.ts
+++ b/src/utils/react.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { React } from "@webpack/common";
+import { React, useState } from "@webpack/common";
import { checkIntersecting } from "./misc";
@@ -30,7 +30,7 @@ export const useIntersection = (intersectOnly = false): [
isIntersecting: boolean,
] => {
const observerRef = React.useRef(null);
- const [isIntersecting, setIntersecting] = React.useState(false);
+ const [isIntersecting, setIntersecting] = useState(false);
const refCallback = (element: Element | null) => {
observerRef.current?.disconnect();
diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx
index 31175f95b..f2c42d105 100644
--- a/src/webpack/common.tsx
+++ b/src/webpack/common.tsx
@@ -31,7 +31,12 @@ export const Margins = findByPropsLazy("marginTop20");
export let FluxDispatcher: Other.FluxDispatcher;
export const Flux = findByPropsLazy("connectStores");
+
export let React: typeof import("react");
+export let useState: typeof React.useState;
+export let useEffect: typeof React.useEffect;
+export let useMemo: typeof React.useMemo;
+
export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render");
export const RestAPI = findByPropsLazy("getAPIBaseURL", "get");
@@ -76,7 +81,7 @@ export const TextArea = findByCodeLazy("handleSetRef", "textArea") as React.Comp
export const Select = LazyComponent(() => findByCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
export const Slider = LazyComponent(() => findByCode("closestMarkerIndex", "stickToMarkers"));
-export let SnowflakeUtils: { fromTimestamp: (timestamp: number) => string, extractTimestamp: (snowflake: string) => number };
+export let SnowflakeUtils: { fromTimestamp: (timestamp: number) => string, extractTimestamp: (snowflake: string) => number; };
waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m);
export let Parser: any;
@@ -151,7 +156,10 @@ export const NavigationRouter = mapMangledModuleLazy("Transitioning to external
goForward: filters.byCode("goForward()"),
});
-waitFor("useState", m => React = m);
+waitFor("useState", m => {
+ React = m;
+ ({ useEffect, useState, useMemo } = React);
+});
waitFor(["dispatch", "subscribe"], m => {
FluxDispatcher = m;
From 0743c1215e539765cf4ea90a690ccdbab8a2cb55 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 20 Dec 2022 02:59:16 +0100
Subject: [PATCH 05/24] Add canary test
---
.github/workflows/reportBrokenPlugins.yml | 14 ++++++++++++++
test/generateReport.ts | 10 ++++++----
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/reportBrokenPlugins.yml b/.github/workflows/reportBrokenPlugins.yml
index 8511b42b9..719eca753 100644
--- a/.github/workflows/reportBrokenPlugins.yml
+++ b/.github/workflows/reportBrokenPlugins.yml
@@ -41,3 +41,17 @@ jobs:
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+
+ - name: Create Report (Canary)
+ timeout-minutes: 10
+ if: success() || failure() # even run if previous one failed
+ run: |
+ export PATH="$PWD/node_modules/.bin:$PATH"
+ export CHROMIUM_BIN=$(which chromium-browser)
+ export USE_CANARY=true
+
+ esbuild test/generateReport.ts > dist/report.mjs
+ node dist/report.mjs >> $GITHUB_STEP_SUMMARY
+ env:
+ DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
diff --git a/test/generateReport.ts b/test/generateReport.ts
index 57d4f96ed..9483225f4 100644
--- a/test/generateReport.ts
+++ b/test/generateReport.ts
@@ -31,13 +31,15 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
}
}
+const CANARY = process.env.USE_CANARY === "true";
+
const browser = await pup.launch({
headless: true,
executablePath: process.env.CHROMIUM_BIN
});
const page = await browser.newPage();
-await page.setUserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36");
+await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
function maybeGetError(handle: JSHandle) {
return (handle as JSHandle)?.getProperty("message")
@@ -65,7 +67,7 @@ function toCodeBlock(s: string) {
}
async function printReport() {
- console.log("# Vencord Report");
+ console.log("# Vencord Report" + (CANARY ? " (Canary)" : ""));
console.log();
console.log("## Bad Patches");
@@ -98,7 +100,7 @@ async function printReport() {
},
body: JSON.stringify({
description: "Here's the latest Vencord Report!",
- username: "Vencord Reporter",
+ username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
avatar_url: "https://cdn.discordapp.com/icons/1015060230222131221/f0204a918c6c9c9a43195997e97d8adf.webp",
embeds: [
{
@@ -271,4 +273,4 @@ await page.evaluateOnNewDocument(`
;(${runTime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
`);
-await page.goto("https://discord.com/login");
+await page.goto(CANARY ? "https://canary.discord.com/login" : "https://discord.com/login");
From 1742bb60204a21cc11c6c19fdcd699be26cac383 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 20 Dec 2022 12:18:15 -0300
Subject: [PATCH 06/24] Fix StartupTimings (#353)
---
src/plugins/startupTimings/index.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx
index 1dd71395e..2ab00a646 100644
--- a/src/plugins/startupTimings/index.tsx
+++ b/src/plugins/startupTimings/index.tsx
@@ -28,8 +28,8 @@ export default definePlugin({
patches: [{
find: "PAYMENT_FLOW_MODAL_TEST_PAGE,",
replacement: {
- match: /({section:[\w.]+?\.PAYMENT_FLOW_MODAL_TEST_PAGE,)/,
- replace: '{section:"StartupTimings",label:"Startup Timings",element:Vencord.Plugins.plugins.StartupTimings.StartupTimingPage},$1'
+ match: /{section:.{1,2}\..{1,3}\.PAYMENT_FLOW_MODAL_TEST_PAGE/,
+ replace: '{section:"StartupTimings",label:"Startup Timings",element:Vencord.Plugins.plugins.StartupTimings.StartupTimingPage},$&'
}
}],
StartupTimingPage: LazyComponent(() => require("./StartupTimingPage").default)
From 9dcafbf4689d36ee790c0a81409e87326798bcf2 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 20 Dec 2022 18:03:58 +0100
Subject: [PATCH 07/24] Fix Notices
Have I ever mentioned how terrible Discord's Notices code is?
---
src/plugins/apiNotices.ts | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/plugins/apiNotices.ts b/src/plugins/apiNotices.ts
index a93334bd1..c362f76db 100644
--- a/src/plugins/apiNotices.ts
+++ b/src/plugins/apiNotices.ts
@@ -16,12 +16,9 @@
* along with this program. If not, see .
*/
-import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
-migratePluginSettings("NoticesAPI", "NoticesApi");
-
export default definePlugin({
name: "NoticesAPI",
description: "Fixes notices being automatically dismissed",
@@ -29,12 +26,12 @@ export default definePlugin({
required: true,
patches: [
{
- find: "updateNotice:",
+ find: 'displayName="NoticeStore"',
replacement: [
{
- match: /;(.{1,2}=null;)(?=.{0,50}updateNotice)/g,
+ match: /;.{1,2}=null;.{0,70}getPremiumSubscription/g,
replace:
- ";if(Vencord.Api.Notices.currentNotice)return !1;$1"
+ ";if(Vencord.Api.Notices.currentNotice)return false$&"
},
{
match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/,
From 103c49931007c2aa223b39f25902b2dd1a015301 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 20 Dec 2022 17:43:55 +0100
Subject: [PATCH 08/24] Monaco Popup: Add metadata, store window instance
---
src/components/Monaco.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/components/Monaco.ts b/src/components/Monaco.ts
index aa98f998c..59ed7bbba 100644
--- a/src/components/Monaco.ts
+++ b/src/components/Monaco.ts
@@ -29,7 +29,12 @@ const setCss = debounce((css: string) => {
});
export async function launchMonacoEditor() {
- const win = open("about:blank", void 0, "popup,width=1000,height=1000")!;
+ const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
+ const win = open("about:blank", "VencordQuickCss", features);
+ if (!win) {
+ alert("Failed to open QuickCSS popup. Make sure to allow popups!");
+ return;
+ }
win.setCss = setCss;
win.getCurrentCss = () => VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
@@ -41,4 +46,6 @@ export async function launchMonacoEditor() {
: "vs-dark";
win.document.write(monacoHtml);
+
+ window.__VENCORD_MONACO_WIN__ = new WeakRef(win);
}
From 7e6077367a83096149578a062cfe7eb8b0fd3ba3 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 20 Dec 2022 19:54:47 -0300
Subject: [PATCH 09/24] feat(plugins): DisableDMCallIdle (#355)
---
src/plugins/DisableDMCallIdle.ts | 35 ++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 src/plugins/DisableDMCallIdle.ts
diff --git a/src/plugins/DisableDMCallIdle.ts b/src/plugins/DisableDMCallIdle.ts
new file mode 100644
index 000000000..c620f5447
--- /dev/null
+++ b/src/plugins/DisableDMCallIdle.ts
@@ -0,0 +1,35 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "DisableDMCallIdle",
+ description: "Disables automatically getting kicked from a DM voice call after 5 minutes.",
+ authors: [Devs.Nuckyz],
+ patches: [
+ {
+ find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
+ replacement: {
+ match: /function (?.{1,3})\(\){.{1,100}\.Messages\.BOT_CALL_IDLE_DISCONNECT.+?}}/,
+ replace: "function $(){}",
+ },
+ },
+ ],
+});
From 1f73cfa91a5a33ad73f3927ec3795a694c26922b Mon Sep 17 00:00:00 2001
From: V3L0C1T13S <51764975+V3L0C1T13S@users.noreply.github.com>
Date: Wed, 21 Dec 2022 10:12:05 -0500
Subject: [PATCH 10/24] EmoteCloner: Use CDN_HOST variable to support
unofficial backends (#356)
---
src/plugins/emoteCloner.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/emoteCloner.tsx b/src/plugins/emoteCloner.tsx
index 36b283e14..c22eebdc5 100644
--- a/src/plugins/emoteCloner.tsx
+++ b/src/plugins/emoteCloner.tsx
@@ -50,7 +50,7 @@ function getGuildCandidates(isAnimated: boolean) {
}
async function doClone(guildId: string, id: string, name: string, isAnimated: boolean) {
- const data = await fetch(`https://cdn.discordapp.com/emojis/${id}.${isAnimated ? "gif" : "png"}`)
+ const data = await fetch(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${id}.${isAnimated ? "gif" : "png"}`)
.then(r => r.blob());
const reader = new FileReader();
@@ -226,7 +226,7 @@ export default definePlugin({
Date: Wed, 21 Dec 2022 21:16:32 +0200
Subject: [PATCH 11/24] feat(PlatformIndicators): add indicator to messages
(#343)
---
src/api/Badges.ts | 17 +--
src/api/MemberListDecorators.ts | 65 +++++++++++
src/api/MessageDecorations.ts | 63 +++++++++++
src/api/index.ts | 12 ++-
src/plugins/apiBadges.tsx | 19 +++-
src/plugins/apiMemberListDecorators.ts | 42 ++++++++
src/plugins/apiMessageDecorations.ts | 35 ++++++
src/plugins/platformIndicators.tsx | 143 ++++++++++++++-----------
8 files changed, 321 insertions(+), 75 deletions(-)
create mode 100644 src/api/MemberListDecorators.ts
create mode 100644 src/api/MessageDecorations.ts
create mode 100644 src/plugins/apiMemberListDecorators.ts
create mode 100644 src/plugins/apiMessageDecorations.ts
diff --git a/src/api/Badges.ts b/src/api/Badges.ts
index 55e9b3a4c..3607f37eb 100644
--- a/src/api/Badges.ts
+++ b/src/api/Badges.ts
@@ -17,7 +17,7 @@
*/
import { User } from "discord-types/general";
-import { HTMLProps } from "react";
+import { ComponentType, HTMLProps } from "react";
import Plugins from "~plugins";
@@ -27,20 +27,21 @@ export enum BadgePosition {
}
export interface ProfileBadge {
- /** The tooltip to show on hover */
- tooltip: string;
+ /** The tooltip to show on hover. Required for image badges */
+ tooltip?: string;
+ /** Custom component for the badge (tooltip not included) */
+ component?: ComponentType;
/** The custom image to use */
image?: string;
/** Action to perform when you click the badge */
onClick?(): void;
/** Should the user display this badge? */
shouldShow?(userInfo: BadgeUserArgs): boolean;
- /** Optional props (e.g. style) for the badge */
+ /** Optional props (e.g. style) for the badge, ignored for component badges */
props?: HTMLProps;
/** Insert at start or end? */
position?: BadgePosition;
-
- /** The badge name to display. Discord uses this, but we don't. */
+ /** The badge name to display, Discord uses this. Required for component badges */
key?: string;
}
@@ -70,8 +71,8 @@ export function inject(badgeArray: ProfileBadge[], args: BadgeUserArgs) {
for (const badge of Badges) {
if (!badge.shouldShow || badge.shouldShow(args)) {
badge.position === BadgePosition.START
- ? badgeArray.unshift(badge)
- : badgeArray.push(badge);
+ ? badgeArray.unshift({ ...badge, ...args })
+ : badgeArray.push({ ...badge, ...args });
}
}
(Plugins.BadgeAPI as any).addDonorBadge(badgeArray, args.user.id);
diff --git a/src/api/MemberListDecorators.ts b/src/api/MemberListDecorators.ts
new file mode 100644
index 000000000..fade2a7ca
--- /dev/null
+++ b/src/api/MemberListDecorators.ts
@@ -0,0 +1,65 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { Channel, User } from "discord-types/general/index.js";
+
+interface DecoratorProps {
+ activities: any[];
+ canUseAvatarDecorations: boolean;
+ channel: Channel;
+ /**
+ * Only for DM members
+ */
+ channelName?: string;
+ /**
+ * Only for server members
+ */
+ currentUser?: User;
+ guildId?: string;
+ isMobile: boolean;
+ isOwner?: boolean;
+ isTyping: boolean;
+ selected: boolean;
+ status: string;
+ user: User;
+ [key: string]: any;
+}
+export type Decorator = (props: DecoratorProps) => JSX.Element | null;
+type OnlyIn = "guilds" | "dms";
+
+export const decorators = new Map();
+
+export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
+ decorators.set(identifier, { decorator, onlyIn });
+}
+
+export function removeDecorator(identifier: string) {
+ decorators.delete(identifier);
+}
+
+export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] {
+ const isInGuild = !!(props.guildId);
+ return [...decorators.values()].map(decoratorObj => {
+ const { decorator, onlyIn } = decoratorObj;
+ // this can most likely be done cleaner
+ if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
+ return decorator(props);
+ }
+ return null;
+ });
+}
diff --git a/src/api/MessageDecorations.ts b/src/api/MessageDecorations.ts
new file mode 100644
index 000000000..d212b15b1
--- /dev/null
+++ b/src/api/MessageDecorations.ts
@@ -0,0 +1,63 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { Channel, Message } from "discord-types/general/index.js";
+
+interface DecorationProps {
+ author: {
+ /**
+ * Will be username if the user has no nickname
+ */
+ nick: string;
+ iconRoleId: string;
+ guildMemberAvatar: string;
+ colorRoleName: string;
+ colorString: string;
+ };
+ channel: Channel;
+ compact: boolean;
+ decorations: {
+ /**
+ * Element for the [BOT] tag if there is one
+ */
+ 0: JSX.Element | null;
+ /**
+ * Other decorations (including ones added with this api)
+ */
+ 1: JSX.Element[];
+ };
+ message: Message;
+ [key: string]: any;
+}
+export type Decoration = (props: DecorationProps) => JSX.Element | null;
+
+export const decorations = new Map();
+
+export function addDecoration(identifier: string, decoration: Decoration) {
+ decorations.set(identifier, decoration);
+}
+
+export function removeDecoration(identifier: string) {
+ decorations.delete(identifier);
+}
+
+export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
+ return [...decorations.values()].map(decoration => {
+ return decoration(props);
+ });
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index b74da6e38..7e981e274 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -19,7 +19,9 @@
import * as $Badges from "./Badges";
import * as $Commands from "./Commands";
import * as $DataStore from "./DataStore";
+import * as $MemberListDecorators from "./MemberListDecorators";
import * as $MessageAccessories from "./MessageAccessories";
+import * as $MessageDecorations from "./MessageDecorations";
import * as $MessageEventsAPI from "./MessageEvents";
import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
@@ -72,5 +74,13 @@ const Badges = $Badges;
* An API allowing you to add custom elements to the server list
*/
const ServerList = $ServerList;
+/**
+ * An API allowing you to add components as message accessories
+ */
+const MessageDecorations = $MessageDecorations;
+/**
+ * An API allowing you to add components to member list users, in both DM's and servers
+ */
+const MemberListDecorators = $MemberListDecorators;
-export { Badges, Commands, DataStore, MessageAccessories, MessageEvents, MessagePopover, Notices, ServerList };
+export { Badges, Commands, DataStore, MemberListDecorators, MessageAccessories, MessageDecorations, MessageEvents, MessagePopover, Notices, ServerList };
diff --git a/src/plugins/apiBadges.tsx b/src/plugins/apiBadges.tsx
index 77ea46ea1..72c19f376 100644
--- a/src/plugins/apiBadges.tsx
+++ b/src/plugins/apiBadges.tsx
@@ -66,11 +66,20 @@ export default definePlugin({
/* Patch the badge list component on user profiles */
{
find: "Messages.PROFILE_USER_BADGES,role:",
- replacement: {
- match: /src:(\w{1,3})\[(\w{1,3})\.key\],/,
- //
- replace: (_, imageMap, badge) => `src: ${badge}.image ?? ${imageMap}[${badge}.key], ...${badge}.props,`
- }
+ replacement: [
+ {
+ match: /src:(\w{1,3})\[(\w{1,3})\.key\],/,
+ //
+ replace: (_, imageMap, badge) => `src: ${badge}.image ?? ${imageMap}[${badge}.key], ...${badge}.props,`
+ },
+ {
+ match: /spacing:(\d{1,2}),children:(.{1,40}(.{1,2})\.jsx.+(.{1,2})\.onClick.+\)})},/,
+ // if the badge provides it's own component, render that instead of an image
+ // the badge also includes info about the user that has it (type BadgeUserArgs), which is why it's passed as props
+ replace: (_, s, origBadgeComponent, React, badge) =>
+ `spacing:${s},children:${badge}.component ? () => (0,${React}.jsx)(${badge}.component, { ...${badge} }) : ${origBadgeComponent}},`
+ }
+ ]
}
],
diff --git a/src/plugins/apiMemberListDecorators.ts b/src/plugins/apiMemberListDecorators.ts
new file mode 100644
index 000000000..6b8cffabc
--- /dev/null
+++ b/src/plugins/apiMemberListDecorators.ts
@@ -0,0 +1,42 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "MemberListDecoratorsAPI",
+ description: "API to add decorators to member list (both in servers and DMs)",
+ authors: [Devs.TheSun],
+ patches: [
+ {
+ find: "lostPermissionTooltipText,",
+ replacement: {
+ match: /Fragment,{children:\[(.{30,80})\]/,
+ replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)"
+ }
+ },
+ {
+ find: "PrivateChannel.renderAvatar",
+ replacement: {
+ match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/,
+ replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)"
+ }
+ }
+ ],
+});
diff --git a/src/plugins/apiMessageDecorations.ts b/src/plugins/apiMessageDecorations.ts
new file mode 100644
index 000000000..47f03f3b4
--- /dev/null
+++ b/src/plugins/apiMessageDecorations.ts
@@ -0,0 +1,35 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "MessageDecorationsAPI",
+ description: "API to add decorations to messages",
+ authors: [Devs.TheSun],
+ patches: [
+ {
+ find: ".withMentionPrefix",
+ replacement: {
+ match: /(\(\).roleDot.{10,50}{children:.{1,2})}\)/,
+ replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
+ }
+ }
+ ],
+});
diff --git a/src/plugins/platformIndicators.tsx b/src/plugins/platformIndicators.tsx
index 5cae38f04..8ca06775e 100644
--- a/src/plugins/platformIndicators.tsx
+++ b/src/plugins/platformIndicators.tsx
@@ -16,6 +16,9 @@
* along with this program. If not, see .
*/
+import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
+import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
+import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
@@ -59,10 +62,12 @@ const PlatformIcon = ({ platform, status }: { platform: Platform, status: string
return ;
};
+const getStatus = (id: string): Record => PresenceStore.getState()?.clientStatuses?.[id];
+
const PlatformIndicator = ({ user }: { user: User; }) => {
if (!user || user.bot) return null;
- const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record;
+ const status = getStatus(user.id);
if (!status) return null;
const icons = Object.entries(status).map(([platform, status]) => (
@@ -75,79 +80,95 @@ const PlatformIndicator = ({ user }: { user: User; }) => {
if (!icons.length) return null;
- return (
-
{icons}
-
- );
+ ;
+
+ return indicator;
+};
+
+const badge: ProfileBadge = {
+ component: PlatformIndicator,
+ position: BadgePosition.START,
+ shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length,
+ key: "indicator"
+};
+
+const indicatorLocations = {
+ list: {
+ description: "In the member list",
+ onEnable: () => addDecorator("platform-indicator", props =>
+
+
+
+ ),
+ onDisable: () => removeDecorator("platform-indicator")
+ },
+ badges: {
+ description: "In user profiles, as badges",
+ onEnable: () => addBadge(badge),
+ onDisable: () => removeBadge(badge)
+ },
+ messages: {
+ description: "Inside messages",
+ onEnable: () => addDecoration("platform-indicator", props =>
+
+ i.key === "new-member")?.props.message?.author
+ } />
+
+ ),
+ onDisable: () => removeDecoration("platform-indicator")
+ }
};
export default definePlugin({
name: "PlatformIndicators",
description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
- authors: [Devs.kemo],
+ authors: [Devs.kemo, Devs.TheSun],
+ dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI"],
- patches: [
- {
- // Server member list decorators
- find: "this.renderPremium()",
- predicate: () => ["both", "list"].includes(Settings.plugins.PlatformIndicators.displayMode),
- replacement: {
- match: /this.renderPremium\(\)[^\]]*?\]/,
- replace: "$&.concat(Vencord.Plugins.plugins.PlatformIndicators.renderPlatformIndicators(this.props))"
- }
- },
- {
- // Dm list decorators
- find: "PrivateChannel.renderAvatar",
- predicate: () => ["both", "list"].includes(Settings.plugins.PlatformIndicators.displayMode),
- replacement: {
- match: /(subText:(.{1,3})\..+?decorators:)(.+?:null)/,
- replace: "$1[$3].concat(Vencord.Plugins.plugins.PlatformIndicators.renderPlatformIndicators($2.props))"
- }
- },
- {
- // User badges
- find: "Messages.PROFILE_USER_BADGES",
- predicate: () => ["both", "badges"].includes(Settings.plugins.PlatformIndicators.displayMode),
- replacement: {
- match: /(Messages\.PROFILE_USER_BADGES,role:"group",children:)(.+?\.key\)\}\)\))/,
- replace: "$1[Vencord.Plugins.plugins.PlatformIndicators.renderPlatformIndicators(e)].concat($2)"
+ start() {
+ const settings = Settings.plugins.PlatformIndicators;
+ const { displayMode } = settings;
+
+ // transfer settings from the old ones, which had a select menu instead of booleans
+ if (displayMode) {
+ if (displayMode !== "both") settings[displayMode] = true;
+ else {
+ settings.list = true;
+ settings.badges = true;
}
+ settings.messages = true;
+ delete settings.displayMode;
}
- ],
- renderPlatformIndicators: ({ user }: { user: User; }) => (
-
-
-
- ),
+ Object.entries(indicatorLocations).forEach(([key, value]) => {
+ if (settings[key]) value.onEnable();
+ });
+ },
+
+ stop() {
+ Object.entries(indicatorLocations).forEach(([_, value]) => {
+ value.onDisable();
+ });
+ },
options: {
- displayMode: {
- type: OptionType.SELECT,
- description: "Where to display the platform indicators",
- restartNeeded: true,
- options: [
- {
- label: "Member List & Badges",
- value: "both",
- default: true
- },
- {
- label: "Member List Only",
- value: "list"
- },
- {
- label: "Badges Only",
- value: "badges"
- }
- ]
- },
+ ...Object.fromEntries(
+ Object.entries(indicatorLocations).map(([key, value]) => {
+ return [key, {
+ type: OptionType.BOOLEAN,
+ description: `Show indicators ${value.description.toLowerCase()}`,
+ // onChange doesn't give any way to know which setting was changed, so restart required
+ restartNeeded: true,
+ default: false
+ }];
+ })
+ )
}
});
From 73b7f11d7a6d271ea02afe5f4911320351147bac Mon Sep 17 00:00:00 2001
From: Elliott Tallis
Date: Wed, 21 Dec 2022 19:58:07 +0000
Subject: [PATCH 12/24] Also push builds to https://github.com/Vencord/builds
(#344)
Co-authored-by: Ven
---
.github/workflows/build.yml | 21 ++++++++++++++++++++-
README.md | 2 +-
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8ef6503b3..1cdebd0cf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -55,10 +55,29 @@ jobs:
run: |
echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- - name: Upload Devbuild
+ - name: Upload Devbuild as release
run: |
gh release upload devbuild --clobber dist/*
gh release edit devbuild --title "DevBuild $RELEASE_TAG"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ env.release_tag }}
+
+ - name: Upload Devbuild to builds repo
+ run: |
+ git config --global user.name "$USERNAME"
+ git config --global user.email actions@github.com
+
+ gh repo clone "$GH_REPO" upload
+ cd upload
+ rm -rf * .*
+ cp -r ../dist/* .
+
+ git add -A
+ git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
+ git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git main
+ env:
+ API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
+ GLOBIGNORE: .git:.gitignore:README.md:LICENSE
+ GH_REPO: Vencord/builds
+ USERNAME: GitHub-Actions
diff --git a/README.md b/README.md
index 8c8466e27..10c73a0e6 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ If you're a power user who wants to contribute and make plugins or just want to
Or install the browser extension for
- [](https://github.com/Vendicated/Vencord/releases/latest/download/Vencord-for-Chrome-and-Edge.zip)
-- [UserScript](https://github.com/Vendicated/Vencord/releases/download/devbuild/Vencord.user.js) - Please note that QuickCSS, shiki and other plugins making use of external resources will not work with the UserScript.
+- [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that QuickCSS, shiki and other plugins making use of external resources will not work with the UserScript.
You may also build them from source, to do that do the same steps as in the manual regular install method,
From a9ee0c7e50eb90fe34b518abbea1dd9d958efe42 Mon Sep 17 00:00:00 2001
From: Ven
Date: Wed, 21 Dec 2022 20:59:06 +0100
Subject: [PATCH 13/24] Delete obsolete FUNDING.yml
---
.github/FUNDING.yml | 13 -------------
1 file changed, 13 deletions(-)
delete mode 100644 .github/FUNDING.yml
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index b23284879..000000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-# These are supported funding model platforms
-
-github: Vendicated
-patreon: Aliucord
-open_collective: # Replace with a single Open Collective username
-ko_fi: # Replace with a single Ko-fi username
-tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
-community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: # Replace with a single Liberapay username
-issuehunt: # Replace with a single IssueHunt username
-otechie: # Replace with a single Otechie username
-lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
-custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
From 42b4eebca16c5f22202e05b0876502c91269ff9c Mon Sep 17 00:00:00 2001
From: Ven
Date: Wed, 21 Dec 2022 21:05:52 +0100
Subject: [PATCH 14/24] Update build.yml
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1cdebd0cf..ecea61b85 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,7 +68,7 @@ jobs:
git config --global user.name "$USERNAME"
git config --global user.email actions@github.com
- gh repo clone "$GH_REPO" upload
+ git clone "https://$USERNAME:$API_TOKEN@github.com/$GH_REPO" upload
cd upload
rm -rf * .*
cp -r ../dist/* .
From cb9eb1f772f478d84194186fea16bc7c8945b15c Mon Sep 17 00:00:00 2001
From: Ven
Date: Wed, 21 Dec 2022 21:12:28 +0100
Subject: [PATCH 15/24] i hate ci i hate ci
---
.github/workflows/build.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ecea61b85..70f50a950 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -70,7 +70,8 @@ jobs:
git clone "https://$USERNAME:$API_TOKEN@github.com/$GH_REPO" upload
cd upload
- rm -rf * .*
+ shopt -s dotglob
+ rm -rf *
cp -r ../dist/* .
git add -A
From 259f0284f0c5442d3743614975f583aff80e1b80 Mon Sep 17 00:00:00 2001
From: Ven
Date: Wed, 21 Dec 2022 22:43:54 +0100
Subject: [PATCH 16/24] Update build.yml
---
.github/workflows/build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 70f50a950..6341c4827 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,7 +68,7 @@ jobs:
git config --global user.name "$USERNAME"
git config --global user.email actions@github.com
- git clone "https://$USERNAME:$API_TOKEN@github.com/$GH_REPO" upload
+ git clone "https://oauth2:$API_TOKEN@github.com/$GH_REPO" upload
cd upload
shopt -s dotglob
rm -rf *
@@ -76,7 +76,7 @@ jobs:
git add -A
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
- git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git main
+ git push --force https://oauth2:$API_TOKEN@github.com/$GH_REPO.git main
env:
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
GLOBIGNORE: .git:.gitignore:README.md:LICENSE
From 6dd705f9512033d78445d76421d552bead2f2746 Mon Sep 17 00:00:00 2001
From: Ven
Date: Thu, 22 Dec 2022 17:25:57 +0100
Subject: [PATCH 17/24] Update build.yml
---
.github/workflows/build.yml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6341c4827..5c9eafee4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -68,15 +68,14 @@ jobs:
git config --global user.name "$USERNAME"
git config --global user.email actions@github.com
- git clone "https://oauth2:$API_TOKEN@github.com/$GH_REPO" upload
+ git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload
cd upload
- shopt -s dotglob
rm -rf *
cp -r ../dist/* .
git add -A
git commit -m "Builds for https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
- git push --force https://oauth2:$API_TOKEN@github.com/$GH_REPO.git main
+ git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
env:
API_TOKEN: ${{ secrets.BUILDS_TOKEN }}
GLOBIGNORE: .git:.gitignore:README.md:LICENSE
From af0d34b155006302eb3ac6caa434b53cef3c70dc Mon Sep 17 00:00:00 2001
From: Elliott Tallis
Date: Thu, 22 Dec 2022 16:42:54 +0000
Subject: [PATCH 18/24] pointy is a "contributor" (#359)
---
src/utils/constants.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 6c7b540ca..aaf6e4dfb 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -177,4 +177,8 @@ export const Devs = Object.freeze({
name: "'ax",
id: 273562710745284628n,
},
+ pointy: {
+ name: "pointy",
+ id: 99914384989519872n
+ }
});
From b0c41d556a5d02befc239e42b67503e039b50049 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Thu, 22 Dec 2022 18:04:37 +0100
Subject: [PATCH 19/24] Improve treeshaking
---
scripts/build/inject/react.mjs | 2 +-
src/utils/constants.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/build/inject/react.mjs b/scripts/build/inject/react.mjs
index 1343b5f2f..591a25b5a 100644
--- a/scripts/build/inject/react.mjs
+++ b/scripts/build/inject/react.mjs
@@ -16,6 +16,6 @@
* along with this program. If not, see .
*/
-export const VencordFragment = Symbol.for("react.fragment");
+export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
export let VencordCreateElement =
(...args) => (VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index aaf6e4dfb..d15615b60 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -24,7 +24,7 @@ export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
// Add yourself here if you made a plugin
-export const Devs = Object.freeze({
+export const Devs = /* #__PURE__*/ Object.freeze({
Ven: {
name: "Vendicated",
id: 343383572805058560n
From 074542f0b36cd61e3d2a1aefc7c716d394e1f751 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 22 Dec 2022 23:00:59 -0300
Subject: [PATCH 20/24] feat(plugins): NoScreensharePreview plugin (#358)
---
src/plugins/noScreensharePreview.ts | 38 +++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 src/plugins/noScreensharePreview.ts
diff --git a/src/plugins/noScreensharePreview.ts b/src/plugins/noScreensharePreview.ts
new file mode 100644
index 000000000..5641ea3d7
--- /dev/null
+++ b/src/plugins/noScreensharePreview.ts
@@ -0,0 +1,38 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "NoScreensharePreview",
+ description: "Disables screenshare previews from being sent.",
+ authors: [Devs.Nuckyz],
+ patches: [
+ {
+ find: '("ApplicationStreamPreviewUploadManager")',
+ replacement: [
+ ".\\.default\\.makeChunkedRequest\\(",
+ ".{1,2}\\..\\.post\\({url:"
+ ].map(match => ({
+ match: new RegExp(`return\\[(?\\d),${match}.\\..{1,3}\\.STREAM_PREVIEW.+?}\\)\\];`),
+ replace: 'return[$,Promise.resolve({body:"",status:204})];'
+ }))
+ },
+ ],
+});
From aff1b68d6b480ecf25d03ee11ee7ca2fb04d945c Mon Sep 17 00:00:00 2001
From: Nickyux <30734036+nmsturcke@users.noreply.github.com>
Date: Fri, 23 Dec 2022 03:17:19 +0100
Subject: [PATCH 21/24] Add a "NEW" Badge for New Plugins (V2)! (#234)
Co-authored-by: Ven
Co-authored-by: Justice Almanzar
Co-authored-by: ArjixWasTaken <53124886+ArjixWasTaken@users.noreply.github.com>
---
package.json | 1 +
pnpm-lock.yaml | 6 +++
.../components/BadgeComponent.tsx | 30 +++++++++++++
.../PluginSettings/components/index.ts | 1 +
src/components/PluginSettings/index.tsx | 43 +++++++++++++++----
src/components/PluginSettings/styles.ts | 13 +++++-
src/globals.d.ts | 2 +
7 files changed, 87 insertions(+), 9 deletions(-)
create mode 100644 src/components/PluginSettings/components/BadgeComponent.tsx
diff --git a/package.json b/package.json
index fe83c6a56..35c4aafe2 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
},
"devDependencies": {
"@types/diff": "^5.0.2",
+ "@types/lodash": "^4.14.0",
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bca41baab..02c2ea6c3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,6 +10,7 @@ patchedDependencies:
specifiers:
'@types/diff': ^5.0.2
+ '@types/lodash': ^4.14.0
'@types/node': ^18.11.9
'@types/react': ^18.0.25
'@types/react-dom': ^18.0.9
@@ -41,6 +42,7 @@ dependencies:
devDependencies:
'@types/diff': 5.0.2
+ '@types/lodash': 4.14.189
'@types/node': 18.11.9
'@types/react': 18.0.25
'@types/react-dom': 18.0.9
@@ -152,6 +154,10 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
+ /@types/lodash/4.14.189:
+ resolution: {integrity: sha512-kb9/98N6X8gyME9Cf7YaqIMvYGnBSWqEci6tiettE6iJWH1XdJz/PO8LB0GtLCG7x8dU3KWhZT+lA1a35127tA==}
+ dev: true
+
/@types/node/18.11.9:
resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==}
dev: true
diff --git a/src/components/PluginSettings/components/BadgeComponent.tsx b/src/components/PluginSettings/components/BadgeComponent.tsx
new file mode 100644
index 000000000..059376fd5
--- /dev/null
+++ b/src/components/PluginSettings/components/BadgeComponent.tsx
@@ -0,0 +1,30 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { BadgeStyle } from "@components/PluginSettings/styles";
+
+export function Badge({ text, color }): JSX.Element {
+ return (
+
{text}
+ );
+}
diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts
index 9e7506816..d44fb386f 100644
--- a/src/components/PluginSettings/components/index.ts
+++ b/src/components/PluginSettings/components/index.ts
@@ -29,6 +29,7 @@ export interface ISettingElementProps {
onError(hasError: boolean): void;
}
+export * from "./BadgeComponent";
export * from "./SettingBooleanComponent";
export * from "./SettingCustomComponent";
export * from "./SettingNumericComponent";
diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx
index 5ee9cc941..93bb48971 100644
--- a/src/components/PluginSettings/index.tsx
+++ b/src/components/PluginSettings/index.tsx
@@ -16,26 +16,27 @@
* along with this program. If not, see .
*/
+import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices";
import { Settings, useSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { handleComponentFailed } from "@components/handleComponentFailed";
+import { Badge } from "@components/PluginSettings/components";
+import PluginModal from "@components/PluginSettings/PluginModal";
+import * as styles from "@components/PluginSettings/styles";
import { ChangeList } from "@utils/ChangeList";
import Logger from "@utils/Logger";
-import { classes, LazyComponent } from "@utils/misc";
+import { classes, LazyComponent, useAwaiter } from "@utils/misc";
import { openModalLazy } from "@utils/modal";
import { Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack";
import { Alerts, Button, Forms, Margins, Parser, React, Select, Switch, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
+import { startDependenciesRecursive, startPlugin, stopPlugin } from "plugins";
import Plugins from "~plugins";
-import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
-import PluginModal from "./PluginModal";
-import * as styles from "./styles";
-
const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
@@ -78,9 +79,10 @@ interface PluginCardProps extends React.HTMLProps {
plugin: Plugin;
disabled: boolean;
onRestartNeeded(name: string): void;
+ isNew?: boolean;
}
-function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave }: PluginCardProps) {
+function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = useSettings();
const pluginSettings = settings.plugins[plugin.name];
@@ -162,8 +164,15 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
}
hideBorder={true}
>
-
- {plugin.name}
+
+
+ {plugin.name}{(isNew) && }
+
+ );
};
const badge: ProfileBadge = {
- component: PlatformIndicator,
+ component: p => ,
position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length,
key: "indicator"
@@ -119,7 +148,7 @@ const indicatorLocations = {
i.key === "new-member")?.props.message?.author
- } />
+ } inline />
),
onDisable: () => removeDecoration("platform-indicator")
@@ -166,7 +195,7 @@ export default definePlugin({
description: `Show indicators ${value.description.toLowerCase()}`,
// onChange doesn't give any way to know which setting was changed, so restart required
restartNeeded: true,
- default: false
+ default: true
}];
})
)
From 2e5d27b6b63097e96e25819df7a8cdd667c521b3 Mon Sep 17 00:00:00 2001
From: Ven
Date: Sun, 25 Dec 2022 20:47:35 +0100
Subject: [PATCH 24/24] feat: Proper CSS api & css bundle (#269)
Co-authored-by: Vap0r1ze
---
browser/content.js | 17 +-
browser/manifestv2.json | 2 +-
browser/manifestv3.json | 2 +-
package.json | 11 ++
scripts/build/buildWeb.mjs | 74 +++++---
scripts/build/common.mjs | 41 ++++-
scripts/build/module/style.js | 26 +++
src/Vencord.ts | 1 -
src/api/Styles.ts | 162 ++++++++++++++++++
src/api/index.ts | 28 +--
src/components/VencordSettings/index.tsx | 8 +-
src/globals.d.ts | 6 +
src/ipcMain/index.ts | 1 +
src/ipcMain/legacy.ts | 31 ++++
src/ipcMain/updater/common.ts | 2 +-
src/ipcMain/updater/http.ts | 2 +-
src/modules.d.ts | 6 +
src/plugins/messageLogger/index.tsx | 39 +----
src/plugins/messageLogger/messageLogger.css | 27 +++
.../shikiCodeblocks/components/Header.tsx | 2 +-
.../components/Highlighter.tsx | 6 +-
src/plugins/shikiCodeblocks/devicon.css | 1 +
src/plugins/shikiCodeblocks/index.ts | 17 +-
src/plugins/shikiCodeblocks/shiki.css | 4 +-
src/plugins/shikiCodeblocks/utils/misc.ts | 3 +-
.../spotifyControls/PlayerComponent.tsx | 2 +
src/plugins/spotifyControls/SpotifyStore.ts | 7 -
src/preload.ts | 28 +++
src/utils/IpcEvents.ts | 3 +-
src/utils/updater.ts | 2 +-
src/webpack/common.tsx | 3 +-
31 files changed, 438 insertions(+), 126 deletions(-)
mode change 100755 => 100644 scripts/build/buildWeb.mjs
create mode 100644 scripts/build/module/style.js
create mode 100644 src/api/Styles.ts
create mode 100644 src/ipcMain/legacy.ts
create mode 100644 src/plugins/messageLogger/messageLogger.css
create mode 100644 src/plugins/shikiCodeblocks/devicon.css
diff --git a/browser/content.js b/browser/content.js
index 2c4b40e75..e47ef8377 100644
--- a/browser/content.js
+++ b/browser/content.js
@@ -2,7 +2,18 @@ if (typeof browser === "undefined") {
var browser = chrome;
}
-var script = document.createElement("script");
+const script = document.createElement("script");
script.src = browser.runtime.getURL("dist/Vencord.js");
-// documentElement because we load before body/head are ready
-document.documentElement.appendChild(script);
+
+const style = document.createElement("link");
+style.type = "text/css";
+style.rel = "stylesheet";
+style.href = browser.runtime.getURL("dist/Vencord.css");
+
+document.documentElement.append(script);
+
+document.addEventListener(
+ "DOMContentLoaded",
+ () => document.documentElement.append(style),
+ { once: true }
+);
diff --git a/browser/manifestv2.json b/browser/manifestv2.json
index 405b2dc99..b28b73f8d 100644
--- a/browser/manifestv2.json
+++ b/browser/manifestv2.json
@@ -18,7 +18,7 @@
"js": ["content.js"]
}
],
- "web_accessible_resources": ["dist/Vencord.js"],
+ "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
"background": {
"scripts": ["background.js"]
}
diff --git a/browser/manifestv3.json b/browser/manifestv3.json
index ea79d1292..d15b80a1b 100644
--- a/browser/manifestv3.json
+++ b/browser/manifestv3.json
@@ -23,7 +23,7 @@
"web_accessible_resources": [
{
- "resources": ["dist/Vencord.js"],
+ "resources": ["dist/Vencord.js", "dist/Vencord.css"],
"matches": ["*://*.discord.com/*"]
}
],
diff --git a/package.json b/package.json
index 35c4aafe2..51d384db9 100644
--- a/package.json
+++ b/package.json
@@ -65,6 +65,17 @@
"patchedDependencies": {
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
"eslint@8.28.0": "patches/eslint@8.28.0.patch"
+ },
+ "peerDependencyRules": {
+ "ignoreMissing": [
+ "eslint-plugin-import"
+ ]
+ },
+ "allowedDeprecatedVersions": {
+ "source-map-resolve": "*",
+ "resolve-url": "*",
+ "source-map-url": "*",
+ "urix": "*"
}
},
"webExt": {
diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs
old mode 100755
new mode 100644
index c85d8aad9..3ad43b2c9
--- a/scripts/build/buildWeb.mjs
+++ b/scripts/build/buildWeb.mjs
@@ -20,13 +20,13 @@
import esbuild from "esbuild";
import { zip } from "fflate";
-import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
-import { readFile } from "fs/promises";
-import { join, resolve } from "path";
+import { readFileSync } from "fs";
+import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
+import { join } from "path";
// wtf is this assert syntax
import PackageJSON from "../../package.json" assert { type: "json" };
-import { commonOpts, fileIncludePlugin, gitHashPlugin, gitRemotePlugin, globPlugins, watch } from "./common.mjs";
+import { commonOpts, globPlugins, watch } from "./common.mjs";
/**
* @type {esbuild.BuildOptions}
@@ -39,9 +39,7 @@ const commonOptions = {
external: ["plugins", "git-hash"],
plugins: [
globPlugins,
- gitHashPlugin,
- gitRemotePlugin,
- fileIncludePlugin
+ ...commonOpts.plugins,
],
target: ["esnext"],
define: {
@@ -77,9 +75,13 @@ await Promise.all(
]
);
+/**
+ * @type {(target: string, files: string[], shouldZip: boolean) => Promise}
+ */
async function buildPluginZip(target, files, shouldZip) {
const entries = {
- "dist/Vencord.js": readFileSync("dist/browser.js"),
+ "dist/Vencord.js": await readFile("dist/browser.js"),
+ "dist/Vencord.css": await readFile("dist/browser.css"),
...Object.fromEntries(await Promise.all(files.map(async f => [
(f.startsWith("manifest") ? "manifest.json" : f),
await readFile(join("browser", f))
@@ -87,29 +89,47 @@ async function buildPluginZip(target, files, shouldZip) {
};
if (shouldZip) {
- zip(entries, {}, (err, data) => {
- if (err) {
- console.error(err);
- process.exitCode = 1;
- } else {
- writeFileSync("dist/" + target, data);
- console.info("Extension written to dist/" + target);
- }
+ return new Promise((resolve, reject) => {
+ zip(entries, {}, (err, data) => {
+ if (err) {
+ reject(err);
+ } else {
+ const out = join("dist", target);
+ writeFile(out, data).then(() => {
+ console.info("Extension written to " + out);
+ resolve();
+ }).catch(reject);
+ }
+ });
});
} else {
- if (existsSync(target))
- rmSync(target, { recursive: true });
- for (const entry in entries) {
- const destination = "dist/" + target + "/" + entry;
- const parentDirectory = resolve(destination, "..");
- mkdirSync(parentDirectory, { recursive: true });
- writeFileSync(destination, entries[entry]);
- }
+ await rm(target, { recursive: true, force: true });
+ await Promise.all(Object.entries(entries).map(async ([file, content]) => {
+ const dest = join("dist", target, file);
+ const parentDirectory = join(dest, "..");
+ await mkdir(parentDirectory, { recursive: true });
+ await writeFile(dest, content);
+ }));
+
console.info("Unpacked Extension written to dist/" + target);
}
}
-await buildPluginZip("extension-v3.zip", ["modifyResponseHeaders.json", "content.js", "manifestv3.json"], true);
-await buildPluginZip("extension-v2.zip", ["background.js", "content.js", "manifestv2.json"], true);
-await buildPluginZip("extension-v2-unpacked", ["background.js", "content.js", "manifestv2.json"], false);
+const cssText = "`" + readFileSync("dist/Vencord.user.css", "utf-8").replaceAll("`", "\\`") + "`";
+const cssRuntime = `
+;document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(
+ Object.assign(document.createElement("style"), {
+ textContent: ${cssText},
+ id: "vencord-css-core"
+ }),
+ { once: true }
+));
+`;
+
+await Promise.all([
+ appendFile("dist/Vencord.user.js", cssRuntime),
+ buildPluginZip("extension-v3.zip", ["modifyResponseHeaders.json", "content.js", "manifestv3.json"], true),
+ buildPluginZip("extension-v2.zip", ["background.js", "content.js", "manifestv2.json"], true),
+ buildPluginZip("extension-v2-unpacked", ["background.js", "content.js", "manifestv2.json"], false),
+]);
diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs
index 11aaa81af..2743c700d 100644
--- a/scripts/build/common.mjs
+++ b/scripts/build/common.mjs
@@ -17,9 +17,9 @@
*/
import { exec, execSync } from "child_process";
-import { existsSync } from "fs";
+import { existsSync, readFileSync } from "fs";
import { readdir, readFile } from "fs/promises";
-import { join } from "path";
+import { join, relative } from "path";
import { promisify } from "util";
export const watch = process.argv.includes("--watch");
@@ -35,7 +35,7 @@ export const banner = {
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const makeAllPackagesExternalPlugin = {
name: "make-all-packages-external",
@@ -46,7 +46,7 @@ export const makeAllPackagesExternalPlugin = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const globPlugins = {
name: "glob-plugins",
@@ -87,7 +87,7 @@ export const globPlugins = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const gitHashPlugin = {
name: "git-hash-plugin",
@@ -103,7 +103,7 @@ export const gitHashPlugin = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const gitRemotePlugin = {
name: "git-remote-plugin",
@@ -125,7 +125,7 @@ export const gitRemotePlugin = {
};
/**
- * @type {esbuild.Plugin}
+ * @type {import("esbuild").Plugin}
*/
export const fileIncludePlugin = {
name: "file-include-plugin",
@@ -147,6 +147,31 @@ export const fileIncludePlugin = {
}
};
+const styleModule = readFileSync("./scripts/build/module/style.js", "utf-8");
+/**
+ * @type {import("esbuild").Plugin}
+ */
+export const stylePlugin = {
+ name: "style-plugin",
+ setup: ({ onResolve, onLoad }) => {
+ onResolve({ filter: /\.css\?managed$/, namespace: "file" }, ({ path, resolveDir }) => ({
+ path: relative(process.cwd(), join(resolveDir, path.replace("?managed", ""))),
+ namespace: "managed-style",
+ }));
+ onLoad({ filter: /\.css$/, namespace: "managed-style" }, async ({ path }) => {
+ const css = await readFile(path, "utf-8");
+ const name = relative(process.cwd(), path).replaceAll("\\", "/");
+
+ return {
+ loader: "js",
+ contents: styleModule
+ .replaceAll("STYLE_SOURCE", JSON.stringify(css))
+ .replaceAll("STYLE_NAME", JSON.stringify(name))
+ };
+ });
+ }
+};
+
/**
* @type {import("esbuild").BuildOptions}
*/
@@ -158,7 +183,7 @@ export const commonOpts = {
sourcemap: watch ? "inline" : "",
legalComments: "linked",
banner,
- plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin],
+ plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote"],
inject: ["./scripts/build/inject/react.mjs"],
jsxFactory: "VencordCreateElement",
diff --git a/scripts/build/module/style.js b/scripts/build/module/style.js
new file mode 100644
index 000000000..5981a3de2
--- /dev/null
+++ b/scripts/build/module/style.js
@@ -0,0 +1,26 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+(window.VencordStyles ??= new Map()).set(STYLE_NAME, {
+ name: STYLE_NAME,
+ source: STYLE_SOURCE,
+ classNames: {},
+ dom: null,
+});
+
+export default STYLE_NAME;
diff --git a/src/Vencord.ts b/src/Vencord.ts
index 464be2d8a..48e628fde 100644
--- a/src/Vencord.ts
+++ b/src/Vencord.ts
@@ -18,7 +18,6 @@
export * as Api from "./api";
export * as Plugins from "./plugins";
-// eslint-disable-next-line @typescript-eslint/no-restricted-imports
export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss";
export * as Updater from "./utils/updater";
diff --git a/src/api/Styles.ts b/src/api/Styles.ts
new file mode 100644
index 000000000..6b189cab8
--- /dev/null
+++ b/src/api/Styles.ts
@@ -0,0 +1,162 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import type { MapValue } from "type-fest/source/entry";
+
+export type Style = MapValue;
+
+export const styleMap = window.VencordStyles ??= new Map();
+
+export function requireStyle(name: string) {
+ const style = styleMap.get(name);
+ if (!style) throw new Error(`Style "${name}" does not exist`);
+ return style;
+}
+
+/**
+ * A style's name can be obtained from importing a stylesheet with `?managed` at the end of the import
+ * @param name The name of the style
+ * @returns `false` if the style was already enabled, `true` otherwise
+ * @example
+ * import pluginStyle from "./plugin.css?managed";
+ *
+ * // Inside some plugin method like "start()" or "[option].onChange()"
+ * enableStyle(pluginStyle);
+ */
+export function enableStyle(name: string) {
+ const style = requireStyle(name);
+
+ if (style.dom?.isConnected)
+ return false;
+
+ if (!style.dom) {
+ style.dom = document.createElement("style");
+ style.dom.dataset.vencordName = style.name;
+ }
+ compileStyle(style);
+
+ document.head.appendChild(style.dom);
+ return true;
+}
+
+/**
+ * @param name The name of the style
+ * @returns `false` if the style was already disabled, `true` otherwise
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export function disableStyle(name: string) {
+ const style = requireStyle(name);
+ if (!style.dom?.isConnected)
+ return false;
+
+ style.dom.remove();
+ style.dom = null;
+ return true;
+}
+
+/**
+ * @param name The name of the style
+ * @returns `true` in most cases, may return `false` in some edge cases
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export const toggleStyle = (name: string) => isStyleEnabled(name) ? disableStyle(name) : enableStyle(name);
+
+/**
+ * @param name The name of the style
+ * @returns Whether the style is enabled
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export const isStyleEnabled = (name: string) => requireStyle(name).dom?.isConnected ?? false;
+
+/**
+ * Sets the variables of a style
+ * ```ts
+ * // -- plugin.ts --
+ * import pluginStyle from "./plugin.css?managed";
+ * import { setStyleVars } from "@api/Styles";
+ * import { findByPropsLazy } from "@webpack";
+ * const classNames = findByPropsLazy("thin", "scrollerBase"); // { thin: "thin-31rlnD scrollerBase-_bVAAt", ... }
+ *
+ * // Inside some plugin method like "start()"
+ * setStyleClassNames(pluginStyle, classNames);
+ * enableStyle(pluginStyle);
+ * ```
+ * ```scss
+ * // -- plugin.css --
+ * .plugin-root [--thin]::-webkit-scrollbar { ... }
+ * ```
+ * ```scss
+ * // -- final stylesheet --
+ * .plugin-root .thin-31rlnD.scrollerBase-_bVAAt::-webkit-scrollbar { ... }
+ * ```
+ * @param name The name of the style
+ * @param classNames An object where the keys are the variable names and the values are the variable values
+ * @param recompile Whether to recompile the style after setting the variables, defaults to `true`
+ * @see {@link enableStyle} for info on getting the name of an imported style
+ */
+export const setStyleClassNames = (name: string, classNames: Record, recompile = true) => {
+ const style = requireStyle(name);
+ style.classNames = classNames;
+ if (recompile && isStyleEnabled(style.name))
+ compileStyle(style);
+};
+
+/**
+ * Updates the stylesheet after doing the following to the sourcecode:
+ * - Interpolate style classnames
+ * @param style **_Must_ be a style with a DOM element**
+ * @see {@link setStyleClassNames} for more info on style classnames
+ */
+export const compileStyle = (style: Style) => {
+ if (!style.dom) throw new Error("Style has no DOM element");
+
+ style.dom.textContent = style.source
+ .replace(/\[--(\w+)\]/g, (match, name) => {
+ const className = style.classNames[name];
+ return className ? classNameToSelector(className) : match;
+ });
+};
+
+/**
+ * @param name The classname
+ * @param prefix A prefix to add each class, defaults to `""`
+ * @return A css selector for the classname
+ * @example
+ * classNameToSelector("foo bar") // => ".foo.bar"
+ */
+export const classNameToSelector = (name: string, prefix = "") => name.split(" ").map(n => `.${prefix}${n}`).join("");
+
+type ClassNameFactoryArg = string | string[] | Record;
+/**
+ * @param prefix The prefix to add to each class, defaults to `""`
+ * @returns A classname generator function
+ * @example
+ * const cl = classNameFactory("plugin-");
+ *
+ * cl("base", ["item", "editable"], { selected: null, disabled: true })
+ * // => "plugin-base plugin-item plugin-editable plugin-disabled"
+ */
+export const classNameFactory = (prefix: string = "") => (...args: ClassNameFactoryArg[]) => {
+ const classNames = new Set();
+ for (const arg of args) {
+ if (typeof arg === "string") classNames.add(arg);
+ else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
+ else if (typeof arg === "object") Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
+ }
+ return Array.from(classNames, name => prefix + name).join(" ");
+};
diff --git a/src/api/index.ts b/src/api/index.ts
index 7e981e274..0fef99cda 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -26,6 +26,7 @@ import * as $MessageEventsAPI from "./MessageEvents";
import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
import * as $ServerList from "./ServerList";
+import * as $Styles from "./Styles";
/**
* An API allowing you to listen to Message Clicks or run your own logic
@@ -33,16 +34,16 @@ import * as $ServerList from "./ServerList";
*
* If your plugin uses this, you must add MessageEventsAPI to its dependencies
*/
-const MessageEvents = $MessageEventsAPI;
+export const MessageEvents = $MessageEventsAPI;
/**
* An API allowing you to create custom notices
* (snackbars on the top, like the Update prompt)
*/
-const Notices = $Notices;
+export const Notices = $Notices;
/**
* An API allowing you to register custom commands
*/
-const Commands = $Commands;
+export const Commands = $Commands;
/**
* A wrapper around IndexedDB. This can store arbitrarily
* large data and supports a lot of datatypes (Blob, Map, ...).
@@ -57,30 +58,33 @@ const Commands = $Commands;
* This is actually just idb-keyval, so if you're familiar with that, you're golden!
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types}
*/
-const DataStore = $DataStore;
+export const DataStore = $DataStore;
/**
* An API allowing you to add custom components as message accessories
*/
-const MessageAccessories = $MessageAccessories;
+export const MessageAccessories = $MessageAccessories;
/**
* An API allowing you to add custom buttons in the message popover
*/
-const MessagePopover = $MessagePopover;
+export const MessagePopover = $MessagePopover;
/**
* An API allowing you to add badges to user profiles
*/
-const Badges = $Badges;
+export const Badges = $Badges;
/**
* An API allowing you to add custom elements to the server list
*/
-const ServerList = $ServerList;
+export const ServerList = $ServerList;
/**
* An API allowing you to add components as message accessories
*/
-const MessageDecorations = $MessageDecorations;
+export const MessageDecorations = $MessageDecorations;
/**
* An API allowing you to add components to member list users, in both DM's and servers
*/
-const MemberListDecorators = $MemberListDecorators;
-
-export { Badges, Commands, DataStore, MemberListDecorators, MessageAccessories, MessageDecorations, MessageEvents, MessagePopover, Notices, ServerList };
+export const MemberListDecorators = $MemberListDecorators;
+/**
+ * An API allowing you to dynamically load styles
+ * a
+ */
+export const Styles = $Styles;
diff --git a/src/components/VencordSettings/index.tsx b/src/components/VencordSettings/index.tsx
index b49e4b49c..b3a3322eb 100644
--- a/src/components/VencordSettings/index.tsx
+++ b/src/components/VencordSettings/index.tsx
@@ -16,22 +16,18 @@
* along with this program. If not, see .
*/
+import "./settingsStyles.css";
+
import ErrorBoundary from "@components/ErrorBoundary";
import { findByCodeLazy } from "@webpack";
import { Forms, Router, Text } from "@webpack/common";
-import cssText from "~fileContent/settingsStyles.css";
-
import BackupRestoreTab from "./BackupRestoreTab";
import PluginsTab from "./PluginsTab";
import ThemesTab from "./ThemesTab";
import Updater from "./Updater";
import VencordSettings from "./VencordTab";
-const style = document.createElement("style");
-style.textContent = cssText;
-document.head.appendChild(style);
-
const st = (style: string) => `vcSettings${style}`;
const TabBar = findByCodeLazy('[role="tab"][aria-disabled="false"]');
diff --git a/src/globals.d.ts b/src/globals.d.ts
index 2e8d444ae..6c5b4376b 100644
--- a/src/globals.d.ts
+++ b/src/globals.d.ts
@@ -38,6 +38,12 @@ declare global {
export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord");
+ export var VencordStyles: Map;
+ dom: HTMLStyleElement | null;
+ }>;
export var appSettings: {
set(setting: string, v: any): void;
};
diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts
index 86a233c71..ae8a96db0 100644
--- a/src/ipcMain/index.ts
+++ b/src/ipcMain/index.ts
@@ -16,6 +16,7 @@
* along with this program. If not, see .
*/
+import "./legacy";
import "./updater";
import { debounce } from "@utils/debounce";
diff --git a/src/ipcMain/legacy.ts b/src/ipcMain/legacy.ts
new file mode 100644
index 000000000..567ad3d06
--- /dev/null
+++ b/src/ipcMain/legacy.ts
@@ -0,0 +1,31 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import IpcEvents from "@utils/IpcEvents";
+import { ipcMain } from "electron";
+import { writeFile } from "fs/promises";
+import { join } from "path";
+
+import { get } from "./simpleGet";
+
+ipcMain.handleOnce(IpcEvents.DOWNLOAD_VENCORD_CSS, async () => {
+ const buf = await get("https://github.com/Vendicated/Vencord/releases/download/devbuild/renderer.css");
+ await writeFile(join(__dirname, "renderer.css"), buf);
+ return buf.toString("utf-8");
+});
+
diff --git a/src/ipcMain/updater/common.ts b/src/ipcMain/updater/common.ts
index 41f08e815..3729c6d59 100644
--- a/src/ipcMain/updater/common.ts
+++ b/src/ipcMain/updater/common.ts
@@ -24,7 +24,7 @@ export async function calculateHashes() {
const hashes = {} as Record;
await Promise.all(
- ["patcher.js", "preload.js", "renderer.js"].map(file => new Promise(r => {
+ ["patcher.js", "preload.js", "renderer.js", "renderer.css"].map(file => new Promise(r => {
const fis = createReadStream(join(__dirname, file));
const hash = createHash("sha1", { encoding: "hex" });
fis.once("end", () => {
diff --git a/src/ipcMain/updater/http.ts b/src/ipcMain/updater/http.ts
index 902d644a3..3b3814477 100644
--- a/src/ipcMain/updater/http.ts
+++ b/src/ipcMain/updater/http.ts
@@ -69,7 +69,7 @@ async function fetchUpdates() {
return false;
data.assets.forEach(({ name, browser_download_url }) => {
- if (["patcher.js", "preload.js", "renderer.js"].some(s => name.startsWith(s))) {
+ if (["patcher.js", "preload.js", "renderer.js", "renderer.css"].some(s => name.startsWith(s))) {
PendingUpdates.push([name, browser_download_url]);
}
});
diff --git a/src/modules.d.ts b/src/modules.d.ts
index 6901260ae..c1a1996e7 100644
--- a/src/modules.d.ts
+++ b/src/modules.d.ts
@@ -37,3 +37,9 @@ declare module "~fileContent/*" {
const content: string;
export default content;
}
+
+declare module "*.css" { }
+declare module "*.css?managed" {
+ const name: string;
+ export default name;
+}
diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx
index abdb2f2f7..e650dbbf6 100644
--- a/src/plugins/messageLogger/index.tsx
+++ b/src/plugins/messageLogger/index.tsx
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import "./messageLogger.css";
+
import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
@@ -42,51 +44,14 @@ export default definePlugin({
timestampModule: null as any,
moment: null as Function | null,
- css: `
- .messagelogger-red-overlay .messageLogger-deleted {
- background-color: rgba(240, 71, 71, 0.15);
- }
- .messagelogger-red-text .messageLogger-deleted div {
- color: #f04747;
- }
-
- .messageLogger-deleted [class^="buttons"] {
- display: none;
- }
-
- .messageLogger-deleted-attachment {
- filter: grayscale(1);
- }
-
- .messageLogger-deleted-attachment:hover {
- filter: grayscale(0);
- transition: 250ms filter linear;
- }
-
- .theme-dark .messageLogger-edited {
- filter: brightness(80%);
- }
-
- .theme-light .messageLogger-edited {
- opacity: 0.5;
- }
- `,
-
start() {
this.moment = findByPropsLazy("relativeTimeRounding", "relativeTimeThreshold");
this.timestampModule = findByPropsLazy("messageLogger_TimestampComponent");
- const style = this.style = document.createElement("style");
- style.textContent = this.css;
- style.id = "MessageLogger-css";
- document.head.appendChild(style);
-
addDeleteStyleClass();
},
stop() {
- this.style?.remove();
-
document.querySelectorAll(".messageLogger-deleted").forEach(e => e.remove());
document.querySelectorAll(".messageLogger-edited").forEach(e => e.remove());
document.body.classList.remove("messagelogger-red-overlay");
diff --git a/src/plugins/messageLogger/messageLogger.css b/src/plugins/messageLogger/messageLogger.css
new file mode 100644
index 000000000..94a3e2509
--- /dev/null
+++ b/src/plugins/messageLogger/messageLogger.css
@@ -0,0 +1,27 @@
+.messagelogger-red-overlay .messageLogger-deleted {
+ background-color: rgba(240, 71, 71, 0.15);
+}
+.messagelogger-red-text .messageLogger-deleted div {
+ color: #f04747;
+}
+
+.messageLogger-deleted [class^="buttons"] {
+ display: none;
+}
+
+.messageLogger-deleted-attachment {
+ filter: grayscale(1);
+}
+
+.messageLogger-deleted-attachment:hover {
+ filter: grayscale(0);
+ transition: 250ms filter linear;
+}
+
+.theme-dark .messageLogger-edited {
+ filter: brightness(80%);
+}
+
+.theme-light .messageLogger-edited {
+ opacity: 0.5;
+}
diff --git a/src/plugins/shikiCodeblocks/components/Header.tsx b/src/plugins/shikiCodeblocks/components/Header.tsx
index c2db38693..320dde973 100644
--- a/src/plugins/shikiCodeblocks/components/Header.tsx
+++ b/src/plugins/shikiCodeblocks/components/Header.tsx
@@ -33,7 +33,7 @@ export function Header({ langName, useDevIcon, shikiLang }: HeaderProps) {
{useDevIcon !== DeviconSetting.Disabled && shikiLang?.devicon && (
)}
{langName}
diff --git a/src/plugins/shikiCodeblocks/components/Highlighter.tsx b/src/plugins/shikiCodeblocks/components/Highlighter.tsx
index d26cd81a4..badb3c8f6 100644
--- a/src/plugins/shikiCodeblocks/components/Highlighter.tsx
+++ b/src/plugins/shikiCodeblocks/components/Highlighter.tsx
@@ -90,14 +90,10 @@ export const Highlighter = ({
let langName;
if (lang) langName = useHljs ? hljs?.getLanguage?.(lang)?.name : shikiLang?.name;
- const preClasses = [cl("root")];
- if (!langName) preClasses.push(cl("plain"));
- if (isPreview) preClasses.push(cl("preview"));
-
return (
.
*/
+import "./shiki.css";
+
+import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
import { parseUrl } from "@utils/misc";
import { wordsFromPascal, wordsToTitle } from "@utils/text";
import definePlugin, { OptionType } from "@utils/types";
import previewExampleText from "~fileContent/previewExample.tsx";
-import cssText from "~fileContent/shiki.css";
import { Settings } from "../../Vencord";
import { shiki } from "./api/shiki";
import { themes } from "./api/themes";
import { createHighlighter } from "./components/Highlighter";
-import { DeviconSetting, HljsSetting, ShikiSettings, StyleSheets } from "./types";
-import { clearStyles, removeStyle, setStyle } from "./utils/createStyle";
+import deviconStyle from "./devicon.css?managed";
+import { DeviconSetting, HljsSetting, ShikiSettings } from "./types";
+import { clearStyles } from "./utils/createStyle";
const themeNames = Object.keys(themes);
-const devIconCss = "@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css');";
const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings;
@@ -50,9 +52,8 @@ export default definePlugin({
},
],
start: async () => {
- setStyle(cssText, StyleSheets.Main);
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
- setStyle(devIconCss, StyleSheets.DevIcons);
+ enableStyle(deviconStyle);
await shiki.init(getSettings().customTheme || getSettings().theme);
},
@@ -135,8 +136,8 @@ export default definePlugin({
},
],
onChange: (newValue: DeviconSetting) => {
- if (newValue === DeviconSetting.Disabled) removeStyle(StyleSheets.DevIcons);
- else setStyle(devIconCss, StyleSheets.DevIcons);
+ if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle);
+ else enableStyle(deviconStyle);
},
},
bgOpacity: {
diff --git a/src/plugins/shikiCodeblocks/shiki.css b/src/plugins/shikiCodeblocks/shiki.css
index b871d9957..d71b67392 100644
--- a/src/plugins/shikiCodeblocks/shiki.css
+++ b/src/plugins/shikiCodeblocks/shiki.css
@@ -1,6 +1,5 @@
.shiki-container {
border: 4px;
- /* fallback background */
background-color: var(--background-secondary);
}
@@ -22,8 +21,7 @@
border: none;
}
-.shiki-root [class^='devicon-'],
-.shiki-root [class*=' devicon-'] {
+.shiki-devicon {
margin-right: 8px;
user-select: none;
}
diff --git a/src/plugins/shikiCodeblocks/utils/misc.ts b/src/plugins/shikiCodeblocks/utils/misc.ts
index 1342ff5d8..fefe938fc 100644
--- a/src/plugins/shikiCodeblocks/utils/misc.ts
+++ b/src/plugins/shikiCodeblocks/utils/misc.ts
@@ -16,13 +16,14 @@
* along with this program. If not, see .
*/
+import { classNameFactory } from "@api/Styles";
import { hljs } from "@webpack/common";
import { resolveLang } from "../api/languages";
import { HighlighterProps } from "../components/Highlighter";
import { HljsSetting, ShikiSettings } from "../types";
-export const cl = (className: string) => `shiki-${className}`;
+export const cl = classNameFactory("shiki-");
export const shouldUseHljs = ({
lang,
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index af53f59c9..f6ad08b83 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import "./spotifyStyles.css";
+
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts
index 75448dc2b..641ba1ac2 100644
--- a/src/plugins/spotifyControls/SpotifyStore.ts
+++ b/src/plugins/spotifyControls/SpotifyStore.ts
@@ -21,8 +21,6 @@ import { proxyLazy } from "@utils/proxyLazy";
import { findByPropsLazy } from "@webpack";
import { Flux, FluxDispatcher } from "@webpack/common";
-import cssText from "~fileContent/spotifyStyles.css";
-
export interface Track {
id: string;
name: string;
@@ -69,11 +67,6 @@ type Repeat = "off" | "track" | "context";
// Don't wanna run before Flux and Dispatcher are ready!
export const SpotifyStore = proxyLazy(() => {
- // TODO: Move this elsewhere
- const style = document.createElement("style");
- style.innerText = cssText;
- document.head.appendChild(style);
-
// For some reason ts hates extends Flux.Store
const { Store } = Flux;
diff --git a/src/preload.ts b/src/preload.ts
index dcf2554f9..746008142 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -44,6 +44,34 @@ contextBridge.exposeInMainWorld("VencordNative", VencordNative);
if (location.protocol !== "data:") {
// Discord
webFrame.executeJavaScript(readFileSync(join(__dirname, "renderer.js"), "utf-8"));
+ const rendererCss = join(__dirname, "renderer.css");
+
+ function insertCss(css: string) {
+ const style = document.createElement("style");
+ style.id = "vencord-css-core";
+ style.textContent = css;
+
+ if (document.readyState === "complete") {
+ document.documentElement.appendChild(style);
+ } else {
+ document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(style), {
+ once: true
+ });
+ }
+ }
+
+ try {
+ const css = readFileSync(rendererCss, "utf-8");
+ insertCss(css);
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException)?.code !== "ENOENT")
+ throw err;
+
+ // hack: the pre update updater does not download this file, so manually download it
+ // TODO: remove this in a future version
+ ipcRenderer.invoke(IpcEvents.DOWNLOAD_VENCORD_CSS)
+ .then(insertCss);
+ }
require(process.env.DISCORD_PRELOAD!);
} else {
// Monaco Popout
diff --git a/src/utils/IpcEvents.ts b/src/utils/IpcEvents.ts
index c6696f80d..345146b22 100644
--- a/src/utils/IpcEvents.ts
+++ b/src/utils/IpcEvents.ts
@@ -44,5 +44,6 @@ export default strEnum({
UPDATE: "VencordUpdate",
BUILD: "VencordBuild",
GET_DESKTOP_CAPTURE_SOURCES: "VencordGetDesktopCaptureSources",
- OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor"
+ OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor",
+ DOWNLOAD_VENCORD_CSS: "VencordDownloadVencordCss"
} as const);
diff --git a/src/utils/updater.ts b/src/utils/updater.ts
index 2ea4953fc..04205a556 100644
--- a/src/utils/updater.ts
+++ b/src/utils/updater.ts
@@ -61,7 +61,7 @@ export function getRepo() {
return Unwrap(VencordNative.ipc.invoke>(IpcEvents.GET_REPO));
}
-type Hashes = Record<"patcher.js" | "preload.js" | "renderer.js", string>;
+type Hashes = Record<"patcher.js" | "preload.js" | "renderer.js" | "renderer.css", string>;
/**
* @returns true if hard restart is required
diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx
index f2c42d105..a732d6b7c 100644
--- a/src/webpack/common.tsx
+++ b/src/webpack/common.tsx
@@ -36,6 +36,7 @@ export let React: typeof import("react");
export let useState: typeof React.useState;
export let useEffect: typeof React.useEffect;
export let useMemo: typeof React.useMemo;
+export let useRef: typeof React.useRef;
export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render");
@@ -158,7 +159,7 @@ export const NavigationRouter = mapMangledModuleLazy("Transitioning to external
waitFor("useState", m => {
React = m;
- ({ useEffect, useState, useMemo } = React);
+ ({ useEffect, useState, useMemo, useRef } = React);
});
waitFor(["dispatch", "subscribe"], m => {