diff --git a/src/def.d.ts b/src/def.d.ts index 842fa781..67e1a303 100644 --- a/src/def.d.ts +++ b/src/def.d.ts @@ -388,6 +388,7 @@ interface VendettaObject { invites: PropIntellisense<"acceptInviteAndTransitionToInviteChannel">; commands: PropIntellisense<"getBuiltInCommands">; navigation: PropIntellisense<"pushLazy">; + messageUtil: PropIntellisense<"sendBotMessage">; navigationStack: PropIntellisense<"createStackNavigator">; NavigationNative: PropIntellisense<"NavigationContainer">; // You may ask: "Why not just install Flux's types?" diff --git a/src/lib/commands/debug.ts b/src/lib/commands/debug.ts new file mode 100644 index 00000000..f29c875a --- /dev/null +++ b/src/lib/commands/debug.ts @@ -0,0 +1,40 @@ +import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@types"; +import { getDebugInfo } from "@lib/debug"; +import { messageUtil } from "@metro/common"; + +export default { + name: "debug", + displayName: "debug", + description: "Send Vendetta debug info.", + displayDescription: "Send Vendetta debug info.", + options: [ + { + name: "ephemeral", + displayName: "ephemeral", + type: ApplicationCommandOptionType.BOOLEAN, + description: "Send debug info ephemerally.", + displayDescription: "Send debug info ephemerally.", + } + ], + applicationId: "-1", + inputType: ApplicationCommandInputType.BUILT_IN_TEXT, + type: ApplicationCommandType.CHAT, + execute([ephemeral], ctx) { + const info = getDebugInfo(); + const content = [ + "**__Vendetta Debug Info__**", + `> Vendetta: ${info.vendetta.version} (${info.vendetta.loader})`, + `> Discord: ${info.discord.version} (${info.discord.build})`, + `> React: ${info.react.version} (RN ${info.react.nativeVersion})`, + `> Hermes: ${info.hermes.version} (bcv${info.hermes.bytecodeVersion})`, + `> System: ${info.os.name} ${info.os.version} (SDK ${info.os.sdk})`, + `> Device: ${info.device.model} (${info.device.codename})`, + ].join("\n"); + + if (ephemeral?.value) { + messageUtil.sendBotMessage(ctx.channel.id, content); + } else { + return { content }; + } + } +} diff --git a/src/lib/commands/eval.ts b/src/lib/commands/eval.ts new file mode 100644 index 00000000..2a0e27bc --- /dev/null +++ b/src/lib/commands/eval.ts @@ -0,0 +1,49 @@ +import { findByProps } from "@metro/filters"; +import { messageUtil } from "@metro/common"; +import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType } from "@types"; + +const util = findByProps("inspect"); +const AsyncFunction = (async () => void 0).constructor; + +const ZERO_WIDTH_SPACE_CHARACTER = "\u200B"; + +function wrapInJSCodeblock(resString: string) { + return "```js\n" + resString.replaceAll("`", "`" + ZERO_WIDTH_SPACE_CHARACTER) + "\n```"; +} + +export default { + name: "eval", + displayName: "eval", + description: "Evaluate JavaScript code.", + displayDescription: "Evaluate JavaScript code.", + options: [ + { + name: "code", + displayName: "code", + type: ApplicationCommandOptionType.STRING, + description: "The code to evaluate.", + displayDescription: "The code to evaluate.", + required: true + }, + { + name: "async", + displayName: "async", + type: ApplicationCommandOptionType.BOOLEAN, + description: "Whether to support 'await' in code. Must explicitly return for result (default: false)", + displayDescription: "Whether to support 'await' in code. Must explicitly return for result (default: false)" + } + ], + applicationId: "-1", + inputType: ApplicationCommandInputType.BUILT_IN_TEXT, + type: ApplicationCommandType.CHAT, + async execute([code, async], ctx) { + try { + const res = util.inspect(async?.value ? await AsyncFunction(code.value)() : eval?.(code.value)); + const trimmedRes = res.length > 2000 ? res.slice(0, 2000) + "..." : res; + + messageUtil.sendBotMessage(ctx.channel.id, wrapInJSCodeblock(trimmedRes)); + } catch (err: any) { + messageUtil.sendBotMessage(ctx.channel.id, wrapInJSCodeblock(err?.stack ?? err)); + } + } +} diff --git a/src/lib/commands.ts b/src/lib/commands/index.ts similarity index 83% rename from src/lib/commands.ts rename to src/lib/commands/index.ts index 4717d0b3..7619625f 100644 --- a/src/lib/commands.ts +++ b/src/lib/commands/index.ts @@ -2,6 +2,10 @@ import { ApplicationCommand, ApplicationCommandType } from "@types"; import { commands as commandsModule } from "@metro/common"; import { after } from "@lib/patcher"; +import evalCommand from "@lib/commands/eval"; +import debugCommand from "@lib/commands/debug"; +import pluginCommand from "@lib/commands/plugins"; + let commands: ApplicationCommand[] = []; export function patchCommands() { @@ -9,6 +13,9 @@ export function patchCommands() { if (type === ApplicationCommandType.CHAT) return res.concat(commands); }); + // Register core commands + [evalCommand, debugCommand, pluginCommand].forEach(registerCommand); + return () => { commands = []; unpatch(); diff --git a/src/lib/commands/plugins.ts b/src/lib/commands/plugins.ts new file mode 100644 index 00000000..523cd1d4 --- /dev/null +++ b/src/lib/commands/plugins.ts @@ -0,0 +1,45 @@ +import { ApplicationCommand, ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Plugin } from "@types"; +import { messageUtil } from "@metro/common"; +import { plugins as pluginStorage } from "../plugins"; + +export default { + name: "plugins", + displayName: "plugins", + description: "Send list of installed plugins.", + displayDescription: "Send list of installed plugins.", + options: [ + { + name: "ephemeral", + displayName: "ephemeral", + type: ApplicationCommandOptionType.BOOLEAN, + description: "Send plugins list ephemerally.", + displayDescription: "Send plugins list ephemerally.", + } + ], + inputType: ApplicationCommandInputType.BUILT_IN_TEXT, + type: ApplicationCommandType.CHAT, + execute([ephemeral], ctx) { + const plugins = Object.values(pluginStorage).sort((a, b) => a.manifest.name.localeCompare(b.manifest.name)); + + const enabled = plugins.filter(p => p.enabled).map(p => p.manifest.name); + const disabled = plugins.filter(p => !p.enabled).map(p => p.manifest.name); + + const content = [ + `**__Installed Plugins (${plugins.length})__**`, + ...(enabled.length > 0 ? [ + `Enabled (${enabled.length}):`, + "> " + enabled.join(", "), + ] : []), + ...(disabled.length > 0 ? [ + `Disabled (${disabled.length}):`, + "> " + disabled.join(", "), + ] : []), + ].join("\n"); + + if (ephemeral?.value) { + messageUtil.sendBotMessage(ctx.channel.id, content); + } else { + return { content }; + } + } +} diff --git a/src/lib/metro/common.ts b/src/lib/metro/common.ts index 80bad8a0..994544c9 100644 --- a/src/lib/metro/common.ts +++ b/src/lib/metro/common.ts @@ -13,6 +13,7 @@ export const assets = findByProps("registerAsset"); export const invites = findByProps("acceptInviteAndTransitionToInviteChannel"); export const commands = findByProps("getBuiltInCommands"); export const navigation = findByProps("pushLazy"); +export const messageUtil = findByProps("sendBotMessage"); export const navigationStack = findByProps("createStackNavigator"); export const NavigationNative = findByProps("NavigationContainer");