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
+154 -101

No files matched your search

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