@somehiddenkey/discord-command-utils 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -0
- package/package.json +21 -0
- package/src/Configs/ConfigError.js +8 -0
- package/src/Configs/GlobalConfig.js +265 -0
- package/src/Configs/LocalConfig.js +52 -0
- package/src/Configs/SecretConfig.js +51 -0
- package/src/Interactions/CommandError.js +34 -0
- package/src/Interactions/Interaction.js +96 -0
- package/src/Interactions/InteractionContainer.js +151 -0
- package/src/Interactions/RestCommands.js +45 -0
- package/src/Utils/checks.js +44 -0
- package/src/Utils/enums.js +32 -0
- package/src/index.js +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# DiscordUtilsLibrary
|
|
2
|
+
The purpose of this Library is to hide and abstract many of the common aspects that comes with coding discord bots in javascript. The main focus is configurations as well as registering all types of commands and interactions between different guilds.
|
|
3
|
+
## Installation
|
|
4
|
+
### SSH Installation
|
|
5
|
+
Use this if your GitHub account already has SSH access -> requires SSH key set up
|
|
6
|
+
```bash
|
|
7
|
+
npm install git+ssh://git@github.com/studytogether-discord/DiscordUtilsLibrary.git
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### HTTPS Installation
|
|
11
|
+
If you want to use HTTPS instead, you must use a GitHub Personal Access Token (classic) with repo permissions (replace "TOKEN" with your PAT)
|
|
12
|
+
```bash
|
|
13
|
+
npm install "https://TOKEN@github.com/studytogether-discord/DiscordUtilsLibrary.git"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Libary Import
|
|
17
|
+
Then import it in your code
|
|
18
|
+
```c#
|
|
19
|
+
import { Something } from "DiscordUtilsLibrary";
|
|
20
|
+
// or
|
|
21
|
+
const { Something } = require("DiscordUtilsLibrary");
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configurations
|
|
25
|
+
We will use 3 different kinds of configurations:
|
|
26
|
+
- local : settings specific to this specific bot like it's prefix etc
|
|
27
|
+
- global : information used by all bot instances
|
|
28
|
+
- secret : environment variables that contain all the secrets of your bot instance
|
|
29
|
+
### Local configuration
|
|
30
|
+
Loads the local configuration of that specific bot. It only contains public configs, no secrets, and may thus be included in the git tracing. Load a new instance of your local config through the following code:
|
|
31
|
+
```js
|
|
32
|
+
export const localConfig = LocalConfig.load("./configs/local_config.json", Environments.DEV);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Your local configuration JSON file should look like this:
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"production": {
|
|
39
|
+
"prefix": "!",
|
|
40
|
+
"client_id": "123456789123456789",
|
|
41
|
+
"local": {
|
|
42
|
+
"any_local_configuration": "any values"
|
|
43
|
+
},
|
|
44
|
+
"rest_options" : {
|
|
45
|
+
"version": "10",
|
|
46
|
+
"rejectOnRateLimit": ["/channels"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"development": {
|
|
50
|
+
"prefix": "!",
|
|
51
|
+
"client_id": "123456789123456789",
|
|
52
|
+
"local": {
|
|
53
|
+
"any_local_configuration": "any values"
|
|
54
|
+
},
|
|
55
|
+
"rest_options" : {
|
|
56
|
+
"version": "10",
|
|
57
|
+
"rejectOnRateLimit": ["/channels"]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"activity": {
|
|
61
|
+
"name": "the communities",
|
|
62
|
+
"type": 0
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
- `client_id` : ID of your bot user
|
|
67
|
+
- `rest_options` : Must be of type [Partial<RESTOptions>](https://discord.js.org/docs/packages/rest/2.6.0/RESTOptions:Interface)
|
|
68
|
+
- `activity` : Must be of type [ActivitiesOptions](https://discord.js.org/docs/packages/discord.js/14.25.1/ActivitiesOptions:Interface)
|
|
69
|
+
|
|
70
|
+
### Global configuration
|
|
71
|
+
Loads the global configuration that contains information related to all bot instances, like snowflake IDs of channels, categories, guild, etc. Load a new instance of your global config through the following code:
|
|
72
|
+
```js
|
|
73
|
+
export const globalConfig = GlobalConfig.load("../global_config.json", Environments.DEV);
|
|
74
|
+
```
|
|
75
|
+
It contains the IDs of the following sections: guilds, categories, channels, log channels and roles. These can be used as constant values throughout your code, making sure that if an ID ever changes, that all bot instances would take the new updated value by simply updating the json once.
|
|
76
|
+
|
|
77
|
+
### Secrets configuration
|
|
78
|
+
Loads the environment variables that contain all secrets for that bot, like your bot token and database credentials. It goes without saying that these actual values may never be git traced. Load new instance of your secret config through the following code:
|
|
79
|
+
```js
|
|
80
|
+
import { config } from 'dotenv';
|
|
81
|
+
|
|
82
|
+
config();
|
|
83
|
+
export const secretConfig = new SecretConfig(process.env);
|
|
84
|
+
```
|
|
85
|
+
Your .env file should look like this:
|
|
86
|
+
```bash
|
|
87
|
+
NODE_ENV="production"
|
|
88
|
+
|
|
89
|
+
TOKEN="someDiscordBotToken"
|
|
90
|
+
|
|
91
|
+
DATABASE__0__HOST="localhost"
|
|
92
|
+
DATABASE__0__USER="userName"
|
|
93
|
+
DATABASE__0__NAME="databaseName"
|
|
94
|
+
DATABASE__0__PASSWORD="passwordValue"
|
|
95
|
+
```
|
|
96
|
+
Please take the following into account:
|
|
97
|
+
- `NODE_ENV` must be of one of the following values:
|
|
98
|
+
- `"development"` : Uses your development configurations for local testing
|
|
99
|
+
- `"production"` : Uses your production configurations for PRD env. Do not use for local tests, it won't work
|
|
100
|
+
- If you use multiple databases, prefix each one with another index, starting at `0`
|
|
101
|
+
|
|
102
|
+
You can retrieve the various database configurations with the following code:
|
|
103
|
+
```js
|
|
104
|
+
export const db_connection_0 = knex(secretConfig.DatabaseConfig[0]);
|
|
105
|
+
```
|
|
106
|
+
You can initiate the bot with the following code:
|
|
107
|
+
```js
|
|
108
|
+
bot.login(secretConfig.Token);
|
|
109
|
+
```
|
|
110
|
+
## Interactions
|
|
111
|
+
### Register Interaction
|
|
112
|
+
You can register new interaction callbacks based on a set of factors:
|
|
113
|
+
- the scope : type `InteractionScope` -- in which server is this interaction called (main, tutor, communities, dm, etc)
|
|
114
|
+
- the type : type `InteractionType` -- is it a button, a modal, a slash command, a message, etc
|
|
115
|
+
- the unique id: custom unique ID per interaction of this type
|
|
116
|
+
|
|
117
|
+
An example for a slash command called in the main server:
|
|
118
|
+
```js
|
|
119
|
+
Interaction.OnSlashCommand(
|
|
120
|
+
"ping",
|
|
121
|
+
InteractionScope.Main,
|
|
122
|
+
async (interaction) => await interaction.reply("pong")
|
|
123
|
+
);
|
|
124
|
+
```
|
|
125
|
+
The same exists for:
|
|
126
|
+
- Button : `OnButton`
|
|
127
|
+
- Selection menu : `OnSelectMenu`
|
|
128
|
+
- Message : `OnMessageCommand`
|
|
129
|
+
- Modal submission : `OnModal`
|
|
130
|
+
### Call Interaction
|
|
131
|
+
All interactions get stored in a so-called `InteractionContainer` that still needs to be called:
|
|
132
|
+
```js
|
|
133
|
+
const interactionContainer = new InteractionContainer(localConfig, globalConfig);
|
|
134
|
+
|
|
135
|
+
bot.on(Events.MessageCreate, async (message) => {
|
|
136
|
+
await interactionContainer.call_message_interaction(message, e => consola.error(e))
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
bot.on(Events.InteractionCreate, async (interaction) =>
|
|
140
|
+
await interactionContainer.call_interaction(message, e => consola.error(e))
|
|
141
|
+
);
|
|
142
|
+
```
|
|
143
|
+
### Register slash commands to REST
|
|
144
|
+
For slash commands to work, you need to register them to a bot. There are two kinds:
|
|
145
|
+
- Guild commands : a slash command that will only be registered in one specific guild
|
|
146
|
+
- Public commands : a slash command available in all guilds that have this bot
|
|
147
|
+
|
|
148
|
+
You can add a set of slash commands through the following abstraction:
|
|
149
|
+
```js
|
|
150
|
+
const rest = interactionContainer.Rest
|
|
151
|
+
.add([firstSlashCommand, secondSlashCommand], localConfig.guilds.tutor)
|
|
152
|
+
.add([firstSlashCommand, secondSlashCommand], localConfig.guilds.community)
|
|
153
|
+
.add([somePublicSlashCommand])
|
|
154
|
+
.register(secretConfig.token);
|
|
155
|
+
```
|
|
156
|
+
## Utils
|
|
157
|
+
Contains various check functions to verify the access level of a user as well as useful enums for scoping, community modlevels, etc
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@somehiddenkey/discord-command-utils",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A utility library for building Discord bot commands using discord.js",
|
|
5
|
+
"author": {
|
|
6
|
+
"email": "k3y.throwaway@gmail.com",
|
|
7
|
+
"name": "Key"
|
|
8
|
+
},
|
|
9
|
+
"license": "ISC",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "index.js",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"private": false,
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"consola": "^3.4.2",
|
|
19
|
+
"discord.js": "^14.25.1"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {string} Snowflake
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import ConfigError from "./ConfigError";
|
|
7
|
+
import consola from 'consola';
|
|
8
|
+
import { InteractionScope } from "../Utils/enums";
|
|
9
|
+
|
|
10
|
+
export default class GlobalConfig {
|
|
11
|
+
/** @type {Guilds} */
|
|
12
|
+
guilds;
|
|
13
|
+
|
|
14
|
+
/** @type {Categories} */
|
|
15
|
+
categories;
|
|
16
|
+
|
|
17
|
+
/** @type {Channels} */
|
|
18
|
+
channels;
|
|
19
|
+
|
|
20
|
+
/** @type {Logs} */
|
|
21
|
+
logs;
|
|
22
|
+
|
|
23
|
+
/** @type {Roles} */
|
|
24
|
+
roles;
|
|
25
|
+
|
|
26
|
+
constructor(data) {
|
|
27
|
+
if (!data)
|
|
28
|
+
throw new ConfigError('No data initialized for GlobalConfig');
|
|
29
|
+
|
|
30
|
+
this.guilds = new Guilds(data.guilds);
|
|
31
|
+
this.categories = new Categories(data.categories);
|
|
32
|
+
this.channels = new Channels(data.channels);
|
|
33
|
+
this.logs = new Logs(data.logs);
|
|
34
|
+
this.roles = new Roles(data.roles);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Loads the JSON file and returns a GlobalConfig instance.
|
|
39
|
+
* @param {string} path
|
|
40
|
+
* @param {string} environment
|
|
41
|
+
* @returns {GlobalConfig}
|
|
42
|
+
*/
|
|
43
|
+
static async load(path, environment) {
|
|
44
|
+
var json
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(path, { encoding: 'utf8', flag: 'r' });
|
|
47
|
+
json = JSON.parse(raw);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
consola.error(`Failed to load GlobalConfig from path: ${path}`, error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
if (environment == "production")
|
|
53
|
+
return new GlobalConfig(json.production);
|
|
54
|
+
else
|
|
55
|
+
return new GlobalConfig(json.development);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
/* -----------------------
|
|
61
|
+
* Nested Classes
|
|
62
|
+
* ----------------------- */
|
|
63
|
+
|
|
64
|
+
class Guilds {
|
|
65
|
+
/** @type {Snowflake} */
|
|
66
|
+
main;
|
|
67
|
+
/** @type {Snowflake} */
|
|
68
|
+
community;
|
|
69
|
+
/** @type {Snowflake} */
|
|
70
|
+
tutor;
|
|
71
|
+
/** @type {Snowflake} */
|
|
72
|
+
staff;
|
|
73
|
+
|
|
74
|
+
constructor(data) {
|
|
75
|
+
Object.assign(this, data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {Snowflake} guild_id
|
|
80
|
+
* @returns {InteractionScope} scope
|
|
81
|
+
*/
|
|
82
|
+
get_scope(guild_id) {
|
|
83
|
+
switch(guild_id) {
|
|
84
|
+
case this.main:
|
|
85
|
+
return InteractionScope.Main;
|
|
86
|
+
case this.community:
|
|
87
|
+
return InteractionScope.Community;
|
|
88
|
+
case this.tutor:
|
|
89
|
+
return InteractionScope.Tutor;
|
|
90
|
+
case this.staff:
|
|
91
|
+
return InteractionScope.Staff;
|
|
92
|
+
default:
|
|
93
|
+
return InteractionScope.OtherGuild;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {InteractionScope} scope
|
|
99
|
+
* @returns {Snowflake} guild_id
|
|
100
|
+
*/
|
|
101
|
+
get_guild_id(scope) {
|
|
102
|
+
switch(scope) {
|
|
103
|
+
case InteractionScope.Main:
|
|
104
|
+
return this.main;
|
|
105
|
+
case InteractionScope.Community:
|
|
106
|
+
return this.community;
|
|
107
|
+
case InteractionScope.Tutor:
|
|
108
|
+
return this.tutor;
|
|
109
|
+
case InteractionScope.Staff:
|
|
110
|
+
return this.staff;
|
|
111
|
+
default:
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class Categories {
|
|
118
|
+
/** @type {Snowflake} */
|
|
119
|
+
chat;
|
|
120
|
+
/** @type {[Snowflake]} */
|
|
121
|
+
custom_rooms;
|
|
122
|
+
/** @type {[Snowflake]} */
|
|
123
|
+
verified_com;
|
|
124
|
+
/** @type {Snowflake} */
|
|
125
|
+
chill;
|
|
126
|
+
/** @type {Snowflake} */
|
|
127
|
+
events;
|
|
128
|
+
/** @type {Snowflake} */
|
|
129
|
+
music;
|
|
130
|
+
/** @type {Snowflake} */
|
|
131
|
+
timer_25;
|
|
132
|
+
/** @type {Snowflake} */
|
|
133
|
+
timer_50;
|
|
134
|
+
/** @type {Snowflake} */
|
|
135
|
+
screen_cam;
|
|
136
|
+
/** @type {Snowflake} */
|
|
137
|
+
staff;
|
|
138
|
+
/** @type {Snowflake} */
|
|
139
|
+
subjects;
|
|
140
|
+
/** @type {Snowflake} */
|
|
141
|
+
subjects_col;
|
|
142
|
+
/** @type {Snowflake} */
|
|
143
|
+
subjects_uni;
|
|
144
|
+
/** @type {Snowflake} */
|
|
145
|
+
to_do;
|
|
146
|
+
|
|
147
|
+
/** @type {[Snowflake]} */
|
|
148
|
+
custom_study_rooms;
|
|
149
|
+
/** @type {[Snowflake]} */
|
|
150
|
+
study_rooms;
|
|
151
|
+
|
|
152
|
+
constructor(data) {
|
|
153
|
+
Object.assign(this, data);
|
|
154
|
+
this.custom_study_rooms = data.custom_rooms.concat(data.verified_com);
|
|
155
|
+
this.study_rooms = this.custom_study_rooms.concat([data.music, data.timer_25, data.timer_50, data.screen_cam]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class Channels {
|
|
160
|
+
/** @type {Snowflake} */
|
|
161
|
+
timer_control;
|
|
162
|
+
/** @type {Snowflake} */
|
|
163
|
+
accountability;
|
|
164
|
+
/** @type {Snowflake} */
|
|
165
|
+
break_afk;
|
|
166
|
+
/** @type {Snowflake} */
|
|
167
|
+
create_room;
|
|
168
|
+
/** @type {Snowflake} */
|
|
169
|
+
commands;
|
|
170
|
+
/** @type {Snowflake} */
|
|
171
|
+
general;
|
|
172
|
+
/** @type {Snowflake} */
|
|
173
|
+
international;
|
|
174
|
+
/** @type {Snowflake} */
|
|
175
|
+
introductions;
|
|
176
|
+
/** @type {Snowflake} */
|
|
177
|
+
mod_commands;
|
|
178
|
+
/** @type {Snowflake} */
|
|
179
|
+
support_commands;
|
|
180
|
+
/** @type {Snowflake} */
|
|
181
|
+
session_goals;
|
|
182
|
+
/** @type {Snowflake} */
|
|
183
|
+
silent_study;
|
|
184
|
+
/** @type {Snowflake} */
|
|
185
|
+
welcome;
|
|
186
|
+
|
|
187
|
+
constructor(data) {
|
|
188
|
+
Object.assign(this, data);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
class Logs {
|
|
193
|
+
/** @type {Snowflake} */
|
|
194
|
+
error;
|
|
195
|
+
/** @type {Snowflake} */
|
|
196
|
+
votekick;
|
|
197
|
+
/** @type {Snowflake} */
|
|
198
|
+
room_commands;
|
|
199
|
+
/** @type {Snowflake} */
|
|
200
|
+
room_admin;
|
|
201
|
+
/** @type {Snowflake} */
|
|
202
|
+
invites;
|
|
203
|
+
/** @type {Snowflake} */
|
|
204
|
+
command;
|
|
205
|
+
/** @type {Snowflake} */
|
|
206
|
+
communities;
|
|
207
|
+
|
|
208
|
+
constructor(data) {
|
|
209
|
+
Object.assign(this, data);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
class Roles {
|
|
214
|
+
/** @type {Snowflake} */
|
|
215
|
+
cuckoo_ping;
|
|
216
|
+
/** @type {Snowflake} */
|
|
217
|
+
developer
|
|
218
|
+
/** @type {Snowflake} */
|
|
219
|
+
forest_ping;
|
|
220
|
+
/** @type {Snowflake} */
|
|
221
|
+
helper;
|
|
222
|
+
/** @type {Snowflake} */
|
|
223
|
+
member;
|
|
224
|
+
/** @type {Snowflake} */
|
|
225
|
+
music;
|
|
226
|
+
/** @type {Snowflake} */
|
|
227
|
+
muted;
|
|
228
|
+
/** @type {Snowflake} */
|
|
229
|
+
no_pc;
|
|
230
|
+
/** @type {Snowflake} */
|
|
231
|
+
screen_cam;
|
|
232
|
+
/** @type {Snowflake} */
|
|
233
|
+
studying;
|
|
234
|
+
/** @type {Snowflake} */
|
|
235
|
+
timer_25;
|
|
236
|
+
/** @type {Snowflake} */
|
|
237
|
+
timer_50;
|
|
238
|
+
/** @type {Snowflake} */
|
|
239
|
+
tutor;
|
|
240
|
+
/** @type {Snowflake} */
|
|
241
|
+
verified_com;
|
|
242
|
+
/** @type {Snowflake} */
|
|
243
|
+
water_ping;
|
|
244
|
+
/** @type {Snowflake} */
|
|
245
|
+
deepfocus;
|
|
246
|
+
/** @type {Snowflake} */
|
|
247
|
+
sr_staff;
|
|
248
|
+
/** @type {Snowflake} */
|
|
249
|
+
staff;
|
|
250
|
+
/** @type {Snowflake} */
|
|
251
|
+
jr_staff;
|
|
252
|
+
/** @type {Snowflake} */
|
|
253
|
+
st_team;
|
|
254
|
+
/** @type {Snowflake} */
|
|
255
|
+
coordinator;
|
|
256
|
+
|
|
257
|
+
/** @type {[Snowflake]} */
|
|
258
|
+
all_staff;
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
constructor(data) {
|
|
262
|
+
Object.assign(this, data);
|
|
263
|
+
this.all_staff = [this.jr_staff, this.sr_staff, this.st_team, this.coordinator];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {string} Snowflake
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RESTOptions } from '@discordjs/rest';
|
|
6
|
+
import { ActivitiesOptions } from 'discord.js';
|
|
7
|
+
|
|
8
|
+
export default class LocalConfig {
|
|
9
|
+
/** @type {string?} */
|
|
10
|
+
prefix;
|
|
11
|
+
/** @type {Snowflake} */
|
|
12
|
+
clientId;
|
|
13
|
+
|
|
14
|
+
/** @type {object} */
|
|
15
|
+
local;
|
|
16
|
+
/** @type {Partial<RESTOptions>} */
|
|
17
|
+
restOptions;
|
|
18
|
+
/** @type {ActivitiesOptions} */
|
|
19
|
+
activity;
|
|
20
|
+
|
|
21
|
+
constructor(data) {
|
|
22
|
+
this.prefix = data.prefix;
|
|
23
|
+
this.clientId = data.client_id;
|
|
24
|
+
this.local = data.local || {};
|
|
25
|
+
this.restOptions = data.rest_options || {};
|
|
26
|
+
this.activity = data.activity || {};
|
|
27
|
+
|
|
28
|
+
if(!this.client_id)
|
|
29
|
+
throw new ConfigError('No client_id initialized for LocalConfig');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads the JSON file and returns a GlobalConfig instance.
|
|
34
|
+
* @param {string} path
|
|
35
|
+
* @param {string} environment
|
|
36
|
+
* @returns {GlobalConfig}
|
|
37
|
+
*/
|
|
38
|
+
static async load(path, environment) {
|
|
39
|
+
var json
|
|
40
|
+
try {
|
|
41
|
+
const raw = fs.readFileSync(path, { encoding: 'utf8', flag: 'r' });
|
|
42
|
+
json = JSON.parse(raw);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
consola.error(`Failed to load GlobalConfig from path: ${path}`, error);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
if (environment == "production")
|
|
48
|
+
return new GlobalConfig({...json.production || json.activity});
|
|
49
|
+
else
|
|
50
|
+
return new GlobalConfig({...json.development || json.activity});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import ConfigError from "./ConfigError";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{client: string, connection: {host: string, user: string, password: string, database: string}}} DatabaseConfig
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default class SecretConfig {
|
|
9
|
+
/** @type {string} */
|
|
10
|
+
token;
|
|
11
|
+
|
|
12
|
+
/** @type {string} */
|
|
13
|
+
environment;
|
|
14
|
+
|
|
15
|
+
/** @type {[DatabaseConfig]} */
|
|
16
|
+
databaseConfig;
|
|
17
|
+
|
|
18
|
+
constructor(env) {
|
|
19
|
+
if (!env)
|
|
20
|
+
throw new ConfigError('env is required to initialize SecretConfig');
|
|
21
|
+
|
|
22
|
+
this.environment = env.NODE_ENV;
|
|
23
|
+
if (!this.environment)
|
|
24
|
+
throw new ConfigError('NODE_ENV is required to initialize SecretConfig');
|
|
25
|
+
if (!['production', 'development', 'testing'].includes(this.environment))
|
|
26
|
+
throw new ConfigError('Unrecognized NODE_ENV: '+this.environment);
|
|
27
|
+
this.token = env.TOKEN;
|
|
28
|
+
|
|
29
|
+
var databaseConfigs = [];
|
|
30
|
+
for (let index = 0, section = "DATABASE__"+index; !!env[section+"__NAME"]; index++) {
|
|
31
|
+
try {
|
|
32
|
+
let db_config = {
|
|
33
|
+
client: 'mysql',
|
|
34
|
+
connection: {
|
|
35
|
+
host: env[section+"__HOST"],
|
|
36
|
+
user: env[section+"__USER"],
|
|
37
|
+
password: env[section+"__PASSWORD"],
|
|
38
|
+
database: env[section+"__NAME"],
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
databaseConfigs.push(db_config);
|
|
43
|
+
consola.start(`Loaded database config for section: ${section}`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
consola.error(`Failed to load database config for section: ${section}`, error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.databaseConfig = databaseConfigs;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Interaction as DiscordBaseInteraction, MessageFlags } from "discord.js";
|
|
2
|
+
|
|
3
|
+
export default class CommandError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "DiscordCommandError";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {DiscordBaseInteraction} interaction
|
|
11
|
+
* @returns {Promise<any>}
|
|
12
|
+
*/
|
|
13
|
+
error_as_message(interaction){
|
|
14
|
+
return interaction.reply(this.message).then(msg => setTimeout(() => msg.delete(), 5000)).catch(() => {});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {DiscordBaseInteraction} interaction
|
|
19
|
+
* @returns {Promise<any>}
|
|
20
|
+
*/
|
|
21
|
+
error_as_command(interaction){
|
|
22
|
+
return interaction
|
|
23
|
+
.editReply({
|
|
24
|
+
content: this.message,
|
|
25
|
+
flags: MessageFlags.Ephemeral,
|
|
26
|
+
})
|
|
27
|
+
.catch(() =>
|
|
28
|
+
interaction.reply({
|
|
29
|
+
content: this.message,
|
|
30
|
+
flags: MessageFlags.Ephemeral,
|
|
31
|
+
}))
|
|
32
|
+
.catch(() => {});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Interaction as DiscordBaseInteraction,
|
|
3
|
+
OmitPartialGroupDMChannel,
|
|
4
|
+
Message,
|
|
5
|
+
ModalSubmitInteraction,
|
|
6
|
+
ButtonInteraction,
|
|
7
|
+
AnySelectMenuInteraction,
|
|
8
|
+
ChatInputCommandInteraction,
|
|
9
|
+
CacheType
|
|
10
|
+
} from 'discord.js';
|
|
11
|
+
import InteractionContainer from "./InteractionContainer";
|
|
12
|
+
import { InteractionType, InteractionScope } from "../Utils/enums";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef { (interaction: DiscordBaseInteraction<CacheType>) => Promise<any> } InteractionFunction
|
|
16
|
+
* @typedef { (message: OmitPartialGroupDMChannel<Message<boolean>>) => Promise<any> } InteractionMsgFunction
|
|
17
|
+
* @typedef { string } InteractionID
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export default class Interaction {
|
|
21
|
+
/** @type {string} */
|
|
22
|
+
id;
|
|
23
|
+
/** @type {InteractionType} */
|
|
24
|
+
type;
|
|
25
|
+
/** @type {InteractionScope} */
|
|
26
|
+
scope;
|
|
27
|
+
/** @type {InteractionFunction} */
|
|
28
|
+
interaction_command_function;
|
|
29
|
+
|
|
30
|
+
constructor(custom_id, type, scope, interaction_command_function) {
|
|
31
|
+
this.id = custom_id;
|
|
32
|
+
this.scope = scope;
|
|
33
|
+
this.type = type;
|
|
34
|
+
this.interaction_command_function = interaction_command_function;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Command wrapper
|
|
39
|
+
* @param {InteractionID} custom_id
|
|
40
|
+
* @param {InteractionType} type
|
|
41
|
+
* @param {InteractionScope|[InteractionScope]} scope
|
|
42
|
+
* @param {InteractionFunction} target
|
|
43
|
+
*/
|
|
44
|
+
static On(custom_id, type, scope, target) {
|
|
45
|
+
if(Array.isArray(scope))
|
|
46
|
+
scope.forEach(s => InteractionContainer.add(new Interaction(custom_id, s, type, target)));
|
|
47
|
+
else;
|
|
48
|
+
InteractionContainer.add(new Interaction(custom_id, scope, type, target));
|
|
49
|
+
return custom_id;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {InteractionID} custom_id
|
|
54
|
+
* @param {InteractionScope|[InteractionScope]} scope
|
|
55
|
+
* @param {(interaction: ChatInputCommandInteraction) => Promise<any>} target
|
|
56
|
+
*/
|
|
57
|
+
static OnSlashCommand(custom_id, scope, target) {
|
|
58
|
+
return Interaction.On(custom_id, InteractionType.Command, scope, target)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {InteractionID} custom_id
|
|
63
|
+
* @param {InteractionScope|[InteractionScope]} scope
|
|
64
|
+
* @param {(interaction: ButtonInteraction) => Promise<any>} target
|
|
65
|
+
*/
|
|
66
|
+
static OnButton(custom_id, scope, target) {
|
|
67
|
+
return Interaction.On(custom_id, InteractionType.Button, scope, target)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {InteractionID} custom_id
|
|
72
|
+
* @param {(interaction: AnySelectMenuInteraction) => Promise<any>} target
|
|
73
|
+
* @param {InteractionScope|[InteractionScope]} scope
|
|
74
|
+
*/
|
|
75
|
+
static OnSelectMenu(custom_id, scope, target) {
|
|
76
|
+
return Interaction.On(custom_id, InteractionType.SelectMenu, scope, target)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {InteractionID} custom_id
|
|
81
|
+
* @param {InteractionMsgFunction} target
|
|
82
|
+
* @param {InteractionScope|[InteractionScope]} scope
|
|
83
|
+
*/
|
|
84
|
+
static OnMessageCommand(custom_id, scope, target) {
|
|
85
|
+
return Interaction.On(custom_id, InteractionType.Message, scope, target)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {InteractionID} custom_id
|
|
90
|
+
* @param {(interaction: ModalSubmitInteraction) => Promise<any>} target
|
|
91
|
+
* @param {InteractionScope|[InteractionScope]} scope
|
|
92
|
+
*/
|
|
93
|
+
static OnModal(custom_id, scope, target) {
|
|
94
|
+
return Interaction.On(custom_id, InteractionType.Modal, scope, target)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Interaction as DiscordBaseInteraction,
|
|
3
|
+
OmitPartialGroupDMChannel,
|
|
4
|
+
Message,
|
|
5
|
+
CacheType
|
|
6
|
+
} from 'discord.js';
|
|
7
|
+
import Interaction from "./Interaction";
|
|
8
|
+
import { InteractionScope, InteractionType } from "../Utils/enums";
|
|
9
|
+
import GlobalConfig from "../Configs/GlobalConfig";
|
|
10
|
+
import LocalConfig from "../Configs/LocalConfig";
|
|
11
|
+
import consola from "consola";
|
|
12
|
+
import CommandError from "./CommandError";
|
|
13
|
+
import RestCommands from './RestCommands';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef { (interaction: DiscordBaseInteraction<CacheType>) => Promise<any> } InteractionFunction
|
|
17
|
+
* @typedef { (message: OmitPartialGroupDMChannel<Message<boolean>>) => Promise<any> } InteractionMsgFunction
|
|
18
|
+
* @typedef { string } InteractionID
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export default class InteractionContainer {
|
|
22
|
+
/** @type {Map<InteractionID, Interaction>} */
|
|
23
|
+
static #interaction_container = new Map();
|
|
24
|
+
|
|
25
|
+
/** @type {GlobalConfig} */
|
|
26
|
+
#global_config;
|
|
27
|
+
/** @type {LocalConfig} */
|
|
28
|
+
#local_config;
|
|
29
|
+
|
|
30
|
+
/** @type {RestCommands} */
|
|
31
|
+
Rest = new RestCommands(this.#global_config, this.#local_config);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {InteractionType} type
|
|
35
|
+
* @param {InteractionScope} scope
|
|
36
|
+
* @param {string} id
|
|
37
|
+
* @returns {InteractionID}
|
|
38
|
+
*/
|
|
39
|
+
static #make_key(type, scope, id) {
|
|
40
|
+
return `${type}__${scope}__${id}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {InteractionType} type
|
|
45
|
+
* @param {InteractionScope} scope
|
|
46
|
+
* @param {string} id
|
|
47
|
+
* @param {Interaction} base_interaction
|
|
48
|
+
*/
|
|
49
|
+
static add(base_interaction) {
|
|
50
|
+
const key = InteractionContainer.#make_key(base_interaction.type, base_interaction.scope, base_interaction.id);
|
|
51
|
+
InteractionContainer.#interaction_container.set(key, base_interaction);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {InteractionType} type
|
|
56
|
+
* @param {InteractionScope} scope
|
|
57
|
+
* @param {string} id
|
|
58
|
+
* @returns {Interaction?}
|
|
59
|
+
*/
|
|
60
|
+
static get(type, scope, id) {
|
|
61
|
+
return InteractionContainer.#interaction_container.get(InteractionContainer.#make_key(type, scope, id));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
constructor(local_config, global_config) {
|
|
65
|
+
this.#local_config = local_config;
|
|
66
|
+
this.#global_config = global_config;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {DiscordBaseInteraction} interaction
|
|
71
|
+
* @param { (error: Error) => Promise<any> } on_error
|
|
72
|
+
* @returns {Promise<any>}
|
|
73
|
+
*/
|
|
74
|
+
call_message_interaction(interaction, on_error = async () => {}) {
|
|
75
|
+
if (interaction.author.bot) return;
|
|
76
|
+
|
|
77
|
+
const scope =
|
|
78
|
+
(interaction.channel.type === ChannelType.DM) ?
|
|
79
|
+
InteractionScope.DM : this.#global_config.guilds.get_scope(interaction.guild.id);
|
|
80
|
+
|
|
81
|
+
const customId = interaction.content.slice(this.#local_config.prefix?.length)
|
|
82
|
+
|
|
83
|
+
return InteractionContainer
|
|
84
|
+
.#call(InteractionType.Message, scope, customId, interaction)
|
|
85
|
+
.catch(error => {
|
|
86
|
+
if (error instanceof CommandError)
|
|
87
|
+
return error.error_as_message()
|
|
88
|
+
else
|
|
89
|
+
return interaction
|
|
90
|
+
.reply("something went wrong")
|
|
91
|
+
.then(msg => setTimeout(() => msg.delete(), 5000))
|
|
92
|
+
.catch(() => {})
|
|
93
|
+
.then(() => on_error(error));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {DiscordBaseInteraction} interaction
|
|
99
|
+
* @param { (error: Error) => Promise<any> } on_error
|
|
100
|
+
* @returns {Promise<any>}
|
|
101
|
+
*/
|
|
102
|
+
call_interaction(interaction, on_error = async () => {}) {
|
|
103
|
+
const scope =
|
|
104
|
+
(interaction.channel.type === ChannelType.DM) ?
|
|
105
|
+
InteractionScope.DM : this.#global_config.guilds.get_scope(interaction.guild.id);
|
|
106
|
+
|
|
107
|
+
const customId = (interaction.commandName || interaction.customId)?.split(/\?(.+)/, 2)
|
|
108
|
+
|
|
109
|
+
var type;
|
|
110
|
+
if (interaction.isChatInputCommand())
|
|
111
|
+
type = InteractionType.Command;
|
|
112
|
+
else if (interaction.isButton())
|
|
113
|
+
type = InteractionType.Button;
|
|
114
|
+
else if (interaction.isAnySelectMenu())
|
|
115
|
+
type = InteractionType.SelectMenu;
|
|
116
|
+
else if (interaction.isModalSubmit())
|
|
117
|
+
type = InteractionType.Modal;
|
|
118
|
+
else
|
|
119
|
+
return;
|
|
120
|
+
|
|
121
|
+
return InteractionContainer
|
|
122
|
+
.#call(type, scope, customId, interaction)
|
|
123
|
+
.catch(error => {
|
|
124
|
+
if (error instanceof CommandError)
|
|
125
|
+
return error.error_as_command(interaction)
|
|
126
|
+
else
|
|
127
|
+
return interaction
|
|
128
|
+
.editReply("something went wrong")
|
|
129
|
+
.catch(() => interaction.reply("something went wrong"))
|
|
130
|
+
.then(() => on_error(error))
|
|
131
|
+
.then();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
*
|
|
137
|
+
* @param {InteractionType} type
|
|
138
|
+
* @param {InteractionScope} scope
|
|
139
|
+
* @param {InteractionID} customId
|
|
140
|
+
* @param {DiscordBaseInteraction} interaction
|
|
141
|
+
* @returns {Promise<any>}
|
|
142
|
+
* @throws {CommandError}
|
|
143
|
+
*/
|
|
144
|
+
static #call(type, scope, customId, interaction) {
|
|
145
|
+
const interaction_command = InteractionContainer.get(type, scope, customId?.[0]);
|
|
146
|
+
if(!interaction_command)
|
|
147
|
+
return consola.error(`No interaction found for id ${customId}`);
|
|
148
|
+
|
|
149
|
+
return interaction_command.interaction_command_function.apply(null, [interaction, ...customId.slice(1)]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { InteractionScope } from "../Utils/enums";
|
|
2
|
+
import GlobalConfig from "../Configs/GlobalConfig";
|
|
3
|
+
import LocalConfig from "../Configs/LocalConfig";
|
|
4
|
+
import { REST } from "discord.js";
|
|
5
|
+
|
|
6
|
+
export default class RestCommands {
|
|
7
|
+
#commands = new Map();
|
|
8
|
+
#global_commands = [];
|
|
9
|
+
/** @type {GlobalConfig} */
|
|
10
|
+
#global_config;
|
|
11
|
+
/** @type {LocalConfig} */
|
|
12
|
+
#local_config;
|
|
13
|
+
|
|
14
|
+
constructor(global_config, local_config) {
|
|
15
|
+
this.#global_config = global_config;
|
|
16
|
+
this.#local_config = local_config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
add(commands, scope) {
|
|
20
|
+
if(!!scope)
|
|
21
|
+
this.#commands.set(scope, commands);
|
|
22
|
+
else
|
|
23
|
+
this.#global_commands = commands;
|
|
24
|
+
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async register(secret_token){
|
|
29
|
+
const rest = new REST(this.#local_config.restOptions).setToken(secret_token);
|
|
30
|
+
|
|
31
|
+
for(const [scope, commands] of this.#commands.entries()) {
|
|
32
|
+
await rest.put(Routes.applicationGuildCommands(this.#local_config.clientId, this.#global_config.guilds.get_guild_id(scope)), {
|
|
33
|
+
body: commands,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if(this.#global_commands.length > 0) {
|
|
38
|
+
await rest.put(Routes.applicationCommands(this.#local_config.clientId), {
|
|
39
|
+
body: this.#global_commands,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return rest;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BaseInteraction } from "discord.js";
|
|
2
|
+
import { Modlevel } from "./enums";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {BaseInteraction} interaction
|
|
7
|
+
* @returns {Boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function check_is_staff(interaction) {
|
|
10
|
+
return interaction.member.permissions.has(PermissionsBitField.Flags.ModerateMembers)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {BaseInteraction} interaction
|
|
15
|
+
* @returns {Boolean}
|
|
16
|
+
*/
|
|
17
|
+
export function check_can_ban(interaction) {
|
|
18
|
+
return interaction.member.permissions.has(PermissionsBitField.Flags.BanMembers)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Modlevel} mod_level
|
|
23
|
+
* @param {Modlevel} required_mod_level
|
|
24
|
+
* @returns {Boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function check_required_mod_level(mod_level, required_mod_level){
|
|
27
|
+
switch(required_mod_level){
|
|
28
|
+
case null:
|
|
29
|
+
return true;
|
|
30
|
+
case undefined:
|
|
31
|
+
return true;
|
|
32
|
+
case Modlevel.Member:
|
|
33
|
+
if(mod_level == Modlevel.Member)
|
|
34
|
+
return true;
|
|
35
|
+
case Modlevel.Admin:
|
|
36
|
+
if(mod_level == Modlevel.Admin)
|
|
37
|
+
return true;
|
|
38
|
+
case Modlevel.Owner:
|
|
39
|
+
if(mod_level == Modlevel.Owner)
|
|
40
|
+
return true;
|
|
41
|
+
default:
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** @enum { String } */
|
|
2
|
+
export const Modlevel = {
|
|
3
|
+
Owner: "owner",
|
|
4
|
+
Admin: "admin",
|
|
5
|
+
Member: "member"
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** @enum { String } */
|
|
9
|
+
export const InteractionScope = {
|
|
10
|
+
Main: "main",
|
|
11
|
+
Community: "comms",
|
|
12
|
+
Tutor: "tutor",
|
|
13
|
+
Staff: "staff",
|
|
14
|
+
OtherGuild: "guild",
|
|
15
|
+
DM: "dm"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** @enum { String } */
|
|
19
|
+
export const InteractionType = {
|
|
20
|
+
Command: "CIP",
|
|
21
|
+
Button: "BTN",
|
|
22
|
+
SelectMenu: "ASM",
|
|
23
|
+
Message: "MSG",
|
|
24
|
+
Modal: "MDL"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** @enum { String } */
|
|
28
|
+
export const Environments = {
|
|
29
|
+
DEV: "development",
|
|
30
|
+
TST: "testing",
|
|
31
|
+
PRD: "production"
|
|
32
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {default as LocalConfig} from "./Configs/LocalConfig.js";
|
|
2
|
+
export {default as GlobalConfig} from "./Configs/GlobalConfig.js";
|
|
3
|
+
export {default as SecretConfig} from "./Configs/SecretConfig.js";
|
|
4
|
+
|
|
5
|
+
export {default as InteractionContainer} from "./Interactions/InteractionContainer.js";
|
|
6
|
+
export {default as Interaction} from "./Interactions/Interaction.js";
|
|
7
|
+
export {default as CommandError} from "./Interactions/CommandError.js";
|
|
8
|
+
|
|
9
|
+
export * from "./Utils/checks.js";
|
|
10
|
+
export * from "./Utils/enums.js";
|