CommandBot 3.0.0 - slash commands (merge pull request)

3.0.0 - Slash commands support, merging pull request to the master branch
This commit was merged in pull request #1.
This commit is contained in:
GRZANA authored and GitHub committed 2021-08-29 17:54:22 +02:00
commit 8398034a44
17 files changed
+999 -674

No files matched your search

+1 -2
View File
@@ -1,4 +1,3 @@
node_modules
test
dist
package-create.bat
dist
+2 -1
View File
@@ -5,7 +5,8 @@
"command": "yarn test",
"name": "Start test env",
"request": "launch",
"type": "node-terminal"
"type": "node-terminal",
"preLaunchTask": "Compile test bot"
}
]
}
+6
View File
@@ -10,6 +10,12 @@
"kind": "build",
"isDefault": true
}
},
{
"label": "Compile test bot",
"type": "shell",
"command": "cd test; tsc",
"problemMatcher": []
}
]
}
+248 -249
View File
@@ -1,329 +1,328 @@
# CommandBot
> [IMPORTANT]
> Upgrading to this version from 1.1.3 may require additional changes in your code since 2.0.0 is not fully backwards compatible
A Discord.js based framework that makes creating Discord bots with support for slash commands easy and fast.
Discord.js framework that helps you build your own Discord bot.
# Table of contents
Key features
- command system
- auto-generated help command
- command permissions
## Table of contents
- [Getting started](#getting-started)
- [Installation](#installation)
- [Creating bot instance](#creating-bot-instance)
- [Bot instance](#bot-instance)
- [CommandBot](#commandbot)
- [Table of contents](#table-of-contents)
- [Instalation](#installation)
- [System requirements](#system-requirements)
- [Creating a project](#creating-a-project)
- [Registering Discord app](#registering-discord-app)
- [Creating application](#creating-application)
- [Commands](#commands)
- [All parameters](#all-parameters)
- [Creating and registering a command](#creating-and-registering-a-command)
- [Command function](#command-function)
- [Arguments](#arguments)
- [Return](#return)
- [Return value](#return-value)
- [Errors](#errors)
- [Command examples](#command-examples)
- [Simple reply](#simple-reply)
- [Example with arguments](#example-with-arguments)
- [Parameters](#parameters)
- [Types](#types)
- [Defining](#defining)
- [Reading input value](#reading-input-value)
- [Events](#events)
- [Customization and built-in messages](#customization-and-built-in-messages)
- [Handling events](#handling-events)
- [Event types](#event-types)
- [Messages](#messages)
- [Information messages](#information-messages)
- [Help message](#help-message)
- [System messages](#system-messages)
- [Complete example](#complete-example)
- [Issues](#issues)
## Getting started
# Installation
### Installation
## System requirements
1. Install this package using npm
- _Node.js_ 16.6.0 or newer
- _npm_ or _yarn_ package manager
```
npm install commandbot@latest
```
## Creating a project
or yarn
### Registering Discord app
1. Visit [Discord Developer Portal](https://discord.com/developers/) and create an app
2. Navigate to the _Bot_ section and register a bot
3. Navigate to _OAuth 2_ section, select _bot_ and _application.commands_ scopes and check bot permissions
4. Copy the link and add your bot to the servers
### Creating application
1. Create empty directory
2. Run `npm init -y` or `yarn init -y`
3. Add the CommandBot package
```javascript
// npm
npm install commandbot@latest
// yarn
yarn add commandbot@latest
```
2. Import the package
4. Create _index.js_ file
5. Import the CommandBot package
```javascript
//CommonJS
// CommonJS
const { Bot, Command } = require("commandbot");
//ES Modules (to use this option add "type": "module" to your package.json file)
// ES Modules (to use ESM add "type": "module" to your package.json file)
import { Bot, Command } from "commandbot";
```
### Creating bot instance
6. Initialize the bot instance
```javascript
const bot = new Bot({
name: "Command bot", // Name of your bot
prefix: "!", // Prefix used to call commands
argumentSeparator: ",", // Used to get arguments from message (optional)
clientOptions: undefined, // Instance of ClientOptions from Discord.js (optional)
token: "", // Bot token from Discord Developer Portal (optional, you can pass the token in the *start* method)
});
```
Start your bot using
```javascript
bot.start();
```
Optional arguments for this method
- **port** - _number_ - if specified, the app will create a http server that will be listening on the specified port
- **token** - _string_ - app token from Discord Developer Portal
## Bot instance
The main object of this library has the following properties
- **name** - _string_ - bot's name specified in the constructor
- **client** - _[Client](https://discord.js.org/#/docs/main/stable/class/Client)_ - Discord.js client instance
- **commands** - _CommandManager_ - base of the command system (see [Commands](#commands))
- **config** - _object_ - object that stores the login token and _helpMessage_ value from the constructor
- **messages** - _SystemMessageManager_ - object instance containing all configuration parameters for built-in messages (see [Customization and built-in messages](#customization-and-built-in-messages))
## Commands
> [IMPORTANT]
> All commands have to be declared above the _start_ method
### All parameters
- **name** - _string_ - command name (used to trigger the command)
- **function** - _Function_ - function that will trigger when the commands gets called
- **visible** - _boolean_ - show command in the _help message_ (optional, defaults to _true_)
- **description** - _string_ - command description shown in the _help message_ (optional)
- **usage** - _string_ - command usage description shown in the _help message_ (optional)
- **permissionCheck** - _"ALL" | "ANY"_ - specifies if the caller has to have all of the specified permissions or any of that (optional, default value: "ANY")
- **permissions** - _[PermissionResolvable](https://discord.js.org/#/docs/main/stable/typedef/PermissionResolvable)_ - permissions needed to run the command (optional)
- **aliases** - _string | string[]_ - other words that can trigger the command with prefix (optional)
- **keywords** - _string | string[]_ - other words that can trigger the command without prefix (optional)
### Command function
#### Arguments
- **m** - _[Message](https://discord.js.org/#/docs/main/stable/class/Message)_ - a message object instance containing message content, author and additional informations
- **a** - _string[]_ - list of arguments passed with the message (splitted with _argumentSeparator_)
#### Return
If command function returns
- _string_ - the returned text will be sent as a reply to the command caller
- _[MessageEmbed](https://discord.js.org/#/docs/main/stable/class/MessageEmbed)_ - the embedded content will be sent as a standalone message
#### Errors
If your command throws an error, it will get logged to the console and sent to the user in a message with embedded content. **The bot will not crash.** ([Example](https://raw.githubusercontent.com/GRZ4NA/CommandBot/master/assets/error_example.png))
### Command examples
#### Simple reply
1. Create a command
```javascript
//CREATE COMMAND
const command = new Command({
name: "ping",
function: (m, a) => {
return "pong";
name: "YOUR_BOT_NAME",
prefix: "BOT_PREFIX",
parameterSeparator: ",",
clientOptions: {
intents: [..."DISCORD_API_INTENTS"],
},
token: "DISCORD_BOT_TOKEN",
applicationId: "APPLICATION_ID",
});
//APPEND IT TO THE COMMANDS LIST
bot.commands.add(command);
```
or
Properties (\* - required):
- **name\*** - _string_ - bot name
- **prefix** - _string_ - bot prefix to use with text commands (if undefined, only slash commands will be available)
- **parameterSeparator** - _string_ - used to separate parameters from messages (default: ',')
- **clientOptions** - _[ClientOptions](https://discord.js.org/#/docs/main/stable/typedef/ClientOptions)_ - Discord.js client options
- **token\*** - _string_ - Discord bot token
- **applicationId\*** - _string_ - Discord application ID
7. Create and add commands to the _Bot_ instance (see [Commands](#commands))
8. Start your bot
```javascript
bot.commands.add(
new Command({
name: "ping",
function: (m, a) => {
return "pong";
},
})
bot.start(
port, // If passed, the application will create a HTTP server
true // If true or undefined, the app will register all slash commands in the Discord API
);
```
2. Call it from Discord text channel (example prefix: "!")
# Commands
```
!ping
## Creating and registering a command
Result:
@mention, pong
```
To create a command, initialize a _Command_ object
#### Example with arguments
1. Create a command
Example:
```javascript
const command = new Command({
name: "repeat",
function: async (m, a) => {
if (a[0] && a[1]) {
const count = parseInt(a[1]);
if (!isNaN(count)) {
for (let i = 0; i < count; i++) {
await m.channel.send(a[0]);
}
return `Repeated "${a[0]}" ${count} times`;
} else {
throw new Error("Number of messages is incorrect");
}
const cmdGreet = new Command({
name: "greet",
parameters: [
{
name: "name",
description: "Name that will be greeted",
optional: true,
type: "string",
},
],
aliases: ["hello"],
description: "Welcomes someone",
usage: "[name]",
permissionCheck: "ALL",
permissions: ["SEND_MESSAGES"],
guilds: undefined,
visible: true,
slash: true,
announceSuccess: true,
function: function(p. m) {
if(p('name')) {
return `Hello, ${p('name')}!`
}
},
else {
return 'Hello!'
}
}
});
bot.commands.add(command);
// Register
bot.commands.add(cmdGreet)
```
or
Properties (\* - required):
- **name\*** - _string_ - command name
- **parameters** - _ParameterSchema[]_ - array of parameters (see [Parameters](#parameters))
- **aliases** - _string | string[]_ - array of alternative strings that can call this command (not available for slash commands)
- **description** - _string_ - command description
- **usage** - _string_ - command usage visible in the help message (if not defined, usage string will be automatically generated based on defined parameters)
- **permissionCheck** - _"ALL" | "ANY"_ - whether to check if caller has all defined permission or at least one of them
- **permissions** - _[PermissionResolvable](https://discord.js.org/#/docs/main/stable/typedef/PermissionResolvable) | (m?: [Message](https://discord.js.org/#/docs/main/stable/class/Message) | [CommandInteraction](https://discord.js.org/#/docs/main/stable/class/CommandInteraction)) => boolean_ - permissions required to run this command
- **guilds** - _string[]_ - array of servers IDs in which this command will be available (if slash command)
- **visible** - _boolean_ - whether this command is visible in the help message
- **slash** - _boolean_ - whether this command should be registered as a slash command
- **announceSuccess** - _boolean_ - whether a command reply should be sent automatically if no other response is defined or the reply should be deleted
- **function\*** - _(p: function, m?: [Message](https://discord.js.org/#/docs/main/stable/class/Message) | [CommandInteraction](https://discord.js.org/#/docs/main/stable/class/CommandInteraction)) => void | [MessageEmbed]() | string | [ReplyMessageOptions](https://discord.js.org/#/docs/main/stable/typedef/ReplyMessageOptions)_ - function that will be executed on call
Register your command in bot client using:
```javascript
bot.commands.add(
new Command({
name: "repeat",
function: async (m, a) => {
if (a[0] && a[1]) {
const count = parseInt(a[1]);
if (!isNaN(count)) {
for (let i = 0; i < count; i++) {
await m.channel.send(a[0]);
}
return `Repeated "${a[0]}" ${count} times`;
} else {
throw new Error("Number of messages is incorrect");
}
}
},
})
);
bot.commands.add(cmd);
```
2. Call it from Discord text channel (example prefix: "!", example separator: "," [default])
where (\* - required):
```
!repeat test, 5
- **cmd** - _Command_
Result:
test
test
test
test
test
@caller, Repeated "test" 5 times
```
## Command function
## Events
### Arguments
Events are emitted using built-in event emitter **(since version 2.0.0)**. You can handle them using _on_ property
- **p** - _function_ - call this function with parameter name to fetch parameter value
- **m?** - _[Message](https://discord.js.org/#/docs/main/stable/class/Message) | [CommandInteraction](https://discord.js.org/#/docs/main/stable/class/CommandInteraction)_ - interaction object
- **READY** - emitted when the client successfully connect to Discord API
- **MESSAGE** - emitted for every message sent in any text channel (a [Message](https://discord.js.org/#/docs/main/stable/class/Message) object is passed to the first argument)
- **COMMAND** - emitted for every message that gets recognized as a command (a [Message](https://discord.js.org/#/docs/main/stable/class/Message) object is passed to the first argument and a CommandMessageStructure object to the second argument)
- **ERROR** - emitted on permission and command errors (an error object is passed to the first argument)
> [IMPORTANT]
> Do not use client.on('message') and client.on('ready') event handler! This handler is a core part of the command system.
### Return value
Example
If function returns (also after resolving a _Promise_):
- **void** - If _announceSuccess_ property is _true_, bot will automatically send a SUCCESS message (see [Messages](#messages)). If command has been called using slash commands and _announceSuccess_ property is set to _false_, reply will be automatically deleted
- **string** - this string will be sent in a reply
- **[MessageEmbed](https://discord.js.org/#/docs/main/stable/class/MessageEmbed)** - embedded content will be sent in a reply
- **[ReplyMessageOptions](https://discord.js.org/#/docs/main/stable/typedef/ReplyMessageOptions)** - these options will get used to send a reply
It is possible to manually send replies directly from the command function using the **m** argument. If you are using slash commands don't forget to use the _[editReply](https://discord.js.org/#/docs/main/stable/class/CommandInteraction?scrollTo=editReply)_ method instead of the _reply_ method since a **reply is already deferred** when a command function is being called (read more [here](https://discord.com/developers/docs/interactions/receiving-and-responding)) If you try to create a new reply, you app will throw an error that will result a crash. If you manually reply to a slash command interaction and return _void_ from the command function, a SUCCESS message will not be sent or reply will not get deleted (if you want to disable SUCCESS messages on prefix interactions set _announceSuccess_ property to _false_).
### Errors
If a command function will throw an error, it will automatically get caught and your bot will send an ERROR message (see [Messages](#messages)). The app **will not** crash.
## Parameters
### Types
- **string** - text value
- **boolean** - True or False
- **number** - number (double) value
- **user** - _ObjectID_ object with ID value (shown as selection menu in Discord)
- **role** - _ObjectID_ object with ID value (shown as selection menu in Discord)
- **channel** - _ObjectID_ object with ID value (shown as selection menu in Discord)
- **mentionable** - _ObjectID_ object with ID value (shown as selection menu in Discord)
To get an entity ID from _ObjectID_ use the _value_ property. You can also call _toObject_ method to retrieve full entity object from Discord API
```javascript
bot.on("ready", () => {
console.log("Bot is ready!");
});
ObjectID.toObject(g, "TYPE");
```
## Customization and built-in messages
where (\* - required):
All configuration parameters for messages are stored in _messages_ property (example: bot.messages)
- **g\*** - _Guild_ - Guild object to fetch from
- **"TYPE"\*** - _"user' | "role" | "channel"_ - defines the entity type
### Help message
### Defining
Configuration parameters are stored in _messages.help_ property
Example:
- **enabled** - _boolean_ - enables or disables the help message
- **title** - _string_ - title of help message
- **bottomText** - _string_ - text shown below the title
- **color** - _[ColorResolvable](https://discord.js.org/#/docs/main/stable/typedef/ColorResolvable)_ - color of the embedded content
- **description** - _string_ - command description (equivalent of the _description_ property in _Command_ object)
- **usage** - _string_ - equivalent of the _usage_ property in _Command_ object (You can pass a command name, alias or keyword with the _help_ command to get detailed information about the specified command)
```javascript
{
name: "user",
description: "User to mention",
optional: false,
type: "user"
}
```
### System messages
Properties (\* - required):
All options are stored in _messages.system_ property
- **name\*** - _string_ - parameter name
- **description** - _string_ - parameter description
- **optional\*** - _boolean_ - whether this parameter is optional
- **type\*** - _"string" | "boolean" | "number" | "user" | "role" | "channel" | "mentionable"_ - parameter type
- **choices** - _string[]_ - parameter value choices (to use this, set type to "string")
There are 3 types of system messages
### Reading input value
- Error message (**ERROR**)
- Command not found message (**NOT_FOUND**)
- Insufficient permissions message (**PERMISSION**)
To read parameter values use a function that is passed in the first argument of a call function (defined in _function_ parameter in _Command_ object)
Each message can be customized using these properties
```javascript
p(query, returnType);
```
- **enabled** - _boolean_ - enables or disables the system message
where (\* - required):
- **query\*** - _string_ - parameter name
- **returnType** - _"value" | "object"_ - whether to return only parameter value or a full object
```javascript
function: (p, m) => {
const userObj = p('user')
if(userObj) {
const user = user.toObject(m.guild, "user");
if(user) {
return `Hello, ${user.toString()}`
}
else {
throw new Error('User not found')
}
}
}
```
# Events
## Handling events
CommandBot is using _[EventEmitter](https://nodejs.org/api/events.html)_ that is built into Node.js. You can listen to events using the _on_ method.
```javascript
bot.on(eventType, callbackFn);
```
where (\* - required):
- **eventType\*** - _"READY" | "COMMAND" | "MESSAGE" | "ERROR"_ - type of event that you want to listen to
- **callbackFn\*** - _Function_ - a function that will get executed when the event is emitted
## Event types
- **READY** - emitted when the app has finished its initialization process
- **MESSAGE** - emitted when message is created (similar to _messageCreate_ event from Discord.js) (not emitted when command gets triggered) - a _[Message](https://discord.js.org/#/docs/main/stable/class/Message)_ object is being passed to the first argument
- **COMMAND** - emitted when command gets triggered - a _[Message](https://discord.js.org/#/docs/main/stable/class/Message)_ or _[CommandInteraction](https://discord.js.org/#/docs/main/stable/class/CommandInteraction)_ object is being passed to the first argument; an object containing fetched _Command_ object and input parameters is being passed to the second
- **ERROR** - emitted when a command function throws an error - an _Error_ object is being passed to the first argument
# Messages
## Information messages
There are 4 information messages:
- **ERROR** - "An error occurred" message (sent when error occurs)
- **SUCCESS** - "Task completed successfully" message (sent after successfully executing a command function)
- **PERMISSION** - "Insufficient permissions" message (sent when the caller doesn't have enough permissions)
- **NOT_FOUND** - "Command not found" message (sent when someone tries to call a command that does not exist)
Each of these messages can be customized with these properties:
- **enabled** - _boolean_ - enables or disables the message
- **title** - _string_ - title of the message
- **bottomText** - _string_ - text shown below the title
- **accentColor** - _[ColorResolvable](https://discord.js.org/#/docs/main/stable/typedef/ColorResolvable)_ - color of the embedded content
- **showTimestamp** - _boolean_ - show time and date at the bottom of the embedded content
- **footer** - _string_ - footer of the embedded content
- **bottomText** - _string_ - text below the title (also known as description)
- **accentColor** - _ColorResolvable_ - embed color
- **showTimestamp** - _boolean_ - whether to show date and time in the footer
- **footer** - _string_ - message footer
The _messages.system_ also contains a _deleteTimeout_ property. It specifies the time (in ms) after which a system message will be deleted. Set it to _Infinity_ to never delete messages (default value).
## Help message
## Complete example
You can customize the help message with these properties:
```javascript
import { Bot, Command } from "commandbot";
- **enabled** - _boolean_ - enables or disables the message
- **title** - _string_ - title of the message
- **bottomText** - _string_ - text below the title (also known as description)
- **accentColor** - _ColorResolvable_ - embed color
- **description** - _string_ - description shown in the message itself
- **usage** - _string_ - usage displayed in the message itself
- **visible** - _boolean_ - whether to show the help command in the list
const bot = new Bot({
name: "CommandBot",
prefix: "!",
});
# Issues
bot.messages.help.accentColor = "#ff0000";
bot.messages.system.NOT_FOUND.accentColor = "BLUE";
Since this package is created by only 1 person it may contain some bugs or behave weirdly. If you notice any problem, typo etc, please create an issue in the _Issues_ tab on GitHub.
//Command triggers: !ping, !pong, pingpong
bot.commands.add(
new Command({
name: "ping",
aliases: "pong",
keywords: "pingpong",
function: (m, a) => {
return "Bot ping: " + bot.client.ws.ping + "ms";
},
})
);
bot.commands.add(
new Command({
name: "repeat",
function: async (m, a) => {
if (a[0] && a[1]) {
const count = parseInt(a[1]);
if (!isNaN(count)) {
for (let i = 0; i < count; i++) {
await m.channel.send(a[0]);
}
return `Repeated "${a[0]}" ${count} times`;
} else {
throw new Error("Number of messages is incorrect");
}
}
},
})
);
Thank you.
bot.start(3000, "TOKEN");
```
Created with ❤️ by [GRZANA](https://github.com/GRZ4NA)
+2 -1
View File
@@ -2,6 +2,7 @@ import { Bot } from "./src/Bot.js";
import { Command } from "./src/Command.js";
import { ParameterResolvable, ParameterType } from "./src/types.js";
import { MessageEmbed } from "discord.js";
import { ObjectID, InputParameter } from "./src/Parameter.js";
export default Bot;
export { Bot, Command, MessageEmbed, ParameterResolvable, ParameterType };
export { Bot, Command, MessageEmbed, ParameterResolvable, ParameterType, ObjectID, InputParameter };
+3
View File
@@ -0,0 +1,3 @@
@echo off
echo { "type": "commonjs" } > dist/cjs/package.json
echo { "type": "module" } > dist/mjs/package.json
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "commandbot",
"version": "3.0.0-beta2a",
"version": "3.0.0",
"description": "A framework that helps you create your own Discord bot easier.",
"main": "dist/cjs/index.js",
"module": "dist/mjs/index.js",
@@ -27,7 +27,8 @@
},
"homepage": "https://github.com/GRZ4NA/CommandBot#readme",
"dependencies": {
"discord.js": "^12.5.3"
"axios": "^0.21.1",
"discord.js": "^13.0.1"
},
"devDependencies": {
"@types/node": "^15.12.4",
+167 -76
View File
@@ -1,24 +1,18 @@
import { Client, Message } from "discord.js";
import axios from "axios";
import { Client, CommandInteraction, GuildMember, Intents, Message } from "discord.js";
import { EventEmitter } from "events";
import * as http from "http";
import { Command } from "./Command.js";
import { CommandManager } from "./CommandManager.js";
import {
CommandMessageStructure,
InitOptions,
HelpMessageParams,
} from "./types.js";
import { PermissionsError } from "./errors.js";
import { OperationSuccess, PermissionsError } from "./errors.js";
import { HelpMessage } from "./Help.js";
import * as http from "http";
import { SystemMessageManager } from "./SystemMessage.js";
import { EventEmitter } from "events";
import { CommandMessageStructure, InitOptions, HelpMessageParams } from "./types.js";
export declare interface Bot {
on(event: "READY", listener: Function): this;
on(event: "MESSAGE", listener: (m: Message) => void): this;
on(
event: "COMMAND",
listener: (m: Message, cmdMsg: CommandMessageStructure) => void
): this;
on(event: "COMMAND", listener: (m: Message | CommandInteraction, cmdMsg: CommandMessageStructure) => void): this;
on(event: "ERROR", listener: (e: any) => void): this;
}
@@ -27,6 +21,7 @@ export class Bot extends EventEmitter {
client: Client;
commands: CommandManager;
token: string;
applicationId: string;
messages: {
help: HelpMessageParams;
system: SystemMessageManager;
@@ -35,22 +30,41 @@ export class Bot extends EventEmitter {
/**
* Bot instance initializer
* @constructor
* @param {ConstructorOptions} options - instance properties
* @param {InitOptions} options - instance properties
* @param {string} options.name - name of your bot
* @param {string} options.prefix - prefix used to call commands
* @param {string} [options.argumentSeparator=','] - used to get parameters from message
* @param {ClientOptions} [options.clientOptions] - client options from Discord.js
* @param {string} [options.token] - bot token from Discord Developer Portal
* @param {string} options.token - bot token from Discord Developer Portal
* @param {string} options.applicationId - Discord application ID used to register slash commands
*/
constructor(options: InitOptions) {
super();
this.name = options.name;
this.client = new Client(options.clientOptions);
this.commands = new CommandManager(
options.prefix,
options.argumentSeparator
this.client = new Client(
options.clientOptions || {
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_BANS,
Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS,
Intents.FLAGS.GUILD_INTEGRATIONS,
Intents.FLAGS.GUILD_INVITES,
Intents.FLAGS.GUILD_MEMBERS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.GUILD_MESSAGE_TYPING,
Intents.FLAGS.GUILD_PRESENCES,
Intents.FLAGS.GUILD_VOICE_STATES,
Intents.FLAGS.GUILD_WEBHOOKS,
Intents.FLAGS.DIRECT_MESSAGES,
Intents.FLAGS.DIRECT_MESSAGE_REACTIONS,
Intents.FLAGS.DIRECT_MESSAGE_TYPING,
],
}
);
this.token = options.token || "";
this.commands = new CommandManager(options.prefix, options.parameterSeparator);
this.token = options.token;
this.applicationId = options.applicationId;
this.messages = {
help: {
enabled: true,
@@ -59,6 +73,7 @@ export class Bot extends EventEmitter {
color: "#ff5500",
usage: "[command name (optional)]",
bottomText: "List of all available commands",
visible: true,
},
system: new SystemMessageManager(this.name),
};
@@ -67,80 +82,121 @@ export class Bot extends EventEmitter {
/**
* Starts your Discord bot
* @param {number} [port] - if specified, the app will create a http server that will be listening on the specified port
* @param {string} [token] - app token from Discord Developer Portal
* @param {boolean} [register] - if true, the bot will register all slash commands in Discord API
* @returns *Promise<boolean>*
*/
async start(port?: number, token?: string): Promise<boolean> {
async start(port?: number, register?: boolean): Promise<boolean> {
try {
console.log(`\nBot name: ${this.name}`);
console.log(`Prefix: ${this.commands.prefix} \n`);
this.token = token || this.token;
console.log(`Prefix: ${this.commands.prefix || "/ (only slash commands)"} \n`);
if (this.token === "") {
throw new ReferenceError(
'No token specified. Please pass your Discord application token as an argument to the "start" method or in the constructor'
);
throw new ReferenceError('No token specified. Please pass your Discord application token as an argument to the "start" method or in the constructor');
}
if (port) {
process.stdout.write(
`Creating http server on port ${port}... `
);
process.stdout.write(`Creating http server on port ${port}... `);
http.createServer().listen(port);
console.log("✔");
}
if (this.messages.help.enabled === true) {
const helpMsg: Command = new HelpMessage(
this.commands,
this.messages.help,
this.name
);
const helpMsg: Command = new HelpMessage(this.commands, this.messages.help, this.name);
this.commands.add(helpMsg);
}
process.stdout.write("Connecting to Discord... ");
await this.client.login(this.token);
this.client.on("ready", () => {
console.log("✔\n");
this.client.login(this.token);
this.client.on("ready", async () => {
if (register === undefined || register === true) {
console.log("✔");
this.register();
} else {
console.log("✔\n");
}
this.emit("READY");
});
this.client.on("message", async (m) => {
const cmdMsg = this.commands.fetch(m);
if (cmdMsg) {
this.emit("COMMAND", m, cmdMsg);
try {
this.client.on("messageCreate", async (m) => {
let cmdMsg: CommandMessageStructure | null = null;
try {
cmdMsg = this.commands.fetchFromMessage(m);
if (cmdMsg) {
this.emit("COMMAND", m, cmdMsg);
await cmdMsg.command.start(m, cmdMsg.parameters);
} catch (e) {
if (e instanceof PermissionsError) {
this.messages.system.send(
"PERMISSION",
{
user: m.member || undefined,
command: cmdMsg.command,
},
m.channel
);
} else {
this.messages.system.send(
"ERROR",
{
command: cmdMsg.command,
user: m.member || undefined,
error: e,
},
m.channel
);
console.error(e);
}
this.emit("ERROR", e);
return;
} else if (this.commands.prefix && m.content.startsWith(this.commands.prefix)) {
this.emit("MESSAGE", m);
await this.messages.system.send("NOT_FOUND", { phrase: m.content, user: m.member || undefined }, m);
} else {
this.emit("MESSAGE", m);
}
} else if (m.content.startsWith(this.commands.prefix)) {
this.emit("MESSAGE", m);
this.messages.system.send(
"NOT_FOUND",
{ phrase: m.content, user: m.member || undefined },
m.channel
);
} else {
this.emit("MESSAGE", m);
} catch (e) {
if (e instanceof PermissionsError) {
await this.messages.system.send(
"PERMISSION",
{
user: m.member || undefined,
command: cmdMsg?.command,
},
m
);
this.emit("ERROR", e);
} else if (e instanceof OperationSuccess) {
await this.messages.system.send("SUCCESS", undefined, m);
} else {
await this.messages.system.send(
"ERROR",
{
command: cmdMsg?.command,
user: m.member || undefined,
error: e,
},
m
);
this.emit("ERROR", e);
}
return;
}
});
this.client.on("interactionCreate", async (i) => {
let cmd: CommandMessageStructure | null = null;
try {
if (!i.isCommand()) return;
cmd = this.commands.fetchFromInteraction(i);
if (cmd) {
this.emit("COMMAND", i, cmd);
await cmd.command.start(i, cmd.parameters);
} else {
await this.messages.system.send(
"NOT_FOUND",
{
phrase: i.commandName,
user: i.member as GuildMember,
},
i as CommandInteraction
);
}
} catch (e) {
if (e instanceof PermissionsError) {
await this.messages.system.send(
"PERMISSION",
{
user: (i.member as GuildMember) || undefined,
command: cmd?.command,
},
i as CommandInteraction
);
this.emit("ERROR", e);
} else if (e instanceof OperationSuccess) {
await this.messages.system.send("SUCCESS", undefined, i as CommandInteraction);
} else {
await this.messages.system.send(
"ERROR",
{
command: cmd?.command,
user: (i.member as GuildMember) || undefined,
error: e,
},
i as CommandInteraction
);
this.emit("ERROR", e);
}
return;
}
});
return true;
@@ -150,6 +206,41 @@ export class Bot extends EventEmitter {
return false;
}
}
/**
* Registers commands in Discord API
* @returns *Promise<void>*
*/
async register(): Promise<void> {
process.stdout.write("Registering commands... ");
await axios.put(
`https://discord.com/api/v8/applications/${this.applicationId}/commands`,
this.commands.list.filter((c) => !Array.isArray(c.guilds) && c.slash).map((c) => c.toCommandObject()),
{ headers: { Authorization: `Bot ${this.token}` } }
);
const guilds: any = {};
await this.commands.list
.filter((c) => Array.isArray(c.guilds) && c.guilds.length > 0)
.map((c) => {
c.guilds?.map(async (g) => {
if (!(await this.client.guilds.fetch(g))) {
throw new Error(`"${g}" for "${c.name}" command is not a valid guild ID`);
}
if (guilds[g]) {
guilds[g].push(c.toCommandObject());
} else {
guilds[g] = [];
guilds[g].push(c.toCommandObject());
}
});
});
for (let i in guilds) {
await axios.put(`https://discord.com/api/v8/applications/${this.applicationId}/guilds/${i}/commands`, guilds[i], {
headers: { Authorization: `Bot ${this.token}` },
});
}
console.log("✔\n");
}
}
export default Bot;
+136 -59
View File
@@ -1,98 +1,173 @@
//IMPORTS
import { Permissions, Message, MessageEmbed } from "discord.js";
import {
CommandBuilder,
PermissionCheckTypes,
ParameterResolvable,
} from "./types.js";
import { MissingParameterError, PermissionsError } from "./errors.js";
import { Parameter, ProcessArgument } from "./Parameter.js";
import { Permissions, Message, MessageEmbed, CommandInteraction, GuildMember, ReplyMessageOptions, PermissionResolvable } from "discord.js";
import { CommandBuilder, ParameterResolvable, PermissionCheckTypes } from "./types.js";
import { OperationSuccess, PermissionsError } from "./errors.js";
import { DefaultParameter, InputParameter, Parameter } from "./Parameter.js";
//CLASSES
export class Command {
name: string;
parameters: Parameter[];
aliases: string[];
keywords: string[];
aliases?: string[];
description: string;
usage: string;
usage?: string;
permissionCheck: PermissionCheckTypes;
permissions: Permissions;
permissions?: Permissions | ((m?: Message | CommandInteraction) => boolean);
guilds?: string[];
visible: boolean;
slash: boolean;
announceSuccess: boolean;
private function: (
message?: Message,
cmdParams?: ParameterResolvable[]
) => void | string | MessageEmbed | Promise<void | string | MessageEmbed>;
params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null,
interaction?: Message | CommandInteraction
) => void | string | MessageEmbed | ReplyMessageOptions | Promise<void | string | MessageEmbed | ReplyMessageOptions>;
/**
* Command constructor
* @constructor
* @param {CommandBuilder} options - all command properties
* @param {string} options.name - command name (used to trigger the command)
* @param {string | string[]} [options.aliases] - other words that can trigger the command with prefix
* @param {string | string[]} [options.keywords] - other words that can trigger the command without prefix
* @param {ParameterSchema} options.parameters - all parameters that can be passed with the command
* @param {string | string[]} [options.aliases] - other words that can trigger the command with prefix (not used in slash commands)
* @param {string} [options.description="No description"] - command description shown in the help message
* @param {string} [options.usage] - command usage description shown in the help message
* @param {string} [options.usage] - command usage description shown in the help message (usage message can be automatically generated using parameters)
* @param {PermissionCheckTypes} [options.permissionCheck='ANY'] - specifies if the caller has to have all of the specified permissions or any of that
* @param {PermissionResolvable} [options.permissions] - permissions needed to run the command
* @param {boolean} [options.visible=true] - show command in the help message
* @param {boolean} [options.visible=true] - show command in the help message (visible as a slash command)
* @param {boolean} [options.slash=true] - whether the command should be available as a slash command
* @param {Function} options.function - function that will trigger when the commands gets called
*/
constructor(options: CommandBuilder) {
this.name = options.name.split(" ").join("_");
this.parameters = options.parameters || [];
if (options.parameters == "no_input" || !options.parameters) {
this.parameters = [];
} else if (options.parameters == "simple") {
this.parameters = [new DefaultParameter()];
} else {
this.parameters = options.parameters.map((ps) => new Parameter(ps));
}
this.aliases = Command.processPhrase(options.aliases);
this.keywords = Command.processPhrase(options.keywords);
this.description = options.description || "No description";
this.usage = options.usage || this.generateUsageFromArguments();
this.permissionCheck =
options.permissionCheck == "ALL" || options.permissionCheck == "ANY"
? options.permissionCheck
: "ANY";
this.permissions = new Permissions(options.permissions || 0);
this.visible = options.visible != undefined ? options.visible : true;
this.permissionCheck = options.permissionCheck == "ALL" || options.permissionCheck == "ANY" ? options.permissionCheck : "ANY";
this.permissions = options.permissions ? (options.permissions instanceof Function ? options.permissions : new Permissions(options.permissions)) : undefined;
this.guilds = options.guilds;
this.visible = options.visible !== undefined ? options.visible : true;
this.slash = options.slash !== undefined ? options.slash : true;
this.announceSuccess = options.announceSuccess !== undefined ? options.announceSuccess : true;
this.function = options.function;
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");
}
}
/**
* Starts the command
* @param {Message} [message] - a *Message* object used to check caller's permissions. It will get passed to the execution function (specified in *function* property of command's constructor)
* @param {string[]} [cmdParams] - list of processed parameters passed in a Discord message
* @param {Message | CommandInteraction} [interaction] - a *Message* or *CommandInteraction* object 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 message
* @returns *Promise<void>*
*/
async start(message?: Message, cmdParams?: string[]): Promise<void> {
const memberPermissions: Readonly<Permissions> =
message?.member?.permissions || new Permissions(0);
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.permissionCheck == "ALL"
? memberPermissions.has(this.permissions, true)
: memberPermissions.any(this.permissions, true)
!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)
) {
let inputArguments: ParameterResolvable[] = cmdParams || [];
if (this.parameters.length > 0) {
this.parameters.map((a, i) => {
if (!inputArguments[i] && !a.optional) {
throw new MissingParameterError(a);
} else if (inputArguments[i]) {
inputArguments[i] = ProcessArgument(
inputArguments[i] as string,
a.type
);
}
});
if (this.guilds && !this.guilds.find((g) => g == interaction?.guild?.id)) {
throw new Error("This command is not available here");
}
const fnResult = await this.function(message, cmdParams);
if (typeof fnResult == "string") {
await message?.reply(fnResult);
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) {
await message?.channel.send(fnResult);
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, message?.member);
throw new PermissionsError(this, interaction?.member as GuildMember);
}
}
private static processPhrase(phrase?: string | string[]): string[] {
/**
* Returns object that is ready to be registered in the Discord API
* @returns {Object} object
*/
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: p.choices ? 3 : type,
choices:
p.choices?.map((c) => {
return { name: c, value: c };
}) || [],
};
});
}
return {
name: this.name,
description: this.description,
options: options,
};
}
/**
* @param {string|string[]} phrase?
* @returns string
*/
private static processPhrase(phrase?: string | string[]): string[] | undefined {
if (Array.isArray(phrase)) {
const buff = phrase.map((p) => {
return p.split(" ").join("_");
@@ -108,17 +183,19 @@ export class Command {
buff.push(phrase.split(" ").join("_"));
return buff;
} else {
return [];
return undefined;
}
}
/**
* @returns string
*/
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.choices ? e.choices.join(" / ") : e.type}${e.optional ? ", optional" : ""})] `;
});
return usageTemplate;
}
}
+128 -88
View File
@@ -1,66 +1,41 @@
import { Message } from "discord.js";
import { CommandInteraction, Message } from "discord.js";
import { MissingParameterError } from "./errors.js";
import { BooleanParameter, InputParameter, NumberParameter, ObjectParameter, StringParameter } from "./Parameter.js";
import { Command } from "./Command.js";
import {
CommandMessageStructure,
GetMode,
PhraseOccurrenceData,
} from "./types.js";
import { CommandMessageStructure, PhraseOccurrenceData } from "./types.js";
export class CommandManager {
list: Command[];
prefix: string;
argumentSeparator: string;
prefix?: string;
parameterSeparator: string;
constructor(prefix: string, argumentSeparator?: string) {
/**
* @param {string} prefix?
* @param {string} parameterSeparator?
*/
constructor(prefix?: string, parameterSeparator?: string) {
this.list = [];
this.prefix = prefix;
this.argumentSeparator = argumentSeparator || ",";
this.parameterSeparator = parameterSeparator || ",";
}
/**
* Retrieves the command by name, alias or keyword
* @param {string} phrase - command name, alias or keyword
* @param {GetMode} [mode='ALL'] - specifies which types of command triggers will be used to find the command [*NO_PREFIX* - only keywords; *PREFIX* - names and aliases]
* @returns *Command* | *null*
*/
get(phrase: string, mode?: GetMode): Command | null {
if (!mode) mode = "ALL";
get(phrase: string): Command | null {
let command: Command | null = null;
this.list.map((c) => {
switch (mode) {
case "PREFIX":
if (c.name == phrase) {
command = c;
}
c.aliases.map((a) => {
if (a == phrase) {
command = c;
}
});
break;
case "NO_PREFIX":
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;
}
});
break;
if (c.name == phrase) {
command = c;
}
c.aliases &&
c.aliases.map((a) => {
if (a == phrase) {
command = c;
}
});
});
return command;
}
@@ -75,33 +50,26 @@ export class CommandManager {
if (!(command instanceof Command)) {
throw new TypeError("Inavlid argument type");
}
const nameOccurrence: PhraseOccurrenceData | null =
this.findPhraseOccurrence(command.name);
const nameOccurrence: PhraseOccurrenceData | null = this.findPhraseOccurrence(command.name);
if (nameOccurrence) {
throw new Error(
`The name "${command.name}" has already been registered as ${nameOccurrence.type} in the "${nameOccurrence.command.name}" command.`
);
throw new Error(`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);
}
});
command.parameters &&
command.parameters.map((p) => {
if (p.type != "string" && p.choices) {
throw new Error('Parameter with defined choices must have a "string" type');
}
});
});
this.list.push(command);
return true;
} catch (e) {
@@ -115,33 +83,51 @@ export class CommandManager {
* @param {Message} message - *Message* object
* @returns *CommandMessagesStructure* | *null*
*/
fetch(message: Message): CommandMessageStructure | null {
if (!message.author.bot) {
let prefix = false;
if (message.content.startsWith(this.prefix)) {
prefix = true;
}
const content = prefix
? message.content.replace(this.prefix, "")
: message.content;
fetchFromMessage(message: Message): CommandMessageStructure | null {
if (!this.prefix) return null;
if (!message.author.bot && message.content.startsWith(this.prefix)) {
const content = message.content.replace(this.prefix, "");
const name = content.split(" ")[0];
const command = this.get(name, prefix ? "PREFIX" : "NO_PREFIX");
const command = this.get(name);
if (command) {
const argumentsText = content.replace(name, "");
const paramsList = argumentsText
.split(this.argumentSeparator)
.map((a) => {
return a.replace(" ", "");
});
if (
(paramsList[0] == "" || paramsList[0] == " ") &&
paramsList.length == 1
) {
const paramsList = argumentsText.split(this.parameterSeparator).map((a) => {
return a.replace(" ", "");
});
if ((paramsList[0] == "" || paramsList[0] == " ") && paramsList.length == 1) {
paramsList.splice(0, 1);
}
const parameters: InputParameter[] = [];
command.parameters.map((p, i) => {
if (!p.optional && !paramsList[i]) {
throw new MissingParameterError(p);
} else if (p.optional && !paramsList[i]) {
return;
}
switch (p.type) {
case "mentionable":
case "channel":
case "role":
case "user":
parameters.push(new ObjectParameter(p, paramsList[i]));
break;
case "string":
parameters.push(new StringParameter(p, paramsList[i]));
break;
case "boolean":
parameters.push(new BooleanParameter(p, paramsList[i]));
break;
case "number":
parameters.push(new NumberParameter(p, paramsList[i]));
break;
default:
parameters.push(new InputParameter(p, paramsList[i]));
break;
}
});
return {
command: command,
parameters: paramsList,
parameters: parameters,
};
} else {
return null;
@@ -151,6 +137,60 @@ export class CommandManager {
}
}
/**
* Fetches command and parameters from the given *CommandInteraction* object
* @param {CommandInteraction} interaction - interaction object
* @returns *CommandMessagesStructure* | *null*
*/
fetchFromInteraction(interaction: CommandInteraction): CommandMessageStructure | null {
const cmd = this.get(interaction.commandName);
if (cmd) {
if (interaction.options?.data) {
const paramsList = interaction.options.data;
const parameters: InputParameter[] = [];
cmd.parameters.map((p) => {
const inputParam = paramsList.find((d) => d.name == p.name);
if (!p.optional && !inputParam) {
throw new MissingParameterError(p);
} else if (p.optional && !inputParam) {
return;
}
switch (p.type) {
case "mentionable":
case "channel":
case "role":
case "user":
parameters.push(new ObjectParameter(p, inputParam?.value));
break;
case "string":
parameters.push(new StringParameter(p, inputParam?.value));
break;
case "boolean":
parameters.push(new BooleanParameter(p, inputParam?.value));
break;
case "number":
parameters.push(new NumberParameter(p, inputParam?.value));
break;
default:
parameters.push(new InputParameter(p, inputParam?.value));
break;
}
});
return {
command: cmd,
parameters: parameters,
};
}
} else {
return null;
}
return null;
}
/**
* @param {string} phrase?
* @returns PhraseOccurrenceData
*/
private findPhraseOccurrence(phrase?: string): PhraseOccurrenceData | null {
let returnValue: PhraseOccurrenceData | null = null;
this.list.map((c) => {
@@ -159,7 +199,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",
+24 -49
View File
@@ -4,74 +4,53 @@ import { CommandManager } from "./CommandManager.js";
import { HelpMessageParams } from "./types.js";
export class HelpMessage extends Command {
constructor(
cmdManager: CommandManager,
params: HelpMessageParams,
botName?: string
) {
constructor(cmdManager: CommandManager, params: HelpMessageParams, botName?: string) {
super({
name: "help",
usage: params.usage,
permissions: 0,
permissions: undefined,
description: params.description,
visible: params.visible,
parameters: [
{
name: "Command name",
name: "command_name",
description: "Name of the command that you want to get details about",
optional: true,
type: "TEXT",
type: "string",
},
],
function: (_, a) => {
function: (param, _) => {
const helpMsg = new MessageEmbed();
helpMsg.setColor(params.color);
helpMsg.setTimestamp();
helpMsg.setFooter(botName || "");
if (helpMsg != null) {
if (a && a[0]) {
const cmd: Command | null = cmdManager.get(
a[0].toString(),
"ALL"
);
if (param("command_name")) {
const cmd: Command | null = cmdManager.get(param("command_name")?.toString() || "");
if (cmd) {
helpMsg.setTitle(
`${cmd.name} ${cmd.visible ? "" : "[HIDDEN]"}`
);
helpMsg.setTitle(`${cmd.name} ${cmd.visible ? "" : "[HIDDEN]"}`);
helpMsg.setDescription(cmd.description);
if (cmd.usage)
helpMsg.addField(
"Usage:",
`${cmdManager.prefix}${cmd.name} ${cmd.usage}`,
false
);
if (cmd.permissions.toArray(false).length > 0) {
let permList: string = "";
cmd.permissions.toArray(false).map((p) => {
permList += p + "\n";
});
helpMsg.addField(
"Permissions:",
permList,
false
);
if (cmd.usage) helpMsg.addField("Usage:", `${cmdManager.prefix || "/"}${cmd.name} ${cmd.usage}`, false);
if (cmd.permissions) {
if (cmd.permissions instanceof Function) {
helpMsg.addField("Permissions:", "Custom", false);
} else if (cmd.permissions.toArray(false).length > 0) {
let permList: string = "";
cmd.permissions.toArray(false).map((p) => {
permList += p + "\n";
});
helpMsg.addField("Permissions:", permList, 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) {
let kwrdList: string = "";
cmd.keywords.map((k) => {
kwrdList += k + "\n";
});
helpMsg.addField("Keywords:", kwrdList, false);
}
} else {
throw new ReferenceError(
`Command "${a[0]}" does not exist`
);
throw new ReferenceError(`Command "${param("command_name")}" does not exist`);
}
} else {
helpMsg.setTitle(params.title);
@@ -79,11 +58,7 @@ export class HelpMessage extends Command {
cmdManager.list.map((c) => {
if (c.visible) {
helpMsg.addField(
`${cmdManager.prefix}${c.name} ${c.usage}`,
c.description,
false
);
helpMsg.addField(`${cmdManager.prefix || "/"}${c.name} ${c.usage}`, c.description, false);
}
});
}
+109 -27
View File
@@ -1,33 +1,115 @@
import { ParameterTypeError } from "./errors.js";
import { CategoryChannel, Guild, GuildMember, NewsChannel, Role, StageChannel, StoreChannel, TextChannel, VoiceChannel } from "discord.js";
import { ParameterType, ParameterResolvable } from "./types.js";
export interface Parameter {
export interface ParameterSchema {
name: string;
description?: string;
optional: boolean;
type: ParameterType;
choices?: string[];
}
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:
throw new TypeError("Invalied type specified");
export class Parameter {
name: string;
description: string;
optional: boolean;
type: ParameterType;
choices?: string[];
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;
}
};
}
export class DefaultParameter extends Parameter {
constructor() {
super({
name: "input",
description: "No description",
type: "string",
optional: true,
});
}
}
export class InputParameter extends Parameter {
value: ParameterResolvable;
constructor(parameter: Parameter, value: ParameterResolvable) {
super(parameter);
this.value = value;
}
}
export class StringParameter extends InputParameter {
constructor(parameter: Parameter, value: ParameterResolvable) {
if (parameter.type != "string") {
throw new Error(`Parameter type mismatch`);
}
if (parameter.choices) {
const match = parameter.choices.filter((c) => c.toLowerCase() === value?.toString().toLowerCase());
if (match.length === 0) {
throw new Error(`Parameter "${parameter.name}" has incorrect value. Please enter one of the following values: ${parameter.choices.join(", ")}`);
} else {
super(parameter, match[0]);
return;
}
}
super(parameter, value?.toString());
}
}
export class BooleanParameter extends InputParameter {
constructor(parameter: Parameter, value: ParameterResolvable) {
if (parameter.type != "boolean") {
throw new Error(`Parameter type mismatch`);
}
if (value?.toString().toLowerCase() === "true") {
super(parameter, true);
} else if (value?.toString().toLowerCase() === "false") {
super(parameter, false);
} else {
throw new Error(`Cannot convert "${parameter.name}" parameter to boolean. Please enter either "true" or "false".`);
}
}
}
export class NumberParameter extends InputParameter {
constructor(parameter: Parameter, value: ParameterResolvable) {
if (parameter.type != "number") {
throw new Error(`Parameter type mismatch`);
}
super(parameter, parseFloat(value ? value.toString() : ""));
}
}
export class ObjectParameter extends InputParameter {
constructor(parameter: Parameter, value: ParameterResolvable) {
if (parameter.type != "channel" && parameter.type != "mentionable" && parameter.type != "user" && parameter.type != "role") {
throw new Error(`Parameter type mismatch`);
}
super(parameter, new ObjectID(value?.toString() || ""));
}
}
export class ObjectID {
id: string;
constructor(id: string) {
this.id = id.replace(">", "").replace("<@!", "").replace("<#!", "");
}
async toObject(
guild: Guild,
type: "channel" | "user" | "role"
): Promise<Role | TextChannel | VoiceChannel | CategoryChannel | GuildMember | NewsChannel | StoreChannel | StageChannel | null> {
switch (type) {
case "channel":
return (await guild.channels.fetch(this.id.toString() || "")) || null;
case "role":
return (await guild.roles.fetch(this.id.toString() || "")) || null;
case "user":
return (await guild.members.fetch(this.id.toString() || "")) || null;
}
}
}
+37 -63
View File
@@ -1,17 +1,8 @@
import {
ColorResolvable,
DMChannel,
GuildMember,
Message,
MessageEmbed,
NewsChannel,
TextChannel,
User,
} from "discord.js";
import { ColorResolvable, CommandInteraction, GuildMember, Message, MessageEmbed, User } from "discord.js";
import { Command } from "./Command.js";
import { PermissionsError } from "./errors.js";
export type MessageType = "PERMISSION" | "ERROR" | "NOT_FOUND";
export type MessageType = "PERMISSION" | "ERROR" | "NOT_FOUND" | "SUCCESS";
export interface SystemMessageAppearance {
enabled: boolean;
title: string;
@@ -31,6 +22,7 @@ export class SystemMessageManager {
PERMISSION: SystemMessageAppearance;
ERROR: SystemMessageAppearance;
NOT_FOUND: SystemMessageAppearance;
SUCCESS: SystemMessageAppearance;
deleteTimeout: number;
constructor(botName?: string) {
@@ -57,6 +49,13 @@ export class SystemMessageManager {
showTimestamp: true,
footer: botName,
};
this.SUCCESS = {
enabled: true,
title: "✅ Task completed successfully",
accentColor: "#00ff00",
showTimestamp: true,
footer: botName,
};
this.deleteTimeout = Infinity;
}
@@ -64,56 +63,35 @@ export class SystemMessageManager {
* Generates and sends a system message
* @param {MessageType} type - 'ERROR' | 'PERMISSION' | 'NOT_FOUND'
* @param {SystemMessageData} [data] - additional data to include in the message
* @param {TextChannel | DMChannel | NewsChannel} [channel] - if specified, the generated message will be sent in this channel
* @param {Message | CommandInteraction} [interaction] - if specified, the generated message will be sent in this channel
* @returns *Promise<MessageEmbed | Message | void>*
*/
async send(
type: MessageType,
data?: SystemMessageData,
channel?: TextChannel | DMChannel | NewsChannel
): Promise<MessageEmbed | Message | void> {
async send(type: MessageType, data?: SystemMessageData, interaction?: Message | CommandInteraction): Promise<MessageEmbed | Message | void> {
if (this[type]) {
if (this[type].enabled === false) {
return;
}
const embed = new MessageEmbed();
embed.setTitle(this[type].title);
if (this[type].bottomText)
embed.setDescription(this[type].bottomText);
if (this[type].bottomText) embed.setDescription(this[type].bottomText || "");
embed.setColor(this[type].accentColor || "#000");
if (this[type].showTimestamp) embed.setTimestamp();
if (this[type].footer) embed.setFooter(this[type].footer);
if (this[type].footer) embed.setFooter(this[type].footer || "");
if (data) {
switch (type) {
case "ERROR":
if (data.command) {
embed.addField(
"Command name:",
data.command.name,
false
);
embed.addField("Command name:", data.command.name, false);
}
if (data.error) {
if (data.error instanceof Error) {
embed.addField(
"Error details:",
data.error.toString(),
false
);
embed.addField("Error details:", data.error.toString(), false);
} else if (typeof data.error == "string") {
embed.addField(
"Error details:",
data.error,
false
);
embed.addField("Error details:", data.error, false);
}
}
if (data.user) {
embed.addField(
"User:",
data.user.toString(),
false
);
embed.addField("User:", data.user.toString(), false);
}
break;
case "NOT_FOUND":
@@ -123,41 +101,37 @@ export class SystemMessageManager {
break;
case "PERMISSION":
if (data.user) {
embed.addField(
"User:",
data.user.toString(),
false
);
embed.addField("User:", data.user.toString(), false);
}
if (data.command) {
embed.addField(
"Command name:",
data.command.name,
false
);
embed.addField("Command name:", data.command.name, false);
if (data.command.permissions) {
let permList: string = "";
data.command.permissions
.toArray(false)
.map((p) => {
permList += p + "\n";
});
embed.addField(
"Required permissions:",
permList,
false
);
data.command.permissions instanceof Function
? (() => {
permList = "Custom";
})()
: data.command.permissions.toArray(false).map((p) => {
permList += p + "\n";
});
embed.addField("Required permissions:", permList, false);
}
}
break;
case "SUCCESS":
break;
}
}
if (channel) {
const message = await channel.send(embed);
if (interaction && !(interaction instanceof CommandInteraction)) {
const message = await interaction.reply({ embeds: [embed] });
if (this.deleteTimeout != Infinity && message.deletable) {
await message.delete({ timeout: this.deleteTimeout });
setTimeout(async () => {
await message.delete();
}, this.deleteTimeout || 0);
}
return message;
} else if (interaction && interaction instanceof CommandInteraction) {
interaction.replied || interaction.deferred ? await interaction.editReply({ embeds: [embed] }) : await interaction.reply({ embeds: [embed] });
} else {
return embed;
}
+7
View File
@@ -39,3 +39,10 @@ export class MissingParameterError extends ReferenceError {
return `Your request is missing a "${this.argument.name}" parameter which is not optional`;
}
}
export class OperationSuccess {
command: Command;
constructor(c: Command) {
this.command = c;
}
}
+18 -14
View File
@@ -1,31 +1,34 @@
import { ClientOptions, ColorResolvable } from "discord.js";
import { Parameter } from "./Parameter.js";
import { ClientOptions, ColorResolvable, CommandInteraction, ReplyMessageOptions } from "discord.js";
import { InputParameter, ObjectID, ParameterSchema } from "./Parameter.js";
import { PermissionResolvable, Message, MessageEmbed } from "discord.js";
import type { Command } from "./Command.js";
export interface InitOptions {
name: string;
prefix: string;
argumentSeparator?: string;
prefix?: string;
parameterSeparator?: string;
clientOptions?: ClientOptions;
token?: string;
token: string;
applicationId: string;
}
export type GetMode = "ALL" | "PREFIX" | "NO_PREFIX";
export type PermissionCheckTypes = "ALL" | "ANY";
export interface CommandBuilder {
name: string;
parameters?: Parameter[];
parameters?: ParameterSchema[] | "simple" | "no_input";
aliases?: string[] | string;
keywords?: string[] | string;
description?: string;
usage?: string;
permissionCheck?: PermissionCheckTypes;
permissions?: PermissionResolvable;
permissions?: PermissionResolvable | ((m?: Message | CommandInteraction) => boolean);
guilds?: string[];
visible?: boolean;
slash?: boolean;
announceSuccess?: boolean;
function: (
message?: Message,
cmdParams?: ParameterResolvable[]
) => void | string | MessageEmbed | Promise<void | string | MessageEmbed>;
params: (query: string, returnType?: "value" | "object") => ParameterResolvable | InputParameter | null,
interaction?: Message | CommandInteraction
) => void | string | MessageEmbed | ReplyMessageOptions | Promise<void | string | MessageEmbed | ReplyMessageOptions>;
}
export interface PhraseOccurrenceData {
command: Command;
@@ -33,7 +36,7 @@ export interface PhraseOccurrenceData {
}
export interface CommandMessageStructure {
command: Command;
parameters: string[];
parameters: InputParameter[];
}
export interface HelpMessageParams {
enabled: boolean;
@@ -42,6 +45,7 @@ export interface HelpMessageParams {
color: ColorResolvable;
description: string;
usage: string;
visible: boolean;
}
export type ParameterType = "TEXT" | "TRUE/FALSE" | "NUMBER";
export type ParameterResolvable = string | boolean | number;
export type ParameterType = "string" | "boolean" | "number" | "user" | "role" | "channel" | "mentionable";
export type ParameterResolvable = string | boolean | number | ObjectID | undefined;
+1 -1
View File
@@ -9,5 +9,5 @@
"pretty": true,
"lib": ["esnext"]
},
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules", "dist", "test"]
}
+107 -42
View File
@@ -2,10 +2,21 @@
# yarn lockfile v1
"@discordjs/collection@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c"
integrity sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==
"@discordjs/builders@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.4.0.tgz#bb41573ce4824aa9194a53b52c29c5219b610010"
integrity sha512-EiwLltKph6TSaPJIzJYdzNc1PnA2ZNaaE0t0ODg3ghnpVHqfgd0YX9/srsleYHW2cw1sfIq+kbM+h0etf7GWLA==
dependencies:
"@sindresorhus/is" "^4.0.1"
discord-api-types "^0.22.0"
ow "^0.27.0"
ts-mixer "^6.0.0"
tslib "^2.3.0"
"@discordjs/collection@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.2.1.tgz#ea4bc7b41b7b7b6daa82e439141222ec95c469b2"
integrity sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==
"@discordjs/form-data@^3.0.1":
version "3.0.1"
@@ -16,6 +27,16 @@
combined-stream "^1.0.8"
mime-types "^2.1.12"
"@sapphire/async-queue@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.4.tgz#ae431310917a8880961cebe8e59df6ffa40f2957"
integrity sha512-fFrlF/uWpGOX5djw5Mu2Hnnrunao75WGey0sP0J3jnhmrJ5TAPzHYOmytD5iN/+pMxS+f+u/gezqHa9tPhRHEA==
"@sindresorhus/is@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5"
integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==
"@types/node@*":
version "16.4.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.10.tgz#e57e2a54fc6da58da94b3571b1cb456d39f88597"
@@ -26,25 +47,30 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.5.tgz#7b5b3532053fd14c771ad6598a4ee2c7a85aceca"
integrity sha512-6ewfMNmkumZieB/EeJ4cdP1bbJyOlOt5MTwbKMr7WKxyCt2j09H8YWRK6zOd/Jh35Vu/gls39ZUmeu4vHu1WKQ==
"@types/ws@^7.4.5":
"@types/ws@^7.4.5", "@types/ws@^7.4.7":
version "7.4.7"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702"
integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==
dependencies:
"@types/node" "*"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
callsites@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@@ -57,24 +83,46 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
discord.js@^12.5.3:
version "12.5.3"
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.5.3.tgz#56820d473c24320871df9ea0bbc6b462f21cf85c"
integrity sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==
dependencies:
"@discordjs/collection" "^0.1.6"
"@discordjs/form-data" "^3.0.1"
abort-controller "^3.0.0"
node-fetch "^2.6.1"
prism-media "^1.2.9"
setimmediate "^1.0.5"
tweetnacl "^1.0.3"
ws "^7.4.4"
discord-api-types@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.22.0.tgz#34dc57fe8e016e5eaac5e393646cd42a7e1ccc2a"
integrity sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
discord.js@^13.0.1:
version "13.0.1"
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.0.1.tgz#58f009706d1d7587fe9ff6c6c781e4540ae085f9"
integrity sha512-pEODCFfxypBnGEYpSgjkn1jt70raCS1um7Zp0AXEfW1DcR29wISzQ/WeWdnjP5KTXGi0LTtkRiUjOsMgSoukxA==
dependencies:
"@discordjs/builders" "^0.4.0"
"@discordjs/collection" "^0.2.1"
"@discordjs/form-data" "^3.0.1"
"@sapphire/async-queue" "^1.1.4"
"@types/ws" "^7.4.7"
discord-api-types "^0.22.0"
node-fetch "^2.6.1"
ws "^7.5.1"
dot-prop@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083"
integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==
dependencies:
is-obj "^2.0.0"
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
is-obj@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
mime-db@1.49.0:
version "1.49.0"
@@ -93,22 +141,39 @@ node-fetch@^2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
prism-media@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.3.1.tgz#418acd2b122bedea2e834056d678f9a5ad2943ae"
integrity sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==
ow@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/ow/-/ow-0.27.0.tgz#d44da088e8184fa11de64b5813206f9f86ab68d0"
integrity sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==
dependencies:
"@sindresorhus/is" "^4.0.1"
callsites "^3.1.0"
dot-prop "^6.0.1"
lodash.isequal "^4.5.0"
type-fest "^1.2.1"
vali-date "^1.0.0"
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
ts-mixer@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.0.tgz#4e631d3a36e3fa9521b973b132e8353bc7267f9f"
integrity sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==
tweetnacl@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
tslib@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
ws@^7.4.4:
type-fest@^1.2.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
vali-date@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6"
integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=
ws@^7.5.1:
version "7.5.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==