Implement core commands
This commit is contained in:
parent
2ff46796bd
commit
c8cd4967de
8 changed files with 246 additions and 93 deletions
|
|
@ -1,92 +0,0 @@
|
|||
import { commands as commandsModule } from "@metro/common";
|
||||
import { after } from "@lib/patcher";
|
||||
|
||||
let commands: ApplicationCommand[] = [];
|
||||
|
||||
export interface ApplicationCommand {
|
||||
description: string;
|
||||
name: string;
|
||||
options: ApplicationCommandOption[];
|
||||
execute: (args: any[], ctx: CommandContext) => CommandResult | void | Promise<CommandResult> | Promise<void>;
|
||||
id?: string;
|
||||
applicationId: string;
|
||||
displayName: string;
|
||||
displayDescription: string;
|
||||
inputType: ApplicationCommandInputType;
|
||||
type: ApplicationCommandType;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandInputType {
|
||||
BUILT_IN,
|
||||
BUILT_IN_TEXT,
|
||||
BUILT_IN_INTEGRATION,
|
||||
BOT,
|
||||
PLACEHOLDER,
|
||||
}
|
||||
|
||||
export interface ApplicationCommandOption {
|
||||
name: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
type: ApplicationCommandOptionType;
|
||||
displayName: string;
|
||||
displayDescription: string;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandOptionType {
|
||||
SUB_COMMAND = 1,
|
||||
SUB_COMMAND_GROUP,
|
||||
STRING,
|
||||
INTEGER,
|
||||
BOOLEAN,
|
||||
USER,
|
||||
CHANNEL,
|
||||
ROLE,
|
||||
MENTIONABLE,
|
||||
NUMBER,
|
||||
ATTACHMENT,
|
||||
}
|
||||
|
||||
export enum ApplicationCommandType {
|
||||
CHAT = 1,
|
||||
USER,
|
||||
MESSAGE,
|
||||
}
|
||||
|
||||
export interface CommandContext {
|
||||
channel: any;
|
||||
guild: any;
|
||||
}
|
||||
|
||||
export interface CommandResult {
|
||||
content: string;
|
||||
tts?: boolean;
|
||||
}
|
||||
|
||||
export function patchCommands() {
|
||||
const unpatch = after("getBuiltInCommands", commandsModule, ([type], res: ApplicationCommand[]) => {
|
||||
if (type === ApplicationCommandType.CHAT) return res.concat(commands);
|
||||
});
|
||||
|
||||
return () => {
|
||||
commands = [];
|
||||
unpatch();
|
||||
};
|
||||
}
|
||||
|
||||
export function registerCommand(command: ApplicationCommand): () => void {
|
||||
// Get built in commands
|
||||
const builtInCommands = commandsModule.getBuiltInCommands(ApplicationCommandType.CHAT, true, false);
|
||||
builtInCommands.sort((a: ApplicationCommand, b: ApplicationCommand) => parseInt(b.id!) - parseInt(a.id!));
|
||||
|
||||
const lastCommand = builtInCommands[builtInCommands.length - 1];
|
||||
|
||||
// Override the new command's id to the last command id - 1
|
||||
command.id = (parseInt(lastCommand.id, 10) - 1).toString();
|
||||
|
||||
// Add it to the commands array
|
||||
commands.push(command);
|
||||
|
||||
// Return command id so it can be unregistered
|
||||
return () => (commands = commands.filter(({ id }) => id !== command.id));
|
||||
}
|
||||
34
src/lib/commands/core/debug.ts
Normal file
34
src/lib/commands/core/debug.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Strings } from "@/lib/i18n";
|
||||
import { getDebugInfo } from "@lib/debug";
|
||||
import { messageUtil } from "@metro/common";
|
||||
import { ApplicationCommand, ApplicationCommandOptionType } from "../types";
|
||||
|
||||
export default () => <ApplicationCommand>{
|
||||
name: "debug",
|
||||
description: Strings.COMMAND_DEBUG_DESC,
|
||||
options: [
|
||||
{
|
||||
name: "ephemeral",
|
||||
type: ApplicationCommandOptionType.BOOLEAN,
|
||||
description: Strings.COMMAND_DEBUG_OPT_EPHEMERALLY,
|
||||
}
|
||||
],
|
||||
execute([ephemeral], ctx) {
|
||||
const info = getDebugInfo();
|
||||
const content = [
|
||||
"**Bunny Debug Info**",
|
||||
`> Bunny: ${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 {
|
||||
messageUtil.sendMessage(ctx.channel.id, { content });
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/lib/commands/core/eval.ts
Normal file
41
src/lib/commands/core/eval.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { findByProps } from "@metro/filters";
|
||||
import { messageUtil } from "@metro/common";
|
||||
import { Strings } from "@/lib/i18n";
|
||||
import { ApplicationCommand, ApplicationCommandOptionType } 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 () => <ApplicationCommand>{
|
||||
name: "eval",
|
||||
description: Strings.COMMAND_EVAL_DESC,
|
||||
options: [
|
||||
{
|
||||
name: "code",
|
||||
type: ApplicationCommandOptionType.STRING,
|
||||
description: Strings.COMMAND_EVAL_OPT_CODE,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "async",
|
||||
type: ApplicationCommandOptionType.BOOLEAN,
|
||||
description: Strings.COMMAND_EVAL_OPT_ASYNC,
|
||||
}
|
||||
],
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/lib/commands/core/plugins.ts
Normal file
41
src/lib/commands/core/plugins.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { messageUtil } from "@metro/common";
|
||||
import { plugins as pluginStorage } from "@lib/plugins";
|
||||
import { Strings } from "@/lib/i18n";
|
||||
import { ApplicationCommand, ApplicationCommandOptionType } from "../types";
|
||||
|
||||
export default () => <ApplicationCommand>{
|
||||
name: "plugins",
|
||||
description: Strings.COMMAND_DEBUG_DESC,
|
||||
options: [
|
||||
{
|
||||
name: "ephemeral",
|
||||
displayName: "ephemeral",
|
||||
type: ApplicationCommandOptionType.BOOLEAN,
|
||||
description: Strings.COMMAND_DEBUG_OPT_EPHEMERALLY,
|
||||
}
|
||||
],
|
||||
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 {
|
||||
messageUtil.sendMessage(ctx.channel.id, { content });
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/lib/commands/index.ts
Normal file
53
src/lib/commands/index.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { commands as commandsModule } from "@metro/common";
|
||||
import { after } from "@lib/patcher";
|
||||
import { ApplicationCommand, ApplicationCommandType, ApplicationCommandInputType } from "./types";
|
||||
|
||||
let commands: ApplicationCommand[] = [];
|
||||
|
||||
export function patchCommands() {
|
||||
const unpatch = after("getBuiltInCommands", commandsModule, ([type], res: ApplicationCommand[]) => {
|
||||
if (type === ApplicationCommandType.CHAT) return res.concat(commands);
|
||||
});
|
||||
|
||||
// Register core commands
|
||||
[
|
||||
require("@lib/commands/core/eval"),
|
||||
require("@lib/commands/core/debug"),
|
||||
require("@lib/commands/core/plugins")
|
||||
].forEach(r => registerCommand(r.default()));
|
||||
|
||||
return () => {
|
||||
commands = [];
|
||||
unpatch();
|
||||
};
|
||||
}
|
||||
|
||||
export function registerCommand(command: ApplicationCommand): () => void {
|
||||
// Get built in commands
|
||||
const builtInCommands = commandsModule.getBuiltInCommands(ApplicationCommandType.CHAT, true, false);
|
||||
builtInCommands.sort((a: ApplicationCommand, b: ApplicationCommand) => parseInt(b.id!) - parseInt(a.id!));
|
||||
|
||||
const lastCommand = builtInCommands[builtInCommands.length - 1];
|
||||
|
||||
// Override the new command's id to the last command id - 1
|
||||
command.id = (parseInt(lastCommand.id, 10) - 1).toString();
|
||||
|
||||
// Fill optional args
|
||||
command.__isBunny = true;
|
||||
command.applicationId ??= "-1";
|
||||
command.type ??= ApplicationCommandType.CHAT;
|
||||
command.inputType = ApplicationCommandInputType.BUILT_IN;
|
||||
command.displayName ??= command.name;
|
||||
command.displayDescription ??= command.description;
|
||||
|
||||
if (command.options) for (const opt of command.options) {
|
||||
opt.displayName ??= opt.name;
|
||||
opt.displayDescription ??= opt.description;
|
||||
}
|
||||
|
||||
// Add it to the commands array
|
||||
commands.push(command);
|
||||
|
||||
// Return command id so it can be unregistered
|
||||
return () => (commands = commands.filter(({ id }) => id !== command.id));
|
||||
}
|
||||
68
src/lib/commands/types.ts
Normal file
68
src/lib/commands/types.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
export interface Argument {
|
||||
type: ApplicationCommandOptionType,
|
||||
name: string,
|
||||
value: string,
|
||||
focused: undefined;
|
||||
options: Argument[];
|
||||
}
|
||||
|
||||
export interface ApplicationCommand {
|
||||
name: string;
|
||||
description: string;
|
||||
execute: (args: Argument[], ctx: CommandContext) => CommandResult | void | Promise<CommandResult> | Promise<void>;
|
||||
options: ApplicationCommandOption[];
|
||||
id?: string;
|
||||
applicationId?: string;
|
||||
displayName?: string;
|
||||
displayDescription?: string;
|
||||
inputType?: ApplicationCommandInputType;
|
||||
type?: ApplicationCommandType;
|
||||
__isBunny?: true;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandInputType {
|
||||
BUILT_IN,
|
||||
BUILT_IN_TEXT,
|
||||
BUILT_IN_INTEGRATION,
|
||||
BOT,
|
||||
PLACEHOLDER,
|
||||
}
|
||||
|
||||
export interface ApplicationCommandOption {
|
||||
name: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
type: ApplicationCommandOptionType;
|
||||
displayName?: string;
|
||||
displayDescription?: string;
|
||||
}
|
||||
|
||||
export enum ApplicationCommandOptionType {
|
||||
SUB_COMMAND = 1,
|
||||
SUB_COMMAND_GROUP,
|
||||
STRING,
|
||||
INTEGER,
|
||||
BOOLEAN,
|
||||
USER,
|
||||
CHANNEL,
|
||||
ROLE,
|
||||
MENTIONABLE,
|
||||
NUMBER,
|
||||
ATTACHMENT,
|
||||
}
|
||||
|
||||
export enum ApplicationCommandType {
|
||||
CHAT = 1,
|
||||
USER,
|
||||
MESSAGE,
|
||||
}
|
||||
|
||||
export interface CommandContext {
|
||||
channel: any;
|
||||
guild: any;
|
||||
}
|
||||
|
||||
export interface CommandResult {
|
||||
content: string;
|
||||
tts?: boolean;
|
||||
}
|
||||
|
|
@ -66,5 +66,12 @@
|
|||
"UH_OH": "Uh oh.",
|
||||
"UNINSTALL": "Uninstall",
|
||||
"UNINSTALL_TITLE": "Uninstall {title}",
|
||||
"VERSIONS": "Versions"
|
||||
"VERSIONS": "Versions",
|
||||
"COMMAND_DEBUG_DESC": "Send Vendetta debug info.",
|
||||
"COMMAND_DEBUG_OPT_EPHEMERALLY": "Send debug info ephemerally.",
|
||||
"COMMAND_EVAL_DESC": "Evaluate JavaScript code.",
|
||||
"COMMAND_EVAL_OPT_CODE": "The code to evaluate.",
|
||||
"COMMAND_EVAL_OPT_ASYNC": "Whether to support 'await' in code. Must explicitly return for result (default: false)",
|
||||
"COMMAND_PLUGINS_DESC": "Send list of installed plugins.",
|
||||
"COMMAND_PLUGINS_OPT_EPHEMERALLY": "Send plugins list ephemerally."
|
||||
}
|
||||
|
|
@ -50,6 +50,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");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue