commandbot
Version:
A framework that helps you create your own Discord bot easier.
294 lines (293 loc) • 8.69 kB
JavaScript
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;
}
}
}