kythia-core 0.9.5-beta → 0.10.1-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/.husky/pre-commit +4 -0
- package/README.md +13 -4
- package/biome.json +40 -0
- package/bun.lock +445 -0
- package/changelog.md +7 -0
- package/index.js +1 -3
- package/package.json +57 -44
- package/src/Kythia.js +530 -449
- package/src/KythiaClient.js +90 -49
- package/src/database/KythiaMigrator.js +116 -0
- package/src/database/KythiaModel.js +1519 -1050
- package/src/database/KythiaSequelize.js +104 -95
- package/src/database/KythiaStorage.js +117 -0
- package/src/database/ModelLoader.js +79 -0
- package/src/managers/AddonManager.js +1190 -946
- package/src/managers/EventManager.js +80 -75
- package/src/managers/InteractionManager.js +794 -589
- package/src/managers/ShutdownManager.js +200 -179
- package/src/structures/BaseCommand.js +40 -36
- package/src/utils/color.js +157 -153
- package/src/utils/formatter.js +81 -81
- package/src/utils/index.js +2 -2
- package/src/database/KythiaORM.js +0 -520
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file src/managers/ShutdownManager.js
|
|
5
5
|
* @copyright © 2025 kenndeclouv
|
|
6
6
|
* @assistant chaa & graa
|
|
7
|
-
* @version 0.
|
|
7
|
+
* @version 0.10.0-beta
|
|
8
8
|
*
|
|
9
9
|
* @description
|
|
10
10
|
* Handles graceful shutdown procedures including interval tracking,
|
|
@@ -14,184 +14,205 @@
|
|
|
14
14
|
const exitHook = require('async-exit-hook');
|
|
15
15
|
|
|
16
16
|
class ShutdownManager {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
17
|
+
/**
|
|
18
|
+
* 🏗️ ShutdownManager Constructor
|
|
19
|
+
* @param {Object} client - Discord client instance
|
|
20
|
+
* @param {Object} container - Dependency container
|
|
21
|
+
*/
|
|
22
|
+
constructor({ client, container }) {
|
|
23
|
+
this.client = client;
|
|
24
|
+
this.container = container;
|
|
25
|
+
this._activeIntervals = new Set();
|
|
26
|
+
this._messagesWithActiveCollectors = new Set();
|
|
27
|
+
this._collectorPatched = false;
|
|
28
|
+
this._cleanupAttached = false;
|
|
29
|
+
|
|
30
|
+
this.logger = this.container.logger;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 🕵️♂️ [GLOBAL PATCH] Overrides global interval functions to track all active intervals.
|
|
35
|
+
* This allows for a truly generic and scalable graceful shutdown of all timed tasks.
|
|
36
|
+
*/
|
|
37
|
+
initializeGlobalIntervalTracker() {
|
|
38
|
+
if (!this._activeIntervals) this._activeIntervals = new Set();
|
|
39
|
+
|
|
40
|
+
const botInstance = this;
|
|
41
|
+
const originalSetInterval = global.setInterval;
|
|
42
|
+
const originalClearInterval = global.clearInterval;
|
|
43
|
+
|
|
44
|
+
global.setInterval = function (...args) {
|
|
45
|
+
const intervalId = originalSetInterval.apply(this, args);
|
|
46
|
+
|
|
47
|
+
botInstance._activeIntervals.add(intervalId);
|
|
48
|
+
return intervalId;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
global.clearInterval = function (intervalId) {
|
|
52
|
+
originalClearInterval.apply(this, [intervalId]);
|
|
53
|
+
|
|
54
|
+
botInstance._activeIntervals.delete(intervalId);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.logger.info(
|
|
58
|
+
'✅ Global setInterval/clearInterval has been patched for tracking.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disableRecursively(components) {
|
|
63
|
+
return components.map((comp) => {
|
|
64
|
+
if (comp.components && Array.isArray(comp.components)) {
|
|
65
|
+
comp.components = disableRecursively(comp.components);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (comp.type === 2 || comp.type === 3 || comp.type >= 5) {
|
|
69
|
+
return { ...comp, disabled: true };
|
|
70
|
+
}
|
|
71
|
+
return comp;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 🛑 [FINAL ARCHITECTURE v5] Manages ALL graceful shutdown procedures.
|
|
77
|
+
* This version patches the core message sending/editing methods to automatically
|
|
78
|
+
* track ANY message with components, regardless of how its interactions are handled.
|
|
79
|
+
*/
|
|
80
|
+
initializeShutdownCollectors() {
|
|
81
|
+
if (!this._messagesWithActiveCollectors)
|
|
82
|
+
this._messagesWithActiveCollectors = new Set();
|
|
83
|
+
|
|
84
|
+
if (!this._collectorPatched) {
|
|
85
|
+
const origCreateCollector =
|
|
86
|
+
require('discord.js').Message.prototype.createMessageComponentCollector;
|
|
87
|
+
const botInstance = this;
|
|
88
|
+
|
|
89
|
+
require('discord.js').Message.prototype.createMessageComponentCollector =
|
|
90
|
+
function (...args) {
|
|
91
|
+
const collector = origCreateCollector.apply(this, args);
|
|
92
|
+
|
|
93
|
+
if (botInstance._messagesWithActiveCollectors) {
|
|
94
|
+
botInstance._messagesWithActiveCollectors.add(this);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
collector.once('end', () => {
|
|
98
|
+
if (botInstance._messagesWithActiveCollectors) {
|
|
99
|
+
botInstance._messagesWithActiveCollectors.delete(this);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return collector;
|
|
104
|
+
};
|
|
105
|
+
this._collectorPatched = true;
|
|
106
|
+
this.logger.info(
|
|
107
|
+
'✅ Corrected collector-based component tracking has been patched.',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!this._cleanupAttached) {
|
|
112
|
+
const cleanupAndFlush = async (callback) => {
|
|
113
|
+
this.logger.info('🛑 Graceful shutdown initiated...');
|
|
114
|
+
|
|
115
|
+
if (this._activeIntervals && this._activeIntervals.size > 0) {
|
|
116
|
+
this.logger.info(
|
|
117
|
+
`🛑 Halting ${this._activeIntervals.size} active global intervals...`,
|
|
118
|
+
);
|
|
119
|
+
for (const intervalId of this._activeIntervals) {
|
|
120
|
+
clearInterval(intervalId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const messagesToProcess = this._messagesWithActiveCollectors;
|
|
125
|
+
|
|
126
|
+
if (messagesToProcess && messagesToProcess.size > 0) {
|
|
127
|
+
this.logger.info(
|
|
128
|
+
`🛑 Disabling components on up to ${messagesToProcess.size} messages.`,
|
|
129
|
+
);
|
|
130
|
+
const editPromises = [];
|
|
131
|
+
|
|
132
|
+
for (const msg of messagesToProcess) {
|
|
133
|
+
if (!msg.editable || !msg.components || msg.components.length === 0)
|
|
134
|
+
continue;
|
|
135
|
+
try {
|
|
136
|
+
const rawComponents = msg.components.map((c) => c.toJSON());
|
|
137
|
+
const disabledComponents = disableRecursively(rawComponents);
|
|
138
|
+
editPromises.push(
|
|
139
|
+
msg.edit({ components: disabledComponents }).catch(() => {}),
|
|
140
|
+
);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
this.logger.error(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
await Promise.allSettled(editPromises);
|
|
146
|
+
}
|
|
147
|
+
this.logger.info('✅ Component cleanup complete.');
|
|
148
|
+
|
|
149
|
+
this.logger.info('🚰 Flushing remaining logs...');
|
|
150
|
+
this.logger.on('finish', () => {
|
|
151
|
+
console.log(
|
|
152
|
+
'⏳ Logger has flushed. Kythia is now safely shutting down.',
|
|
153
|
+
);
|
|
154
|
+
if (callback) callback();
|
|
155
|
+
});
|
|
156
|
+
this.logger.end();
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
console.log('⏳ Logger flush timeout. Forcing exit.');
|
|
159
|
+
if (callback) callback();
|
|
160
|
+
}, 4000);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
exitHook(cleanupAndFlush);
|
|
164
|
+
process.on('unhandledRejection', (error) => {
|
|
165
|
+
this.logger.error('‼️ UNHANDLED PROMISE REJECTION:', error);
|
|
166
|
+
});
|
|
167
|
+
process.on('uncaughtException', (error) => {
|
|
168
|
+
this.logger.error('‼️ UNCAUGHT EXCEPTION! Bot will shutdown.', error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
this._cleanupAttached = true;
|
|
173
|
+
this.logger.info(
|
|
174
|
+
'🛡️ Graceful shutdown and error handlers are now active.',
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Initialize all shutdown procedures
|
|
181
|
+
*/
|
|
182
|
+
initialize() {
|
|
183
|
+
this.initializeGlobalIntervalTracker();
|
|
184
|
+
this.initializeShutdownCollectors();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get active intervals count
|
|
189
|
+
* @returns {number} Number of active intervals
|
|
190
|
+
*/
|
|
191
|
+
getActiveIntervalsCount() {
|
|
192
|
+
return this._activeIntervals ? this._activeIntervals.size : 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get messages with active collectors count
|
|
197
|
+
* @returns {number} Number of messages with active collectors
|
|
198
|
+
*/
|
|
199
|
+
getActiveCollectorsCount() {
|
|
200
|
+
return this._messagesWithActiveCollectors
|
|
201
|
+
? this._messagesWithActiveCollectors.size
|
|
202
|
+
: 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Force cleanup (for testing purposes)
|
|
207
|
+
*/
|
|
208
|
+
forceCleanup() {
|
|
209
|
+
if (this._activeIntervals) {
|
|
210
|
+
for (const intervalId of this._activeIntervals) {
|
|
211
|
+
clearInterval(intervalId);
|
|
212
|
+
}
|
|
213
|
+
this._activeIntervals.clear();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
195
216
|
}
|
|
196
217
|
|
|
197
218
|
module.exports = ShutdownManager;
|
|
@@ -4,46 +4,50 @@
|
|
|
4
4
|
* @description Base class for all command types (slash commands, context menus, etc.)
|
|
5
5
|
*/
|
|
6
6
|
class BaseCommand {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} container - Dependency injection container
|
|
9
|
+
*/
|
|
10
|
+
constructor(container) {
|
|
11
|
+
if (!container) {
|
|
12
|
+
throw new Error('Container is required for BaseCommand');
|
|
13
|
+
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
this.container = container;
|
|
16
|
+
this.client = container.client;
|
|
17
|
+
this.logger = container.logger;
|
|
18
|
+
this.t = container.t;
|
|
19
|
+
this.models = container.models;
|
|
20
|
+
this.kythiaConfig = container.kythiaConfig;
|
|
21
|
+
this.helpers = container.helpers;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
this.data = {
|
|
24
|
+
name: 'base-command',
|
|
25
|
+
description: 'Base command description',
|
|
26
|
+
cooldown: 10,
|
|
27
|
+
permissions: [],
|
|
28
|
+
ownerOnly: false,
|
|
29
|
+
teamOnly: false,
|
|
30
|
+
guildOnly: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Main command execution method (must be implemented by child classes)
|
|
36
|
+
* @param {import('discord.js').CommandInteraction} interaction - The interaction object
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
async execute(interaction) {
|
|
40
|
+
if (interaction.options?.getSubcommand?.(false)) {
|
|
41
|
+
this.logger.warn(
|
|
42
|
+
`Command group ${this.constructor.name} execute called - this should be handled by subcommand`,
|
|
43
|
+
);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Execute method not implemented for ${this.constructor.name}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
module.exports = BaseCommand;
|