highrise.bot 1.0.2 → 1.1.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "highrise.bot",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Unofficial JavaScript SDK for the Highrise platform. Feature-complete WebSocket client with TypeScript support, built for production environments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"highrise.bot",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const RoleManager = require("highrise.bot/src/classes/Managers/RoleManager");
|
|
2
2
|
const CooldownManager = require("highrise.bot/src/classes/Managers/CooldownManager");
|
|
3
3
|
const DanceFloor = require("highrise.bot/src/classes/Managers/DanceFloorManagers");
|
|
4
|
-
const { Logger } = require("highrise.bot/src/classes/Managers/Helpers/LoggerManager")
|
|
4
|
+
const { Logger } = require("highrise.bot/src/classes/Managers/Helpers/LoggerManager");
|
|
5
|
+
const CommandHandler = require("highrise.bot/src/classes/Handlers/CommandHandler");
|
|
5
6
|
|
|
6
7
|
class Utils {
|
|
7
8
|
constructor(bot, options) {
|
|
@@ -14,7 +15,7 @@ class Utils {
|
|
|
14
15
|
dataDir: './Json/permissions',
|
|
15
16
|
filename: 'roles.json',
|
|
16
17
|
customRoles: options.customRoles || [],
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -72,6 +73,10 @@ class Utils {
|
|
|
72
73
|
return result;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
command(relativeDir, options = {}) {
|
|
77
|
+
return new CommandHandler(this.bot, relativeDir, options);
|
|
78
|
+
};
|
|
79
|
+
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
module.exports = Utils
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
const fsp = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
class CommandHandler {
|
|
5
|
+
constructor(bot, relativeDir, options = {}) {
|
|
6
|
+
this.options = {
|
|
7
|
+
autoLoad: options.autoLoad || true
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
this.bot = bot
|
|
11
|
+
this.logger = bot.utils.logger
|
|
12
|
+
this.dir = path.resolve(relativeDir);
|
|
13
|
+
|
|
14
|
+
this.commands = new Map() // name -> module
|
|
15
|
+
this.aliases = new Map() // alias -> commandName
|
|
16
|
+
|
|
17
|
+
this.preMiddlewares = new Map()
|
|
18
|
+
this.postMiddlewares = new Map()
|
|
19
|
+
|
|
20
|
+
this.errorHeader = `CommandHandler (${this.dir})`
|
|
21
|
+
this.handlerError = (...args) => this.logger.error(this.errorHeader, args)
|
|
22
|
+
|
|
23
|
+
if (this.options.autoLoad) this._initialize()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async _initialize() {
|
|
27
|
+
const files = await this._getFiles()
|
|
28
|
+
const modules = await this._loadModules(files)
|
|
29
|
+
const result = await this._registerCommands(modules)
|
|
30
|
+
this.logger.success(this.errorHeader, `initializing Commands | Success: ${result.success} - Failed: ${result.failed}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async _getFiles() {
|
|
34
|
+
try {
|
|
35
|
+
const files = await fsp.readdir(this.dir, {
|
|
36
|
+
recursive: true,
|
|
37
|
+
withFileTypes: true
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return files
|
|
41
|
+
.filter(file => file.name.endsWith('.js'))
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return this._handleGetFilesError(this.dir, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_handleGetFilesError(dir, error) {
|
|
48
|
+
if (error.code === 'ENOENT') {
|
|
49
|
+
this.logger.warn(`CommandHandler`, `Directory does not exist: ${dir}\n`);
|
|
50
|
+
return [];
|
|
51
|
+
} else if (error.code === 'EACCES') {
|
|
52
|
+
this.logger.warn(`CommandHandler`, `Access failed to directory: ${dir}\n`);
|
|
53
|
+
return [];
|
|
54
|
+
} else {
|
|
55
|
+
this.logger.error(`CommandHandler`, `Error reading directory: ${dir}`, error.message);
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async _loadModules(files) {
|
|
61
|
+
let modules = []
|
|
62
|
+
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const modulePath = path.join(file.parentPath, file.name);
|
|
65
|
+
|
|
66
|
+
if (require.cache[modulePath]) {
|
|
67
|
+
delete require.cache[modulePath];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
let module = require(modulePath)
|
|
72
|
+
Object.assign(module, {
|
|
73
|
+
filePath: modulePath
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
modules.push(module)
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const causePart = this._dissectSyntaxErrorStack(error)
|
|
79
|
+
const message = this._buildSyntaxErrorMessage(file, causePart)
|
|
80
|
+
this.logger.error(`CommandHandler - Loading Modules`, message)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return modules
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_dissectSyntaxErrorStack(error) {
|
|
88
|
+
const stack = error.stack
|
|
89
|
+
const parts = stack.split('\n')
|
|
90
|
+
const causePart = parts.slice(0, 3).join('\n')
|
|
91
|
+
return causePart
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_buildSyntaxErrorMessage(fileName, causePart) {
|
|
95
|
+
let message = `Error: Caught a syntax error in ${fileName}:\n`
|
|
96
|
+
message += `Cause:\n ${causePart}`
|
|
97
|
+
return message
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_buildValidationWarning(module, warns) {
|
|
101
|
+
let warningMessage = `Error Registering command "${module.filePath}":\n`
|
|
102
|
+
for (const warn of warns) {
|
|
103
|
+
warningMessage += warn.concat('\n')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.logger.warn(`CommandHandler - Module Register`, warningMessage.trim())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_validateModule(module) {
|
|
110
|
+
let warn = []
|
|
111
|
+
|
|
112
|
+
if (typeof module !== 'object' || module === null) {
|
|
113
|
+
warn.push(`module must be an object`)
|
|
114
|
+
return warn;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (('name' in module)) {
|
|
118
|
+
if (typeof module.name !== 'string' || module.name.length === 0) {
|
|
119
|
+
warn.push(`module.name must be a non-empty string`)
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
warn.push(`module.name is required`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if ('description' in module) {
|
|
126
|
+
if (typeof module.description !== 'string' || module.description.length === 0) {
|
|
127
|
+
warn.push(`module.description must be a non-empty string`)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (('roles' in module)) {
|
|
132
|
+
if (!Array.isArray(module.roles)) {
|
|
133
|
+
warn.push(`module.roles must be an array`)
|
|
134
|
+
} else if (!module.roles.every(role => typeof role === 'string' && role.length !== 0)) {
|
|
135
|
+
warn.push(`module.roles elements must be only non-empty strings`)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (('execute' in module)) {
|
|
140
|
+
if (typeof module.execute !== 'function') {
|
|
141
|
+
warn.push(`module.execute must be a function`)
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
warn.push(`module.execute is required`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if ('aliases' in module) {
|
|
148
|
+
if (!Array.isArray(module.aliases)) {
|
|
149
|
+
warn.push(`module.aliases must be an array`)
|
|
150
|
+
} else if (!module.aliases.every(alias => typeof alias === 'string' && alias.length !== 0)) {
|
|
151
|
+
warn.push(`module.aliases elements must be only non-empty strings`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return warn;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_registerAliases(commandName, aliases) {
|
|
159
|
+
let success = 0
|
|
160
|
+
let failed = 0
|
|
161
|
+
|
|
162
|
+
for (const alias of aliases) {
|
|
163
|
+
const status = this._registerAlias(commandName, alias)
|
|
164
|
+
status ? success++ : failed++
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { success, failed }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_validateAlias(alias) {
|
|
171
|
+
if (this.aliases.has(alias)) {
|
|
172
|
+
const existingCommand = this.aliases.get(alias)
|
|
173
|
+
this.handlerError(`alias "${alias}" is already set to command "${existingCommand}"`)
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (this.commands.has(alias)) {
|
|
178
|
+
this.handlerError(`alias "${alias}" conflicts with existing command name`)
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_registerAlias(commandName, alias) {
|
|
186
|
+
const isValid = this._validateAlias(alias)
|
|
187
|
+
if (!isValid) return false
|
|
188
|
+
|
|
189
|
+
this.aliases.set(alias, commandName)
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async _registerCommands(modules) {
|
|
194
|
+
let success = 0
|
|
195
|
+
let failed = 0
|
|
196
|
+
|
|
197
|
+
for (const module of modules) {
|
|
198
|
+
const status = await this.registerCommand(module)
|
|
199
|
+
status ? success++ : failed++
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { success, failed }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async _unRegisterCommands(modules) {
|
|
206
|
+
let success = 0
|
|
207
|
+
let failed = 0
|
|
208
|
+
|
|
209
|
+
for (const module of modules) {
|
|
210
|
+
const status = this.unRegisterCommand(module.name)
|
|
211
|
+
status ? success++ : failed++
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { success, failed }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_getPreMiddlewares() {
|
|
218
|
+
return this.preMiddlewares.entries()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
_getPostMiddlewares() {
|
|
222
|
+
return this.postMiddlewares.entries()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async _middlewareErrorHandler(middleware, bot, context, result = null) {
|
|
226
|
+
try {
|
|
227
|
+
const output = await middleware.fn(bot, context, result);
|
|
228
|
+
return output !== false;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
this.logger.error(`${middleware.type} Middleware - ${middleware.name}`, error.message)
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async _runPreMiddleware(bot, context) {
|
|
236
|
+
const pres = this._getPreMiddlewares()
|
|
237
|
+
for (const [_, pre] of pres) {
|
|
238
|
+
const proceed = await this._middlewareErrorHandler(pre, bot, context)
|
|
239
|
+
if (!proceed) return false
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async _runPostMiddleware(bot, context, result) {
|
|
245
|
+
const posts = this._getPostMiddlewares()
|
|
246
|
+
for (const [_, post] of posts) {
|
|
247
|
+
await this._middlewareErrorHandler(post, bot, context, result)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
_validateMiddleware(name, fn) {
|
|
252
|
+
if (!name || typeof name !== 'string') {
|
|
253
|
+
this.handlerError(`name must be a non-empty string`)
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!fn || typeof fn !== 'function') {
|
|
258
|
+
this.handlerError(`fn must be a function`)
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_validateContext(context) {
|
|
266
|
+
if (!('user' in context)) {
|
|
267
|
+
this.handlerError(`context.user must be provided.`)
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (typeof context.user !== 'object') {
|
|
272
|
+
this.handlerError(`context.user must be an object.`)
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!('id' in context.user)) {
|
|
277
|
+
this.handlerError(`context.user.id must be provided.`)
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (typeof context.user.id !== 'string' || context.user.id.trim() === '') {
|
|
282
|
+
this.handlerError(`context.user.id must be a non-empty string.`)
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!('username' in context.user)) {
|
|
287
|
+
this.handlerError(`context.user.username must be provided.`)
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof context.user.username !== 'string' || context.user.username.trim() === '') {
|
|
292
|
+
this.handlerError(`context.user.username must be a non-empty string.`)
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_roleCheck(bot, user, module) {
|
|
300
|
+
const commandRoles = module.roles
|
|
301
|
+
if (!commandRoles) return true
|
|
302
|
+
|
|
303
|
+
return commandRoles.some(requiredRole => {
|
|
304
|
+
const hasRequiredRole = bot.utils.roles.hasRole(requiredRole, user.id)
|
|
305
|
+
return hasRequiredRole
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async _executeCommand(user, module, context) {
|
|
310
|
+
const eligible = this._roleCheck(this.bot, user, module);
|
|
311
|
+
if (eligible) {
|
|
312
|
+
try {
|
|
313
|
+
const proceed = await this._runPreMiddleware(this.bot, context)
|
|
314
|
+
if (!proceed) return
|
|
315
|
+
|
|
316
|
+
const result = await module.execute(this.bot, context);
|
|
317
|
+
await this._runPostMiddleware(this.bot, context, result ?? { success: true });
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.logger.error(`Command ${module.filePath}`, error.message)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
preMiddleware(name, fn) {
|
|
325
|
+
const status = this._validateMiddleware(name, fn)
|
|
326
|
+
|
|
327
|
+
if (!status) return
|
|
328
|
+
|
|
329
|
+
if (this.preMiddlewares.has(name)) {
|
|
330
|
+
this.logger.warn(`Pre middleware "${name}" already exists and will be overwritten`)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.preMiddlewares.set(name, { name, fn, type: 'Pre' })
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
postMiddleware(name, fn) {
|
|
337
|
+
const status = this._validateMiddleware(name, fn)
|
|
338
|
+
|
|
339
|
+
if (!status) return
|
|
340
|
+
|
|
341
|
+
if (this.postMiddlewares.has(name)) {
|
|
342
|
+
this.logger.warn(`Post middleware "${name}" already exists and will be overwritten`)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.postMiddlewares.set(name, { name, fn, type: 'Post' })
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async registerCommand(module) {
|
|
349
|
+
const warns = this._validateModule(module)
|
|
350
|
+
if (warns.length > 0) {
|
|
351
|
+
this._buildValidationWarning(module, warns)
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const { name, aliases } = module
|
|
356
|
+
|
|
357
|
+
if (this.commands.has(name)) {
|
|
358
|
+
return false
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (aliases) this._registerAliases(name, aliases)
|
|
362
|
+
|
|
363
|
+
this.commands.set(name, module)
|
|
364
|
+
return true
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async unRegisterCommand(commandName) {
|
|
368
|
+
if (!this.commands.has(commandName)) {
|
|
369
|
+
return false
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const module = this.commands.get(commandName)
|
|
373
|
+
|
|
374
|
+
if (module.aliases) {
|
|
375
|
+
module.aliases.forEach(alias => this.aliases.delete(alias))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return this.commands.delete(commandName)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
async handle(commandName, context = {}) {
|
|
383
|
+
if (!this._validateContext(context)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const module = this.commands.get(commandName) ||
|
|
388
|
+
this.commands.get(this.aliases.get(commandName));
|
|
389
|
+
|
|
390
|
+
if (!module) return;
|
|
391
|
+
|
|
392
|
+
await this._executeCommand(context.user, module, context);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = CommandHandler
|
package/typings/index.d.ts
CHANGED
|
@@ -153,8 +153,76 @@ type TipFilter = (sender: Sender, receiver: Receiver, currency: Currency) => boo
|
|
|
153
153
|
*/
|
|
154
154
|
type MovementFilter = (user: User, position: Position | null, anchor: AnchorPosition | null) => boolean;
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Function type for middleware that executes BEFORE command execution
|
|
158
|
+
*
|
|
159
|
+
* Use cases:
|
|
160
|
+
* - Authentication/authorization checks (return false to deny access)
|
|
161
|
+
* - Input validation and sanitization
|
|
162
|
+
* - Rate limiting and cooldown enforcement
|
|
163
|
+
* - Logging access attempts
|
|
164
|
+
* - Setting up command prerequisites
|
|
165
|
+
*
|
|
166
|
+
* Return values:
|
|
167
|
+
* - `true` or `undefined`: Command continues to execute normally
|
|
168
|
+
* - `false`: Command execution is halted immediately
|
|
169
|
+
* - Thrown errors: Caught by middleware handler, command execution halted
|
|
170
|
+
*
|
|
171
|
+
* @param bot - Highrise bot instance for API interactions
|
|
172
|
+
* @param context - Command execution context containing user data and parameters
|
|
173
|
+
* @returns Boolean indicating whether to proceed with command execution
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // Admin-only command protection
|
|
177
|
+
* preMiddleware('admin-check', async (bot, context) => {
|
|
178
|
+
* const result = await bot.room.privilege.isModerator(context.user.id);
|
|
179
|
+
* if (!result.success || !result.isModerator) {
|
|
180
|
+
* await bot.whisper.send(context.user.id, 'Admin access required');
|
|
181
|
+
* return false; // Block command execution
|
|
182
|
+
* }
|
|
183
|
+
* return true; // Allow command to run
|
|
184
|
+
* })
|
|
185
|
+
*/
|
|
186
|
+
type preMiddlewareFunction = (bot: Highrise, context: CommandContext) => boolean | void;
|
|
156
187
|
|
|
157
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Function type for middleware that executes AFTER command execution
|
|
190
|
+
*
|
|
191
|
+
* Use cases:
|
|
192
|
+
* - Processing command results and responses
|
|
193
|
+
* - Logging successful command outcomes
|
|
194
|
+
* - Analytics and metrics collection
|
|
195
|
+
* - Cleanup operations
|
|
196
|
+
* - Audit trail maintenance
|
|
197
|
+
* - Error handling for command results
|
|
198
|
+
*
|
|
199
|
+
* Return values:
|
|
200
|
+
* - Any value (`true`, `false`, `undefined`): No impact on execution flow
|
|
201
|
+
* (post-middleware always runs after command completes)
|
|
202
|
+
* - Thrown errors: Caught by middleware handler, doesn't affect original command result
|
|
203
|
+
*
|
|
204
|
+
* @param bot - Highrise bot instance for API interactions
|
|
205
|
+
* @param context - Command execution context containing user data and parameters
|
|
206
|
+
* @param result - Output returned by the executed command (may be undefined)
|
|
207
|
+
* @returns (Optional) No functional impact on execution flow
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // Command usage analytics
|
|
211
|
+
* postMiddleware('analytics', async (bot, context, result) => {
|
|
212
|
+
* // Log successful commands to channel
|
|
213
|
+
* if (result?.success) {
|
|
214
|
+
* await bot.channel.send([
|
|
215
|
+
* 'command-execution',
|
|
216
|
+
* context.commandName
|
|
217
|
+
* ], JSON.stringify({
|
|
218
|
+
* userId: context.user.id,
|
|
219
|
+
* command: context.commandName,
|
|
220
|
+
* timestamp: Date.now()
|
|
221
|
+
* }));
|
|
222
|
+
* }
|
|
223
|
+
* })
|
|
224
|
+
*/
|
|
225
|
+
type postMiddlewareFunction = (bot: Highrise, context: CommandContext, result?: any) => boolean | void;interface User {
|
|
158
226
|
id: string;
|
|
159
227
|
username: string;
|
|
160
228
|
}
|
|
@@ -756,6 +824,34 @@ interface CooldownManagerStats {
|
|
|
756
824
|
memoryUsage: CooldownMemoryUsage;
|
|
757
825
|
}
|
|
758
826
|
|
|
827
|
+
/**
|
|
828
|
+
* Context passed to command handlers containing user and additional data
|
|
829
|
+
*/
|
|
830
|
+
interface CommandContext {
|
|
831
|
+
user: User; // Using SDK's User type
|
|
832
|
+
args?: string[];
|
|
833
|
+
message?: string;
|
|
834
|
+
[key: string]: any;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Command module definition
|
|
839
|
+
*/
|
|
840
|
+
interface CommandModule {
|
|
841
|
+
/** Unique command name */
|
|
842
|
+
name: string;
|
|
843
|
+
/** Optional command description */
|
|
844
|
+
description?: string;
|
|
845
|
+
/** Required roles for command access */
|
|
846
|
+
roles?: string[];
|
|
847
|
+
/** Command execution function */
|
|
848
|
+
execute: (bot: Highrise, context: CommandContext) => Promise<any>;
|
|
849
|
+
/** Alternative names for the command */
|
|
850
|
+
aliases?: string[];
|
|
851
|
+
/** Additional properties */
|
|
852
|
+
[key: string]: any;
|
|
853
|
+
}
|
|
854
|
+
|
|
759
855
|
interface Job {
|
|
760
856
|
id: string;
|
|
761
857
|
interval: number;
|
|
@@ -4511,7 +4607,150 @@ declare class Logger {
|
|
|
4511
4607
|
debug(method: string, message: string, data?: any): void;
|
|
4512
4608
|
}
|
|
4513
4609
|
|
|
4610
|
+
|
|
4611
|
+
|
|
4612
|
+
/**
|
|
4613
|
+
* Handles bot commands with support for modules, aliases, and middleware
|
|
4614
|
+
*/
|
|
4615
|
+
declare class CommandHandler {
|
|
4616
|
+
/**
|
|
4617
|
+
* Creates a new command handler
|
|
4618
|
+
* @param bot - Highrise Bot instance
|
|
4619
|
+
* @param relativeDir - Directory containing command modules
|
|
4620
|
+
* @param options - Configuration options
|
|
4621
|
+
*/
|
|
4622
|
+
constructor(
|
|
4623
|
+
bot: Highrise,
|
|
4624
|
+
relativeDir: string,
|
|
4625
|
+
options?: {
|
|
4626
|
+
/** Auto-load command modules on initialization (default: true) */
|
|
4627
|
+
autoLoad?: boolean;
|
|
4628
|
+
}
|
|
4629
|
+
);
|
|
4630
|
+
|
|
4631
|
+
/**
|
|
4632
|
+
* Handles a command execution
|
|
4633
|
+
* @param commandName - Name or alias of the command to execute
|
|
4634
|
+
* @param context - Execution context with user information
|
|
4635
|
+
* @example
|
|
4636
|
+
* const [command, ...args] = message.trim().split(/\s+/);
|
|
4637
|
+
* let context = {
|
|
4638
|
+
* args: args,
|
|
4639
|
+
* user
|
|
4640
|
+
* }
|
|
4641
|
+
*
|
|
4642
|
+
* const roomCommand = bot.utils.command(`./src/commands/`)
|
|
4643
|
+
*
|
|
4644
|
+
* roomCommand.handle(command, context)
|
|
4645
|
+
*/
|
|
4646
|
+
handle(commandName: string, context?: CommandContext): Promise<void>;
|
|
4647
|
+
|
|
4648
|
+
/**
|
|
4649
|
+
* Registers middleware to run before command execution
|
|
4650
|
+
*
|
|
4651
|
+
* Use cases:
|
|
4652
|
+
* - Authentication/authorization checks (return false to deny access)
|
|
4653
|
+
* - Input validation and sanitization
|
|
4654
|
+
* - Rate limiting and cooldown enforcement
|
|
4655
|
+
* - Logging access attempts
|
|
4656
|
+
* - Setting up command prerequisites
|
|
4657
|
+
*
|
|
4658
|
+
* Return values:
|
|
4659
|
+
* - `true` or `undefined`: Command continues to execute normally
|
|
4660
|
+
* - `false`: Command execution is halted immediately
|
|
4661
|
+
* - Thrown errors: Caught by middleware handler, command execution halted
|
|
4662
|
+
*
|
|
4663
|
+
* @param name - Middleware identifier for reference and debugging
|
|
4664
|
+
* @param fn - Middleware function that receives bot instance and context
|
|
4665
|
+
* @example
|
|
4666
|
+
* const roomCommand = bot.utils.command(`./src/commands`)
|
|
4667
|
+
*
|
|
4668
|
+
* roomCommand.preMiddleware('auth', async (bot, context) => {
|
|
4669
|
+
* if (!context.user.isAdmin) {
|
|
4670
|
+
* return false; // Prevent command execution
|
|
4671
|
+
* }
|
|
4672
|
+
* return true;
|
|
4673
|
+
* });
|
|
4674
|
+
*/
|
|
4675
|
+
preMiddleware(name: string, fn: preMiddlewareFunction): void;
|
|
4676
|
+
|
|
4677
|
+
/**
|
|
4678
|
+
* Registers middleware to run after command execution
|
|
4679
|
+
*
|
|
4680
|
+
* Use cases:
|
|
4681
|
+
* - Processing command results and responses
|
|
4682
|
+
* - Logging successful command outcomes
|
|
4683
|
+
* - Analytics and metrics collection
|
|
4684
|
+
* - Cleanup operations
|
|
4685
|
+
* - Audit trail maintenance
|
|
4686
|
+
* - Error handling for command results
|
|
4687
|
+
*
|
|
4688
|
+
* Return values:
|
|
4689
|
+
* - Any value (`true`, `false`, `undefined`): No impact on execution flow
|
|
4690
|
+
* (post-middleware always runs after command completes)
|
|
4691
|
+
* - Thrown errors: Caught by middleware handler, doesn't affect original command result
|
|
4692
|
+
*
|
|
4693
|
+
* @param name - Middleware identifier for reference and debugging
|
|
4694
|
+
* @param fn - Middleware function that processes bot, context, and command result
|
|
4695
|
+
* The result parameter contains the output returned by the executed command
|
|
4696
|
+
* @example
|
|
4697
|
+
* const roomCommand = bot.utils.command(`./src/commands`)
|
|
4698
|
+
*
|
|
4699
|
+
* roomCommand.postMiddleware('log', async (bot, context, result) => {
|
|
4700
|
+
* console.log(`Command executed: ${result?.success}`);
|
|
4701
|
+
* });
|
|
4702
|
+
*/
|
|
4703
|
+
postMiddleware(name: string, fn: postMiddlewareFunction): void;
|
|
4704
|
+
|
|
4705
|
+
/**
|
|
4706
|
+
* Registers a command module
|
|
4707
|
+
* @param module - Command configuration
|
|
4708
|
+
* @returns Success status
|
|
4709
|
+
* @example
|
|
4710
|
+
* const success = handler.register({
|
|
4711
|
+
* name: 'ping',
|
|
4712
|
+
* description: 'Responds with pong',
|
|
4713
|
+
* execute: async (bot, ctx) => {
|
|
4714
|
+
* return { message: 'pong' };
|
|
4715
|
+
* }
|
|
4716
|
+
* });
|
|
4717
|
+
*/
|
|
4718
|
+
register(module: CommandModule): boolean;
|
|
4719
|
+
|
|
4720
|
+
/**
|
|
4721
|
+
* Unregisters a command by name
|
|
4722
|
+
* @param commandName - Name of command to remove
|
|
4723
|
+
* @returns Success status
|
|
4724
|
+
* @example
|
|
4725
|
+
* const success = handler.unregister('ping');
|
|
4726
|
+
*/
|
|
4727
|
+
unregister(commandName: string): boolean;
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
/**
|
|
4731
|
+
* Function type for creating CommandHandler instances
|
|
4732
|
+
*/
|
|
4733
|
+
type CommandHandlerFactory = (
|
|
4734
|
+
relativeDir: string,
|
|
4735
|
+
options?: {
|
|
4736
|
+
/** Auto-load command modules on initialization (default: true) */
|
|
4737
|
+
autoLoad?: boolean;
|
|
4738
|
+
}
|
|
4739
|
+
) => CommandHandler;
|
|
4740
|
+
|
|
4514
4741
|
declare class BotUtils {
|
|
4742
|
+
/**
|
|
4743
|
+
* Factory function to create CommandHandler instances
|
|
4744
|
+
* @param relativeDir - Directory containing command modules
|
|
4745
|
+
* @param options - Configuration options
|
|
4746
|
+
* @returns New CommandHandler instance
|
|
4747
|
+
* @example
|
|
4748
|
+
* const command = bot.utils.command('./src/commands/room');
|
|
4749
|
+
* await command.handle('help', { user: { id: '123', username: 'john' } });
|
|
4750
|
+
*/
|
|
4751
|
+
command: CommandHandlerFactory;
|
|
4752
|
+
|
|
4753
|
+
// ... other properties remain the same ...
|
|
4515
4754
|
/**
|
|
4516
4755
|
* Logger instance for structured, color-coded logging throughout the application
|
|
4517
4756
|
* Provides different log levels (SUCCESS, ERROR, WARN, INFO, DEBUG) with timestamps and method tracking
|