Improve SupportHelper

- improve update check when entering support channel
- add "Run Snippet" button to venbot messages with codeblock
- add "Send /vencord-debug" button to messages that contain /vencord-debug
- add "Update Now" button to messages by venbot and in #known-issues that contain "update"
- add some common issues like RPC disabled / NoRPC enabled to /vencord-debug
- split plugin list into separate /vencord-plugins command to reduce size & avoid >2000 chars errors
This commit is contained in:
Vendicated 2024-06-21 19:15:48 +02:00
parent 7dc1d4c498
commit b9392c3be2
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
3 changed files with 233 additions and 73 deletions

View file

@ -16,24 +16,33 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { addAccessory } from "@api/MessageAccessories";
import { getUserSettingLazy } from "@api/UserSettings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants"; import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
import { sendMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc"; import { isPluginDev, tryOrElse } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { onlyOnce } from "@utils/onlyOnce";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { isOutdated, update } from "@utils/updater"; import { checkForUpdates, isOutdated, update } from "@utils/updater";
import { Alerts, Card, ChannelStore, Forms, GuildMemberStore, NavigationRouter, Parser, RelationshipStore, UserStore } from "@webpack/common"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Toasts, UserStore } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import plugins from "~plugins"; import plugins, { PluginMeta } from "~plugins";
import settings from "./settings"; import settings from "./settings";
const VENCORD_GUILD_ID = "1015060230222131221"; const VENCORD_GUILD_ID = "1015060230222131221";
const VENBOT_USER_ID = "1017176847865352332";
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
const CodeBlockRe = /```js\n(.+?)```/s;
const AllowedChannelIds = [ const AllowedChannelIds = [
SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_ID,
@ -47,12 +56,88 @@ const TrustedRolesIds = [
"1042507929485586532", // donor "1042507929485586532", // donor
]; ];
const AsyncFunction = async function () { }.constructor;
const ShowCurrentGame = getUserSettingLazy<boolean>("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}](<https://github.com/Vendicated/Vencord/commit/${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({ export default definePlugin({
name: "SupportHelper", name: "SupportHelper",
required: true, required: true,
description: "Helps us provide support to you", description: "Helps us provide support to you",
authors: [Devs.Ven], authors: [Devs.Ven],
dependencies: ["CommandsAPI"], dependencies: ["CommandsAPI", "UserSettingsAPI"],
patches: [{ patches: [{
find: ".BEGINNING_DM.format", find: ".BEGINNING_DM.format",
@ -62,51 +147,20 @@ export default definePlugin({
} }
}], }],
commands: [{ commands: [
name: "vencord-debug", {
description: "Send Vencord Debug info", name: "vencord-debug",
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), description: "Send Vencord debug info",
async execute() { predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
const { RELEASE_CHANNEL } = window.GLOBAL_ENV; execute: async () => ({ content: await generateDebugInfoMessage() })
},
const client = (() => { {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`; name: "vencord-plugins",
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`; description: "Send Vencord plugin list",
if ("armcord" in window) return `ArmCord v${window.armcord.version}`; predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
execute: () => ({ content: generatePluginList() })
// @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}](<https://github.com/Vendicated/Vencord/commit/${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", "```")
};
} }
}], ],
flux: { flux: {
async CHANNEL_SELECT({ channelId }) { async CHANNEL_SELECT({ channelId }) {
@ -115,24 +169,25 @@ ${makeCodeblock(enabledPlugins.join(", "))}
const selfId = UserStore.getCurrentUser()?.id; const selfId = UserStore.getCurrentUser()?.id;
if (!selfId || isPluginDev(selfId)) return; if (!selfId || isPluginDev(selfId)) return;
if (isOutdated) { if (!IS_UPDATER_DISABLED) {
return Alerts.show({ await checkForUpdatesOnce().catch(() => { });
title: "Hold on!",
body: <div> if (isOutdated) {
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText> return Alerts.show({
<Forms.FormText className={Margins.top8}> title: "Hold on!",
Please first update before asking for support! body: <div>
</Forms.FormText> <Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
</div>, <Forms.FormText className={Margins.top8}>
onCancel: () => openUpdaterModal!(), Please first update before asking for support!
cancelText: "View Updates", </Forms.FormText>
confirmText: "Update & Restart Now", </div>,
async onConfirm() { onCancel: () => openUpdaterModal!(),
await update(); cancelText: "View Updates",
relaunch(); confirmText: "Update & Restart Now",
}, onConfirm: forceUpdate,
secondaryConfirmText: "I know what I'm doing or I can't update" secondaryConfirmText: "I know what I'm doing or I can't update"
}); });
}
} }
// @ts-ignore outdated type // @ts-ignore outdated type
@ -148,8 +203,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
contact your package maintainer for support instead. contact your package maintainer for support instead.
</Forms.FormText> </Forms.FormText>
</div>, </div>
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
}); });
} }
@ -163,8 +217,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
contact your package maintainer for support instead. contact your package maintainer for support instead.
</Forms.FormText> </Forms.FormText>
</div>, </div>
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
}); });
} }
} }
@ -172,7 +225,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => { ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
if (!isPluginDev(userId)) return null; if (!isPluginDev(userId)) return null;
if (RelationshipStore.isFriend(userId)) return null; if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
return ( return (
<Card className={`vc-plugins-restart-card ${Margins.top8}`}> <Card className={`vc-plugins-restart-card ${Margins.top8}`}>
@ -182,5 +235,86 @@ ${makeCodeblock(enabledPlugins.join(", "))}
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"} {!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
</Card> </Card>
); );
}, { noop: true }) }, { noop: true }),
start() {
addAccessory("vencord-debug", props => {
const buttons = [] as JSX.Element[];
const shouldAddUpdateButton =
!IS_UPDATER_DISABLED
&& (
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
)
&& props.message.content?.includes("update");
if (shouldAddUpdateButton) {
buttons.push(
<Button
key="vc-update"
color={Button.Colors.GREEN}
onClick={async () => {
try {
if (await forceUpdate())
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
else
showToast("Already up to date!", Toasts.Type.MESSAGE);
} catch (e) {
new Logger(this.name).error("Error while updating:", e);
showToast("Failed to update :(", Toasts.Type.FAILURE);
}
}}
>
Update Now
</Button>
);
}
if (props.channel.id === SUPPORT_CHANNEL_ID) {
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
buttons.push(
<Button
key="vc-dbg"
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
>
Run /vencord-debug
</Button>,
<Button
key="vc-plg-list"
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
>
Run /vencord-plugins
</Button>
);
}
if (props.message.author.id === VENBOT_USER_ID) {
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
if (match) {
buttons.push(
<Button
key="vc-run-snippet"
onClick={async () => {
try {
await AsyncFunction(match[1])();
showToast("Success!", Toasts.Type.SUCCESS);
} catch (e) {
new Logger(this.name).error("Error while running snippet:", e);
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
}
}}
>
Run Snippet
</Button>
);
}
}
}
return buttons.length
? <Flex>{buttons}</Flex>
: null;
});
},
}); });

View file

@ -99,3 +99,14 @@ export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id);
export function pluralise(amount: number, singular: string, plural = singular + "s") { export function pluralise(amount: number, singular: string, plural = singular + "s") {
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
} }
export function tryOrElse<T>(func: () => T, fallback: T): T {
try {
const res = func();
return res instanceof Promise
? res.catch(() => fallback) as T
: res;
} catch {
return fallback;
}
}

View file

@ -131,3 +131,18 @@ export function makeCodeblock(text: string, language?: string) {
const chars = "```"; const chars = "```";
return `${chars}${language || ""}\n${text.replaceAll("```", "\\`\\`\\`")}\n${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 + "``";
}