From 484d70fb1537c5540f4377e5fcde982c421f74d6 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 28 Jun 2024 13:20:24 +0200 Subject: [PATCH 01/11] bump to v1.9.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2f5e3a2ed..dc90a646c5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.2", + "version": "1.9.3", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From e4bf71784e158ad1a690427c4720240bf8b58964 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 28 Jun 2024 16:04:51 +0200 Subject: [PATCH 02/11] TextReplace: support /v regex flag --- src/plugins/textReplace/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/textReplace/index.tsx b/src/plugins/textReplace/index.tsx index 416ce83fb6..615477d079 100644 --- a/src/plugins/textReplace/index.tsx +++ b/src/plugins/textReplace/index.tsx @@ -77,7 +77,7 @@ const settings = definePluginSettings({ }); function stringToRegex(str: string) { - const match = str.match(/^(\/)?(.+?)(?:\/([gimsuy]*))?$/); // Regex to match regex + const match = str.match(/^(\/)?(.+?)(?:\/([gimsuyv]*))?$/); // Regex to match regex return match ? new RegExp( match[2], // Pattern From 62485e8694b539128eb5555c5da2dc37bf219861 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:17:38 -0300 Subject: [PATCH 03/11] Obliterate Sentry --- scripts/generateReport.ts | 2 ++ src/plugins/_core/noTrack.ts | 56 +++++++++++++++++++++++++++++ src/webpack/patchWebpack.ts | 70 ++++++++++++++++++------------------ 3 files changed, 92 insertions(+), 36 deletions(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 0b94aaf726..9ffe6fb08d 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -289,6 +289,8 @@ page.on("console", async e => { page.on("error", e => console.error("[Error]", e.message)); page.on("pageerror", e => { + if (e.message.includes("Sentry successfully disabled")) return; + if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) { console.error("[Page Error]", e.message); report.otherErrors.push(e.message); diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index 26b36735a4..ef2849bbcc 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -18,6 +18,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; import definePlugin, { OptionType, StartAt } from "@utils/types"; const settings = definePluginSettings({ @@ -71,9 +72,64 @@ export default definePlugin({ startAt: StartAt.Init, start() { + // Sentry is initialized in its own WebpackInstance. + // It has everything it needs preloaded, so, it doesn't include any chunk loading functionality. + // Because of that, its WebpackInstance doesnt export wreq.m or wreq.c + + // To circuvent this and disable Sentry we are gonna hook when wreq.g of its WebpackInstance is set. + // When that happens we are gonna obtain a reference to its internal module cache (wreq.c) and proxy its prototype, + // so, when the first require to initialize the Sentry is attempted, we are gonna forcefully throw an error and abort everything. + Object.defineProperty(Function.prototype, "g", { + configurable: true, + + set(v: any) { + Object.defineProperty(this, "g", { + value: v, + configurable: true, + enumerable: true, + writable: true + }); + + // Ensure this is most likely the Sentry WebpackInstance. + // Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: ) to include it + const { stack } = new Error(); + if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || this.c != null || !String(this).includes("exports:{}")) { + return; + } + + const cacheExtractSym = Symbol("vencord.cacheExtract"); + Object.defineProperty(Object.prototype, cacheExtractSym, { + configurable: true, + + get() { + // One more condition to check if this is the Sentry WebpackInstance + if (Array.isArray(this)) { + return { exports: {} }; + } + + new Logger("NoTrack", "#8caaee").info("Disabling Sentry by proxying its WebpackInstance cache"); + Object.setPrototypeOf(this, new Proxy(this, { + get() { + throw new Error("Sentry successfully disabled"); + } + })); + + Reflect.deleteProperty(Object.prototype, cacheExtractSym); + Reflect.deleteProperty(window, "DiscordSentry"); + return { exports: {} }; + } + }); + + // WebpackRequire our fake module id + this(cacheExtractSym); + } + }); + Object.defineProperty(window, "DiscordSentry", { configurable: true, + set() { + new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry"); Reflect.deleteProperty(window, "DiscordSentry"); } }); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index bd47cefad4..4b3b28a8e3 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -56,58 +56,56 @@ Object.defineProperty(window, WEBPACK_CHUNK, { // normally, this is populated via webpackGlobal.push, which we patch below. // However, Discord has their .m prepopulated. // Thus, we use this hack to immediately access their wreq.m and patch all already existing factories -// -// Update: Discord now has TWO webpack instances. Their normal one and sentry -// Sentry does not push chunks to the global at all, so this same patch now also handles their sentry modules Object.defineProperty(Function.prototype, "m", { configurable: true, set(v: any) { + Object.defineProperty(this, "m", { + value: v, + configurable: true, + enumerable: true, + writable: true + }); + // When using react devtools or other extensions, we may also catch their webpack here. // This ensures we actually got the right one const { stack } = new Error(); - if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(v)) { - const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? ""; + if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || Array.isArray(v)) { + return; + } - logger.info("Found Webpack module factory", fileName); - patchFactories(v); + const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? ""; + logger.info("Found Webpack module factory", fileName); - // Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property. - // So if the setter is called, this means we can initialize the internal references to WebpackRequire. - Object.defineProperty(this, "p", { - configurable: true, + patchFactories(v); - set(this: WebpackInstance, bundlePath: string) { - Object.defineProperty(this, "p", { - value: bundlePath, - configurable: true, - enumerable: true, - writable: true - }); + // Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property. + // So if the setter is called, this means we can initialize the internal references to WebpackRequire. + Object.defineProperty(this, "p", { + configurable: true, - clearTimeout(setterTimeout); + set(this: WebpackInstance, bundlePath: string) { + Object.defineProperty(this, "p", { + value: bundlePath, + configurable: true, + enumerable: true, + writable: true + }); - if (bundlePath !== "/assets/") return; + clearTimeout(setterTimeout); + if (bundlePath !== "/assets/") return; - logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`); - _initWebpack(this); + logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`); + _initWebpack(this); - for (const beforeInitListener of beforeInitListeners) { - beforeInitListener(this); - } + for (const beforeInitListener of beforeInitListeners) { + beforeInitListener(this); } - }); - // setImmediate to clear this property setter if this is not the main Webpack. - // If this is the main Webpack, wreq.p will always be set before the timeout runs. - const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0); - } - - Object.defineProperty(this, "m", { - value: v, - configurable: true, - enumerable: true, - writable: true + } }); + // setImmediate to clear this property setter if this is not the main Webpack. + // If this is the main Webpack, wreq.p will always be set before the timeout runs. + const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0); } }); From 5c1c786cf9065c8d2e510a655229713a85c1834e Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:29:40 -0300 Subject: [PATCH 04/11] Fix ReviewDB patch --- src/plugins/reviewDB/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 800de4a1fa..7fdc1509a9 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -87,7 +87,7 @@ export default definePlugin({ } }, { - find: ".VIEW_FULL_PROFILE,", + find: ".BITE_SIZE,user:", replacement: { match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/, replace: "$self.BiteSizeReviewsButton({user:$1}),$&" From 2fa56b80abee1bfea4c3435191db88141ab30c87 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 29 Jun 2024 01:26:03 -0300 Subject: [PATCH 05/11] Harder conditions for Sentry patching --- src/plugins/_core/noTrack.ts | 48 +++++++++++++++++------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index ef2849bbcc..de1c205622 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -77,8 +77,7 @@ export default definePlugin({ // Because of that, its WebpackInstance doesnt export wreq.m or wreq.c // To circuvent this and disable Sentry we are gonna hook when wreq.g of its WebpackInstance is set. - // When that happens we are gonna obtain a reference to its internal module cache (wreq.c) and proxy its prototype, - // so, when the first require to initialize the Sentry is attempted, we are gonna forcefully throw an error and abort everything. + // When that happens we are gonna forcefully throw an error and abort everything. Object.defineProperty(Function.prototype, "g", { configurable: true, @@ -93,35 +92,30 @@ export default definePlugin({ // Ensure this is most likely the Sentry WebpackInstance. // Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: ) to include it const { stack } = new Error(); - if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || this.c != null || !String(this).includes("exports:{}")) { + if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) { return; } - const cacheExtractSym = Symbol("vencord.cacheExtract"); - Object.defineProperty(Object.prototype, cacheExtractSym, { - configurable: true, + const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0]; + if (!assetPath) { + return; + } - get() { - // One more condition to check if this is the Sentry WebpackInstance - if (Array.isArray(this)) { - return { exports: {} }; - } - - new Logger("NoTrack", "#8caaee").info("Disabling Sentry by proxying its WebpackInstance cache"); - Object.setPrototypeOf(this, new Proxy(this, { - get() { - throw new Error("Sentry successfully disabled"); - } - })); - - Reflect.deleteProperty(Object.prototype, cacheExtractSym); - Reflect.deleteProperty(window, "DiscordSentry"); - return { exports: {} }; - } - }); + const srcRequest = new XMLHttpRequest(); + srcRequest.open("GET", assetPath, false); + srcRequest.send(); - // WebpackRequire our fake module id - this(cacheExtractSym); + // Final condition to see if this is the Sentry WebpackInstance + if (!srcRequest.responseText.includes("window.DiscordSentry=")) { + return; + } + + new Logger("NoTrack", "#8caaee").info("Disabling Sentry by erroring its WebpackInstance"); + + Reflect.deleteProperty(Function.prototype, "g"); + Reflect.deleteProperty(window, "DiscordSentry"); + + throw new Error("Sentry successfully disabled"); } }); @@ -130,6 +124,8 @@ export default definePlugin({ set() { new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry"); + + Reflect.deleteProperty(Function.prototype, "g"); Reflect.deleteProperty(window, "DiscordSentry"); } }); From bda0e1844ba94bd0728dc723f12ead91cc3ef3ad Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 28 Jun 2024 18:04:05 +0200 Subject: [PATCH 06/11] browser: increase minimum browser versions --- browser/manifest.json | 2 +- browser/manifestv2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/manifest.json b/browser/manifest.json index c527c75b8e..357312b09c 100644 --- a/browser/manifest.json +++ b/browser/manifest.json @@ -1,6 +1,6 @@ { "manifest_version": 3, - "minimum_chrome_version": "91", + "minimum_chrome_version": "111", "name": "Vencord Web", "description": "The cutest Discord mod now in your browser", diff --git a/browser/manifestv2.json b/browser/manifestv2.json index f5b08571a4..0cb7cb32af 100644 --- a/browser/manifestv2.json +++ b/browser/manifestv2.json @@ -43,7 +43,7 @@ "browser_specific_settings": { "gecko": { "id": "vencord-firefox@vendicated.dev", - "strict_min_version": "91.0" + "strict_min_version": "128.0" } } } From 169edcb5b7974069d9cae04d7b23d8fa5b543e8e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 29 Jun 2024 17:34:13 +0200 Subject: [PATCH 07/11] improve support helper --- src/plugins/_core/supportHelper.tsx | 30 ++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index faea66bfcd..de8e37c79e 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -17,6 +17,7 @@ */ import { addAccessory } from "@api/MessageAccessories"; +import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; @@ -32,12 +33,12 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; -import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Toasts, UserStore } from "@webpack/common"; +import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; import gitHash from "~git-hash"; import plugins, { PluginMeta } from "~plugins"; -import settings from "./settings"; +import SettingsPlugin from "./settings"; const VENCORD_GUILD_ID = "1015060230222131221"; const VENBOT_USER_ID = "1017176847865352332"; @@ -86,7 +87,7 @@ async function generateDebugInfoMessage() { const info = { Vencord: `v${VERSION} • [${gitHash}]()` + - `${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, + `${SettingsPlugin.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, Client: `${RELEASE_CHANNEL} ~ ${client}`, Platform: window.navigator.platform }; @@ -132,6 +133,10 @@ function generatePluginList() { const checkForUpdatesOnce = onlyOnce(checkForUpdates); +const settings = definePluginSettings({}).withPrivateSettings<{ + dismissedDevBuildWarning?: boolean; +}>(); + export default definePlugin({ name: "SupportHelper", required: true, @@ -139,6 +144,8 @@ export default definePlugin({ authors: [Devs.Ven], dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"], + settings, + patches: [{ find: ".BEGINNING_DM.format", replacement: { @@ -207,17 +214,22 @@ export default definePlugin({ }); } - const repo = await VencordNative.updater.getRepo(); - if (repo.ok && !repo.value.includes("Vendicated/Vencord")) { + if (!IS_STANDALONE && !settings.store.dismissedDevBuildWarning) { return Alerts.show({ title: "Hold on!", body:
- You are using a fork of Vencord, which we do not provide support for! + You are using a custom build of Vencord, which we do not provide support for! + - Please either switch to an officially supported version of Vencord, or - contact your package maintainer for support instead. + We only provide support for official builds. + Either switch to an official build or figure your issue out yourself. -
+ + You will be banned from receiving support if you ignore this rule. + , + confirmText: "Understood", + secondaryConfirmText: "Don't show again", + onConfirmSecondary: () => settings.store.dismissedDevBuildWarning = true }); } } From 086c31c890ab264d5ad221d0eb8ce40e8c0ee396 Mon Sep 17 00:00:00 2001 From: Luna Date: Sat, 29 Jun 2024 17:43:25 +0200 Subject: [PATCH 08/11] new plugin ShowAllRoles: show all roles in simplified profiles (#2624) Co-authored-by: vee --- src/plugins/showAllRoles/README.md | 6 ++++++ src/plugins/showAllRoles/index.ts | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/plugins/showAllRoles/README.md create mode 100644 src/plugins/showAllRoles/index.ts diff --git a/src/plugins/showAllRoles/README.md b/src/plugins/showAllRoles/README.md new file mode 100644 index 0000000000..d5d99c794b --- /dev/null +++ b/src/plugins/showAllRoles/README.md @@ -0,0 +1,6 @@ +# ShowAllRoles + +Display all roles on the new profiles instead of limiting them to the default two rows. + +![image](https://github.com/Vendicated/Vencord/assets/71079641/3f021f03-c6f9-4fe5-83ac-a1891b5e4b37) + diff --git a/src/plugins/showAllRoles/index.ts b/src/plugins/showAllRoles/index.ts new file mode 100644 index 0000000000..02d9f964cb --- /dev/null +++ b/src/plugins/showAllRoles/index.ts @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "ShowAllRoles", + description: "Show all roles in new profiles.", + authors: [Devs.Luna], + patches: [ + { + find: ".Messages.VIEW_ALL_ROLES", + replacement: { + match: /return null!=\i(?=\?\i\.slice)/, + replace: "return false" + } + } + ] +}); From 2d570a524b18cd9ca4585481a1ca5862fab82da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20Ellil=C3=A4?= Date: Sat, 29 Jun 2024 19:39:09 +0300 Subject: [PATCH 09/11] friendsSince: add support for new profiles (#2623) Co-authored-by: vee --- src/plugins/friendsSince/index.tsx | 96 +++++++++++++++++++++++------ src/plugins/friendsSince/styles.css | 12 ---- src/utils/constants.ts | 4 ++ 3 files changed, 81 insertions(+), 31 deletions(-) delete mode 100644 src/plugins/friendsSince/styles.css diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index b290a44503..629e8e7193 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -4,50 +4,64 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import { Logger } from "@utils/Logger"; -import { classes } from "@utils/misc"; import definePlugin from "@utils/types"; -import { findByCodeLazy, findByPropsLazy } from "@webpack"; -import { Heading, React, RelationshipStore, Text } from "@webpack/common"; +import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack"; +import { Heading, RelationshipStore, Text } from "@webpack/common"; -const container = findByPropsLazy("memberSinceWrapper"); +const containerWrapper = findByPropsLazy("memberSinceWrapper"); +const container = findByPropsLazy("memberSince"); const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const locale = findByPropsLazy("getLocale"); const lastSection = findByPropsLazy("lastSection"); - -const cl = classNameFactory("vc-friendssince-"); +const section = findLazy((m: any) => m.section !== void 0 && Object.values(m).length === 1); export default definePlugin({ name: "FriendsSince", description: "Shows when you became friends with someone in the user popout", - authors: [Devs.Elvyra], + authors: [Devs.Elvyra, Devs.Antti], patches: [ - // User popup + // User popup - old layout { find: ".USER_PROFILE}};return", replacement: { match: /,{userId:(\i.id).{0,30}}\)/, - replace: "$&,$self.friendsSince({ userId: $1 })" + replace: "$&,$self.friendsSinceOld({ userId: $1 })" } }, - // User DMs "User Profile" popup in the right + // DM User Sidebar - old layout { find: ".PROFILE_PANEL,", replacement: { match: /,{userId:([^,]+?)}\)/, - replace: "$&,$self.friendsSince({ userId: $1 })" + replace: "$&,$self.friendsSinceOld({ userId: $1 })" } }, - // User Profile Modal + // User Profile Modal - old layout { find: ".userInfoSectionHeader,", replacement: { match: /(\.Messages\.USER_PROFILE_MEMBER_SINCE.+?userId:(.+?),textClassName:)(\i\.userInfoText)}\)/, - replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSince({ userId: ${userId}, textClassName: ${textClassName} })` + replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSinceOld({ userId: ${userId}, textClassName: ${textClassName} })` + } + }, + // DM User Sidebar - new layout + { + find: ".PANEL}),nicknameIcons", + replacement: { + match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/, + replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})" + } + }, + // User Profile Modal - new layout + { + find: "action:\"PRESS_APP_CONNECTION\"", + replacement: { + match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/, + replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:false})," } } ], @@ -63,7 +77,7 @@ export default definePlugin({ } }, - friendsSince: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => { + friendsSinceOld: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => { if (!RelationshipStore.isFriend(userId)) return null; const friendsSince = RelationshipStore.getSince(userId); @@ -71,11 +85,11 @@ export default definePlugin({ return (
- + Friends Since -
+
{!!getCurrentChannel()?.guild_id && ( )} - + {getCreatedAtDate(friendsSince, locale.getLocale())}
); - }, { noop: true }) + }, { noop: true }), + + friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => { + if (!RelationshipStore.isFriend(userId)) return null; + + const friendsSince = RelationshipStore.getSince(userId); + if (!friendsSince) return null; + + return ( +
+ + Friends Since + + + { + isSidebar ? ( + + {getCreatedAtDate(friendsSince, locale.getLocale())} + + ) : ( +
+
+ {!!getCurrentChannel()?.guild_id && ( + + )} + + {getCreatedAtDate(friendsSince, locale.getLocale())} + +
+
+ ) + } + +
+ ); + }, { noop: true }), }); diff --git a/src/plugins/friendsSince/styles.css b/src/plugins/friendsSince/styles.css deleted file mode 100644 index 9f73db0baf..0000000000 --- a/src/plugins/friendsSince/styles.css +++ /dev/null @@ -1,12 +0,0 @@ -/* copy pasted from discord */ - -.vc-friendssince-title { - display: flex; - font-weight: 700; - margin-bottom: 6px -} - -.vc-friendssince-body { - font-size: 14px; - line-height: 18px -} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 09fc0285b0..c399baafea 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -529,6 +529,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ nekohaxx: { name: "nekohaxx", id: 1176270221628153886n + }, + Antti: { + name: "Antti", + id: 312974985876471810n } } satisfies Record); From 8a7c0d7e61503ce60cc5308e96d54c420125aad0 Mon Sep 17 00:00:00 2001 From: Masterjoona <69722179+Masterjoona@users.noreply.github.com> Date: Sat, 29 Jun 2024 19:42:27 +0300 Subject: [PATCH 10/11] WebContextMenus: implement context menu for text selection (#2577) Co-authored-by: vee --- src/plugins/webContextMenus.web/index.ts | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index f99fd97203..bae780109b 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -200,6 +200,34 @@ export default definePlugin({ match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/, replace: "$&.DISABLE_VIDEO:return true;$1" } + }, + { + find: ".Messages.SEARCH_WITH_GOOGLE", + replacement: { + match: /\i\.isPlatformEmbedded/, + replace: "true" + } + }, + { + find: ".Messages.COPY,hint:", + replacement: [ + { + match: /\i\.isPlatformEmbedded/, + replace: "true" + }, + { + match: /\i\.\i\.copy/, + replace: "Vencord.Webpack.Common.Clipboard.copy" + }] + }, + // Automod add filter words + { + find: '("interactionUsernameProfile', + replacement: + { + match: /\i\.isPlatformEmbedded(?=.{0,50}\.tagName)/, + replace: "true" + }, } ], From 5c05443f454dd04288108330875f437889dc6812 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 29 Jun 2024 20:18:29 +0200 Subject: [PATCH 11/11] improve settings ui --- src/api/Notifications/notificationLog.tsx | 45 +++-- src/components/Grid.tsx | 28 +++ src/components/Icons.tsx | 95 ++++++++- .../PluginSettings/LinkIconButton.tsx | 20 +- src/components/PluginSettings/PluginModal.tsx | 12 +- src/components/PluginSettings/index.tsx | 24 +-- src/components/VencordSettings/CloudTab.tsx | 70 +++++-- .../VencordSettings/NotificationSettings.tsx | 106 ++++++++++ src/components/VencordSettings/VencordTab.tsx | 186 +++++++----------- .../VencordSettings/settingsStyles.css | 15 +- src/plugins/showHiddenChannels/index.tsx | 13 +- 11 files changed, 421 insertions(+), 193 deletions(-) create mode 100644 src/components/Grid.tsx create mode 100644 src/components/VencordSettings/NotificationSettings.tsx diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 6f79ef70a6..5df31d4cdd 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -19,6 +19,8 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { Flex } from "@components/Flex"; +import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; @@ -170,24 +172,31 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void - + + + + + ); diff --git a/src/components/Grid.tsx b/src/components/Grid.tsx new file mode 100644 index 0000000000..1f757f457f --- /dev/null +++ b/src/components/Grid.tsx @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { CSSProperties } from "react"; + +interface Props { + columns: number; + gap?: string; + inline?: boolean; +} + +export function Grid(props: Props & JSX.IntrinsicElements["div"]) { + const style: CSSProperties = { + display: props.inline ? "inline-grid" : "grid", + gridTemplateColumns: `repeat(${props.columns}, 1fr)`, + gap: props.gap, + ...props.style + }; + + return ( +
+ {props.children} +
+ ); +} diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index d076df75bc..d82ce0b006 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -18,19 +18,17 @@ import "./iconStyles.css"; +import { getTheme, Theme } from "@utils/discord"; import { classes } from "@utils/misc"; import { i18n } from "@webpack/common"; -import type { PropsWithChildren, SVGProps } from "react"; +import type { PropsWithChildren } from "react"; interface BaseIconProps extends IconProps { viewBox: string; } -interface IconProps extends SVGProps { - className?: string; - height?: string | number; - width?: string | number; -} +type IconProps = JSX.IntrinsicElements["svg"]; +type ImageProps = JSX.IntrinsicElements["img"]; function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren) { return ( @@ -329,3 +327,88 @@ export function NotesIcon(props: IconProps) { ); } + +export function FolderIcon(props: IconProps) { + return ( + + + + ); +} + +export function LogIcon(props: IconProps) { + return ( + + + + ); +} + +export function RestartIcon(props: IconProps) { + return ( + + + + ); +} + +export function PaintbrushIcon(props: IconProps) { + return ( + + + + ); +} + +const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg"; +const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg"; +const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg"; +const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg"; + +export function GithubIcon(props: ImageProps) { + const src = getTheme() === Theme.Light + ? GithubIconLight + : GithubIconDark; + + return ; +} + +export function WebsiteIcon(props: ImageProps) { + const src = getTheme() === Theme.Light + ? WebsiteIconLight + : WebsiteIconDark; + + return ; +} diff --git a/src/components/PluginSettings/LinkIconButton.tsx b/src/components/PluginSettings/LinkIconButton.tsx index ea36dda24b..dd840f52ec 100644 --- a/src/components/PluginSettings/LinkIconButton.tsx +++ b/src/components/PluginSettings/LinkIconButton.tsx @@ -6,22 +6,16 @@ import "./LinkIconButton.css"; -import { getTheme, Theme } from "@utils/discord"; import { MaskedLink, Tooltip } from "@webpack/common"; -const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg"; -const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg"; -const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg"; -const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg"; +import { GithubIcon, WebsiteIcon } from ".."; -export function GithubIcon() { - const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark; - return ; +export function GithubLinkIcon() { + return ; } -export function WebsiteIcon() { - const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark; - return ; +export function WebsiteLinkIcon() { + return ; } interface Props { @@ -41,5 +35,5 @@ function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) ); } -export const WebsiteButton = (props: Props) => ; -export const GithubButton = (props: Props) => ; +export const WebsiteButton = (props: Props) => ; +export const GithubButton = (props: Props) => ; diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index e5da01f36d..8b14283b84 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -27,7 +27,7 @@ import { gitRemote } from "@shared/vencordUserAgent"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common"; @@ -310,3 +310,13 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti ); } + +export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) { + openModal(modalProps => ( + onRestartNeeded?.(plugin.name)} + /> + )); +} diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index c659e78385..38ddc4a90c 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -23,7 +23,7 @@ import { showNotice } from "@api/Notices"; import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { CogWheel, InfoIcon } from "@components/Icons"; -import PluginModal from "@components/PluginSettings/PluginModal"; +import { openPluginModal } from "@components/PluginSettings/PluginModal"; import { AddonCard } from "@components/VencordSettings/AddonCard"; import { SettingsTab } from "@components/VencordSettings/shared"; import { ChangeList } from "@utils/ChangeList"; @@ -31,7 +31,6 @@ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; -import { openModalLazy } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { Plugin } from "@utils/types"; import { findByPropsLazy } from "@webpack"; @@ -45,7 +44,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => const cl = classNameFactory("vc-plugins-"); const logger = new Logger("PluginSettings", "#a6d189"); -const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); +const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); @@ -96,14 +95,6 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on const isEnabled = () => settings.enabled ?? false; - function openModal() { - openModalLazy(async () => { - return modalProps => { - return onRestartNeeded(plugin.name)} />; - }; - }); - } - function toggleEnabled() { const wasEnabled = isEnabled(); @@ -160,7 +151,11 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} infoButton={ -