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
|
- Various changes to core
|
||||||
- Added `guild` subcommand type (only accessible when `id: "guild"`)
|
- Added `guild` subcommand type (only accessible when `id: "guild"`)
|
||||||
- Further reduced `channel.send()` to `send()` because it's used in *every, single, command*
|
- 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)
|
# 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.
|
- 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({
|
export default new NamedCommand({
|
||||||
description: "Answers your question in an 8-ball manner.",
|
description: "Answers your question in an 8-ball manner.",
|
||||||
endpoint: false,
|
|
||||||
usage: "<question>",
|
usage: "<question>",
|
||||||
run: "Please provide a question.",
|
run: "Please provide a question.",
|
||||||
any: new Command({
|
any: new Command({
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const BetCommand = new NamedCommand({
|
||||||
|
|
||||||
// handle invalid target
|
// handle invalid target
|
||||||
if (target.id == author.id) return send("You can't bet Mons with yourself!");
|
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
|
// handle invalid amount
|
||||||
if (amount <= 0) return send("You must bet at least one Mon!");
|
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)
|
else if (sender.money < amount)
|
||||||
return send("You don't have enough Mons for that.", getMoneyEmbed(author));
|
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.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;
|
sender.money -= amount;
|
||||||
receiver.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[]} = {
|
const letters: {[letter: string]: string[]} = {
|
||||||
a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""),
|
a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""),
|
||||||
|
@ -35,7 +35,6 @@ export default new NamedCommand({
|
||||||
description: "Transforms your text into vietnamese.",
|
description: "Transforms your text into vietnamese.",
|
||||||
usage: "thonk ([text])",
|
usage: "thonk ([text])",
|
||||||
async run({send, message, channel, guild, author, member, client, args}) {
|
async run({send, message, channel, guild, author, member, client, args}) {
|
||||||
if (args.length > 0) phrase = args.join(" ");
|
|
||||||
const msg = await send(transform(phrase));
|
const msg = await send(transform(phrase));
|
||||||
msg.createReactionCollector(
|
msg.createReactionCollector(
|
||||||
(reaction, user) => {
|
(reaction, user) => {
|
||||||
|
@ -44,5 +43,17 @@ export default new NamedCommand({
|
||||||
},
|
},
|
||||||
{time: 60000}
|
{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.");
|
send("Activity set to default.");
|
||||||
},
|
},
|
||||||
any: new Command({
|
any: new RestCommand({
|
||||||
description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``,
|
description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``,
|
||||||
async run({send, message, channel, guild, author, member, client, args}) {
|
async run({send, message, channel, guild, author, member, client, args}) {
|
||||||
const type = args[0];
|
const type = args[0];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {Command, NamedCommand} from "../core";
|
import {Command, NamedCommand, RestCommand} from "../core";
|
||||||
|
|
||||||
export default new NamedCommand({
|
export default new NamedCommand({
|
||||||
async run({send, message, channel, guild, author, member, client, args}) {
|
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";
|
import {processEmoteQueryFormatted} from "./modules/emote-utils";
|
||||||
|
|
||||||
export default new NamedCommand({
|
export default new NamedCommand({
|
||||||
description:
|
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.",
|
"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.",
|
run: "Please provide a list of emotes.",
|
||||||
any: new Command({
|
any: new RestCommand({
|
||||||
description: "The emote(s) to send.",
|
description: "The emote(s) to send.",
|
||||||
usage: "<emotes...>",
|
usage: "<emotes...>",
|
||||||
async run({send, guild, channel, message, args}) {
|
async run({send, guild, channel, message, args}) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {GuildEmoji, MessageEmbed, User} from "discord.js";
|
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 {split} from "../../lib";
|
||||||
import vm from "vm";
|
import vm from "vm";
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ export default new NamedCommand({
|
||||||
async run({send, message, channel, guild, author, member, client, args}) {
|
async run({send, message, channel, guild, author, member, client, args}) {
|
||||||
displayEmoteList(client.emojis.cache.array(), send, author);
|
displayEmoteList(client.emojis.cache.array(), send, author);
|
||||||
},
|
},
|
||||||
any: new Command({
|
any: new RestCommand({
|
||||||
description:
|
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",
|
"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}) {
|
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 {Message, Channel, TextChannel} from "discord.js";
|
||||||
import {processEmoteQueryArray} from "./modules/emote-utils";
|
import {processEmoteQueryArray} from "./modules/emote-utils";
|
||||||
|
|
||||||
|
@ -6,109 +6,112 @@ export default new NamedCommand({
|
||||||
description:
|
description:
|
||||||
"Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.",
|
"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">)',
|
usage: 'react <emotes...> (<distance / message ID / "Copy ID" / "Copy Message Link">)',
|
||||||
async run({send, message, channel, guild, author, member, client, args}) {
|
run: "You need to enter some emotes first.",
|
||||||
let target: Message | undefined;
|
any: new RestCommand({
|
||||||
let distance = 1;
|
async run({send, message, channel, guild, author, member, client, args}) {
|
||||||
|
let target: Message | undefined;
|
||||||
|
let distance = 1;
|
||||||
|
|
||||||
if (message.reference) {
|
if (message.reference) {
|
||||||
// If the command message is a reply to another message, use that as the react target.
|
// If the command message is a reply to another message, use that as the react target.
|
||||||
target = await channel.messages.fetch(message.reference.messageID!);
|
target = await channel.messages.fetch(message.reference.messageID!);
|
||||||
}
|
}
|
||||||
// handles reacts by message id/distance
|
// handles reacts by message id/distance
|
||||||
else if (args.length >= 2) {
|
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 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 URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/;
|
||||||
const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/;
|
const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/;
|
||||||
|
|
||||||
// https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
|
// https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
|
||||||
if (URLPattern.test(last)) {
|
if (URLPattern.test(last)) {
|
||||||
const match = URLPattern.exec(last)!;
|
const match = URLPattern.exec(last)!;
|
||||||
const guildID = match[1];
|
const guildID = match[1];
|
||||||
const channelID = match[2];
|
const channelID = match[2];
|
||||||
const messageID = match[3];
|
const messageID = match[3];
|
||||||
let tmpChannel: Channel | undefined = channel;
|
let tmpChannel: Channel | undefined = channel;
|
||||||
|
|
||||||
if (guild?.id !== guildID) {
|
if (guild?.id !== guildID) {
|
||||||
try {
|
try {
|
||||||
guild = await client.guilds.fetch(guildID);
|
guild = await client.guilds.fetch(guildID);
|
||||||
} catch {
|
} catch {
|
||||||
return send(`\`${guildID}\` is an invalid guild ID!`);
|
return send(`\`${guildID}\` is an invalid guild ID!`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID);
|
if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID);
|
||||||
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
||||||
|
|
||||||
if (message.id !== messageID) {
|
if (message.id !== messageID) {
|
||||||
try {
|
try {
|
||||||
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
||||||
} catch {
|
} catch {
|
||||||
return send(`\`${messageID}\` is an invalid message ID!`);
|
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();
|
if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID);
|
||||||
}
|
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
||||||
// <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 (message.id !== messageID) {
|
||||||
if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`);
|
try {
|
||||||
|
target = await (tmpChannel as TextChannel).messages.fetch(messageID);
|
||||||
if (message.id !== messageID) {
|
} catch {
|
||||||
try {
|
return send(`\`${messageID}\` is an invalid message ID!`);
|
||||||
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();
|
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}\`!`);
|
|
||||||
}
|
}
|
||||||
|
// 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();
|
if (!target) {
|
||||||
else return send("Your distance must be between 0 and 99!");
|
// 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";
|
import {streamList} from "../../modules/streamNotifications";
|
||||||
|
|
||||||
export default new NamedCommand({
|
export default new NamedCommand({
|
||||||
|
@ -8,12 +8,11 @@ export default new NamedCommand({
|
||||||
|
|
||||||
if (streamList.has(userID)) {
|
if (streamList.has(userID)) {
|
||||||
const stream = streamList.get(userID)!;
|
const stream = streamList.get(userID)!;
|
||||||
const description = args.join(" ") || "No description set.";
|
stream.description = "No description set.";
|
||||||
stream.description = description;
|
|
||||||
stream.update();
|
stream.update();
|
||||||
send(`Successfully set the stream description to:`, {
|
send(`Successfully set the stream description to:`, {
|
||||||
embed: {
|
embed: {
|
||||||
description,
|
description: "No description set.",
|
||||||
color: member!.displayColor
|
color: member!.displayColor
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -21,5 +20,25 @@ export default new NamedCommand({
|
||||||
// Alternatively, I could make descriptions last outside of just one stream.
|
// Alternatively, I could make descriptions last outside of just one stream.
|
||||||
send("You can only use this command when streaming.");
|
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";
|
import translate from "translate-google";
|
||||||
|
|
||||||
export default new NamedCommand({
|
export default new NamedCommand({
|
||||||
description: "Translates your input.",
|
description: "Translates your input.",
|
||||||
usage: "<lang ID> <input>",
|
usage: "<lang ID> <input>",
|
||||||
async run({send, message, channel, guild, author, member, client, args}) {
|
run: "You need to specify a language to translate to.",
|
||||||
const lang = args[0];
|
any: new Command({
|
||||||
const input = args.slice(1).join(" ");
|
run: "You need to enter some text to translate.",
|
||||||
translate(input, {
|
any: new RestCommand({
|
||||||
to: lang
|
async run({send, message, channel, guild, author, member, client, args}) {
|
||||||
})
|
const lang = args[0];
|
||||||
.then((res) => {
|
const input = args.slice(1).join(" ");
|
||||||
send({
|
translate(input, {
|
||||||
embed: {
|
to: lang
|
||||||
title: "Translation",
|
})
|
||||||
fields: [
|
.then((res) => {
|
||||||
{
|
send({
|
||||||
name: "Input",
|
embed: {
|
||||||
value: `\`\`\`${input}\`\`\``
|
title: "Translation",
|
||||||
},
|
fields: [
|
||||||
{
|
{
|
||||||
name: "Output",
|
name: "Input",
|
||||||
value: `\`\`\`${res}\`\`\``
|
value: `\`\`\`${input}\`\`\``
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Output",
|
||||||
|
value: `\`\`\`${res}\`\`\``
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
});
|
||||||
}
|
})
|
||||||
});
|
.catch((error) => {
|
||||||
})
|
console.error(error);
|
||||||
.catch((error) => {
|
send(
|
||||||
console.error(error);
|
`${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`
|
||||||
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 {
|
interface CommandOptionsBase {
|
||||||
readonly description?: string;
|
readonly description?: string;
|
||||||
readonly endpoint?: boolean;
|
|
||||||
readonly usage?: string;
|
readonly usage?: string;
|
||||||
readonly permission?: number;
|
readonly permission?: number;
|
||||||
readonly nsfw?: boolean;
|
readonly nsfw?: boolean;
|
||||||
readonly channelType?: CHANNEL_TYPE;
|
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.
|
// 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.
|
// Role pings, maybe not, but it's not a big deal.
|
||||||
interface CommandOptionsNonEndpoint {
|
interface CommandOptions extends CommandOptionsBase {
|
||||||
readonly endpoint?: false;
|
|
||||||
readonly run?: (($: CommandMenu) => Promise<any>) | string;
|
readonly run?: (($: CommandMenu) => Promise<any>) | string;
|
||||||
readonly subcommands?: {[key: string]: NamedCommand};
|
readonly subcommands?: {[key: string]: NamedCommand};
|
||||||
readonly channel?: Command;
|
readonly channel?: Command;
|
||||||
|
@ -103,11 +95,14 @@ interface CommandOptionsNonEndpoint {
|
||||||
readonly any?: Command | RestCommand;
|
readonly any?: Command | RestCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint);
|
interface NamedCommandOptions extends CommandOptions {
|
||||||
type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string};
|
readonly aliases?: string[];
|
||||||
type RestCommandOptions = CommandOptionsBase & {
|
readonly nameOverride?: string;
|
||||||
run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string;
|
}
|
||||||
};
|
|
||||||
|
interface RestCommandOptions extends CommandOptionsBase {
|
||||||
|
readonly run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ExecuteCommandMetadata {
|
interface ExecuteCommandMetadata {
|
||||||
readonly header: string;
|
readonly header: string;
|
||||||
|
@ -164,7 +159,6 @@ abstract class BaseCommand {
|
||||||
|
|
||||||
// Each Command instance represents a block that links other Command instances under it.
|
// Each Command instance represents a block that links other Command instances under it.
|
||||||
export class Command extends BaseCommand {
|
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 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.
|
// The class will handle checking for null fields.
|
||||||
private run: (($: CommandMenu) => Promise<any>) | string;
|
private run: (($: CommandMenu) => Promise<any>) | string;
|
||||||
|
@ -182,31 +176,20 @@ export class Command extends BaseCommand {
|
||||||
|
|
||||||
constructor(options?: CommandOptions) {
|
constructor(options?: CommandOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.endpoint = !!options?.endpoint;
|
|
||||||
this.run = options?.run || "No action was set on this command!";
|
this.run = options?.run || "No action was set on this command!";
|
||||||
this.subcommands = new Collection(); // Populate this collection after setting subcommands.
|
this.subcommands = new Collection(); // Populate this collection after setting subcommands.
|
||||||
this.channel = null;
|
this.channel = options?.channel || null;
|
||||||
this.role = null;
|
this.role = options?.role || null;
|
||||||
this.emote = null;
|
this.emote = options?.emote || null;
|
||||||
this.message = null;
|
this.message = options?.message || null;
|
||||||
this.user = null;
|
this.user = options?.user || null;
|
||||||
this.guild = null;
|
this.guild = options?.guild || null;
|
||||||
this.id = null;
|
this.id = null;
|
||||||
this.idType = null;
|
this.idType = options?.id || null;
|
||||||
this.number = null;
|
this.number = options?.number || null;
|
||||||
this.any = null;
|
this.any = options?.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;
|
|
||||||
|
|
||||||
|
if (options)
|
||||||
switch (options.id) {
|
switch (options.id) {
|
||||||
case "channel":
|
case "channel":
|
||||||
this.id = this.channel;
|
this.id = this.channel;
|
||||||
|
@ -232,30 +215,29 @@ export class Command extends BaseCommand {
|
||||||
requireAllCasesHandledFor(options.id);
|
requireAllCasesHandledFor(options.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.subcommands) {
|
if (options?.subcommands) {
|
||||||
const baseSubcommands = Object.keys(options.subcommands);
|
const baseSubcommands = Object.keys(options.subcommands);
|
||||||
|
|
||||||
// Loop once to set the base subcommands.
|
// Loop once to set the base subcommands.
|
||||||
for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]);
|
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.
|
// 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.
|
// 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) {
|
for (const name in options.subcommands) {
|
||||||
const subcmd = options.subcommands[name];
|
const subcmd = options.subcommands[name];
|
||||||
subcmd.name = name;
|
subcmd.name = name;
|
||||||
const aliases = subcmd.aliases;
|
const aliases = subcmd.aliases;
|
||||||
|
|
||||||
for (const alias of aliases) {
|
for (const alias of aliases) {
|
||||||
if (baseSubcommands.includes(alias))
|
if (baseSubcommands.includes(alias))
|
||||||
console.warn(
|
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.)`
|
`"${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))
|
else if (this.subcommands.has(alias))
|
||||||
console.warn(
|
console.warn(
|
||||||
`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`
|
`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);
|
else this.subcommands.set(alias, subcmd);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,9 +301,6 @@ export class Command extends BaseCommand {
|
||||||
return null;
|
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),
|
// 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).
|
// then pass the thread of execution to whichever subcommand is valid (if any).
|
||||||
const isMessageLink = patterns.messageLink.test(param);
|
const isMessageLink = patterns.messageLink.test(param);
|
||||||
|
@ -506,11 +485,15 @@ export class Command extends BaseCommand {
|
||||||
} else if (this.any instanceof RestCommand) {
|
} else if (this.any instanceof RestCommand) {
|
||||||
metadata.symbolicArgs.push("<...>");
|
metadata.symbolicArgs.push("<...>");
|
||||||
args.unshift(param);
|
args.unshift(param);
|
||||||
|
menu.args.push(...args);
|
||||||
return this.any.execute(args.join(" "), menu, metadata);
|
return this.any.execute(args.join(" "), menu, metadata);
|
||||||
} else {
|
} else {
|
||||||
// Continue adding on the rest of the arguments if there's no valid subcommand.
|
metadata.symbolicArgs.push(`"${param}"`);
|
||||||
menu.args.push(param);
|
return {
|
||||||
return this.execute(args, menu, metadata);
|
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
|
// 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 {
|
} else {
|
||||||
// Then capture any potential errors.
|
// Then capture any potential errors.
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
const errorMessage = error.stack ?? error;
|
const errorMessage = error.stack ?? error;
|
||||||
console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`);
|
console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`);
|
||||||
|
|
|
@ -21,8 +21,7 @@ const lastCommandInfo: {
|
||||||
const defaultMetadata = {
|
const defaultMetadata = {
|
||||||
permission: 0,
|
permission: 0,
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
|
channelType: 0 // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
|
||||||
symbolicArgs: []
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
|
// 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, {
|
const result = await command.execute(args, menu, {
|
||||||
header,
|
header,
|
||||||
args: [...args],
|
args: [...args],
|
||||||
...defaultMetadata
|
...defaultMetadata,
|
||||||
|
symbolicArgs: []
|
||||||
});
|
});
|
||||||
|
|
||||||
// If something went wrong, let the user know (like if they don't have permission to use a command).
|
// 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, {
|
const result = await command.execute(args, menu, {
|
||||||
header,
|
header,
|
||||||
args: [...args],
|
args: [...args],
|
||||||
...defaultMetadata
|
...defaultMetadata,
|
||||||
|
symbolicArgs: []
|
||||||
});
|
});
|
||||||
|
|
||||||
// If something went wrong, let the user know (like if they don't have permission to use a command).
|
// 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