mirror of
https://github.com/keanuplayz/TravBot-v3.git
synced 2024-08-15 02:33:12 +00:00
Removed lenient command handling
This commit is contained in:
parent
15012c7d17
commit
3798c27df9
14 changed files with 228 additions and 201 deletions
|
@ -11,7 +11,8 @@
|
|||
- Various changes to core
|
||||
- Added `guild` subcommand type (only accessible when `id: "guild"`)
|
||||
- Further reduced `channel.send()` to `send()` because it's used in *every, single, command*
|
||||
- Added `rest` subcommand type (only available when `endpoint: true`), declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added
|
||||
- Added a `RestCommand` type, declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added
|
||||
- Is no longer lenient to arguments when no proper subcommand fits (now it doesn't silently fail anymore), you now have to explicitly declare a `RestCommand` to get an arbitrary number of arguments
|
||||
|
||||
# 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09)
|
||||
- The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger.
|
||||
|
|
|
@ -26,7 +26,6 @@ const responses = [
|
|||
|
||||
export default new NamedCommand({
|
||||
description: "Answers your question in an 8-ball manner.",
|
||||
endpoint: false,
|
||||
usage: "<question>",
|
||||
run: "Please provide a question.",
|
||||
any: new Command({
|
||||
|
|
|
@ -62,7 +62,7 @@ export const BetCommand = new NamedCommand({
|
|||
|
||||
// handle invalid target
|
||||
if (target.id == author.id) return send("You can't bet Mons with yourself!");
|
||||
else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!");
|
||||
else if (target.bot && !IS_DEV_MODE) return send("You can't bet Mons with a bot!");
|
||||
|
||||
// handle invalid amount
|
||||
if (amount <= 0) return send("You must bet at least one Mon!");
|
||||
|
|
|
@ -128,7 +128,7 @@ export const PayCommand = new NamedCommand({
|
|||
else if (sender.money < amount)
|
||||
return send("You don't have enough Mons for that.", getMoneyEmbed(author));
|
||||
else if (target.id === author.id) return send("You can't send Mons to yourself!");
|
||||
else if (target.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!");
|
||||
else if (target.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!");
|
||||
|
||||
sender.money -= amount;
|
||||
receiver.money += amount;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Command, NamedCommand} from "../../core";
|
||||
import {Command, NamedCommand, RestCommand} from "../../core";
|
||||
|
||||
const letters: {[letter: string]: string[]} = {
|
||||
a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""),
|
||||
|
@ -35,7 +35,6 @@ export default new NamedCommand({
|
|||
description: "Transforms your text into vietnamese.",
|
||||
usage: "thonk ([text])",
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
if (args.length > 0) phrase = args.join(" ");
|
||||
const msg = await send(transform(phrase));
|
||||
msg.createReactionCollector(
|
||||
(reaction, user) => {
|
||||
|
@ -44,5 +43,17 @@ export default new NamedCommand({
|
|||
},
|
||||
{time: 60000}
|
||||
);
|
||||
}
|
||||
},
|
||||
any: new RestCommand({
|
||||
async run({send, message, channel, guild, author, member, client, args, combined}) {
|
||||
const msg = await send(transform(combined));
|
||||
msg.createReactionCollector(
|
||||
(reaction, user) => {
|
||||
if (user.id === author.id && reaction.emoji.name === "❌") msg.delete();
|
||||
return false;
|
||||
},
|
||||
{time: 60000}
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -293,7 +293,7 @@ export default new NamedCommand({
|
|||
});
|
||||
send("Activity set to default.");
|
||||
},
|
||||
any: new Command({
|
||||
any: new RestCommand({
|
||||
description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``,
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
const type = args[0];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Command, NamedCommand} from "../core";
|
||||
import {Command, NamedCommand, RestCommand} from "../core";
|
||||
|
||||
export default new NamedCommand({
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {Command, NamedCommand} from "../../core";
|
||||
import {Command, NamedCommand, RestCommand} from "../../core";
|
||||
import {processEmoteQueryFormatted} from "./modules/emote-utils";
|
||||
|
||||
export default new NamedCommand({
|
||||
description:
|
||||
"Send the specified emote list. Enter + to move an emote list to the next line, - to add a space, and _ to add a zero-width space.",
|
||||
run: "Please provide a list of emotes.",
|
||||
any: new Command({
|
||||
any: new RestCommand({
|
||||
description: "The emote(s) to send.",
|
||||
usage: "<emotes...>",
|
||||
async run({send, guild, channel, message, args}) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {GuildEmoji, MessageEmbed, User} from "discord.js";
|
||||
import {Command, NamedCommand, paginate, SendFunction} from "../../core";
|
||||
import {Command, NamedCommand, RestCommand, paginate, SendFunction} from "../../core";
|
||||
import {split} from "../../lib";
|
||||
import vm from "vm";
|
||||
|
||||
|
@ -11,7 +11,7 @@ export default new NamedCommand({
|
|||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
displayEmoteList(client.emojis.cache.array(), send, author);
|
||||
},
|
||||
any: new Command({
|
||||
any: new RestCommand({
|
||||
description:
|
||||
"Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i",
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Command, NamedCommand} from "../../core";
|
||||
import {Command, NamedCommand, RestCommand} from "../../core";
|
||||
import {Message, Channel, TextChannel} from "discord.js";
|
||||
import {processEmoteQueryArray} from "./modules/emote-utils";
|
||||
|
||||
|
@ -6,109 +6,112 @@ export default new NamedCommand({
|
|||
description:
|
||||
"Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.",
|
||||
usage: 'react <emotes...> (<distance / message ID / "Copy ID" / "Copy Message Link">)',
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
let target: Message | undefined;
|
||||
let distance = 1;
|
||||
run: "You need to enter some emotes first.",
|
||||
any: new RestCommand({
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
let target: Message | undefined;
|
||||
let distance = 1;
|
||||
|
||||
if (message.reference) {
|
||||
// If the command message is a reply to another message, use that as the react target.
|
||||
target = await channel.messages.fetch(message.reference.messageID!);
|
||||
}
|
||||
// handles reacts by message id/distance
|
||||
else if (args.length >= 2) {
|
||||
const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator.
|
||||
const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/;
|
||||
const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/;
|
||||
if (message.reference) {
|
||||
// If the command message is a reply to another message, use that as the react target.
|
||||
target = await channel.messages.fetch(message.reference.messageID!);
|
||||
}
|
||||
// handles reacts by message id/distance
|
||||
else if (args.length >= 2) {
|
||||
const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator.
|
||||
const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/;
|
||||
const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/;
|
||||
|
||||
// https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
|
||||
if (URLPattern.test(last)) {
|
||||
const match = URLPattern.exec(last)!;
|
||||
const guildID = match[1];
|
||||
const channelID = match[2];
|
||||
const messageID = match[3];
|
||||
let tmpChannel: Channel | undefined = channel;
|
||||
// https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
|
||||
if (URLPattern.test(last)) {
|
||||
const match = URLPattern.exec(last)!;
|
||||
const guildID = match[1];
|
||||
const channelID = match[2];
|
||||
const messageID = match[3];
|
||||
let tmpChannel: Channel | undefined = channel;
|
||||
|
||||
if (guild?.id !== guildID) {
|
||||
try {
|
||||
guild = await client.guilds.fetch(guildID);
|
||||
} catch {
|
||||
return send(`\`${guildID}\` is an invalid guild ID!`);
|
||||
if (guild?.id !== guildID) {
|
||||
try {
|
||||
guild = await client.guilds.fetch(guildID);
|
||||
} catch {
|
||||
return send(`\`${guildID}\` is an invalid guild ID!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID);
|
||||
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
||||
if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID);
|
||||
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
||||
|
||||
if (message.id !== messageID) {
|
||||
try {
|
||||
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
||||
} catch {
|
||||
return send(`\`${messageID}\` is an invalid message ID!`);
|
||||
if (message.id !== messageID) {
|
||||
try {
|
||||
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
||||
} catch {
|
||||
return send(`\`${messageID}\` is an invalid message ID!`);
|
||||
}
|
||||
}
|
||||
|
||||
args.pop();
|
||||
}
|
||||
// <Channel ID>-<Message ID> ("Copy ID" Button)
|
||||
else if (copyIDPattern.test(last)) {
|
||||
const match = copyIDPattern.exec(last)!;
|
||||
const channelID = match[1];
|
||||
const messageID = match[2];
|
||||
let tmpChannel: Channel | undefined = channel;
|
||||
|
||||
args.pop();
|
||||
}
|
||||
// <Channel ID>-<Message ID> ("Copy ID" Button)
|
||||
else if (copyIDPattern.test(last)) {
|
||||
const match = copyIDPattern.exec(last)!;
|
||||
const channelID = match[1];
|
||||
const messageID = match[2];
|
||||
let tmpChannel: Channel | undefined = channel;
|
||||
if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID);
|
||||
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
||||
|
||||
if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID);
|
||||
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
||||
|
||||
if (message.id !== messageID) {
|
||||
try {
|
||||
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
||||
} catch {
|
||||
return send(`\`${messageID}\` is an invalid message ID!`);
|
||||
if (message.id !== messageID) {
|
||||
try {
|
||||
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
||||
} catch {
|
||||
return send(`\`${messageID}\` is an invalid message ID!`);
|
||||
}
|
||||
}
|
||||
|
||||
args.pop();
|
||||
}
|
||||
// <Message ID>
|
||||
else if (/^\d{17,}$/.test(last)) {
|
||||
try {
|
||||
target = await channel.messages.fetch(last);
|
||||
} catch {
|
||||
return send(`No valid message found by the ID \`${last}\`!`);
|
||||
}
|
||||
|
||||
args.pop();
|
||||
}
|
||||
// <Message ID>
|
||||
else if (/^\d{17,}$/.test(last)) {
|
||||
try {
|
||||
target = await channel.messages.fetch(last);
|
||||
} catch {
|
||||
return send(`No valid message found by the ID \`${last}\`!`);
|
||||
args.pop();
|
||||
}
|
||||
// The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this.
|
||||
else if (/^\d+$/.test(last)) {
|
||||
distance = parseInt(last);
|
||||
|
||||
args.pop();
|
||||
if (distance >= 0 && distance <= 99) args.pop();
|
||||
else return send("Your distance must be between 0 and 99!");
|
||||
}
|
||||
}
|
||||
// The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this.
|
||||
else if (/^\d+$/.test(last)) {
|
||||
distance = parseInt(last);
|
||||
|
||||
if (distance >= 0 && distance <= 99) args.pop();
|
||||
else return send("Your distance must be between 0 and 99!");
|
||||
if (!target) {
|
||||
// Messages are ordered from latest to earliest.
|
||||
// You also have to add 1 as well because fetchMessages includes your own message.
|
||||
target = (
|
||||
await message.channel.messages.fetch({
|
||||
limit: distance + 1
|
||||
})
|
||||
).last();
|
||||
}
|
||||
|
||||
for (const emote of processEmoteQueryArray(args)) {
|
||||
// Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want
|
||||
const reaction = await target!.react(emote);
|
||||
|
||||
// This part is called with a promise because you don't want to wait 5 seconds between each reaction.
|
||||
setTimeout(() => {
|
||||
// This reason for this null assertion is that by the time you use this command, the client is going to be loaded.
|
||||
reaction.users.remove(client.user!);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
// Messages are ordered from latest to earliest.
|
||||
// You also have to add 1 as well because fetchMessages includes your own message.
|
||||
target = (
|
||||
await message.channel.messages.fetch({
|
||||
limit: distance + 1
|
||||
})
|
||||
).last();
|
||||
}
|
||||
|
||||
for (const emote of processEmoteQueryArray(args)) {
|
||||
// Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want
|
||||
const reaction = await target!.react(emote);
|
||||
|
||||
// This part is called with a promise because you don't want to wait 5 seconds between each reaction.
|
||||
setTimeout(() => {
|
||||
// This reason for this null assertion is that by the time you use this command, the client is going to be loaded.
|
||||
reaction.users.remove(client.user!);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Command, NamedCommand} from "../../core";
|
||||
import {Command, NamedCommand, RestCommand} from "../../core";
|
||||
import {streamList} from "../../modules/streamNotifications";
|
||||
|
||||
export default new NamedCommand({
|
||||
|
@ -8,12 +8,11 @@ export default new NamedCommand({
|
|||
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
const description = args.join(" ") || "No description set.";
|
||||
stream.description = description;
|
||||
stream.description = "No description set.";
|
||||
stream.update();
|
||||
send(`Successfully set the stream description to:`, {
|
||||
embed: {
|
||||
description,
|
||||
description: "No description set.",
|
||||
color: member!.displayColor
|
||||
}
|
||||
});
|
||||
|
@ -21,5 +20,25 @@ export default new NamedCommand({
|
|||
// Alternatively, I could make descriptions last outside of just one stream.
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
}
|
||||
},
|
||||
any: new RestCommand({
|
||||
async run({send, message, channel, guild, author, member, client, args, combined}) {
|
||||
const userID = author.id;
|
||||
|
||||
if (streamList.has(userID)) {
|
||||
const stream = streamList.get(userID)!;
|
||||
stream.description = combined;
|
||||
stream.update();
|
||||
send(`Successfully set the stream description to:`, {
|
||||
embed: {
|
||||
description: stream.description,
|
||||
color: member!.displayColor
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Alternatively, I could make descriptions last outside of just one stream.
|
||||
send("You can only use this command when streaming.");
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,35 +1,43 @@
|
|||
import {Command, NamedCommand} from "../../core";
|
||||
import {Command, NamedCommand, RestCommand} from "../../core";
|
||||
import translate from "translate-google";
|
||||
|
||||
export default new NamedCommand({
|
||||
description: "Translates your input.",
|
||||
usage: "<lang ID> <input>",
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
const lang = args[0];
|
||||
const input = args.slice(1).join(" ");
|
||||
translate(input, {
|
||||
to: lang
|
||||
})
|
||||
.then((res) => {
|
||||
send({
|
||||
embed: {
|
||||
title: "Translation",
|
||||
fields: [
|
||||
{
|
||||
name: "Input",
|
||||
value: `\`\`\`${input}\`\`\``
|
||||
},
|
||||
{
|
||||
name: "Output",
|
||||
value: `\`\`\`${res}\`\`\``
|
||||
run: "You need to specify a language to translate to.",
|
||||
any: new Command({
|
||||
run: "You need to enter some text to translate.",
|
||||
any: new RestCommand({
|
||||
async run({send, message, channel, guild, author, member, client, args}) {
|
||||
const lang = args[0];
|
||||
const input = args.slice(1).join(" ");
|
||||
translate(input, {
|
||||
to: lang
|
||||
})
|
||||
.then((res) => {
|
||||
send({
|
||||
embed: {
|
||||
title: "Translation",
|
||||
fields: [
|
||||
{
|
||||
name: "Input",
|
||||
value: `\`\`\`${input}\`\`\``
|
||||
},
|
||||
{
|
||||
name: "Output",
|
||||
value: `\`\`\`${res}\`\`\``
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
send(`${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
send(
|
||||
`${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
|
|
@ -73,23 +73,15 @@ interface CommandMenu {
|
|||
|
||||
interface CommandOptionsBase {
|
||||
readonly description?: string;
|
||||
readonly endpoint?: boolean;
|
||||
readonly usage?: string;
|
||||
readonly permission?: number;
|
||||
readonly nsfw?: boolean;
|
||||
readonly channelType?: CHANNEL_TYPE;
|
||||
}
|
||||
|
||||
interface CommandOptionsEndpoint {
|
||||
readonly endpoint: true;
|
||||
readonly run?: (($: CommandMenu) => Promise<any>) | string;
|
||||
}
|
||||
|
||||
// Prevents subcommands from being added by compile-time.
|
||||
// Also, contrary to what you might think, channel pings do still work in DM channels.
|
||||
// Role pings, maybe not, but it's not a big deal.
|
||||
interface CommandOptionsNonEndpoint {
|
||||
readonly endpoint?: false;
|
||||
interface CommandOptions extends CommandOptionsBase {
|
||||
readonly run?: (($: CommandMenu) => Promise<any>) | string;
|
||||
readonly subcommands?: {[key: string]: NamedCommand};
|
||||
readonly channel?: Command;
|
||||
|
@ -103,11 +95,14 @@ interface CommandOptionsNonEndpoint {
|
|||
readonly any?: Command | RestCommand;
|
||||
}
|
||||
|
||||
type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint);
|
||||
type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string};
|
||||
type RestCommandOptions = CommandOptionsBase & {
|
||||
run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string;
|
||||
};
|
||||
interface NamedCommandOptions extends CommandOptions {
|
||||
readonly aliases?: string[];
|
||||
readonly nameOverride?: string;
|
||||
}
|
||||
|
||||
interface RestCommandOptions extends CommandOptionsBase {
|
||||
readonly run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string;
|
||||
}
|
||||
|
||||
interface ExecuteCommandMetadata {
|
||||
readonly header: string;
|
||||
|
@ -164,7 +159,6 @@ abstract class BaseCommand {
|
|||
|
||||
// Each Command instance represents a block that links other Command instances under it.
|
||||
export class Command extends BaseCommand {
|
||||
public readonly endpoint: boolean;
|
||||
// The execute and subcommand properties are restricted to the class because subcommand recursion could easily break when manually handled.
|
||||
// The class will handle checking for null fields.
|
||||
private run: (($: CommandMenu) => Promise<any>) | string;
|
||||
|
@ -182,31 +176,20 @@ export class Command extends BaseCommand {
|
|||
|
||||
constructor(options?: CommandOptions) {
|
||||
super(options);
|
||||
this.endpoint = !!options?.endpoint;
|
||||
this.run = options?.run || "No action was set on this command!";
|
||||
this.subcommands = new Collection(); // Populate this collection after setting subcommands.
|
||||
this.channel = null;
|
||||
this.role = null;
|
||||
this.emote = null;
|
||||
this.message = null;
|
||||
this.user = null;
|
||||
this.guild = null;
|
||||
this.channel = options?.channel || null;
|
||||
this.role = options?.role || null;
|
||||
this.emote = options?.emote || null;
|
||||
this.message = options?.message || null;
|
||||
this.user = options?.user || null;
|
||||
this.guild = options?.guild || null;
|
||||
this.id = null;
|
||||
this.idType = null;
|
||||
this.number = null;
|
||||
this.any = null;
|
||||
|
||||
if (options && !options.endpoint) {
|
||||
if (options.channel) this.channel = options.channel;
|
||||
if (options.role) this.role = options.role;
|
||||
if (options.emote) this.emote = options.emote;
|
||||
if (options.message) this.message = options.message;
|
||||
if (options.user) this.user = options.user;
|
||||
if (options.guild) this.guild = options.guild;
|
||||
if (options.number) this.number = options.number;
|
||||
if (options.any) this.any = options.any;
|
||||
if (options.id) this.idType = options.id;
|
||||
this.idType = options?.id || null;
|
||||
this.number = options?.number || null;
|
||||
this.any = options?.any || null;
|
||||
|
||||
if (options)
|
||||
switch (options.id) {
|
||||
case "channel":
|
||||
this.id = this.channel;
|
||||
|
@ -232,30 +215,29 @@ export class Command extends BaseCommand {
|
|||
requireAllCasesHandledFor(options.id);
|
||||
}
|
||||
|
||||
if (options.subcommands) {
|
||||
const baseSubcommands = Object.keys(options.subcommands);
|
||||
if (options?.subcommands) {
|
||||
const baseSubcommands = Object.keys(options.subcommands);
|
||||
|
||||
// Loop once to set the base subcommands.
|
||||
for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]);
|
||||
// Loop once to set the base subcommands.
|
||||
for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]);
|
||||
|
||||
// Then loop again to make aliases point to the base subcommands and warn if something's not right.
|
||||
// This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object.
|
||||
for (const name in options.subcommands) {
|
||||
const subcmd = options.subcommands[name];
|
||||
subcmd.name = name;
|
||||
const aliases = subcmd.aliases;
|
||||
// Then loop again to make aliases point to the base subcommands and warn if something's not right.
|
||||
// This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object.
|
||||
for (const name in options.subcommands) {
|
||||
const subcmd = options.subcommands[name];
|
||||
subcmd.name = name;
|
||||
const aliases = subcmd.aliases;
|
||||
|
||||
for (const alias of aliases) {
|
||||
if (baseSubcommands.includes(alias))
|
||||
console.warn(
|
||||
`"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`
|
||||
);
|
||||
else if (this.subcommands.has(alias))
|
||||
console.warn(
|
||||
`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`
|
||||
);
|
||||
else this.subcommands.set(alias, subcmd);
|
||||
}
|
||||
for (const alias of aliases) {
|
||||
if (baseSubcommands.includes(alias))
|
||||
console.warn(
|
||||
`"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`
|
||||
);
|
||||
else if (this.subcommands.has(alias))
|
||||
console.warn(
|
||||
`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`
|
||||
);
|
||||
else this.subcommands.set(alias, subcmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,9 +301,6 @@ export class Command extends BaseCommand {
|
|||
return null;
|
||||
}
|
||||
|
||||
// If the current command is an endpoint but there are still some arguments left, don't continue unless there's a RestCommand.
|
||||
if (this.endpoint) return {content: "Too many arguments!"};
|
||||
|
||||
// Resolve the value of the current command's argument (adding it to the resolved args),
|
||||
// then pass the thread of execution to whichever subcommand is valid (if any).
|
||||
const isMessageLink = patterns.messageLink.test(param);
|
||||
|
@ -506,11 +485,15 @@ export class Command extends BaseCommand {
|
|||
} else if (this.any instanceof RestCommand) {
|
||||
metadata.symbolicArgs.push("<...>");
|
||||
args.unshift(param);
|
||||
menu.args.push(...args);
|
||||
return this.any.execute(args.join(" "), menu, metadata);
|
||||
} else {
|
||||
// Continue adding on the rest of the arguments if there's no valid subcommand.
|
||||
menu.args.push(param);
|
||||
return this.execute(args, menu, metadata);
|
||||
metadata.symbolicArgs.push(`"${param}"`);
|
||||
return {
|
||||
content: `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join(
|
||||
" "
|
||||
)}\` found.`
|
||||
};
|
||||
}
|
||||
|
||||
// Note: Do NOT add a return statement here. In case one of the other sections is missing
|
||||
|
@ -726,7 +709,9 @@ export class RestCommand extends BaseCommand {
|
|||
} else {
|
||||
// Then capture any potential errors.
|
||||
try {
|
||||
await this.run({...menu, combined});
|
||||
// Args will still be kept intact. A common pattern is popping some parameters off the end then doing some branching.
|
||||
// That way, you can still declaratively mark an argument list as continuing while also handling the individual args.
|
||||
await this.run({...menu, args: menu.args, combined});
|
||||
} catch (error) {
|
||||
const errorMessage = error.stack ?? error;
|
||||
console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`);
|
||||
|
|
|
@ -21,8 +21,7 @@ const lastCommandInfo: {
|
|||
const defaultMetadata = {
|
||||
permission: 0,
|
||||
nsfw: false,
|
||||
channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
|
||||
symbolicArgs: []
|
||||
channelType: 0 // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
|
||||
};
|
||||
|
||||
// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
|
||||
|
@ -67,7 +66,8 @@ export function attachMessageHandlerToClient(client: Client) {
|
|||
const result = await command.execute(args, menu, {
|
||||
header,
|
||||
args: [...args],
|
||||
...defaultMetadata
|
||||
...defaultMetadata,
|
||||
symbolicArgs: []
|
||||
});
|
||||
|
||||
// If something went wrong, let the user know (like if they don't have permission to use a command).
|
||||
|
@ -104,7 +104,8 @@ export function attachMessageHandlerToClient(client: Client) {
|
|||
const result = await command.execute(args, menu, {
|
||||
header,
|
||||
args: [...args],
|
||||
...defaultMetadata
|
||||
...defaultMetadata,
|
||||
symbolicArgs: []
|
||||
});
|
||||
|
||||
// If something went wrong, let the user know (like if they don't have permission to use a command).
|
||||
|
|
Loading…
Reference in a new issue