Fixed some bugs and added proper event handler
This commit is contained in:
parent
5c3896c2db
commit
44cae5c0cb
|
@ -1,5 +1,5 @@
|
||||||
import {Command, NamedCommand, loadableCommands, categories, getPermissionName} from "../../core";
|
import {Command, NamedCommand, loadableCommands, categories, getPermissionName, CHANNEL_TYPE} from "../../core";
|
||||||
import {toTitleCase} from "../../lib";
|
import {toTitleCase, requireAllCasesHandledFor} from "../../lib";
|
||||||
|
|
||||||
export default new NamedCommand({
|
export default new NamedCommand({
|
||||||
description: "Lists all commands. If a command is specified, their arguments are listed as well.",
|
description: "Lists all commands. If a command is specified, their arguments are listed as well.",
|
||||||
|
@ -10,14 +10,19 @@ export default new NamedCommand({
|
||||||
let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``;
|
let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``;
|
||||||
|
|
||||||
for (const [category, headers] of categories) {
|
for (const [category, headers] of categories) {
|
||||||
output += `\n\n===[ ${toTitleCase(category)} ]===`;
|
let tmp = `\n\n===[ ${toTitleCase(category)} ]===`;
|
||||||
|
// Ignore empty categories, including ["test"].
|
||||||
|
let hasActualCommands = false;
|
||||||
|
|
||||||
for (const header of headers) {
|
for (const header of headers) {
|
||||||
if (header !== "test") {
|
if (header !== "test") {
|
||||||
const command = commands.get(header)!;
|
const command = commands.get(header)!;
|
||||||
output += `\n- \`${header}\`: ${command.description}`;
|
tmp += `\n- \`${header}\`: ${command.description}`;
|
||||||
|
hasActualCommands = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasActualCommands) output += tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.send(output, {split: true});
|
channel.send(output, {split: true});
|
||||||
|
@ -50,6 +55,8 @@ export default new NamedCommand({
|
||||||
let append = "";
|
let append = "";
|
||||||
command = result.command;
|
command = result.command;
|
||||||
|
|
||||||
|
if (result.args.length > 0) header += " " + result.args.join(" ");
|
||||||
|
|
||||||
if (command.usage === "") {
|
if (command.usage === "") {
|
||||||
const list: string[] = [];
|
const list: string[] = [];
|
||||||
|
|
||||||
|
@ -80,9 +87,24 @@ export default new NamedCommand({
|
||||||
return channel.send(
|
return channel.send(
|
||||||
`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName(
|
`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName(
|
||||||
result.permission
|
result.permission
|
||||||
)}\` (${result.permission})\nDescription: ${command.description}\n${append}`,
|
)}\` (${result.permission})\nChannel Type: ${getChannelTypeName(result.channelType)}\nNSFW Only: ${
|
||||||
|
result.nsfw ? "Yes" : "No"
|
||||||
|
}\nDescription: ${command.description}\n${append}`,
|
||||||
{split: true}
|
{split: true}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getChannelTypeName(type: CHANNEL_TYPE): string {
|
||||||
|
switch (type) {
|
||||||
|
case CHANNEL_TYPE.ANY:
|
||||||
|
return "Any";
|
||||||
|
case CHANNEL_TYPE.GUILD:
|
||||||
|
return "Guild Only";
|
||||||
|
case CHANNEL_TYPE.DM:
|
||||||
|
return "DM Only";
|
||||||
|
default:
|
||||||
|
requireAllCasesHandledFor(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ const patterns = {
|
||||||
// Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed.
|
// Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed.
|
||||||
// Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious.
|
// Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious.
|
||||||
// Just use type assertions when you specify a channel type.
|
// Just use type assertions when you specify a channel type.
|
||||||
enum CHANNEL_TYPE {
|
export enum CHANNEL_TYPE {
|
||||||
ANY,
|
ANY,
|
||||||
GUILD,
|
GUILD,
|
||||||
DM
|
DM
|
||||||
|
@ -126,7 +126,6 @@ export class Command {
|
||||||
protected user: Command | null;
|
protected user: Command | null;
|
||||||
protected number: Command | null;
|
protected number: Command | null;
|
||||||
protected any: Command | null;
|
protected any: Command | null;
|
||||||
public static readonly CHANNEL_TYPE = CHANNEL_TYPE;
|
|
||||||
|
|
||||||
constructor(options?: CommandOptions) {
|
constructor(options?: CommandOptions) {
|
||||||
this.description = options?.description || "No description.";
|
this.description = options?.description || "No description.";
|
||||||
|
@ -182,6 +181,13 @@ export class Command {
|
||||||
menu: CommandMenu,
|
menu: CommandMenu,
|
||||||
metadata: ExecuteCommandMetadata
|
metadata: ExecuteCommandMetadata
|
||||||
): Promise<SingleMessageOptions | null> {
|
): Promise<SingleMessageOptions | null> {
|
||||||
|
// Update inherited properties if the current command specifies a property.
|
||||||
|
// In case there are no initial arguments, these should go first so that it can register.
|
||||||
|
if (this.permission !== -1) metadata.permission = this.permission;
|
||||||
|
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
||||||
|
if (this.channelType !== null) metadata.channelType = this.channelType;
|
||||||
|
|
||||||
|
// Take off the leftmost argument from the list.
|
||||||
const param = args.shift();
|
const param = args.shift();
|
||||||
|
|
||||||
// If there are no arguments left, execute the current command. Otherwise, continue on.
|
// If there are no arguments left, execute the current command. Otherwise, continue on.
|
||||||
|
@ -239,7 +245,7 @@ export class Command {
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error.stack ?? error;
|
const errorMessage = error.stack ?? error;
|
||||||
console.error(`Command Error: ${metadata.header} (${metadata.args})\n${errorMessage}`);
|
console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``
|
content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\``
|
||||||
|
@ -250,11 +256,6 @@ export class Command {
|
||||||
// If the current command is an endpoint but there are still some arguments left, don't continue.
|
// If the current command is an endpoint but there are still some arguments left, don't continue.
|
||||||
if (this.endpoint) return {content: "Too many arguments!"};
|
if (this.endpoint) return {content: "Too many arguments!"};
|
||||||
|
|
||||||
// Update inherited properties if the current command specifies a property.
|
|
||||||
if (this.permission !== -1) metadata.permission = this.permission;
|
|
||||||
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
|
||||||
if (this.channelType !== null) metadata.channelType = this.channelType;
|
|
||||||
|
|
||||||
// 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).
|
||||||
if (this.subcommands.has(param)) {
|
if (this.subcommands.has(param)) {
|
||||||
|
@ -295,6 +296,14 @@ export class Command {
|
||||||
args: string[],
|
args: string[],
|
||||||
metadata: CommandInfoMetadata
|
metadata: CommandInfoMetadata
|
||||||
): Promise<CommandInfo | CommandInfoError> {
|
): Promise<CommandInfo | CommandInfoError> {
|
||||||
|
// Update inherited properties if the current command specifies a property.
|
||||||
|
// In case there are no initial arguments, these should go first so that it can register.
|
||||||
|
if (this.permission !== -1) metadata.permission = this.permission;
|
||||||
|
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
||||||
|
if (this.channelType !== null) metadata.channelType = this.channelType;
|
||||||
|
if (this.usage !== "") metadata.usage = this.usage;
|
||||||
|
|
||||||
|
// Take off the leftmost argument from the list.
|
||||||
const param = args.shift();
|
const param = args.shift();
|
||||||
|
|
||||||
// If there are no arguments left, return the data or an error message.
|
// If there are no arguments left, return the data or an error message.
|
||||||
|
@ -324,12 +333,6 @@ export class Command {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update inherited properties if the current command specifies a property.
|
|
||||||
if (this.permission !== -1) metadata.permission = this.permission;
|
|
||||||
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
|
||||||
if (this.channelType !== null) metadata.channelType = this.channelType;
|
|
||||||
if (this.usage !== "") metadata.usage = this.usage;
|
|
||||||
|
|
||||||
// Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand.
|
// Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand.
|
||||||
if (param === "<user>") {
|
if (param === "<user>") {
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {client} from "../index";
|
import {client} from "../index";
|
||||||
import {loadableCommands} from "./loader";
|
import {loadableCommands} from "./loader";
|
||||||
import {Permissions, Message} from "discord.js";
|
import {Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js";
|
||||||
import {getPrefix} from "../structures";
|
import {getPrefix} from "../structures";
|
||||||
import {defaultMetadata} from "./command";
|
import {defaultMetadata} from "./command";
|
||||||
|
|
||||||
|
@ -11,6 +11,16 @@ export function addInterceptRule(handler: (message: Message) => boolean) {
|
||||||
interceptRules.push(handler);
|
interceptRules.push(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastCommandInfo: {
|
||||||
|
header: string;
|
||||||
|
args: string[];
|
||||||
|
channel: TextChannel | DMChannel | NewsChannel | null;
|
||||||
|
} = {
|
||||||
|
header: "N/A",
|
||||||
|
args: [],
|
||||||
|
channel: null
|
||||||
|
};
|
||||||
|
|
||||||
// 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.
|
||||||
// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
|
// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
|
||||||
client.on("message", async (message) => {
|
client.on("message", async (message) => {
|
||||||
|
@ -41,6 +51,11 @@ client.on("message", async (message) => {
|
||||||
if (commands.has(header)) {
|
if (commands.has(header)) {
|
||||||
const command = commands.get(header)!;
|
const command = commands.get(header)!;
|
||||||
|
|
||||||
|
// Set last command info in case of unhandled rejections.
|
||||||
|
lastCommandInfo.header = header;
|
||||||
|
lastCommandInfo.args = [...args];
|
||||||
|
lastCommandInfo.channel = channel;
|
||||||
|
|
||||||
// Send the arguments to the command to resolve and execute.
|
// Send the arguments to the command to resolve and execute.
|
||||||
const result = await command.execute(args, menu, {
|
const result = await command.execute(args, menu, {
|
||||||
header,
|
header,
|
||||||
|
@ -73,6 +88,11 @@ client.on("message", async (message) => {
|
||||||
if (commands.has(header)) {
|
if (commands.has(header)) {
|
||||||
const command = commands.get(header)!;
|
const command = commands.get(header)!;
|
||||||
|
|
||||||
|
// Set last command info in case of unhandled rejections.
|
||||||
|
lastCommandInfo.header = header;
|
||||||
|
lastCommandInfo.args = [...args];
|
||||||
|
lastCommandInfo.channel = channel;
|
||||||
|
|
||||||
// Send the arguments to the command to resolve and execute.
|
// Send the arguments to the command to resolve and execute.
|
||||||
const result = await command.execute(args, menu, {
|
const result = await command.execute(args, menu, {
|
||||||
header,
|
header,
|
||||||
|
@ -98,3 +118,12 @@ client.on("message", async (message) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
process.on("unhandledRejection", (reason: any) => {
|
||||||
|
if (reason?.name === "DiscordAPIError") {
|
||||||
|
console.error(`Command Error: ${lastCommandInfo.header} (${lastCommandInfo.args.join(", ")})\n${reason.stack}`);
|
||||||
|
lastCommandInfo.channel?.send(
|
||||||
|
`There was an error while trying to execute that command!\`\`\`${reason.stack}\`\`\``
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export {Command, NamedCommand} from "./command";
|
export {Command, NamedCommand, CHANNEL_TYPE} from "./command";
|
||||||
export {addInterceptRule} from "./handler";
|
export {addInterceptRule} from "./handler";
|
||||||
export {
|
export {
|
||||||
SingleMessageOptions,
|
SingleMessageOptions,
|
||||||
|
|
|
@ -22,3 +22,27 @@ attachClientToLavalink(client, {
|
||||||
helpCmd: "mhelp",
|
helpCmd: "mhelp",
|
||||||
admins: ["717352467280691331"]
|
admins: ["717352467280691331"]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Disable the unhandledRejection listener by Lavalink because it captures every single unhandled
|
||||||
|
// rejection and adds its message with it. Then replace it with a better, more selective error handler.
|
||||||
|
for (const listener of process.listeners("unhandledRejection")) {
|
||||||
|
if (listener.toString().includes("discord.js-lavalink-musicbot")) {
|
||||||
|
process.off("unhandledRejection", listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("unhandledRejection", (reason: any) => {
|
||||||
|
if (reason?.code === "ECONNREFUSED") {
|
||||||
|
console.error(
|
||||||
|
`[discord.js-lavalink-musicbot] Caught unhandled rejection: ${reason.stack}\nIf this is causing issues, head to the support server at https://discord.gg/dNN4azK`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// It's unsafe to process uncaughtException because after an uncaught exception, the system
|
||||||
|
// becomes corrupted. So disable Lavalink from adding a hook to it.
|
||||||
|
for (const listener of process.listeners("uncaughtException")) {
|
||||||
|
if (listener.toString().includes("discord.js-lavalink-musicbot")) {
|
||||||
|
process.off("uncaughtException", listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue