BaseCommand major changes

Changed a lot of properties, splitted start method into separate methods
This commit is contained in:
mmacierzanka committed 2021-09-05 03:00:28 +02:00
1 parent 95d538d5a1
commit df26e12cc8
8 files changed
+189 -120

No files matched your search

+115 -6
View File
@@ -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();
}
}
}
+4 -1
View File
@@ -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();
+9 -6
View File
@@ -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;
}
}
+18 -94
View File
@@ -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);
}
/**
+19 -2
View File
@@ -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;
+2 -6
View File
@@ -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 {}
+18 -5
View File
@@ -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";
}
+4
View File
@@ -1 +1,5 @@
import { PermissionResolvable, Message, CommandInteraction } from "discord.js";
export type PermissionCheckTypes = "ALL" | "ANY";
export type PermissionTypes = PermissionResolvable | ((m?: Message | CommandInteraction) => boolean);