From 62f74f59179c875cb6f867a7e3da2012e2aa3507 Mon Sep 17 00:00:00 2001 From: Remty Date: Sun, 2 Apr 2023 16:12:19 +0200 Subject: [PATCH] feat(plugin): FakeProfileThemes (#710) --- package.json | 4 +- pnpm-lock.yaml | 16 ++-- src/plugins/fakeProfileThemes.tsx | 130 ++++++++++++++++++++++++++++++ src/utils/constants.ts | 8 ++ 4 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 src/plugins/fakeProfileThemes.tsx diff --git a/package.json b/package.json index 3107f4f27..a8ba62c84 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "@vap/core": "0.0.12", "@vap/shiki": "0.10.3", "fflate": "^0.7.4", - "nanoid": "^4.0.2" + "nanoid": "^4.0.2", + "virtual-merge": "^1.0.1" }, "devDependencies": { "@types/diff": "^5.0.2", "@types/lodash": "^4.14.191", - "@types/nanoid": "^3.0.0", "@types/node": "^18.11.18", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ac1fb7ca..aa891a21c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,7 +11,6 @@ patchedDependencies: specifiers: '@types/diff': ^5.0.2 '@types/lodash': ^4.14.191 - '@types/nanoid': ^3.0.0 '@types/node': ^18.11.18 '@types/react': ^18.0.27 '@types/react-dom': ^18.0.10 @@ -40,17 +39,18 @@ specifiers: tsx: ^3.12.6 type-fest: ^3.5.3 typescript: ^4.9.4 + virtual-merge: ^1.0.1 dependencies: '@vap/core': 0.0.12 '@vap/shiki': 0.10.3 fflate: 0.7.4 nanoid: 4.0.2 + virtual-merge: 1.0.1 devDependencies: '@types/diff': 5.0.2 '@types/lodash': 4.14.191 - '@types/nanoid': 3.0.0 '@types/node': 18.11.18 '@types/react': 18.0.27 '@types/react-dom': 18.0.10 @@ -421,13 +421,6 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/nanoid/3.0.0: - resolution: {integrity: sha512-UXitWSmXCwhDmAKe7D3hNQtQaHeHt5L8LO1CB8GF8jlYVzOv5cBWDNqiJ+oPEWrWei3i3dkZtHY/bUtd0R/uOQ==} - deprecated: This is a stub types definition. nanoid provides its own type definitions, so you do not need this installed. - dependencies: - nanoid: 4.0.2 - dev: true - /@types/node/18.11.18: resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} dev: true @@ -2260,6 +2253,7 @@ packages: resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} engines: {node: ^14 || ^16 || >=18} hasBin: true + dev: false /nanomatch/1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} @@ -3139,6 +3133,10 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /virtual-merge/1.0.1: + resolution: {integrity: sha512-h7rzV6n5fZJbDu2lP4iu+IOtsZ00uqECFUxFePK1uY0pz/S5B7FNDJpmdDVfyGL7poyJECEHfTaIpJaknNkU0Q==} + dev: false + /vscode-oniguruma/1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} dev: false diff --git a/src/plugins/fakeProfileThemes.tsx b/src/plugins/fakeProfileThemes.tsx new file mode 100644 index 000000000..de42dd916 --- /dev/null +++ b/src/plugins/fakeProfileThemes.tsx @@ -0,0 +1,130 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +// This plugin is a port from Alyxia's Vendetta plugin +import { definePluginSettings } from "@api/settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { Margins } from "@utils/margins"; +import { copyWithToast } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { Button } from "@webpack/common"; +import { User } from "discord-types/general"; +import virtualMerge from "virtual-merge"; + +interface UserProfile extends User { + themeColors?: Array; +} + +interface Colors { + primary: number; + accent: number; +} + +function encode(primary: number, accent: number): string { + const message = `[#${primary.toString(16).padStart(6, "0")},#${accent.toString(16).padStart(6, "0")}]`; + const padding = ""; + const encoded = Array.from(message) + .map(x => x.codePointAt(0)) + .filter(x => x! >= 0x20 && x! <= 0x7f) + .map(x => String.fromCodePoint(x! + 0xe0000)) + .join(""); + + return (padding || "") + " " + encoded; +} + +// Courtesy of Cynthia. +function decode(bio: string): Array | null { + if (bio == null) return null; + + const colorString = bio.match( + /\u{e005b}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e002c}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e005d}/u, + ); + if (colorString != null) { + const parsed = [...colorString[0]] + .map(x => String.fromCodePoint(x.codePointAt(0)! - 0xe0000)) + .join(""); + const colors = parsed + .substring(1, parsed.length - 1) + .split(",") + .map(x => parseInt(x.replace("#", "0x"), 16)); + + return colors; + } else { + return null; + } +} + +const settings = definePluginSettings({ + nitroFirst: { + description: "Default color source if both are present", + type: OptionType.SELECT, + options: [ + { label: "Nitro colors", value: true, default: true }, + { label: "Fake colors", value: false }, + ] + } +}); + +export default definePlugin({ + name: "FakeProfileThemes", + description: "Allows profile theming by hiding the colors in your bio thanks to invisible 3y3 encoding.", + authors: [Devs.Alyxia, Devs.Remty], + patches: [ + { + find: "getUserProfile=", + replacement: { + match: /(?<=getUserProfile=function\(\i\){return )(\i\[\i\])/, + replace: "$self.colorDecodeHook($1)" + } + }, { + find: ".USER_SETTINGS_PROFILE_THEME_ACCENT", + replacement: { + match: /RESET_PROFILE_THEME}\)(?<=},color:(\i).+?},color:(\i).+?)/, + replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" + } + } + ], + settings, + colorDecodeHook(user: UserProfile) { + if (user) { + // don't replace colors if already set with nitro + if (settings.store.nitroFirst && user.themeColors) return user; + const colors = decode(user.bio); + if (colors) { + return virtualMerge(user, { + premiumType: 2, + themeColors: colors + }); + } + } + return user; + }, + addCopy3y3Button: ErrorBoundary.wrap(function ({ primary, accent }: Colors) { + return ; + }, { noop: true }), +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 473674af3..c54676f4b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -226,6 +226,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "TheKodeToad", id: 706152404072267788n }, + Alyxia: { + name: "Alyxia Sother", + id: 952185386350829688n + }, + Remty: { + name: "Remty", + id: 335055032204656642n + }, skyevg: { name: "skyevg", id: 1090310844283363348n