Working slash command registration

This commit is contained in:
mmacierzanka committed 2021-08-07 16:14:56 +02:00
1 parent b5e6cf5d00
commit b1b981e3ac
6 files changed
+111 -58

No files matched your search

+9 -5
View File
@@ -75,7 +75,7 @@ export class Bot extends EventEmitter {
* @param {string} [token] - app token from Discord Developer Portal * @param {string} [token] - app token from Discord Developer Portal
* @returns *Promise<boolean>* * @returns *Promise<boolean>*
*/ */
async start(port?: number): Promise<boolean> { async start(port?: number, register?: boolean): Promise<boolean> {
try { try {
console.log(`\nBot name: ${this.name}`); console.log(`\nBot name: ${this.name}`);
console.log(`Prefix: ${this.commands.prefix} \n`); console.log(`Prefix: ${this.commands.prefix} \n`);
@@ -102,16 +102,20 @@ export class Bot extends EventEmitter {
process.stdout.write("Connecting to Discord... "); process.stdout.write("Connecting to Discord... ");
this.client.login(this.token); this.client.login(this.token);
this.client.on("ready", async () => { this.client.on("ready", async () => {
if (register === undefined || register === true) {
console.log("✔"); console.log("✔");
process.stdout.write("Registering commands... "); process.stdout.write("Registering commands... ");
this.commands.list.map(async (c) => { await axios.put(
await axios.post(
`https://discord.com/api/v8/applications/${this.applicationId}/commands`, `https://discord.com/api/v8/applications/${this.applicationId}/commands`,
c.toCommandObject(), this.commands.list.map((c) =>
!c.guilds ? c.toCommandObject() : {}
),
{ headers: { Authorization: `Bot ${this.token}` } } { headers: { Authorization: `Bot ${this.token}` } }
); );
});
console.log("✔\n"); console.log("✔\n");
} else {
console.log("✔\n");
}
this.emit("READY"); this.emit("READY");
}); });
this.client.on("message", async (m) => { this.client.on("message", async (m) => {
+59 -19
View File
@@ -6,19 +6,19 @@ import {
ParameterResolvable, ParameterResolvable,
} from "./types.js"; } from "./types.js";
import { MissingParameterError, PermissionsError } from "./errors.js"; import { MissingParameterError, PermissionsError } from "./errors.js";
import { Parameter, ProcessArgument } from "./Parameter.js"; import { Parameter } from "./Parameter.js";
//CLASSES //CLASSES
export class Command { export class Command {
name: string; name: string;
parameters: Parameter[]; parameters?: Parameter[];
aliases: string[]; aliases?: string[];
keywords: string[]; keywords?: string[];
description: string; description: string;
usage: string; usage?: string;
permissionCheck: PermissionCheckTypes; permissionCheck: PermissionCheckTypes;
permissions: Permissions; permissions?: Permissions;
guilds: Guild[]; guilds?: Guild[];
visible: boolean; visible: boolean;
private function: ( private function: (
message?: Message, message?: Message,
@@ -41,7 +41,9 @@ export class Command {
*/ */
constructor(options: CommandBuilder) { constructor(options: CommandBuilder) {
this.name = options.name.split(" ").join("_"); this.name = options.name.split(" ").join("_");
this.parameters = options.parameters || []; this.parameters = options.parameters
? options.parameters.map((p) => new Parameter(p))
: undefined;
this.aliases = Command.processPhrase(options.aliases); this.aliases = Command.processPhrase(options.aliases);
this.keywords = Command.processPhrase(options.keywords); this.keywords = Command.processPhrase(options.keywords);
this.description = options.description || "No description"; this.description = options.description || "No description";
@@ -50,13 +52,18 @@ export class Command {
options.permissionCheck == "ALL" || options.permissionCheck == "ANY" options.permissionCheck == "ALL" || options.permissionCheck == "ANY"
? options.permissionCheck ? options.permissionCheck
: "ANY"; : "ANY";
this.permissions = new Permissions(options.permissions || undefined); this.permissions = options.permissions
this.guilds = options.guilds || []; ? new Permissions(options.permissions)
: undefined;
this.guilds = options.guilds;
this.visible = options.visible != undefined ? options.visible : true; this.visible = options.visible != undefined ? options.visible : true;
this.function = options.function; this.function = options.function;
/*if (!new RegExp("^[w-]{1,32}$").test(this.name)) { if (!/^[\w-]{1,32}$/.test(this.name)) {
throw new Error(`Incorrect command name: ${this.name}`); throw new Error(`Incorrect command name: ${this.name}`);
}*/ }
if (this.description.length > 100) {
throw new Error("Command description is too long");
}
} }
/** /**
@@ -69,17 +76,18 @@ export class Command {
const memberPermissions: Readonly<Permissions> = const memberPermissions: Readonly<Permissions> =
message?.member?.permissions || new Permissions(); message?.member?.permissions || new Permissions();
if ( if (
this.permissionCheck == "ALL" !this.permissions ||
(this.permissionCheck == "ALL"
? memberPermissions.has(this.permissions, true) ? memberPermissions.has(this.permissions, true)
: memberPermissions.any(this.permissions, true) : memberPermissions.any(this.permissions, true))
) { ) {
let inputArguments: ParameterResolvable[] = cmdParams || []; let inputArguments: ParameterResolvable[] = cmdParams || [];
if (this.parameters.length > 0) { if (this.parameters) {
this.parameters.map((a, i) => { this.parameters.map((a, i) => {
if (!inputArguments[i] && !a.optional) { if (!inputArguments[i] && !a.optional) {
throw new MissingParameterError(a); throw new MissingParameterError(a);
} else if (inputArguments[i]) { } else if (inputArguments[i]) {
inputArguments[i] = ProcessArgument( inputArguments[i] = Parameter.process(
inputArguments[i] as string, inputArguments[i] as string,
a.type a.type
); );
@@ -98,14 +106,45 @@ export class Command {
} }
toCommandObject() { toCommandObject() {
let options: any[] = [];
if (this.parameters) {
options = this.parameters.map((p) => {
let type = 3;
if (!/^[\w-]{1,32}$/.test(p.name)) {
throw new Error(
`Failed to register ${p.name} parameter for ${this.name}: The option name is incorrect`
);
}
if ((p.description || "No description").length > 100) {
throw new Error(
`Failed to register ${p.name} parameter for ${this.name}: The description is too long`
);
}
if (p.type == "boolean") type = 5;
else if (p.type == "user") type = 6;
else if (p.type == "channel") type = 7;
else if (p.type == "role") type = 8;
else if (p.type == "mentionable") type = 9;
else if (p.type == "number") type = 10;
return {
name: p.name,
description: p.description || "No description",
required: !p.optional,
type: type,
choices: p.choices || [],
};
});
}
return { return {
name: this.name, name: this.name,
description: this.description, description: this.description,
options: [], options: options,
}; };
} }
private static processPhrase(phrase?: string | string[]): string[] { private static processPhrase(
phrase?: string | string[]
): string[] | undefined {
if (Array.isArray(phrase)) { if (Array.isArray(phrase)) {
const buff = phrase.map((p) => { const buff = phrase.map((p) => {
return p.split(" ").join("_"); return p.split(" ").join("_");
@@ -121,12 +160,13 @@ export class Command {
buff.push(phrase.split(" ").join("_")); buff.push(phrase.split(" ").join("_"));
return buff; return buff;
} else { } else {
return []; return undefined;
} }
} }
private generateUsageFromArguments(): string { private generateUsageFromArguments(): string {
let usageTemplate: string = ""; let usageTemplate: string = "";
this.parameters &&
this.parameters.map((e) => { this.parameters.map((e) => {
usageTemplate += `[${e.name} (${e.type}${ usageTemplate += `[${e.name} (${e.type}${
e.optional ? ", optional" : "" e.optional ? ", optional" : ""
+8 -2
View File
@@ -32,6 +32,7 @@ export class CommandManager {
if (c.name == phrase) { if (c.name == phrase) {
command = c; command = c;
} }
c.aliases &&
c.aliases.map((a) => { c.aliases.map((a) => {
if (a == phrase) { if (a == phrase) {
command = c; command = c;
@@ -39,6 +40,7 @@ export class CommandManager {
}); });
break; break;
case "NO_PREFIX": case "NO_PREFIX":
c.keywords &&
c.keywords.map((k) => { c.keywords.map((k) => {
if (k == phrase) { if (k == phrase) {
command = c; command = c;
@@ -49,11 +51,13 @@ export class CommandManager {
if (c.name == phrase) { if (c.name == phrase) {
command = c; command = c;
} }
c.aliases &&
c.aliases.map((a) => { c.aliases.map((a) => {
if (a == phrase) { if (a == phrase) {
command = c; command = c;
} }
}); });
c.keywords &&
c.keywords.map((k) => { c.keywords.map((k) => {
if (k == phrase) { if (k == phrase) {
command = c; command = c;
@@ -82,6 +86,7 @@ export class CommandManager {
`The name "${command.name}" has already been registered as ${nameOccurrence.type} in the "${nameOccurrence.command.name}" command.` `The name "${command.name}" has already been registered as ${nameOccurrence.type} in the "${nameOccurrence.command.name}" command.`
); );
} }
command.aliases &&
command.aliases.map((a, i, ar) => { command.aliases.map((a, i, ar) => {
const aliasOccurrence: PhraseOccurrenceData | null = const aliasOccurrence: PhraseOccurrenceData | null =
this.findPhraseOccurrence(a); this.findPhraseOccurrence(a);
@@ -93,8 +98,9 @@ export class CommandManager {
} }
}); });
this.list.map((c) => { this.list.map((c) => {
command.keywords &&
command.keywords.map((k, i, a) => { command.keywords.map((k, i, a) => {
if (c.keywords.indexOf(k) != -1) { if (c.keywords && c.keywords.indexOf(k) != -1) {
console.warn( console.warn(
`[⚠ WARN] The name "${k}" is already a registered KEYWORD for the "${c.name}" command. It will be removed from the "${command.name}" command` `[⚠ WARN] The name "${k}" is already a registered KEYWORD for the "${c.name}" command. It will be removed from the "${command.name}" command`
); );
@@ -159,7 +165,7 @@ export class CommandManager {
command: c, command: c,
type: "NAME", type: "NAME",
}; };
} else if (c.aliases.indexOf(phrase || "") != -1) { } else if (c.aliases && c.aliases.indexOf(phrase || "") != -1) {
returnValue = { returnValue = {
command: c, command: c,
type: "ALIAS", type: "ALIAS",
+7 -4
View File
@@ -16,7 +16,7 @@ export class HelpMessage extends Command {
description: params.description, description: params.description,
parameters: [ parameters: [
{ {
name: "Command name", name: "command_name",
description: description:
"Name of the command that you want to get details about", "Name of the command that you want to get details about",
optional: true, optional: true,
@@ -45,7 +45,10 @@ export class HelpMessage extends Command {
`${cmdManager.prefix}${cmd.name} ${cmd.usage}`, `${cmdManager.prefix}${cmd.name} ${cmd.usage}`,
false false
); );
if (cmd.permissions.toArray(false).length > 0) { if (
cmd.permissions &&
cmd.permissions.toArray(false).length > 0
) {
let permList: string = ""; let permList: string = "";
cmd.permissions.toArray(false).map((p) => { cmd.permissions.toArray(false).map((p) => {
permList += p + "\n"; permList += p + "\n";
@@ -56,14 +59,14 @@ export class HelpMessage extends Command {
false false
); );
} }
if (cmd.aliases.length > 0) { if (cmd.aliases && cmd.aliases.length > 0) {
let aList: string = ""; let aList: string = "";
cmd.aliases.map((a) => { cmd.aliases.map((a) => {
aList += a + "\n"; aList += a + "\n";
}); });
helpMsg.addField("Aliases:", aList, false); helpMsg.addField("Aliases:", aList, false);
} }
if (cmd.keywords.length > 0) { if (cmd.keywords && cmd.keywords.length > 0) {
let kwrdList: string = ""; let kwrdList: string = "";
cmd.keywords.map((k) => { cmd.keywords.map((k) => {
kwrdList += k + "\n"; kwrdList += k + "\n";
+26 -26
View File
@@ -1,35 +1,35 @@
import { ParameterType, ParameterResolvable, Choice } from "./types.js"; import { ParameterType, ParameterResolvable, Choice } from "./types.js";
export interface Parameter { export interface ParameterSchema {
name: string; name: string;
description?: string; description?: string;
optional: boolean; optional: boolean;
type: ParameterType; type: ParameterType;
choices?: Choice[]; choices?: Choice[];
} }
export const ProcessArgument = ( export class Parameter {
a: string, name: string;
type: ParameterType description: string;
): ParameterResolvable => { optional: boolean;
switch (type) { type: ParameterType;
/* case "TEXT": choices?: Choice[];
return a;
case "NUMBER": constructor(options: ParameterSchema) {
const numF = parseFloat(a); this.name = options.name;
if (isNaN(numF)) { this.description = options.description || "No description";
throw new ParameterTypeError(a, type); this.optional = options.optional;
} else { this.type = options.type;
return numF; this.choices = options.choices;
if (!/^[\w-]{1,32}$/.test(this.name)) {
throw new Error(
`Parameter name ${this.name} doesn't match the pattern`
);
} }
case "TRUE/FALSE": if (this.description.length > 100) {
if (a.toLowerCase() == "true") { throw new Error(`Parameter ${this.name}: Description too long`);
return true;
} else if (a.toLowerCase() == "false") {
return false;
} else {
throw new ParameterTypeError(a, type);
}*/
default:
return a;
//throw new TypeError("Invalied type specified");
} }
}; return;
}
static process(a: string, type: ParameterType): ParameterResolvable {
return a;
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ import {
TextChannel, TextChannel,
VoiceChannel, VoiceChannel,
} from "discord.js"; } from "discord.js";
import { Parameter } from "./Parameter.js"; import { ParameterSchema } from "./Parameter.js";
import { PermissionResolvable, Message, MessageEmbed } from "discord.js"; import { PermissionResolvable, Message, MessageEmbed } from "discord.js";
import type { Command } from "./Command.js"; import type { Command } from "./Command.js";
@@ -24,7 +24,7 @@ export type GetMode = "ALL" | "PREFIX" | "NO_PREFIX";
export type PermissionCheckTypes = "ALL" | "ANY"; export type PermissionCheckTypes = "ALL" | "ANY";
export interface CommandBuilder { export interface CommandBuilder {
name: string; name: string;
parameters?: Parameter[]; parameters?: ParameterSchema[];
aliases?: string[] | string; aliases?: string[] | string;
keywords?: string[] | string; keywords?: string[] | string;
description?: string; description?: string;