lumisjs 1.0.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/LICENSE +21 -0
- package/README.md +274 -0
- package/SECURITY.md +160 -0
- package/index.js +90 -0
- package/package.json +68 -0
- package/src/cache/CacheManager.js +393 -0
- package/src/cache/MemoryAdapter.js +259 -0
- package/src/cache/MultiLevelCache.js +362 -0
- package/src/cache/RedisAdapter.js +329 -0
- package/src/cache/SQLiteAdapter.js +280 -0
- package/src/cache/index.js +15 -0
- package/src/client/Client.js +554 -0
- package/src/client/ShardingManager.js +274 -0
- package/src/config/ConfigValidator.js +172 -0
- package/src/config/index.js +7 -0
- package/src/dashboard/DashboardManager.js +362 -0
- package/src/datagen/DataGenerator.js +47 -0
- package/src/datagen/apis/GraphqlMocker.js +16 -0
- package/src/datagen/apis/OpenApiMocker.js +18 -0
- package/src/datagen/cli.js +130 -0
- package/src/datagen/formatters/csv.js +45 -0
- package/src/datagen/formatters/index.js +15 -0
- package/src/datagen/formatters/json.js +33 -0
- package/src/datagen/formatters/mongo.js +22 -0
- package/src/datagen/formatters/sql.js +32 -0
- package/src/datagen/index.js +17 -0
- package/src/datagen/plugin/PluginManager.js +43 -0
- package/src/datagen/plugins/example-plugin.js +21 -0
- package/src/datagen/schema/SchemaGenerator.js +172 -0
- package/src/datagen/schema/loadSchema.js +14 -0
- package/src/datagen/utils/seed.js +26 -0
- package/src/di/ServiceContainer.js +229 -0
- package/src/errors/ErrorCodes.js +110 -0
- package/src/errors/LumisError.js +85 -0
- package/src/game/EconomyManager.js +161 -0
- package/src/game/GameSessionManager.js +76 -0
- package/src/game/GuildManager.js +197 -0
- package/src/game/InventoryManager.js +140 -0
- package/src/game/LevelingSystem.js +194 -0
- package/src/game/MusicManager.js +587 -0
- package/src/health/HealthChecker.js +170 -0
- package/src/health/index.js +7 -0
- package/src/performance/PerformanceMonitor.js +244 -0
- package/src/performance/index.js +7 -0
- package/src/security/InputSanitizer.js +185 -0
- package/src/security/SecurityMiddleware.js +231 -0
- package/src/security/index.js +9 -0
- package/src/shutdown/GracefulShutdown.js +159 -0
- package/src/shutdown/index.js +7 -0
- package/src/utils/StructuredLogger.js +118 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events');
|
|
4
|
+
const RESTManager = require('../rest/RESTManager');
|
|
5
|
+
const APIRouter = require('../rest/APIRouter');
|
|
6
|
+
const WebSocketManager = require('../ws/WebSocketManager');
|
|
7
|
+
const CacheManager = require('../cache/CacheManager');
|
|
8
|
+
const PluginManager = require('../plugins/PluginManager');
|
|
9
|
+
const I18nManager = require('../i18n/I18nManager');
|
|
10
|
+
const EconomyManager = require('../game/EconomyManager');
|
|
11
|
+
const InventoryManager = require('../game/InventoryManager');
|
|
12
|
+
const LevelingSystem = require('../game/LevelingSystem');
|
|
13
|
+
const GameSessionManager = require('../game/GameSessionManager');
|
|
14
|
+
const GuildManager = require('../game/GuildManager');
|
|
15
|
+
const MusicManager = require('../game/MusicManager');
|
|
16
|
+
const CommandManager = require('../interactions/CommandManager');
|
|
17
|
+
const InteractionHandler = require('../interactions/InteractionHandler');
|
|
18
|
+
const { createInteraction } = require('../structures/Interaction');
|
|
19
|
+
const Collection = require('../utils/Collection');
|
|
20
|
+
const Intents = require('../utils/Intents');
|
|
21
|
+
const Logger = require('../utils/Logger');
|
|
22
|
+
const { Events } = require('../utils/Constants');
|
|
23
|
+
const User = require('../structures/User');
|
|
24
|
+
const Guild = require('../structures/Guild');
|
|
25
|
+
const { Channel } = require('../structures/Channel');
|
|
26
|
+
const Message = require('../structures/Message');
|
|
27
|
+
const Member = require('../structures/Member');
|
|
28
|
+
const { LumisError } = require('../errors/LumisError');
|
|
29
|
+
const { ErrorCodes } = require('../errors/ErrorCodes');
|
|
30
|
+
|
|
31
|
+
// ── New Differentiation Modules ──
|
|
32
|
+
const MiddlewareManager = require('../middleware/MiddlewareManager');
|
|
33
|
+
const EventInterceptor = require('../interceptors/EventInterceptor');
|
|
34
|
+
const ServiceContainer = require('../di/ServiceContainer');
|
|
35
|
+
const HotReloadManager = require('../hotreload/HotReloadManager');
|
|
36
|
+
const DashboardManager = require('../dashboard/DashboardManager');
|
|
37
|
+
|
|
38
|
+
class Client extends EventEmitter {
|
|
39
|
+
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
super();
|
|
42
|
+
|
|
43
|
+
this.options = options;
|
|
44
|
+
|
|
45
|
+
this.ready = false;
|
|
46
|
+
|
|
47
|
+
this.user = null;
|
|
48
|
+
|
|
49
|
+
this.token = null;
|
|
50
|
+
|
|
51
|
+
this.intents = this._resolveIntents(options.intents);
|
|
52
|
+
|
|
53
|
+
this.logger = new Logger({
|
|
54
|
+
prefix: 'Lumis',
|
|
55
|
+
level: options.logLevel || 'info',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.rest = new RESTManager(this, { logLevel: options.logLevel });
|
|
59
|
+
|
|
60
|
+
this.api = new APIRouter(this.rest);
|
|
61
|
+
|
|
62
|
+
this.ws = new WebSocketManager(this, { logLevel: options.logLevel });
|
|
63
|
+
|
|
64
|
+
this.cache = new CacheManager(options.cache || { adapter: 'memory' });
|
|
65
|
+
|
|
66
|
+
this.plugins = new PluginManager(this);
|
|
67
|
+
|
|
68
|
+
this.commands = new CommandManager(this, options.commands || {});
|
|
69
|
+
|
|
70
|
+
this.interactions = new InteractionHandler(this);
|
|
71
|
+
|
|
72
|
+
this.i18n = new I18nManager({
|
|
73
|
+
locale: options.locale || 'en',
|
|
74
|
+
fallback: 'en',
|
|
75
|
+
directory: options.localesDirectory,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.economy = new EconomyManager(this, options.economy || {});
|
|
79
|
+
|
|
80
|
+
this.inventory = new InventoryManager(this);
|
|
81
|
+
|
|
82
|
+
this.leveling = new LevelingSystem(this, options.leveling || {});
|
|
83
|
+
|
|
84
|
+
this.games = new GameSessionManager();
|
|
85
|
+
|
|
86
|
+
this.guildManager = new GuildManager(this);
|
|
87
|
+
|
|
88
|
+
this.music = new MusicManager(this, options.music || {});
|
|
89
|
+
|
|
90
|
+
// ── New Differentiation Features ──
|
|
91
|
+
|
|
92
|
+
this.middleware = new MiddlewareManager(this);
|
|
93
|
+
|
|
94
|
+
this.interceptors = new EventInterceptor(this);
|
|
95
|
+
|
|
96
|
+
this.services = new ServiceContainer(this);
|
|
97
|
+
|
|
98
|
+
this.hotReload = new HotReloadManager(this, this.commands);
|
|
99
|
+
|
|
100
|
+
this.dashboard = new DashboardManager(this, options.dashboard || {});
|
|
101
|
+
|
|
102
|
+
// Auto-start hot-reload if configured
|
|
103
|
+
if (options.commands?.hotReload && options.commands?.directory) {
|
|
104
|
+
this.on(Events.READY, () => {
|
|
105
|
+
this.hotReload.watch(options.commands.directory);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.guilds = new Collection();
|
|
110
|
+
|
|
111
|
+
this.channels = new Collection();
|
|
112
|
+
|
|
113
|
+
this.users = new Collection();
|
|
114
|
+
|
|
115
|
+
this.createdAt = new Date();
|
|
116
|
+
|
|
117
|
+
this.readyAt = null;
|
|
118
|
+
|
|
119
|
+
this.shard = process.env.SHARD_ID ? {
|
|
120
|
+
id: Number(process.env.SHARD_ID),
|
|
121
|
+
count: Number(process.env.TOTAL_SHARDS) || 1,
|
|
122
|
+
} : null;
|
|
123
|
+
|
|
124
|
+
if (this.shard) {
|
|
125
|
+
this._setupIPC();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._setupWSListeners();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Map of registered broadcast handlers for safe IPC actions.
|
|
133
|
+
* Handlers can be registered by name and invoked via ShardingManager.broadcastAction.
|
|
134
|
+
*/
|
|
135
|
+
_broadcastHandlers = new Map();
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Register a handler that can be invoked remotely via the sharding manager.
|
|
139
|
+
* @param {string} name
|
|
140
|
+
* @param {Function} fn
|
|
141
|
+
*/
|
|
142
|
+
registerBroadcastHandler(name, fn) {
|
|
143
|
+
if (typeof name !== 'string' || typeof fn !== 'function') {
|
|
144
|
+
throw new TypeError('registerBroadcastHandler requires a string name and a function');
|
|
145
|
+
}
|
|
146
|
+
this._broadcastHandlers.set(name, fn);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Unregister a previously registered handler.
|
|
151
|
+
* @param {string} name
|
|
152
|
+
*/
|
|
153
|
+
unregisterBroadcastHandler(name) {
|
|
154
|
+
this._broadcastHandlers.delete(name);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_setupIPC() {
|
|
158
|
+
process.on('message', async (message) => {
|
|
159
|
+
if (!message || typeof message !== 'object' || message._type !== 'BROADCAST_ACTION') return;
|
|
160
|
+
|
|
161
|
+
// Action-based, safe RPC handlers registered in each shard
|
|
162
|
+
if (typeof message.action === 'string') {
|
|
163
|
+
const handler = this._broadcastHandlers.get(message.action);
|
|
164
|
+
if (!handler) {
|
|
165
|
+
process.send({
|
|
166
|
+
_type: 'ACTION_RESULT',
|
|
167
|
+
_nonce: message._nonce,
|
|
168
|
+
_shardId: this.shard?.id ?? null,
|
|
169
|
+
_error: `No handler registered: ${message.action}`,
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const args = Array.isArray(message.args) ? message.args : [];
|
|
176
|
+
const result = await handler.apply(this, args);
|
|
177
|
+
process.send({
|
|
178
|
+
_type: 'ACTION_RESULT',
|
|
179
|
+
_nonce: message._nonce,
|
|
180
|
+
_shardId: this.shard?.id ?? null,
|
|
181
|
+
_result: result,
|
|
182
|
+
});
|
|
183
|
+
} catch (err) {
|
|
184
|
+
process.send({
|
|
185
|
+
_type: 'ACTION_RESULT',
|
|
186
|
+
_nonce: message._nonce,
|
|
187
|
+
_shardId: this.shard?.id ?? null,
|
|
188
|
+
_error: err?.message ?? String(err),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_setupWSListeners() {
|
|
196
|
+
this.ws.on('dispatch', (eventName, data) => {
|
|
197
|
+
this._handleDispatch(eventName, data);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
this.ws.on('reconnecting', (attempt) => {
|
|
201
|
+
this.emit(Events.RECONNECTING, attempt);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.ws.on('disconnect', (reason) => {
|
|
205
|
+
this.ready = false;
|
|
206
|
+
this.emit(Events.DISCONNECT, reason);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
this.ws.on('error', (error) => {
|
|
210
|
+
this.emit(Events.ERROR, error);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_handleDispatch(eventName, data) {
|
|
215
|
+
switch (eventName) {
|
|
216
|
+
case 'READY':
|
|
217
|
+
this._handleReady(data);
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'GUILD_CREATE':
|
|
221
|
+
this._handleGuildCreate(data);
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case 'GUILD_UPDATE': {
|
|
225
|
+
const guild = this.guilds.get(data.id);
|
|
226
|
+
if (guild) {
|
|
227
|
+
const old = { ...guild };
|
|
228
|
+
guild._patch(data);
|
|
229
|
+
this.emit(Events.GUILD_UPDATE, old, guild);
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case 'GUILD_DELETE': {
|
|
235
|
+
const guild = this.guilds.get(data.id);
|
|
236
|
+
if (guild) {
|
|
237
|
+
this.guilds.delete(data.id);
|
|
238
|
+
|
|
239
|
+
for (const [id, channel] of this.channels) {
|
|
240
|
+
if (channel.guild?.id === data.id) {
|
|
241
|
+
this.channels.delete(id);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
this.emit(Events.GUILD_DELETE, guild);
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case 'CHANNEL_CREATE': {
|
|
250
|
+
const guild = data.guild_id ? this.guilds.get(data.guild_id) : null;
|
|
251
|
+
const channel = Channel.create(this, data, guild);
|
|
252
|
+
this.channels.set(channel.id, channel);
|
|
253
|
+
if (guild) guild.channels.set(channel.id, channel);
|
|
254
|
+
this.emit(Events.CHANNEL_CREATE, channel);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case 'CHANNEL_UPDATE': {
|
|
259
|
+
const oldChannel = this.channels.get(data.id);
|
|
260
|
+
const guild = data.guild_id ? this.guilds.get(data.guild_id) : null;
|
|
261
|
+
const channel = Channel.create(this, data, guild);
|
|
262
|
+
this.channels.set(channel.id, channel);
|
|
263
|
+
if (guild) guild.channels.set(channel.id, channel);
|
|
264
|
+
this.emit(Events.CHANNEL_UPDATE, oldChannel, channel);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'CHANNEL_DELETE': {
|
|
269
|
+
const channel = this.channels.get(data.id);
|
|
270
|
+
if (channel) {
|
|
271
|
+
this.channels.delete(data.id);
|
|
272
|
+
if (channel.guild) channel.guild.channels.delete(data.id);
|
|
273
|
+
this.emit(Events.CHANNEL_DELETE, channel);
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'MESSAGE_CREATE': {
|
|
279
|
+
const channel = this.channels.get(data.channel_id);
|
|
280
|
+
const message = new Message(this, data, channel);
|
|
281
|
+
|
|
282
|
+
if (message.author) {
|
|
283
|
+
this.users.set(message.author.id, message.author);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (channel && channel.messages) {
|
|
287
|
+
channel.messages.set(message.id, message);
|
|
288
|
+
|
|
289
|
+
if (channel.messages.size > 200) {
|
|
290
|
+
const oldest = channel.messages.first();
|
|
291
|
+
if (oldest) channel.messages.delete(oldest.id);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.emit(Events.MESSAGE_CREATE, message);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case 'MESSAGE_UPDATE': {
|
|
300
|
+
const channel = this.channels.get(data.channel_id);
|
|
301
|
+
const oldMessage = channel?.messages?.get(data.id);
|
|
302
|
+
const message = new Message(this, data, channel);
|
|
303
|
+
|
|
304
|
+
if (channel && channel.messages) {
|
|
305
|
+
channel.messages.set(message.id, message);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.emit(Events.MESSAGE_UPDATE, oldMessage || null, message);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case 'MESSAGE_DELETE': {
|
|
313
|
+
const channel = this.channels.get(data.channel_id);
|
|
314
|
+
const message = channel?.messages?.get(data.id);
|
|
315
|
+
|
|
316
|
+
if (channel && channel.messages) {
|
|
317
|
+
channel.messages.delete(data.id);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.emit(Events.MESSAGE_DELETE, message || { id: data.id, channelId: data.channel_id });
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case 'GUILD_MEMBER_ADD': {
|
|
325
|
+
const guild = this.guilds.get(data.guild_id);
|
|
326
|
+
if (guild) {
|
|
327
|
+
const member = new Member(this, guild, data);
|
|
328
|
+
guild.members.set(member.user.id, member);
|
|
329
|
+
guild.memberCount++;
|
|
330
|
+
this.emit(Events.GUILD_MEMBER_ADD, member);
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case 'GUILD_MEMBER_UPDATE': {
|
|
336
|
+
const guild = this.guilds.get(data.guild_id);
|
|
337
|
+
if (guild) {
|
|
338
|
+
const oldMember = guild.members.get(data.user.id);
|
|
339
|
+
const member = new Member(this, guild, data);
|
|
340
|
+
guild.members.set(member.user.id, member);
|
|
341
|
+
this.emit(Events.GUILD_MEMBER_UPDATE, oldMember, member);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case 'GUILD_MEMBER_REMOVE': {
|
|
347
|
+
const guild = this.guilds.get(data.guild_id);
|
|
348
|
+
if (guild) {
|
|
349
|
+
const member = guild.members.get(data.user.id);
|
|
350
|
+
guild.members.delete(data.user.id);
|
|
351
|
+
guild.memberCount--;
|
|
352
|
+
this.emit(Events.GUILD_MEMBER_REMOVE, member || data.user);
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
case 'GUILD_ROLE_CREATE': {
|
|
358
|
+
const guild = this.guilds.get(data.guild_id);
|
|
359
|
+
if (guild) {
|
|
360
|
+
const Role = require('../structures/Role');
|
|
361
|
+
const role = new Role(this, guild, data.role);
|
|
362
|
+
guild.roles.set(role.id, role);
|
|
363
|
+
this.emit(Events.GUILD_ROLE_CREATE, role);
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
case 'GUILD_ROLE_UPDATE': {
|
|
369
|
+
const guild = this.guilds.get(data.guild_id);
|
|
370
|
+
if (guild) {
|
|
371
|
+
const Role = require('../structures/Role');
|
|
372
|
+
const oldRole = guild.roles.get(data.role.id);
|
|
373
|
+
const role = new Role(this, guild, data.role);
|
|
374
|
+
guild.roles.set(role.id, role);
|
|
375
|
+
this.emit(Events.GUILD_ROLE_UPDATE, oldRole, role);
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
case 'GUILD_ROLE_DELETE': {
|
|
381
|
+
const guild = this.guilds.get(data.guild_id);
|
|
382
|
+
if (guild) {
|
|
383
|
+
const role = guild.roles.get(data.role_id);
|
|
384
|
+
guild.roles.delete(data.role_id);
|
|
385
|
+
this.emit(Events.GUILD_ROLE_DELETE, role || { id: data.role_id });
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
case 'INTERACTION_CREATE': {
|
|
391
|
+
const interaction = createInteraction(this, data);
|
|
392
|
+
this.emit(Events.INTERACTION_CREATE, interaction);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
case 'VOICE_STATE_UPDATE': {
|
|
397
|
+
this.emit(Events.VOICE_STATE_UPDATE, data);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
case 'VOICE_SERVER_UPDATE': {
|
|
402
|
+
this.emit(Events.VOICE_SERVER_UPDATE, data);
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
case 'TYPING_START': {
|
|
407
|
+
this.emit(Events.TYPING_START, data);
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
case 'PRESENCE_UPDATE': {
|
|
412
|
+
this.emit(Events.PRESENCE_UPDATE, data);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
default:
|
|
417
|
+
|
|
418
|
+
this.emit(Events.DEBUG, `Unhandled dispatch: ${eventName}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_handleReady(data) {
|
|
423
|
+
this.user = new User(this, data.user);
|
|
424
|
+
this.users.set(this.user.id, this.user);
|
|
425
|
+
this.ready = true;
|
|
426
|
+
this.readyAt = new Date();
|
|
427
|
+
|
|
428
|
+
this.logger.info(`Ready! Logged in as ${this.user.tag}`);
|
|
429
|
+
|
|
430
|
+
this.plugins._notifyReady();
|
|
431
|
+
|
|
432
|
+
this.emit(Events.READY, this);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
_handleGuildCreate(data) {
|
|
436
|
+
let guild = this.guilds.get(data.id);
|
|
437
|
+
if (guild) {
|
|
438
|
+
guild._patch(data);
|
|
439
|
+
} else {
|
|
440
|
+
guild = new Guild(this, data);
|
|
441
|
+
this.guilds.set(guild.id, guild);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
this.emit(Events.GUILD_CREATE, guild);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
_resolveIntents(intents) {
|
|
448
|
+
if (!intents) return Intents.DEFAULT.bitfield;
|
|
449
|
+
if (intents instanceof Intents) return intents.bitfield;
|
|
450
|
+
if (typeof intents === 'bigint') return intents;
|
|
451
|
+
if (typeof intents === 'number') return BigInt(intents);
|
|
452
|
+
if (Array.isArray(intents)) {
|
|
453
|
+
return intents.reduce((acc, intent) => {
|
|
454
|
+
if (typeof intent === 'bigint') return acc | intent;
|
|
455
|
+
if (typeof intent === 'number') return acc | BigInt(intent);
|
|
456
|
+
if (typeof intent === 'string' && Intents.FLAGS[intent]) {
|
|
457
|
+
return acc | Intents.FLAGS[intent];
|
|
458
|
+
}
|
|
459
|
+
return acc;
|
|
460
|
+
}, 0n);
|
|
461
|
+
}
|
|
462
|
+
return Intents.DEFAULT.bitfield;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async login(token) {
|
|
466
|
+
if (!token || typeof token !== 'string') {
|
|
467
|
+
throw new LumisError(ErrorCodes.INVALID_TOKEN);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (this.ready) {
|
|
471
|
+
throw new LumisError(ErrorCodes.CLIENT_ALREADY_LOGGED_IN);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.token = token.replace(/^Bot\s*/i, '');
|
|
475
|
+
this.rest.setToken(this.token);
|
|
476
|
+
|
|
477
|
+
this.logger.info('Connecting to Discord...');
|
|
478
|
+
|
|
479
|
+
await this.ws.connect(this.token, this.intents);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async destroy() {
|
|
483
|
+
this.logger.info('Destroying client...');
|
|
484
|
+
|
|
485
|
+
this.plugins.unloadAll();
|
|
486
|
+
|
|
487
|
+
this.hotReload.destroy();
|
|
488
|
+
|
|
489
|
+
await this.dashboard.destroy();
|
|
490
|
+
|
|
491
|
+
await this.services.destroy();
|
|
492
|
+
|
|
493
|
+
this.middleware.clear();
|
|
494
|
+
|
|
495
|
+
this.interceptors.clear();
|
|
496
|
+
|
|
497
|
+
this.ws.destroy();
|
|
498
|
+
|
|
499
|
+
this.rest.destroy();
|
|
500
|
+
|
|
501
|
+
await this.cache.destroy();
|
|
502
|
+
|
|
503
|
+
this.guilds.clear();
|
|
504
|
+
this.channels.clear();
|
|
505
|
+
this.users.clear();
|
|
506
|
+
|
|
507
|
+
this.ready = false;
|
|
508
|
+
this.user = null;
|
|
509
|
+
this.token = null;
|
|
510
|
+
this.readyAt = null;
|
|
511
|
+
|
|
512
|
+
this.logger.success('Client destroyed successfully.');
|
|
513
|
+
this.emit(Events.DISCONNECT, 'Client destroyed');
|
|
514
|
+
this.removeAllListeners();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
static async gracefulShutdown(client) {
|
|
518
|
+
const logger = client?.logger || new Logger();
|
|
519
|
+
logger.warn('Shutdown signal received, cleaning up...');
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
if (client) await client.destroy();
|
|
523
|
+
logger.success('Graceful shutdown complete.');
|
|
524
|
+
process.exit(0);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
logger.error('Error during shutdown:', error);
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Register a global middleware (shortcut for client.middleware.use).
|
|
533
|
+
* @param {Function} fn - (ctx, next) => Promise<void>
|
|
534
|
+
*/
|
|
535
|
+
use(fn) {
|
|
536
|
+
this.middleware.use(fn);
|
|
537
|
+
return this;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
get ping() {
|
|
541
|
+
return this.ws.ping;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
get uptime() {
|
|
545
|
+
if (!this.readyAt) return null;
|
|
546
|
+
return Date.now() - this.readyAt.getTime();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
t(key, replacements) {
|
|
550
|
+
return this.i18n.t(key, replacements);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
module.exports = Client;
|