kythia-core 0.9.4-beta.2 → 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 +14 -0
- package/package.json +1 -1
- package/src/Kythia.js +12 -1
- package/src/database/KythiaModel.js +63 -17
- package/src/database/KythiaORM.js +46 -12
- package/src/database/KythiaSequelize.js +32 -7
- package/src/managers/AddonManager.js +13 -1
- package/src/managers/EventManager.js +1 -1
- package/src/managers/InteractionManager.js +66 -9
- package/src/managers/ShutdownManager.js +1 -1
package/changelog.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
|
|
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)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🔧 Changed
|
|
16
|
+
|
|
17
|
+
* Enhance KythiaORM with dialect-specific UPSERT queries for improved database compatibility. Adjusted InteractionManager button handler logic for better flexibility in handling interactions. ([2bfafc4](https://github.com/kenndeclouv/kythia-core/commit/2bfafc47e83b06f5f72a57da162d0c6b244bfe6d))
|
|
18
|
+
|
|
5
19
|
### [0.9.4-beta.2](https://github.com/kenndeclouv/kythia-core/compare/v0.9.4-beta.1...v0.9.4-beta.2) (2025-11-10)
|
|
6
20
|
|
|
7
21
|
|
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/database/KythiaORM.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
|
* A utility for intelligent, hash-based syncing of Sequelize models.
|
|
@@ -448,7 +448,7 @@ async function KythiaORM({ kythiaInstance, sequelize, KythiaModel, logger, confi
|
|
|
448
448
|
}
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
const modelNamesToSync = modelsToSync.map(m => m.model.name);
|
|
451
|
+
const modelNamesToSync = modelsToSync.map((m) => m.model.name);
|
|
452
452
|
|
|
453
453
|
logger.info(`🔄 Syncing models via sequelize.sync(): ${modelNamesToSync.join(', ')}...`);
|
|
454
454
|
await sequelize.sync({
|
|
@@ -459,16 +459,50 @@ async function KythiaORM({ kythiaInstance, sequelize, KythiaModel, logger, confi
|
|
|
459
459
|
|
|
460
460
|
logger.info('💾 Updating version hashes in model_versions table...');
|
|
461
461
|
for (const { model, newHash } of modelsToSync) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
462
|
+
const dialect = sequelize.getDialect();
|
|
463
|
+
let upsertQuery;
|
|
464
|
+
|
|
465
|
+
switch (dialect) {
|
|
466
|
+
case 'mysql':
|
|
467
|
+
case 'mariadb':
|
|
468
|
+
upsertQuery = `INSERT INTO ${versionTableName} (model_name, version_hash, updated_at)
|
|
469
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
470
|
+
ON DUPLICATE KEY UPDATE
|
|
471
|
+
version_hash = VALUES(version_hash), updated_at = CURRENT_TIMESTAMP`;
|
|
472
|
+
break;
|
|
473
|
+
|
|
474
|
+
case 'sqlite':
|
|
475
|
+
case 'postgres':
|
|
476
|
+
upsertQuery = `INSERT INTO ${versionTableName} (model_name, version_hash, updated_at)
|
|
477
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
478
|
+
ON CONFLICT(model_name)
|
|
479
|
+
DO UPDATE SET version_hash = excluded.version_hash, updated_at = CURRENT_TIMESTAMP`;
|
|
480
|
+
break;
|
|
481
|
+
|
|
482
|
+
case 'mssql':
|
|
483
|
+
upsertQuery = `MERGE INTO ${versionTableName} AS T
|
|
484
|
+
USING (VALUES (?, ?, CURRENT_TIMESTAMP)) AS S (model_name, version_hash, updated_at)
|
|
485
|
+
ON (T.model_name = S.model_name)
|
|
486
|
+
WHEN MATCHED THEN
|
|
487
|
+
UPDATE SET T.version_hash = S.version_hash, T.updated_at = S.updated_at
|
|
488
|
+
WHEN NOT MATCHED THEN
|
|
489
|
+
INSERT (model_name, version_hash, updated_at)
|
|
490
|
+
VALUES (S.model_name, S.version_hash, S.updated_at);`;
|
|
491
|
+
break;
|
|
492
|
+
|
|
493
|
+
default:
|
|
494
|
+
logger.warn(`Dialect "${dialect}" does not have a custom UPSERT, attempting fallback to ON CONFLICT...`);
|
|
495
|
+
upsertQuery = `INSERT INTO ${versionTableName} (model_name, version_hash, updated_at)
|
|
496
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
497
|
+
ON CONFLICT(model_name)
|
|
498
|
+
DO UPDATE SET version_hash = excluded.version_hash, updated_at = CURRENT_TIMESTAMP`;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
await sequelize.query(upsertQuery, {
|
|
502
|
+
replacements: [model.name, newHash],
|
|
503
|
+
|
|
504
|
+
type: sequelize.QueryTypes.INSERT,
|
|
505
|
+
});
|
|
472
506
|
}
|
|
473
507
|
|
|
474
508
|
logger.info('✨ Database sync completed successfully!');
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* @file src/database/KythiaSequelize.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
|
-
* Main Sequelize connection factory for the application
|
|
10
|
+
* Main Sequelize connection factory for the application.
|
|
11
11
|
*/
|
|
12
12
|
const { Sequelize } = require('sequelize');
|
|
13
13
|
|
|
@@ -21,14 +21,35 @@ const { Sequelize } = require('sequelize');
|
|
|
21
21
|
*/
|
|
22
22
|
function createSequelizeInstance(config, logger) {
|
|
23
23
|
const dbConfig = config.db || {};
|
|
24
|
+
if (!config.db) config.db = dbConfig;
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
let driver = dbConfig.driver || process.env.DB_DRIVER;
|
|
27
|
+
let name = dbConfig.name || process.env.DB_NAME;
|
|
28
|
+
|
|
29
|
+
if (!driver || driver === '') {
|
|
30
|
+
driver = 'sqlite';
|
|
31
|
+
if (logger) logger.info('💡 DB driver not specified. Defaulting to: sqlite');
|
|
32
|
+
} else {
|
|
33
|
+
driver = driver.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (driver === 'sqlite') {
|
|
37
|
+
if (!name || name === '') {
|
|
38
|
+
name = 'kythiadata.sqlite';
|
|
39
|
+
if (logger) logger.info('💡 DB name for sqlite not specified. Defaulting to: kythiadata.sqlite');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
dbConfig.driver = driver;
|
|
44
|
+
dbConfig.name = name;
|
|
45
|
+
|
|
46
|
+
const dialect = dbConfig.driver;
|
|
47
|
+
const dbName = dbConfig.name;
|
|
27
48
|
const dbUser = dbConfig.user || process.env.DB_USER;
|
|
28
49
|
const dbPassword = dbConfig.password || process.env.DB_PASSWORD;
|
|
29
50
|
const dbHost = dbConfig.host || process.env.DB_HOST;
|
|
30
51
|
const dbPort = dbConfig.port || process.env.DB_PORT;
|
|
31
|
-
|
|
52
|
+
|
|
32
53
|
const dbSocket = dbConfig.socketPath || process.env.DB_SOCKET_PATH;
|
|
33
54
|
const dbSsl = dbConfig.ssl || process.env.DB_SSL;
|
|
34
55
|
const dbDialectOptions = dbConfig.dialectOptions || process.env.DB_DIALECT_OPTIONS;
|
|
@@ -45,12 +66,16 @@ function createSequelizeInstance(config, logger) {
|
|
|
45
66
|
charset: 'utf8mb4',
|
|
46
67
|
collate: 'utf8mb4_unicode_ci',
|
|
47
68
|
},
|
|
48
|
-
timezone: dbConfig.timezone || '+00:00',
|
|
49
69
|
};
|
|
50
70
|
|
|
71
|
+
if (dialect !== 'sqlite') {
|
|
72
|
+
seqConfig.timezone = dbConfig.timezone || '+00:00';
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
switch (dialect) {
|
|
52
76
|
case 'sqlite':
|
|
53
|
-
seqConfig.storage =
|
|
77
|
+
seqConfig.storage = dbConfig.name;
|
|
78
|
+
delete seqConfig.database;
|
|
54
79
|
break;
|
|
55
80
|
|
|
56
81
|
case 'mysql':
|
|
@@ -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,8 +281,23 @@ class InteractionManager {
|
|
|
280
281
|
* @private
|
|
281
282
|
*/
|
|
282
283
|
async _handleButton(interaction) {
|
|
283
|
-
const
|
|
284
|
-
|
|
284
|
+
const customIdPrefix = interaction.customId.includes('|') ? interaction.customId.split('|')[0] : interaction.customId.split(':')[0];
|
|
285
|
+
|
|
286
|
+
const handler = this.buttonHandlers.get(customIdPrefix);
|
|
287
|
+
|
|
288
|
+
if (handler) {
|
|
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
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
this.logger.error(`Handler for button ${customIdPrefix} has an invalid format`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
285
301
|
}
|
|
286
302
|
|
|
287
303
|
/**
|
|
@@ -290,10 +306,52 @@ class InteractionManager {
|
|
|
290
306
|
*/
|
|
291
307
|
async _handleModalSubmit(interaction) {
|
|
292
308
|
const customIdPrefix = interaction.customId.includes('|') ? interaction.customId.split('|')[0] : interaction.customId.split(':')[0];
|
|
293
|
-
|
|
309
|
+
|
|
310
|
+
this.logger.info(`Modal submit - customId: ${interaction.customId}, prefix: ${customIdPrefix}`);
|
|
311
|
+
|
|
294
312
|
const handler = this.modalHandlers.get(customIdPrefix);
|
|
295
|
-
this.logger.info(
|
|
296
|
-
|
|
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
|
+
}
|
|
297
355
|
}
|
|
298
356
|
|
|
299
357
|
/**
|
|
@@ -406,7 +464,6 @@ class InteractionManager {
|
|
|
406
464
|
}
|
|
407
465
|
}
|
|
408
466
|
|
|
409
|
-
// Ensure logger is available in the execution context
|
|
410
467
|
if (!interaction.logger) {
|
|
411
468
|
interaction.logger = this.logger;
|
|
412
469
|
}
|