UNPKG

commandbot

Version:

A framework that helps you create your own Discord bot easier.

294 lines (293 loc) 8.69 kB
import { MissingParameterError, ParameterTypeError } from "../errors.js"; /** * Representation of command parameter * @class */ export class Parameter { /** * Command associated with this parameter * @type {Command} * @public * @readonly */ command; /** * Parameter name * @type {string} * @public * @readonly */ name; /** * Parameter description * @type {string} * @public * @readonly */ description; /** * Whether this parameter is optional * @type {boolean} * @public * @readonly */ optional; /** * Parameter input type * @type {ParameterType} * @public * @readonly */ type; /** * List of value choices (available only when type is set to "STRING") * @type {?Array<string>} * @public * @readonly */ choices; /** * Parameter name check regular expression * @type {RegExp} * @public * @static */ static nameRegExp = /^[\w-]{1,32}$/; /** * Parameter description check regular expression * @type {RegExp} * @public * @static */ static descriptionRegExp = /^.{1,100}$/; /** * @constructor * @param {Command} command - parameter parent command * @param {ParameterSchema} options - options used to compute a Parameter object */ constructor(command, options) { this.command = command; this.name = options.name; this.description = options.description || "No description"; this.optional = options.optional; this.type = options.type; this.choices = options.choices; if (!Parameter.nameRegExp.test(this.name)) { throw new Error(`Parameter name ${this.name} doesn't match the pattern`); } if (!Parameter.descriptionRegExp.test(this.description)) { throw new Error(`Parameter ${this.name}: Incorrect description`); } return; } } export class DefaultParameter extends Parameter { constructor(command) { super(command, { name: "input", description: "No description", type: "string", optional: true, }); } } /** * Parameter with input value from interaction (reffered to as argument) * @class */ export class InputParameter extends Parameter { /** * Value of an argument * @type {InputParameterValue<T>} * @public * @readonly */ value; /** * @constructor * @param {Parameter<T>} param - parent parameter * @param {?InputParameterValue<T>} value - input value */ constructor(param, value) { super(param.command, { ...param, }); let val = null; if ((value === null || value === undefined) && !this.optional) { throw new MissingParameterError(this); } else if (value !== null && value !== undefined) { switch (this.type) { case "mentionable": case "channel": case "role": case "user": if (!(value instanceof ObjectID)) { throw new ParameterTypeError(value, this.type); } val = value; break; case "boolean": if (typeof value === "string") { if (value.toLowerCase() === "true") { val = true; } else if (value.toLowerCase() === "false") { val = false; } else { throw new ParameterTypeError(value, this.type); } } else if (value === true || value === false) { val = value; } else { throw new ParameterTypeError(value, this.type); } break; case "number": const num = parseInt(value.toString()); if (isNaN(num)) { throw new ParameterTypeError(value, this.type); } val = num; break; case "string": if (typeof value === "string" || value.toString()) { val = value.toString(); } else { throw new ParameterTypeError(value, this.type); } if (this.choices && this.choices.findIndex((ch) => ch === value) === -1) { if (val !== "") { throw new TypeError(`Invalid choice. Please enter of the following options: ${this.choices.join(", ")}`); } else { val = null; } } break; } } this.value = val; } } /** * Wrapped representation of Discord user, role, channel or other mentionable Discord arugment object * @class */ export class ObjectID { /** * Object ID * @type {string} * @public * @readonly */ id; /** * Guild needed to convert the object * @type {?Guild} * @public * @readonly */ guild; /** * Object type * @type {T} * @public * @readonly */ type; /** * @constructor * @param {string} id - object Discord ID * @param {ObjectIdType} type - object type * @param {?Guild} [guild] - guild needed to convert the object */ constructor(id, type, guild) { this.id = id.replace(">", "").replace("<@!", "").replace("<#!", "").split(" ").join(""); this.type = type; this.guild = guild; } /** * Uses informations associated with the object to generate a Discord.js representation * @returns {?Promise<ObjectIdReturnType<T>>} A fetched object (or null) * @public * @async */ async toObject() { switch (this.type) { case "channel": return (await this.guild?.channels.fetch(this.id.toString() || "")) ?? null; case "role": return (await this.guild?.roles.fetch(this.id.toString() || "")) ?? null; case "user": return (await this.guild?.members.fetch(this.id.toString() || "")) ?? null; default: return null; } } } /** * Wrapped representation of Discord target object (target of context menu interactions) * @class */ export class TargetID { /** * Object ID * @type {string} * @public * @readonly */ id; /** * Interaction associated with the target * @type {Interaction | Message} * @public * @readonly */ interaction; /** * Target type * @type {T} * @private * @readonly */ type; /** * @constructor * @param {string} id - object Discord ID * @param {TargetType} - target type * @param {Interaction | Message} [interaction] - interaction associated with the target */ constructor(id, type, interaction) { this.id = id; this.type = type; this.interaction = interaction; } /** * Uses informations associated with the object to generate a Discord.js representation * @returns {?Promise<TargetIdReturnType<T>>} A fetched object (or null) * @public * @async */ toObject() { switch (this.type) { case "MESSAGE": if (!this.interaction.channel) { throw new Error("Channel not found"); } return this.interaction.channel.messages.cache.get(this.id) ?? null; case "USER": const guild = this.interaction.guild; if (!guild) { throw new Error("Guild not found"); } return guild.members.cache.get(this.id) ?? null; default: return null; } } }