kythia-core 0.9.4-beta.3 → 0.9.5-beta
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/changelog.md +7 -0
- package/package.json +1 -1
- package/src/Kythia.js +12 -1
- package/src/database/KythiaModel.js +63 -17
- package/src/database/KythiaORM.js +1 -1
- package/src/database/KythiaSequelize.js +1 -1
- package/src/managers/AddonManager.js +13 -1
- package/src/managers/EventManager.js +1 -1
- package/src/managers/InteractionManager.js +60 -14
- package/src/managers/ShutdownManager.js +1 -1
package/changelog.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.9.5-beta](https://github.com/kenndeclouv/kythia-core/compare/v0.9.4-beta.3...v0.9.5-beta) (2025-11-11)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ Added
|
|
9
|
+
|
|
10
|
+
* Add select menu handler registration and interaction handling in Kythia and InteractionManager, enhancing user interaction capabilities. ([a27e84d](https://github.com/kenndeclouv/kythia-core/commit/a27e84d60f6f6077b073582f7c705eb28bc4d329))
|
|
11
|
+
|
|
5
12
|
### [0.9.4-beta.3](https://github.com/kenndeclouv/kythia-core/compare/v0.9.4-beta.2...v0.9.4-beta.3) (2025-11-10)
|
|
6
13
|
|
|
7
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kythia-core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5-beta",
|
|
4
4
|
"description": "Core library for the Kythia main Discord bot: extensible, modular, and scalable foundation for commands, components, and event management.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/src/Kythia.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file src/Kythia.js
|
|
5
5
|
* @copyright © 2025 kenndeclouv
|
|
6
6
|
* @assistant chaa & graa
|
|
7
|
-
* @version 0.9.
|
|
7
|
+
* @version 0.9.5-beta
|
|
8
8
|
*
|
|
9
9
|
* @description
|
|
10
10
|
* This file contains the main Bot class - acting as an orchestrator (CEO) that
|
|
@@ -187,6 +187,17 @@ class Kythia {
|
|
|
187
187
|
this.addonManager.registerModalHandler(customIdPrefix, handler);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* 🟦 Register Select Menu Handler
|
|
192
|
+
* Delegates to AddonManager
|
|
193
|
+
* @param {string} customIdPrefix - The prefix of the select menu customId
|
|
194
|
+
* @param {Function} handler - The handler function to execute
|
|
195
|
+
*/
|
|
196
|
+
registerSelectMenuHandler(customIdPrefix, handler) {
|
|
197
|
+
if (this.addonManager) {
|
|
198
|
+
this.addonManager.registerSelectMenuHandler(customIdPrefix, handler);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
190
201
|
|
|
191
202
|
/**
|
|
192
203
|
* 🛡️ Validate License (Stub)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file src/database/KythiaModel.js
|
|
5
5
|
* @copyright © 2025 kenndeclouv
|
|
6
6
|
* @assistant chaa & graa
|
|
7
|
-
* @version 0.9.
|
|
7
|
+
* @version 0.9.5-beta
|
|
8
8
|
*
|
|
9
9
|
* @description
|
|
10
10
|
* Caching layer for Sequelize Models, now sharding-aware. When config.db.redis.shard === true,
|
|
@@ -800,43 +800,89 @@ class KythiaModel extends Model {
|
|
|
800
800
|
return queryPromise;
|
|
801
801
|
}
|
|
802
802
|
|
|
803
|
+
|
|
803
804
|
/**
|
|
804
|
-
* 📦
|
|
805
|
+
* 📦 Finds a record by the specified where condition, using cache if available; if not found, creates it and caches the result.
|
|
806
|
+
* Will update an existing cached instance with defaults (if necessary) and save any new/changed data to both DB and cache.
|
|
805
807
|
*/
|
|
806
808
|
static async findOrCreateWithCache(options) {
|
|
807
809
|
if (!options || !options.where) {
|
|
808
810
|
throw new Error("findOrCreateWithCache requires a 'where' option.");
|
|
809
811
|
}
|
|
810
812
|
|
|
811
|
-
const { cacheTags, noCache, ...
|
|
813
|
+
const { where, defaults, cacheTags, noCache, ...otherOptions } = options;
|
|
814
|
+
|
|
815
|
+
if (noCache) {
|
|
816
|
+
return this.findOrCreate(options);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const normalizedWhere = this._normalizeFindOptions(where).where;
|
|
820
|
+
const cacheKey = this.getCacheKey(normalizedWhere);
|
|
821
|
+
|
|
822
|
+
const cacheResult = await this.getCachedEntry(cacheKey, otherOptions.include);
|
|
812
823
|
|
|
813
|
-
const cacheKey = this.getCacheKey(options.where);
|
|
814
|
-
const cacheResult = await this.getCachedEntry(cacheKey);
|
|
815
824
|
if (cacheResult.hit && cacheResult.data) {
|
|
816
|
-
|
|
825
|
+
const instance = cacheResult.data;
|
|
826
|
+
let needsUpdate = false;
|
|
827
|
+
|
|
828
|
+
if (defaults && typeof defaults === 'object') {
|
|
829
|
+
for (const key in defaults) {
|
|
830
|
+
if (instance[key] === undefined || String(instance[key]) !== String(defaults[key])) {
|
|
831
|
+
instance[key] = defaults[key];
|
|
832
|
+
needsUpdate = true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (needsUpdate) {
|
|
838
|
+
await instance.saveAndUpdateCache();
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
return [instance, false];
|
|
817
842
|
}
|
|
843
|
+
|
|
818
844
|
this.cacheStats.misses++;
|
|
819
845
|
if (this.pendingQueries.has(cacheKey)) {
|
|
820
846
|
return this.pendingQueries.get(cacheKey);
|
|
821
847
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
848
|
+
|
|
849
|
+
const findPromise = this.findOne({ where, ...otherOptions })
|
|
850
|
+
.then(async (instance) => {
|
|
851
|
+
if (instance) {
|
|
852
|
+
let needsUpdate = false;
|
|
853
|
+
if (defaults && typeof defaults === 'object') {
|
|
854
|
+
for (const key in defaults) {
|
|
855
|
+
if (instance[key] === undefined || String(instance[key]) !== String(defaults[key])) {
|
|
856
|
+
instance[key] = defaults[key];
|
|
857
|
+
needsUpdate = true;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
829
860
|
}
|
|
830
|
-
|
|
861
|
+
|
|
862
|
+
if (needsUpdate) {
|
|
863
|
+
await instance.saveAndUpdateCache();
|
|
864
|
+
} else {
|
|
865
|
+
const tags = [`${this.name}`, `${this.name}:${this.primaryKeyAttribute}:${instance[this.primaryKeyAttribute]}`];
|
|
866
|
+
await this.setCacheEntry(cacheKey, instance, undefined, tags);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return [instance, false];
|
|
870
|
+
} else {
|
|
871
|
+
const createData = { ...where, ...defaults };
|
|
872
|
+
const newInstance = await this.create(createData);
|
|
873
|
+
|
|
874
|
+
const tags = [`${this.name}`, `${this.name}:${this.primaryKeyAttribute}:${newInstance[this.primaryKeyAttribute]}`];
|
|
875
|
+
await this.setCacheEntry(cacheKey, newInstance, undefined, tags);
|
|
876
|
+
|
|
877
|
+
return [newInstance, true];
|
|
831
878
|
}
|
|
832
|
-
return [instance, created];
|
|
833
879
|
})
|
|
834
880
|
.finally(() => {
|
|
835
881
|
this.pendingQueries.delete(cacheKey);
|
|
836
882
|
});
|
|
837
883
|
|
|
838
|
-
this.pendingQueries.set(cacheKey,
|
|
839
|
-
return
|
|
884
|
+
this.pendingQueries.set(cacheKey, findPromise);
|
|
885
|
+
return findPromise;
|
|
840
886
|
}
|
|
841
887
|
|
|
842
888
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file src/managers/AddonManager.js
|
|
5
5
|
* @copyright © 2025 kenndeclouv
|
|
6
6
|
* @assistant chaa & graa
|
|
7
|
-
* @version 0.9.
|
|
7
|
+
* @version 0.9.5-beta
|
|
8
8
|
*
|
|
9
9
|
* @description
|
|
10
10
|
* Handles all addon loading, command registration, and component management.
|
|
@@ -57,6 +57,18 @@ class AddonManager {
|
|
|
57
57
|
}
|
|
58
58
|
this.buttonHandlers.set(customId, handler);
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* 🔽 Register Select Menu Handler
|
|
62
|
+
* Registers a handler function for a specific select menu customId prefix.
|
|
63
|
+
* @param {string} customIdPrefix - The prefix of the select menu customId
|
|
64
|
+
* @param {Function} handler - The handler function to execute
|
|
65
|
+
*/
|
|
66
|
+
registerSelectMenuHandler(customIdPrefix, handler) {
|
|
67
|
+
if (this.selectMenuHandlers.has(customIdPrefix)) {
|
|
68
|
+
this.logger.warn(`[REGISTRATION] Warning: Select menu handler for [${customIdPrefix}] already exists and will be overwritten.`);
|
|
69
|
+
}
|
|
70
|
+
this.selectMenuHandlers.set(customIdPrefix, handler);
|
|
71
|
+
}
|
|
60
72
|
|
|
61
73
|
/**
|
|
62
74
|
* 📝 Register Modal Handler
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file src/managers/InteractionManager.js
|
|
5
5
|
* @copyright © 2025 kenndeclouv
|
|
6
6
|
* @assistant chaa & graa
|
|
7
|
-
* @version 0.9.
|
|
7
|
+
* @version 0.9.5-beta
|
|
8
8
|
*
|
|
9
9
|
* @description
|
|
10
10
|
* Handles all Discord interaction events including slash commands, buttons, modals,
|
|
@@ -77,6 +77,8 @@ class InteractionManager {
|
|
|
77
77
|
await this._handleButton(interaction);
|
|
78
78
|
} else if (interaction.isModalSubmit()) {
|
|
79
79
|
await this._handleModalSubmit(interaction);
|
|
80
|
+
} else if (interaction.isAnySelectMenu()) {
|
|
81
|
+
await this._handleSelectMenu(interaction);
|
|
80
82
|
} else if (interaction.isUserContextMenuCommand() || interaction.isMessageContextMenuCommand()) {
|
|
81
83
|
await this._handleContextMenuCommand(interaction, formatPerms);
|
|
82
84
|
}
|
|
@@ -222,11 +224,10 @@ class InteractionManager {
|
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
if (typeof command.execute === 'function') {
|
|
225
|
-
// Ensure logger is defined for the command execution context
|
|
226
227
|
if (!interaction.logger) {
|
|
227
228
|
interaction.logger = this.logger;
|
|
228
229
|
}
|
|
229
|
-
|
|
230
|
+
|
|
230
231
|
if (this.container && !this.container.logger) {
|
|
231
232
|
this.container.logger = this.logger;
|
|
232
233
|
}
|
|
@@ -280,17 +281,21 @@ class InteractionManager {
|
|
|
280
281
|
* @private
|
|
281
282
|
*/
|
|
282
283
|
async _handleButton(interaction) {
|
|
283
|
-
const customIdPrefix = interaction.customId.includes('|')
|
|
284
|
-
? interaction.customId.split('|')[0]
|
|
285
|
-
: interaction.customId.split(':')[0];
|
|
284
|
+
const customIdPrefix = interaction.customId.includes('|') ? interaction.customId.split('|')[0] : interaction.customId.split(':')[0];
|
|
286
285
|
|
|
287
|
-
const handler = this.buttonHandlers.get(customIdPrefix);
|
|
286
|
+
const handler = this.buttonHandlers.get(customIdPrefix);
|
|
288
287
|
|
|
289
288
|
if (handler) {
|
|
290
|
-
if (handler.
|
|
291
|
-
await handler(interaction, this.container);
|
|
289
|
+
if (typeof handler === 'object' && typeof handler.execute === 'function') {
|
|
290
|
+
await handler.execute(interaction, this.container);
|
|
291
|
+
} else if (typeof handler === 'function') {
|
|
292
|
+
if (handler.length === 2) {
|
|
293
|
+
await handler(interaction, this.container);
|
|
294
|
+
} else {
|
|
295
|
+
await handler(interaction);
|
|
296
|
+
}
|
|
292
297
|
} else {
|
|
293
|
-
|
|
298
|
+
this.logger.error(`Handler for button ${customIdPrefix} has an invalid format`);
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
}
|
|
@@ -301,10 +306,52 @@ class InteractionManager {
|
|
|
301
306
|
*/
|
|
302
307
|
async _handleModalSubmit(interaction) {
|
|
303
308
|
const customIdPrefix = interaction.customId.includes('|') ? interaction.customId.split('|')[0] : interaction.customId.split(':')[0];
|
|
304
|
-
|
|
309
|
+
|
|
310
|
+
this.logger.info(`Modal submit - customId: ${interaction.customId}, prefix: ${customIdPrefix}`);
|
|
311
|
+
|
|
305
312
|
const handler = this.modalHandlers.get(customIdPrefix);
|
|
306
|
-
this.logger.info(
|
|
307
|
-
|
|
313
|
+
this.logger.info(`Modal handler found: ${!!handler}`);
|
|
314
|
+
|
|
315
|
+
if (handler) {
|
|
316
|
+
if (typeof handler === 'object' && typeof handler.execute === 'function') {
|
|
317
|
+
await handler.execute(interaction, this.container);
|
|
318
|
+
} else if (typeof handler === 'function') {
|
|
319
|
+
if (handler.length === 2) {
|
|
320
|
+
await handler(interaction, this.container);
|
|
321
|
+
} else {
|
|
322
|
+
await handler(interaction);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
this.logger.error(`Handler untuk modal ${customIdPrefix} formatnya salah (bukan fungsi atau { execute: ... })`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Handle select menu interactions
|
|
332
|
+
* @private
|
|
333
|
+
*/
|
|
334
|
+
async _handleSelectMenu(interaction) {
|
|
335
|
+
const customIdPrefix = interaction.customId.includes('|') ? interaction.customId.split('|')[0] : interaction.customId.split(':')[0];
|
|
336
|
+
|
|
337
|
+
this.logger.info(`Select menu submit - customId: ${interaction.customId}, prefix: ${customIdPrefix}`);
|
|
338
|
+
|
|
339
|
+
const handler = this.selectMenuHandlers.get(customIdPrefix);
|
|
340
|
+
this.logger.info(`Select menu handler found: ${!!handler}`);
|
|
341
|
+
|
|
342
|
+
if (handler) {
|
|
343
|
+
if (typeof handler === 'object' && typeof handler.execute === 'function') {
|
|
344
|
+
await handler.execute(interaction, this.container);
|
|
345
|
+
} else if (typeof handler === 'function') {
|
|
346
|
+
if (handler.length === 2) {
|
|
347
|
+
await handler(interaction, this.container);
|
|
348
|
+
} else {
|
|
349
|
+
await handler(interaction);
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
this.logger.error(`Handler untuk select menu ${customIdPrefix} formatnya salah`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
308
355
|
}
|
|
309
356
|
|
|
310
357
|
/**
|
|
@@ -417,7 +464,6 @@ class InteractionManager {
|
|
|
417
464
|
}
|
|
418
465
|
}
|
|
419
466
|
|
|
420
|
-
// Ensure logger is available in the execution context
|
|
421
467
|
if (!interaction.logger) {
|
|
422
468
|
interaction.logger = this.logger;
|
|
423
469
|
}
|