diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index d59d82afc..95a2c05b3 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -16,24 +16,33 @@ * along with this program. If not, see . */ +import { addAccessory } from "@api/MessageAccessories"; +import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; +import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants"; +import { sendMessage } from "@utils/discord"; +import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { isPluginDev } from "@utils/misc"; +import { isPluginDev, tryOrElse } from "@utils/misc"; import { relaunch } from "@utils/native"; +import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; -import { isOutdated, update } from "@utils/updater"; -import { Alerts, Card, ChannelStore, Forms, GuildMemberStore, NavigationRouter, Parser, RelationshipStore, UserStore } from "@webpack/common"; +import { checkForUpdates, isOutdated, update } from "@utils/updater"; +import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Toasts, UserStore } from "@webpack/common"; import gitHash from "~git-hash"; -import plugins from "~plugins"; +import plugins, { PluginMeta } from "~plugins"; import settings from "./settings"; const VENCORD_GUILD_ID = "1015060230222131221"; +const VENBOT_USER_ID = "1017176847865352332"; +const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; +const CodeBlockRe = /```js\n(.+?)```/s; const AllowedChannelIds = [ SUPPORT_CHANNEL_ID, @@ -47,12 +56,88 @@ const TrustedRolesIds = [ "1042507929485586532", // donor ]; +const AsyncFunction = async function () { }.constructor; + +const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!; + +async function forceUpdate() { + const outdated = await checkForUpdates(); + if (outdated) { + await update(); + relaunch(); + } + + return outdated; +} + +async function generateDebugInfoMessage() { + const { RELEASE_CHANNEL } = window.GLOBAL_ENV; + + const client = (() => { + if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`; + if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`; + if ("armcord" in window) return `ArmCord v${window.armcord.version}`; + + // @ts-expect-error + const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web"; + return `${name} (${navigator.userAgent})`; + })(); + + const info = { + Vencord: + `v${VERSION} • [${gitHash}]()` + + `${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, + Client: `${RELEASE_CHANNEL} ~ ${client}`, + Platform: window.navigator.platform + }; + + if (IS_DISCORD_DESKTOP) { + info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A"; + } + + const commonIssues = { + "NoRPC enabled": Vencord.Plugins.isPluginEnabled("NoRPC"), + "Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false), + "Vencord DevBuild": !IS_STANDALONE, + "Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin), + "More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5, + }; + + let content = `>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}`; + content += "\n" + Object.entries(commonIssues) + .filter(([, v]) => v).map(([k]) => `⚠️ ${k}`) + .join("\n"); + + return content.trim(); +} + +function generatePluginList() { + const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required; + + const enabledPlugins = Object.keys(plugins) + .filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p)); + + const enabledStockPlugins = enabledPlugins.filter(p => !PluginMeta[p].userPlugin); + const enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin); + + + let content = `**Enabled Plugins (${enabledStockPlugins.length}):**\n${makeCodeblock(enabledStockPlugins.join(", "))}`; + + if (enabledUserPlugins.length) { + content += `**Enabled UserPlugins (${enabledUserPlugins.length}):**\n${makeCodeblock(enabledUserPlugins.join(", "))}`; + } + + return content; +} + +const checkForUpdatesOnce = onlyOnce(checkForUpdates); + export default definePlugin({ name: "SupportHelper", required: true, description: "Helps us provide support to you", authors: [Devs.Ven], - dependencies: ["CommandsAPI"], + dependencies: ["CommandsAPI", "UserSettingsAPI"], patches: [{ find: ".BEGINNING_DM.format", @@ -62,51 +147,20 @@ export default definePlugin({ } }], - commands: [{ - name: "vencord-debug", - description: "Send Vencord Debug info", - predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), - async execute() { - const { RELEASE_CHANNEL } = window.GLOBAL_ENV; - - const client = (() => { - if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`; - if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`; - if ("armcord" in window) return `ArmCord v${window.armcord.version}`; - - // @ts-expect-error - const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web"; - return `${name} (${navigator.userAgent})`; - })(); - - const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required; - - const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p)); - - const info = { - Vencord: - `v${VERSION} • [${gitHash}]()` + - `${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`, - Client: `${RELEASE_CHANNEL} ~ ${client}`, - Platform: window.navigator.platform - }; - - if (IS_DISCORD_DESKTOP) { - info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A"; - } - - const debugInfo = ` ->>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")} - -Enabled Plugins (${enabledPlugins.length}): -${makeCodeblock(enabledPlugins.join(", "))} -`; - - return { - content: debugInfo.trim().replaceAll("```\n", "```") - }; + commands: [ + { + name: "vencord-debug", + description: "Send Vencord debug info", + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), + execute: async () => ({ content: await generateDebugInfoMessage() }) + }, + { + name: "vencord-plugins", + description: "Send Vencord plugin list", + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), + execute: () => ({ content: generatePluginList() }) } - }], + ], flux: { async CHANNEL_SELECT({ channelId }) { @@ -115,24 +169,25 @@ ${makeCodeblock(enabledPlugins.join(", "))} const selfId = UserStore.getCurrentUser()?.id; if (!selfId || isPluginDev(selfId)) return; - if (isOutdated) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using an outdated version of Vencord! Chances are, your issue is already fixed. - - Please first update before asking for support! - -
, - onCancel: () => openUpdaterModal!(), - cancelText: "View Updates", - confirmText: "Update & Restart Now", - async onConfirm() { - await update(); - relaunch(); - }, - secondaryConfirmText: "I know what I'm doing or I can't update" - }); + if (!IS_UPDATER_DISABLED) { + await checkForUpdatesOnce().catch(() => { }); + + if (isOutdated) { + return Alerts.show({ + title: "Hold on!", + body:
+ You are using an outdated version of Vencord! Chances are, your issue is already fixed. + + Please first update before asking for support! + +
, + onCancel: () => openUpdaterModal!(), + cancelText: "View Updates", + confirmText: "Update & Restart Now", + onConfirm: forceUpdate, + secondaryConfirmText: "I know what I'm doing or I can't update" + }); + } } // @ts-ignore outdated type @@ -148,8 +203,7 @@ ${makeCodeblock(enabledPlugins.join(", "))} Please either switch to an officially supported version of Vencord, or contact your package maintainer for support instead. - , - onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50) + }); } @@ -163,8 +217,7 @@ ${makeCodeblock(enabledPlugins.join(", "))} Please either switch to an officially supported version of Vencord, or contact your package maintainer for support instead. - , - onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50) + }); } } @@ -172,7 +225,7 @@ ${makeCodeblock(enabledPlugins.join(", "))} ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => { if (!isPluginDev(userId)) return null; - if (RelationshipStore.isFriend(userId)) return null; + if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null; return ( @@ -182,5 +235,86 @@ ${makeCodeblock(enabledPlugins.join(", "))} {!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"} ); - }, { noop: true }) + }, { noop: true }), + + start() { + addAccessory("vencord-debug", props => { + const buttons = [] as JSX.Element[]; + + const shouldAddUpdateButton = + !IS_UPDATER_DISABLED + && ( + (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || + (props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) + ) + && props.message.content?.includes("update"); + + if (shouldAddUpdateButton) { + buttons.push( + + ); + } + + if (props.channel.id === SUPPORT_CHANNEL_ID) { + if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { + buttons.push( + , + + ); + } + + if (props.message.author.id === VENBOT_USER_ID) { + const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); + if (match) { + buttons.push( + + ); + } + } + } + + return buttons.length + ? {buttons} + : null; + }); + }, }); diff --git a/src/utils/misc.tsx b/src/utils/misc.ts similarity index 92% rename from src/utils/misc.tsx rename to src/utils/misc.ts index fb08c93f6..7d6b4affc 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.ts @@ -99,3 +99,14 @@ export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id); export function pluralise(amount: number, singular: string, plural = singular + "s") { return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; } + +export function tryOrElse(func: () => T, fallback: T): T { + try { + const res = func(); + return res instanceof Promise + ? res.catch(() => fallback) as T + : res; + } catch { + return fallback; + } +} diff --git a/src/utils/text.ts b/src/utils/text.ts index 63f600742..2e85af4ef 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -131,3 +131,18 @@ export function makeCodeblock(text: string, language?: string) { const chars = "```"; return `${chars}${language || ""}\n${text.replaceAll("```", "\\`\\`\\`")}\n${chars}`; } + +export function stripIndent(strings: TemplateStringsArray, ...values: any[]) { + const string = String.raw({ raw: strings }, ...values); + + const match = string.match(/^[ \t]*(?=\S)/gm); + if (!match) return string.trim(); + + const minIndent = match.reduce((r, a) => Math.min(r, a.length), Infinity); + return string.replace(new RegExp(`^[ \\t]{${minIndent}}`, "gm"), "").trim(); +} + +export const ZWSP = "\u200b"; +export function toInlineCode(s: string) { + return "``" + ZWSP + s.replaceAll("`", ZWSP + "`" + ZWSP) + ZWSP + "``"; +}