Archived
BaseCommand major changes
Changed a lot of properties, splitted start method into separate methods
This commit is contained in:
1 parent
95d538d5a1
commit
df26e12cc8
8 files changed
+189
-120
No files matched your search
@@ -1,4 +1,8 @@
|
||||
import { CommandType, BaseCommandInit } from "../types/BaseCommand";
|
||||
import { CommandInteraction, Message, Permissions, ReplyMessageOptions, MessageEmbed, GuildMember } from "discord.js";
|
||||
import { PermissionCheckTypes } from "types/permissions.js";
|
||||
import { CommandType, BaseCommandInit, CommandFunction } from "../types/BaseCommand.js";
|
||||
import { InputParameter } from "./Parameter.js";
|
||||
import { OperationSuccess, PermissionsError } from "../errors.js";
|
||||
|
||||
export class BaseCommand {
|
||||
/**
|
||||
@@ -6,23 +10,128 @@ export class BaseCommand {
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly name: string;
|
||||
|
||||
/**
|
||||
* Application command type
|
||||
* @type {CommandType}
|
||||
*/
|
||||
public readonly type: CommandType;
|
||||
|
||||
/**
|
||||
* List of Discord guild (server) IDs in which this command can be used
|
||||
* @type {Array} *string*
|
||||
*/
|
||||
public readonly guilds?: string[];
|
||||
|
||||
/**
|
||||
* Whether to check if a caller has all defined in *permissions* property permissions or at least one of them (doesn't apply to custom function permissions)
|
||||
* @type {PermissionCheckTypes} "ALL" | "ANY"
|
||||
*/
|
||||
public readonly permissionCheckMethod: PermissionCheckTypes;
|
||||
|
||||
/**
|
||||
* Command permissions (if *undefined*, no permissions check will be performed)
|
||||
* @type {Permissions | Function}
|
||||
*/
|
||||
public readonly permissions?: Permissions | ((i: Message | CommandInteraction) => boolean);
|
||||
|
||||
/**
|
||||
* Whether to send a SUCCESS message if no other response is defined (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly announceSuccess: boolean;
|
||||
|
||||
/**
|
||||
* Command execution function (triggered when someone invokes the command)
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
private readonly function: CommandFunction;
|
||||
|
||||
private readonly permissionChecker: (i: Message | CommandInteraction) => boolean;
|
||||
|
||||
public static nameRegExp: RegExp = /^[a-zA-Z]{1,32}$/;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {BaseCommandInit} o - BaseCommand initialization options
|
||||
*/
|
||||
constructor(o: BaseCommandInit) {
|
||||
if (BaseCommand.nameRegExp.test(o.name)) {
|
||||
this.name = o.name;
|
||||
} else {
|
||||
constructor(type: CommandType, o: BaseCommandInit) {
|
||||
if (!BaseCommand.nameRegExp.test(o.name)) {
|
||||
throw new Error("Incorrect command name");
|
||||
}
|
||||
this.type = o.type;
|
||||
this.name = o.name;
|
||||
this.type = type;
|
||||
this.guilds = o.guilds;
|
||||
this.permissionCheckMethod = o.permissionCheck !== undefined ? o.permissionCheck : "ANY";
|
||||
this.permissions = o.permissions ? (!(o.permissions instanceof Function) ? new Permissions(o.permissions) : o.permissions) : undefined;
|
||||
this.announceSuccess = o.announceSuccess !== undefined ? o.announceSuccess : true;
|
||||
this.function = o.function;
|
||||
this.permissionChecker =
|
||||
this.permissions instanceof Function
|
||||
? this.permissions
|
||||
: this.permissions instanceof Permissions
|
||||
? (i) => {
|
||||
if (Array.isArray(this.guilds) && this.guilds.length > 0 && !this.guilds.find((g) => g == i.guild?.id)) {
|
||||
return false;
|
||||
}
|
||||
const memberPermissions = (i.member?.permissions as Permissions) || new Permissions();
|
||||
if (!memberPermissions) {
|
||||
return false;
|
||||
} else {
|
||||
if (this.permissionCheckMethod === "ALL") {
|
||||
return memberPermissions.has(this.permissions as Permissions, true);
|
||||
} else {
|
||||
return memberPermissions.any(this.permissions as Permissions, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
: () => true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the command
|
||||
* @param {Message | CommandInteraction} [interaction] - Used to check caller's permissions. It will get passed to the execution function (specified in *function* property of command's constructor)
|
||||
* @param {InputParameter[]} [cmdParams] - list of processed parameters passed in a Discord interaction
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async start(interaction: Message | CommandInteraction, args?: InputParameter[]): Promise<void> {
|
||||
if (this.permissionChecker(interaction)) {
|
||||
if (interaction instanceof CommandInteraction) {
|
||||
await interaction.deleteReply();
|
||||
}
|
||||
await this.handleReply(interaction, await this.function.call(this, interaction, this.createAccessor(args || [])));
|
||||
} else {
|
||||
throw new PermissionsError(this, interaction.member as GuildMember);
|
||||
}
|
||||
}
|
||||
|
||||
private createAccessor(args: InputParameter[]) {
|
||||
return function (query: string, returnType?: "value" | "object") {
|
||||
return returnType === "object" ? args.find((p) => p.name === query) || null : args.find((p) => p.name === query)?.value || null;
|
||||
};
|
||||
}
|
||||
|
||||
private async handleReply(interaction: Message | CommandInteraction, result: void | string | MessageEmbed | ReplyMessageOptions) {
|
||||
if (
|
||||
result instanceof Object &&
|
||||
("content" in (result as any) || "embeds" in (result as any) || "files" in (result as any) || "components" in (result as any) || "sticker" in (result as any))
|
||||
) {
|
||||
if (interaction instanceof Message) await interaction.reply(result as ReplyMessageOptions);
|
||||
else if (interaction instanceof CommandInteraction) await interaction.editReply(result as ReplyMessageOptions);
|
||||
} else if (typeof result == "string") {
|
||||
if (interaction instanceof Message) await interaction?.reply({ content: result });
|
||||
else if (interaction instanceof CommandInteraction)
|
||||
await interaction.editReply({
|
||||
content: result,
|
||||
});
|
||||
} else if (result instanceof MessageEmbed) {
|
||||
if (interaction instanceof Message) await interaction?.reply({ embeds: [result] });
|
||||
else if (interaction instanceof CommandInteraction) await interaction.editReply({ embeds: [result] });
|
||||
} else if (this.announceSuccess && (interaction instanceof CommandInteraction ? !interaction.replied : true)) {
|
||||
throw new OperationSuccess(this);
|
||||
} else if (interaction instanceof CommandInteraction && !interaction.replied) {
|
||||
await interaction.deleteReply();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,10 @@ export class HelpMessage extends TextCommand {
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
function: (p, i) => {
|
||||
function: (i, p) => {
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
const helpMsg = new MessageEmbed();
|
||||
helpMsg.setColor(params.color);
|
||||
helpMsg.setTimestamp();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import { MessageCommandInit } from "../types/MessageCommand.js";
|
||||
import { CommandFunctionReturnTypes } from "../types/BaseCommand.js";
|
||||
import { BaseCommand } from "./BaseCommand.js";
|
||||
|
||||
export class MessageCommand extends BaseCommand {
|
||||
private readonly function: (i: CommandInteraction) => CommandFunctionReturnTypes;
|
||||
public static nameRegExp: RegExp = /^[a-zA-Z]{1,32}$/;
|
||||
|
||||
constructor(o: MessageCommandInit) {
|
||||
super({
|
||||
if (!MessageCommand.nameRegExp.test(o.name)) {
|
||||
throw new Error("Incorrect command name. Command names must have 1-32 characters");
|
||||
}
|
||||
super("MESSAGE", {
|
||||
name: o.name,
|
||||
type: "MESSAGE",
|
||||
function: o.function,
|
||||
announceSuccess: o.announceSuccess,
|
||||
guilds: o.guilds,
|
||||
permissionCheck: o.permissionCheck,
|
||||
permissions: o.permissions,
|
||||
});
|
||||
this.function = o.function;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { Permissions, Message, MessageEmbed, CommandInteraction, GuildMember, ReplyMessageOptions, PermissionResolvable } from "discord.js";
|
||||
import { Message, CommandInteraction } from "discord.js";
|
||||
import { BaseCommand } from "./BaseCommand.js";
|
||||
import { TextCommandInit } from "../types/TextCommand.js";
|
||||
import { ParameterResolvable } from "../types/Parameter.js";
|
||||
import { PermissionCheckTypes } from "../types/permissions.js";
|
||||
import { OperationSuccess, PermissionsError } from "../errors.js";
|
||||
import { DefaultParameter, InputParameter, Parameter } from "./Parameter.js";
|
||||
import { CommandFunctionReturnTypes } from "../types/BaseCommand.js";
|
||||
|
||||
/**
|
||||
* @class Class that represents a command instance
|
||||
@@ -17,64 +13,39 @@ export class TextCommand extends BaseCommand {
|
||||
* @type {Array} {@link Parameter}
|
||||
*/
|
||||
public readonly parameters: Parameter[];
|
||||
|
||||
/**
|
||||
* List of different names that can be used to invoke a command (when using prefix interactions)
|
||||
* @type {Array} *string*
|
||||
*/
|
||||
public readonly aliases?: string[];
|
||||
|
||||
/**
|
||||
* Command description displayed in the help message (Default description: "No description")
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly description: string;
|
||||
|
||||
/**
|
||||
* Command usage displayed in the help message
|
||||
* @type {string}
|
||||
*/
|
||||
public readonly usage?: string;
|
||||
/**
|
||||
* Whether to check if a caller has all defined in *permissions* property permissions or at least one of them (doesn't apply to custom function permissions)
|
||||
* @type {PermissionCheckTypes} "ALL" | "ANY"
|
||||
*/
|
||||
public readonly permissionCheck: PermissionCheckTypes;
|
||||
/**
|
||||
* Command permissions (if *undefined*, no permissions check will be performed)
|
||||
* @type {PermissionResolvable}
|
||||
* @type {Function} should return boolean value
|
||||
*/
|
||||
public readonly permissions?: Permissions | ((m?: Message | CommandInteraction) => boolean);
|
||||
/**
|
||||
* List of Discord guild (server) IDs in which this command can be used
|
||||
* @type {Array} *string*
|
||||
*/
|
||||
public readonly guilds?: string[];
|
||||
|
||||
/**
|
||||
* Whether this command is visible in the help message (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly visible: boolean;
|
||||
|
||||
/**
|
||||
* Whether this command should be registered as a slash command (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly slash: boolean;
|
||||
/**
|
||||
* Whether to send a SUCCESS message if no other response is defined (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
public readonly announceSuccess: boolean;
|
||||
/**
|
||||
* Command execution function (triggered when someone invokes the command)
|
||||
* @type {Function}
|
||||
* @param {Function} p - function to fetch input parameters' values
|
||||
* @param {Message | CommandInteraction} i - interaction object
|
||||
* @private
|
||||
*/
|
||||
private readonly function: (
|
||||
params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null,
|
||||
interaction?: Message | CommandInteraction
|
||||
) => CommandFunctionReturnTypes;
|
||||
|
||||
public static nameRegExp: RegExp = /^[\w-]{1,32}$/;
|
||||
|
||||
public static descriptionRegExp: RegExp = /^[a-zA-Z]{1,100}$/;
|
||||
|
||||
/**
|
||||
@@ -89,9 +60,13 @@ export class TextCommand extends BaseCommand {
|
||||
if (o.description && !TextCommand.descriptionRegExp.test(o.description)) {
|
||||
throw new Error("Command descriptions must be 1-100 characters long");
|
||||
}
|
||||
super({
|
||||
super("CHAT", {
|
||||
name: o.name,
|
||||
type: "CHAT",
|
||||
function: o.function,
|
||||
announceSuccess: o.announceSuccess,
|
||||
guilds: o.guilds,
|
||||
permissionCheck: o.permissionCheck,
|
||||
permissions: o.permissions,
|
||||
});
|
||||
if (o.parameters == "no_input" || !o.parameters) {
|
||||
this.parameters = [];
|
||||
@@ -103,13 +78,8 @@ export class TextCommand extends BaseCommand {
|
||||
this.aliases = TextCommand.processPhrase(o.aliases);
|
||||
this.description = o.description || "No description";
|
||||
this.usage = o.usage || this.generateUsageFromArguments();
|
||||
this.permissionCheck = o.permissionCheck == "ALL" || o.permissionCheck == "ANY" ? o.permissionCheck : "ANY";
|
||||
this.permissions = o.permissions ? (o.permissions instanceof Function ? o.permissions : new Permissions(o.permissions)) : undefined;
|
||||
this.guilds = o.guilds;
|
||||
this.visible = o.visible !== undefined ? o.visible : true;
|
||||
this.slash = o.slash !== undefined ? o.slash : true;
|
||||
this.announceSuccess = o.announceSuccess !== undefined ? o.announceSuccess : true;
|
||||
this.function = o.function;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,57 +88,11 @@ export class TextCommand extends BaseCommand {
|
||||
* @param {InputParameter[]} [cmdParams] - list of processed parameters passed in a Discord interaction
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async start(interaction?: Message | CommandInteraction, cmdParams?: InputParameter[]): Promise<void> {
|
||||
const paramFindFn = function (query: string, returnType?: "value" | "object") {
|
||||
return returnType === "object" ? cmdParams?.find((p) => p.name === query) || null : cmdParams?.find((p) => p.name === query)?.value || null;
|
||||
};
|
||||
const memberPermissions: Readonly<Permissions> = (interaction?.member?.permissions as Permissions) || new Permissions();
|
||||
if (
|
||||
!this.permissions ||
|
||||
(this.permissions instanceof Function && this.permissions(interaction)) ||
|
||||
(!(this.permissions instanceof Function)
|
||||
? this.permissionCheck == "ALL"
|
||||
? memberPermissions.has(this.permissions as PermissionResolvable, true)
|
||||
: memberPermissions.any(this.permissions as PermissionResolvable, true)
|
||||
: false)
|
||||
) {
|
||||
if (this.guilds && !this.guilds.find((g) => g == interaction?.guild?.id)) {
|
||||
throw new Error("This command is not available here");
|
||||
}
|
||||
if (!this.slash && interaction instanceof CommandInteraction) {
|
||||
throw new Error("This command is not available as a slash command");
|
||||
}
|
||||
if (interaction instanceof CommandInteraction) {
|
||||
await interaction.deferReply();
|
||||
}
|
||||
const fnResult = await this.function.call(this, paramFindFn, interaction);
|
||||
if (
|
||||
fnResult instanceof Object &&
|
||||
("content" in (fnResult as any) ||
|
||||
"embeds" in (fnResult as any) ||
|
||||
"files" in (fnResult as any) ||
|
||||
"components" in (fnResult as any) ||
|
||||
"sticker" in (fnResult as any))
|
||||
) {
|
||||
if (interaction instanceof Message) await interaction.reply(fnResult as ReplyMessageOptions);
|
||||
else if (interaction instanceof CommandInteraction) await interaction.editReply(fnResult as ReplyMessageOptions);
|
||||
} else if (typeof fnResult == "string") {
|
||||
if (interaction instanceof Message) await interaction?.reply({ content: fnResult });
|
||||
else if (interaction instanceof CommandInteraction)
|
||||
await interaction.editReply({
|
||||
content: fnResult,
|
||||
});
|
||||
} else if (fnResult instanceof MessageEmbed) {
|
||||
if (interaction instanceof Message) await interaction?.reply({ embeds: [fnResult] });
|
||||
else if (interaction instanceof CommandInteraction) await interaction.editReply({ embeds: [fnResult] });
|
||||
} else if (this.announceSuccess && (interaction instanceof CommandInteraction ? !interaction.replied : true)) {
|
||||
throw new OperationSuccess(this);
|
||||
} else if (interaction instanceof CommandInteraction && !interaction.replied) {
|
||||
await interaction.deleteReply();
|
||||
}
|
||||
} else {
|
||||
throw new PermissionsError(this, interaction?.member as GuildMember);
|
||||
public async start(interaction: Message | CommandInteraction, cmdParams?: InputParameter[]): Promise<void> {
|
||||
if (!this.slash && interaction instanceof CommandInteraction) {
|
||||
throw new Error("This command is not available as a slash command");
|
||||
}
|
||||
super.start(interaction, cmdParams);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import { MessageEmbed, ReplyMessageOptions } from "discord.js";
|
||||
import { MessageEmbed, ReplyMessageOptions, Message, CommandInteraction } from "discord.js";
|
||||
import { InputParameter } from "structures/Parameter.js";
|
||||
import { ParameterResolvable } from "./Parameter.js";
|
||||
import { PermissionCheckTypes, PermissionTypes } from "./permissions.js";
|
||||
|
||||
export interface BaseCommandInit {
|
||||
name: string;
|
||||
type: CommandType;
|
||||
|
||||
guilds?: string[];
|
||||
|
||||
permissionCheck?: PermissionCheckTypes;
|
||||
|
||||
permissions?: PermissionTypes;
|
||||
|
||||
announceSuccess?: boolean;
|
||||
|
||||
function: CommandFunction;
|
||||
}
|
||||
|
||||
export type CommandType = "CHAT" | "MESSAGE" | "USER";
|
||||
|
||||
export type CommandFunctionReturnTypes = void | string | MessageEmbed | ReplyMessageOptions | Promise<void | string | MessageEmbed | ReplyMessageOptions>;
|
||||
|
||||
export type CommandFunction = (
|
||||
interaction: Message | CommandInteraction,
|
||||
argumentAccessor?: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null
|
||||
) => CommandFunctionReturnTypes;
|
||||
@@ -1,7 +1,3 @@
|
||||
import { CommandInteraction } from "discord.js";
|
||||
import { CommandFunctionReturnTypes } from "./BaseCommand.js";
|
||||
import { BaseCommandInit } from "./BaseCommand.js";
|
||||
|
||||
export interface MessageCommandInit {
|
||||
name: string;
|
||||
function: (i: CommandInteraction) => CommandFunctionReturnTypes;
|
||||
}
|
||||
export interface MessageCommandInit extends BaseCommandInit {}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CommandInteraction, PermissionResolvable, Message } from "discord.js";
|
||||
import { CommandInteraction, Message } from "discord.js";
|
||||
import { ParameterSchema, ParameterResolvable } from "./Parameter.js";
|
||||
import { InputParameter } from "../structures/Parameter.js";
|
||||
import { TextCommand } from "../structures/TextCommand.js";
|
||||
import { PermissionCheckTypes } from "./permissions.js";
|
||||
import { PermissionCheckTypes, PermissionTypes } from "./permissions.js";
|
||||
import { CommandFunctionReturnTypes } from "./BaseCommand.js";
|
||||
|
||||
/**
|
||||
@@ -15,57 +15,68 @@ export interface TextCommandInit {
|
||||
* @type {string}
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* List of parameters that can passed to this command
|
||||
* @type {Array | string} List of {@link Parameter}s or template ("simple" - one string input, "no_input")
|
||||
*/
|
||||
parameters?: ParameterSchema[] | "simple" | "no_input";
|
||||
|
||||
/**
|
||||
* List of different names that can be used to invoke a command (when using prefix interactions)
|
||||
* @type {Array} *string*
|
||||
*/
|
||||
aliases?: string[] | string;
|
||||
|
||||
/**
|
||||
* Command description displayed in the help message (Default description: "No description")
|
||||
* @type {string}
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Command usage displayed in the help message
|
||||
* @type {string}
|
||||
*/
|
||||
usage?: string;
|
||||
|
||||
/**
|
||||
* Whether to check if a caller has all defined in *permissions* property permissions or at least one of them (doesn't apply to custom function permissions)
|
||||
* @type {PermissionCheckTypes} "ALL" | "ANY"
|
||||
*/
|
||||
permissionCheck?: PermissionCheckTypes;
|
||||
|
||||
/**
|
||||
* Command permissions (if *undefined*, no permissions check will be performed)
|
||||
* @type {PermissionResolvable}
|
||||
* @type {Function} should return boolean value
|
||||
*/
|
||||
permissions?: PermissionResolvable | ((m?: Message | CommandInteraction) => boolean);
|
||||
permissions?: PermissionTypes;
|
||||
|
||||
/**
|
||||
* List of Discord guild (server) IDs in which this command can be used
|
||||
* @type {Array} *string*
|
||||
*/
|
||||
guilds?: string[];
|
||||
|
||||
/**
|
||||
* Whether this command is visible in the help message (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
visible?: boolean;
|
||||
|
||||
/**
|
||||
* Whether this command should be registered as a slash command (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
slash?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to send a SUCCESS message if no other response is defined (default: true)
|
||||
* @type {boolean}
|
||||
*/
|
||||
announceSuccess?: boolean;
|
||||
|
||||
/**
|
||||
* Command execution function (triggered when someone invokes the command)
|
||||
* @type {Function}
|
||||
@@ -73,13 +84,14 @@ export interface TextCommandInit {
|
||||
* @param {Message | CommandInteraction} i - interaction object
|
||||
*/
|
||||
function: (
|
||||
params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null,
|
||||
interaction?: Message | CommandInteraction
|
||||
interaction: Message | CommandInteraction,
|
||||
params?: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null
|
||||
) => CommandFunctionReturnTypes;
|
||||
}
|
||||
|
||||
export interface CommandMessageStructure {
|
||||
command: TextCommand;
|
||||
|
||||
parameters: InputParameter[];
|
||||
}
|
||||
|
||||
@@ -87,5 +99,6 @@ export type GetMode = "ALL" | "PREFIX" | "NO_PREFIX";
|
||||
|
||||
export interface PhraseOccurrenceData {
|
||||
command: TextCommand;
|
||||
|
||||
type: "NAME" | "ALIAS";
|
||||
}
|
||||
@@ -1 +1,5 @@
|
||||
import { PermissionResolvable, Message, CommandInteraction } from "discord.js";
|
||||
|
||||
export type PermissionCheckTypes = "ALL" | "ANY";
|
||||
|
||||
export type PermissionTypes = PermissionResolvable | ((m?: Message | CommandInteraction) => boolean);
|
||||
Reference in new issue
Block a user