@julanzw/ttoolbox-discordjs-framework 1.0.1

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.
Files changed (45) hide show
  1. package/LICENSE +675 -0
  2. package/README.md +77 -0
  3. package/dist/classes/Command.class.d.ts +169 -0
  4. package/dist/classes/Command.class.js +156 -0
  5. package/dist/classes/CommandManager.class.d.ts +69 -0
  6. package/dist/classes/CommandManager.class.js +149 -0
  7. package/dist/classes/DiscordHandler.class.d.ts +241 -0
  8. package/dist/classes/DiscordHandler.class.js +222 -0
  9. package/dist/classes/InteractionError.class.d.ts +8 -0
  10. package/dist/classes/InteractionError.class.js +11 -0
  11. package/dist/classes/ModalManager.class.d.ts +154 -0
  12. package/dist/classes/ModalManager.class.js +205 -0
  13. package/dist/classes/SubcommandGroup.class.d.ts +127 -0
  14. package/dist/classes/SubcommandGroup.class.js +156 -0
  15. package/dist/index.d.ts +18 -0
  16. package/dist/index.js +17 -0
  17. package/dist/types/button.d.ts +2 -0
  18. package/dist/types/button.js +1 -0
  19. package/dist/types/channel.d.ts +2 -0
  20. package/dist/types/channel.js +1 -0
  21. package/dist/types/logger.d.ts +37 -0
  22. package/dist/types/logger.js +1 -0
  23. package/dist/types/modal.d.ts +77 -0
  24. package/dist/types/modal.js +1 -0
  25. package/dist/types/permission.d.ts +2 -0
  26. package/dist/types/permission.js +1 -0
  27. package/dist/utils/PaginatedEmbed.class.d.ts +25 -0
  28. package/dist/utils/PaginatedEmbed.class.js +102 -0
  29. package/dist/utils/TToolboxLogger.class.d.ts +176 -0
  30. package/dist/utils/TToolboxLogger.class.js +252 -0
  31. package/dist/utils/cooldown.d.ts +13 -0
  32. package/dist/utils/cooldown.js +43 -0
  33. package/dist/utils/editAndReply.d.ts +37 -0
  34. package/dist/utils/editAndReply.js +85 -0
  35. package/dist/utils/embeds.d.ts +55 -0
  36. package/dist/utils/embeds.js +94 -0
  37. package/dist/utils/formatting.d.ts +44 -0
  38. package/dist/utils/formatting.js +87 -0
  39. package/dist/utils/miliseconds.d.ts +10 -0
  40. package/dist/utils/miliseconds.js +11 -0
  41. package/dist/utils/permissions.d.ts +8 -0
  42. package/dist/utils/permissions.js +24 -0
  43. package/dist/utils/slashCommandOptions.d.ts +8 -0
  44. package/dist/utils/slashCommandOptions.js +11 -0
  45. package/package.json +50 -0
@@ -0,0 +1,25 @@
1
+ import { ChatInputCommandInteraction } from 'discord.js';
2
+ import { createButton } from './embeds.js';
3
+ export type PaginatedEmbedOptions<T> = {
4
+ extraButtons?: ReturnType<typeof createButton>[];
5
+ timeout?: number;
6
+ onCustomButton?: (action: string, index: number, items: T[]) => Promise<{
7
+ handled: boolean;
8
+ newItems?: T[];
9
+ stopCollector?: boolean;
10
+ }>;
11
+ };
12
+ export declare class PaginatedEmbed<T> {
13
+ private interaction;
14
+ private buildEmbed;
15
+ private options?;
16
+ private index;
17
+ private items;
18
+ private collector?;
19
+ constructor(interaction: ChatInputCommandInteraction, items: T[], buildEmbed: (item: T, index: number, total: number) => any[], options?: PaginatedEmbedOptions<T> | undefined);
20
+ private buildButtons;
21
+ start(): Promise<void>;
22
+ stop(): void;
23
+ getCurrentIndex(): number;
24
+ getItems(): T[];
25
+ }
@@ -0,0 +1,102 @@
1
+ import { ComponentType, } from 'discord.js';
2
+ import { InteractionError } from '../classes/InteractionError.class.js';
3
+ import { createButtonsRow, createPaginationButtons, } from './embeds.js';
4
+ import { safeReply } from './editAndReply.js';
5
+ import { TIMES_MILISECONDS } from './miliseconds.js';
6
+ export class PaginatedEmbed {
7
+ constructor(interaction, items, buildEmbed, options) {
8
+ this.interaction = interaction;
9
+ this.buildEmbed = buildEmbed;
10
+ this.options = options;
11
+ this.index = 0;
12
+ this.items = items;
13
+ }
14
+ buildButtons() {
15
+ const paginationButtons = createPaginationButtons(this.index, this.items.length);
16
+ const extraButtons = this.options?.extraButtons ?? [];
17
+ return [
18
+ createButtonsRow(extraButtons, {
19
+ buttons: paginationButtons,
20
+ location: 'embrace',
21
+ }),
22
+ ];
23
+ }
24
+ async start() {
25
+ await safeReply(this.interaction, '', false, this.buildEmbed(this.items[this.index], this.index, this.items.length), this.buildButtons());
26
+ const msg = await this.interaction.fetchReply();
27
+ this.collector = msg.createMessageComponentCollector({
28
+ componentType: ComponentType.Button,
29
+ time: this.options?.timeout ?? TIMES_MILISECONDS.MINUTE * 2,
30
+ });
31
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
32
+ this.collector.on('collect', async (btnInteraction) => {
33
+ if (btnInteraction.user.id !== this.interaction.user.id) {
34
+ return await safeReply(btnInteraction, 'You cannot use this button.', true);
35
+ }
36
+ const action = btnInteraction.customId;
37
+ // Handle custom buttons first
38
+ if (this.options?.onCustomButton) {
39
+ const result = await this.options.onCustomButton(action, this.index, this.items);
40
+ if (result.handled) {
41
+ if (result.newItems) {
42
+ this.items = result.newItems;
43
+ this.index = Math.min(this.index, this.items.length - 1);
44
+ if (this.items.length === 0 || result.stopCollector) {
45
+ this.stop();
46
+ return;
47
+ }
48
+ }
49
+ // Don't update for confirmation dialogs
50
+ if (action !== 'delete') {
51
+ await btnInteraction.update({
52
+ embeds: this.buildEmbed(this.items[this.index], this.index, this.items.length),
53
+ components: this.buildButtons(),
54
+ });
55
+ }
56
+ return;
57
+ }
58
+ }
59
+ // Handle pagination
60
+ switch (action) {
61
+ case 'prev':
62
+ this.index = Math.max(0, this.index - 1);
63
+ break;
64
+ case 'next':
65
+ this.index = Math.min(this.items.length - 1, this.index + 1);
66
+ break;
67
+ default:
68
+ return;
69
+ }
70
+ await btnInteraction.update({
71
+ embeds: this.buildEmbed(this.items[this.index], this.index, this.items.length),
72
+ components: this.buildButtons(),
73
+ });
74
+ });
75
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
76
+ this.collector.on('end', async () => {
77
+ try {
78
+ await this.interaction.editReply({ components: [] });
79
+ }
80
+ catch (err) {
81
+ throw new InteractionError(
82
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
83
+ `Failed to clear components: ${err.message}`, this.interaction.id, 'failed');
84
+ }
85
+ });
86
+ }
87
+ // Manually stop if needed
88
+ stop() {
89
+ if (this.collector) {
90
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
91
+ this.collector.stop();
92
+ }
93
+ }
94
+ // For custom logic
95
+ getCurrentIndex() {
96
+ return this.index;
97
+ }
98
+ // For custom logic
99
+ getItems() {
100
+ return this.items;
101
+ }
102
+ }
@@ -0,0 +1,176 @@
1
+ import { ILogger, LoggerOptions } from '../types/logger.js';
2
+ /**
3
+ * Logger class for TToolbox framework with file and console logging capabilities.
4
+ *
5
+ * Provides timestamped logging with different severity levels and optional
6
+ * console output with color coding. Supports custom log levels and colors.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // Basic usage with defaults
11
+ * const logger = new TToolboxLogger();
12
+ * logger.info('Bot started', 'startup');
13
+ *
14
+ * // With custom log levels
15
+ * const logger = new TToolboxLogger({
16
+ * customLevels: {
17
+ * debug: '\x1b[35m' , // Magenta
18
+ * success: color: '\x1b[32m', // Green
19
+ * },
20
+ * extendDefaultLevels: true
21
+ * });
22
+ *
23
+ * logger.log('Debug info', 'debug', 'system');
24
+ * logger.log('Operation succeeded', 'success', 'api', true);
25
+ * ```
26
+ */
27
+ export declare class TToolboxLogger implements ILogger {
28
+ private logFilePath;
29
+ private levels;
30
+ private reset;
31
+ private static readonly DEFAULT_LEVELS;
32
+ /**
33
+ * Creates a new TToolboxLogger instance.
34
+ *
35
+ * @param optionsOrLogDir - Either a LoggerOptions object or a string path to the log directory
36
+ * @param logFileName - The name of the log file when using the string path signature (defaults to 'latest.log')
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * // Simple usage - defaults to ./logs/latest.log
41
+ * const logger = new TToolboxLogger();
42
+ *
43
+ * // Just custom directory
44
+ * const logger = new TToolboxLogger('./my-logs');
45
+ *
46
+ * // Custom directory and filename
47
+ * const logger = new TToolboxLogger('./logs', 'bot.log');
48
+ *
49
+ * // With options object
50
+ * const logger = new TToolboxLogger({
51
+ * logDir: './logs',
52
+ * logFileName: 'bot.log',
53
+ * customLevels: {
54
+ * debug: { color: '\x1b[35m' },
55
+ * },
56
+ * extendDefaultLevels: true
57
+ * });
58
+ * ```
59
+ */
60
+ constructor(optionsOrLogDir?: string | LoggerOptions, logFileName?: string);
61
+ /**
62
+ * Logs a message with timestamp, level, and scope.
63
+ *
64
+ * @param message - The message to log
65
+ * @param level - The severity level of the log (must match a configured level)
66
+ * @param scope - The scope or context of the log (e.g., 'database', 'api', 'command')
67
+ * @param logToConsole - Whether to also output to console with color coding (defaults to false)
68
+ *
69
+ * @throws {Error} If the specified level doesn't exist
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * logger.log('User joined', 'info', 'events');
74
+ * logger.log('Debug info', 'debug', 'system', true);
75
+ * logger.log('Critical error', 'error', 'database', true);
76
+ * ```
77
+ */
78
+ log(message: string, level: string, scope: string, logToConsole?: boolean): void;
79
+ /**
80
+ * Logs an info-level message.
81
+ *
82
+ * Convenience method for logging informational messages.
83
+ *
84
+ * @param message - The message to log
85
+ * @param scope - The scope or context of the log
86
+ * @param logToConsole - Whether to also output to console (defaults to false)
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * logger.info('Bot started successfully', 'startup', true);
91
+ * ```
92
+ */
93
+ info(message: string, scope: string, logToConsole?: boolean): void;
94
+ /**
95
+ * Logs a warning-level message.
96
+ *
97
+ * Convenience method for logging warning messages.
98
+ *
99
+ * @param message - The message to log
100
+ * @param scope - The scope or context of the log
101
+ * @param logToConsole - Whether to also output to console (defaults to false)
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * logger.warn('Rate limit approaching', 'api', true);
106
+ * ```
107
+ */
108
+ warn(message: string, scope: string, logToConsole?: boolean): void;
109
+ /**
110
+ * Logs an error-level message.
111
+ *
112
+ * Convenience method for logging error messages.
113
+ *
114
+ * @param message - The message to log
115
+ * @param scope - The scope or context of the log
116
+ * @param logToConsole - Whether to also output to console (defaults to true)
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * logger.error('Database connection failed', 'database', true);
121
+ * ```
122
+ */
123
+ error(message: string, scope: string, logToConsole?: boolean): void;
124
+ /**
125
+ * Gets all available log levels with their colors.
126
+ *
127
+ * @returns Record of log level names to their color
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const levels = logger.getAvailableLevels();
132
+ * console.log(Object.keys(levels)); // ['info', 'warn', 'error', 'debug']
133
+ * ```
134
+ */
135
+ getAvailableLevels(): Record<string, string>;
136
+ /**
137
+ * Gets the path to the current log file.
138
+ *
139
+ * @returns The absolute path to the log file
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * console.log('Logging to:', logger.getLogFilePath());
144
+ * ```
145
+ */
146
+ getLogFilePath(): string;
147
+ /**
148
+ * Clears the current log file.
149
+ *
150
+ * Useful for rotating logs or starting fresh.
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * logger.clearLog();
155
+ * logger.info('Log cleared, starting fresh', 'system');
156
+ * ```
157
+ */
158
+ clearLog(): void;
159
+ /**
160
+ * Creates a new log file with a timestamp-based name and returns a new logger instance.
161
+ *
162
+ * Useful for log rotation - creates a new file while keeping the old one.
163
+ *
164
+ * @returns A new TToolboxLogger instance pointing to the new log file
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * // Rotate logs daily
169
+ * let logger = new TToolboxLogger('./logs');
170
+ *
171
+ * // Later, create a new log file
172
+ * logger = logger.rotate();
173
+ * ```
174
+ */
175
+ rotate(): TToolboxLogger;
176
+ }
@@ -0,0 +1,252 @@
1
+ /* eslint-disable no-inline-comments */
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { formatDateToYYYYMMDDHHMMSS } from './formatting.js';
5
+ /**
6
+ * Logger class for TToolbox framework with file and console logging capabilities.
7
+ *
8
+ * Provides timestamped logging with different severity levels and optional
9
+ * console output with color coding. Supports custom log levels and colors.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Basic usage with defaults
14
+ * const logger = new TToolboxLogger();
15
+ * logger.info('Bot started', 'startup');
16
+ *
17
+ * // With custom log levels
18
+ * const logger = new TToolboxLogger({
19
+ * customLevels: {
20
+ * debug: '\x1b[35m' , // Magenta
21
+ * success: color: '\x1b[32m', // Green
22
+ * },
23
+ * extendDefaultLevels: true
24
+ * });
25
+ *
26
+ * logger.log('Debug info', 'debug', 'system');
27
+ * logger.log('Operation succeeded', 'success', 'api', true);
28
+ * ```
29
+ */
30
+ export class TToolboxLogger {
31
+ /**
32
+ * Creates a new TToolboxLogger instance.
33
+ *
34
+ * @param optionsOrLogDir - Either a LoggerOptions object or a string path to the log directory
35
+ * @param logFileName - The name of the log file when using the string path signature (defaults to 'latest.log')
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * // Simple usage - defaults to ./logs/latest.log
40
+ * const logger = new TToolboxLogger();
41
+ *
42
+ * // Just custom directory
43
+ * const logger = new TToolboxLogger('./my-logs');
44
+ *
45
+ * // Custom directory and filename
46
+ * const logger = new TToolboxLogger('./logs', 'bot.log');
47
+ *
48
+ * // With options object
49
+ * const logger = new TToolboxLogger({
50
+ * logDir: './logs',
51
+ * logFileName: 'bot.log',
52
+ * customLevels: {
53
+ * debug: { color: '\x1b[35m' },
54
+ * },
55
+ * extendDefaultLevels: true
56
+ * });
57
+ * ```
58
+ */
59
+ constructor(optionsOrLogDir, logFileName = 'latest.log') {
60
+ this.reset = '\x1b[0m';
61
+ let options;
62
+ if (typeof optionsOrLogDir === 'string') {
63
+ // Basic signature: new TToolboxLogger('./logs', 'file.log')
64
+ options = {
65
+ logDir: optionsOrLogDir,
66
+ logFileName: logFileName,
67
+ };
68
+ }
69
+ else if (optionsOrLogDir) {
70
+ // Advanced signature: new TToolboxLogger({ logDir: './logs', ... })
71
+ options = optionsOrLogDir;
72
+ }
73
+ else {
74
+ // No arguments: new TToolboxLogger()
75
+ options = {};
76
+ }
77
+ const { logDir = './logs', logFileName: fileName = logFileName, customLevels = {}, extendDefaultLevels = true, } = options;
78
+ // Set up log levels
79
+ if (extendDefaultLevels) {
80
+ this.levels = { ...TToolboxLogger.DEFAULT_LEVELS, ...customLevels };
81
+ }
82
+ else {
83
+ this.levels =
84
+ Object.keys(customLevels).length > 0
85
+ ? customLevels
86
+ : TToolboxLogger.DEFAULT_LEVELS;
87
+ }
88
+ // Resolve log directory
89
+ const resolvedLogDir = path.isAbsolute(logDir)
90
+ ? logDir
91
+ : path.resolve(process.cwd(), logDir);
92
+ if (!fs.existsSync(resolvedLogDir)) {
93
+ fs.mkdirSync(resolvedLogDir, { recursive: true });
94
+ }
95
+ this.logFilePath = path.join(resolvedLogDir, fileName);
96
+ }
97
+ /**
98
+ * Logs a message with timestamp, level, and scope.
99
+ *
100
+ * @param message - The message to log
101
+ * @param level - The severity level of the log (must match a configured level)
102
+ * @param scope - The scope or context of the log (e.g., 'database', 'api', 'command')
103
+ * @param logToConsole - Whether to also output to console with color coding (defaults to false)
104
+ *
105
+ * @throws {Error} If the specified level doesn't exist
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * logger.log('User joined', 'info', 'events');
110
+ * logger.log('Debug info', 'debug', 'system', true);
111
+ * logger.log('Critical error', 'error', 'database', true);
112
+ * ```
113
+ */
114
+ log(message, level, scope, logToConsole = false) {
115
+ const color = this.levels[level];
116
+ if (!color) {
117
+ throw new Error(`Unknown log level: "${level}". Available levels: ${Object.keys(this.levels).join(', ')}`);
118
+ }
119
+ const logMessage = `[${formatDateToYYYYMMDDHHMMSS(new Date())}] [${level.toUpperCase()}] [${scope}] ${message}\n`;
120
+ // Write to file
121
+ fs.appendFileSync(this.logFilePath, logMessage, 'utf8');
122
+ // Optionally log to console with colors
123
+ if (logToConsole) {
124
+ console.log(`${color}${logMessage.trim()}${this.reset}`);
125
+ }
126
+ }
127
+ /**
128
+ * Logs an info-level message.
129
+ *
130
+ * Convenience method for logging informational messages.
131
+ *
132
+ * @param message - The message to log
133
+ * @param scope - The scope or context of the log
134
+ * @param logToConsole - Whether to also output to console (defaults to false)
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * logger.info('Bot started successfully', 'startup', true);
139
+ * ```
140
+ */
141
+ info(message, scope, logToConsole = false) {
142
+ this.log(message, 'info', scope, logToConsole);
143
+ }
144
+ /**
145
+ * Logs a warning-level message.
146
+ *
147
+ * Convenience method for logging warning messages.
148
+ *
149
+ * @param message - The message to log
150
+ * @param scope - The scope or context of the log
151
+ * @param logToConsole - Whether to also output to console (defaults to false)
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * logger.warn('Rate limit approaching', 'api', true);
156
+ * ```
157
+ */
158
+ warn(message, scope, logToConsole = false) {
159
+ this.log(message, 'warn', scope, logToConsole);
160
+ }
161
+ /**
162
+ * Logs an error-level message.
163
+ *
164
+ * Convenience method for logging error messages.
165
+ *
166
+ * @param message - The message to log
167
+ * @param scope - The scope or context of the log
168
+ * @param logToConsole - Whether to also output to console (defaults to true)
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * logger.error('Database connection failed', 'database', true);
173
+ * ```
174
+ */
175
+ error(message, scope, logToConsole = true) {
176
+ this.log(message, 'error', scope, logToConsole);
177
+ }
178
+ /**
179
+ * Gets all available log levels with their colors.
180
+ *
181
+ * @returns Record of log level names to their color
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * const levels = logger.getAvailableLevels();
186
+ * console.log(Object.keys(levels)); // ['info', 'warn', 'error', 'debug']
187
+ * ```
188
+ */
189
+ getAvailableLevels() {
190
+ return this.levels;
191
+ }
192
+ /**
193
+ * Gets the path to the current log file.
194
+ *
195
+ * @returns The absolute path to the log file
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * console.log('Logging to:', logger.getLogFilePath());
200
+ * ```
201
+ */
202
+ getLogFilePath() {
203
+ return this.logFilePath;
204
+ }
205
+ /**
206
+ * Clears the current log file.
207
+ *
208
+ * Useful for rotating logs or starting fresh.
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * logger.clearLog();
213
+ * logger.info('Log cleared, starting fresh', 'system');
214
+ * ```
215
+ */
216
+ clearLog() {
217
+ fs.writeFileSync(this.logFilePath, '', 'utf8');
218
+ }
219
+ /**
220
+ * Creates a new log file with a timestamp-based name and returns a new logger instance.
221
+ *
222
+ * Useful for log rotation - creates a new file while keeping the old one.
223
+ *
224
+ * @returns A new TToolboxLogger instance pointing to the new log file
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * // Rotate logs daily
229
+ * let logger = new TToolboxLogger('./logs');
230
+ *
231
+ * // Later, create a new log file
232
+ * logger = logger.rotate();
233
+ * ```
234
+ */
235
+ rotate() {
236
+ const timestamp = formatDateToYYYYMMDDHHMMSS(new Date());
237
+ const logDir = path.dirname(this.logFilePath);
238
+ const newFileName = `log-${timestamp}.log`;
239
+ return new TToolboxLogger({
240
+ logDir,
241
+ logFileName: newFileName,
242
+ customLevels: this.levels,
243
+ extendDefaultLevels: false,
244
+ });
245
+ }
246
+ }
247
+ // Default log levels
248
+ TToolboxLogger.DEFAULT_LEVELS = {
249
+ info: '\x1b[36m', // Cyan
250
+ warn: '\x1b[33m', // Yellow
251
+ error: '\x1b[31m', // Red
252
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Checks whether a user can run a given command considering its cooldown.
3
+ *
4
+ * If the command has no cooldown, it is always allowed.
5
+ * If the user is on cooldown, returns how long they still need to wait.
6
+ *
7
+ * This function also automatically cleans up expired cooldowns.
8
+ *
9
+ * @param command The command being checked.
10
+ * @param userId The ID of the user executing the command.
11
+ * @returns A CooldownResult with `allowed` and optional `remaining` time.
12
+ */
13
+ export declare function checkCooldown(commandName: string, commandCooldown: number | undefined, userId: string): number;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A tracker with the command name as key and a map as value.
3
+ *
4
+ * Within the value map the userId is used as the key and the timestamp (in ms)
5
+ * when the command can be used again as the value.
6
+ *
7
+ * So `Map<commandName, Map<userId, canBeUsedAgainAt>>`.
8
+ */
9
+ const tracker = new Map();
10
+ /**
11
+ * Checks whether a user can run a given command considering its cooldown.
12
+ *
13
+ * If the command has no cooldown, it is always allowed.
14
+ * If the user is on cooldown, returns how long they still need to wait.
15
+ *
16
+ * This function also automatically cleans up expired cooldowns.
17
+ *
18
+ * @param command The command being checked.
19
+ * @param userId The ID of the user executing the command.
20
+ * @returns A CooldownResult with `allowed` and optional `remaining` time.
21
+ */
22
+ export function checkCooldown(commandName, commandCooldown, userId) {
23
+ if (!commandCooldown)
24
+ return 0;
25
+ const now = Date.now();
26
+ let cooldownMap = tracker.get(commandName);
27
+ if (!cooldownMap) {
28
+ cooldownMap = new Map();
29
+ tracker.set(commandName, cooldownMap);
30
+ }
31
+ // Clean up expired entries
32
+ for (const [id, expiry] of cooldownMap) {
33
+ if (expiry <= now) {
34
+ cooldownMap.delete(id);
35
+ }
36
+ }
37
+ const canBeUsedAgainAt = cooldownMap.get(userId);
38
+ if (canBeUsedAgainAt && now < canBeUsedAgainAt) {
39
+ return canBeUsedAgainAt - now;
40
+ }
41
+ cooldownMap.set(userId, now + commandCooldown);
42
+ return 0;
43
+ }
@@ -0,0 +1,37 @@
1
+ import Stream from 'stream';
2
+ import { ActionRowBuilder, APIAttachment, Attachment, AttachmentBuilder, AttachmentPayload, BufferResolvable, ButtonInteraction, ChannelSelectMenuInteraction, ChatInputCommandInteraction, EmbedBuilder, JSONEncodable, Message, ModalSubmitInteraction, StringSelectMenuInteraction } from 'discord.js';
3
+ /**
4
+ * Safely replies to an interaction, handling deferred/replied states.
5
+ *
6
+ * @param interaction - The interaction to reply to
7
+ * @param content - The message content
8
+ * @param ephemeral - Whether the reply should be ephemeral
9
+ * @param embeds - Optional embeds to include
10
+ * @param components - Optional components to include
11
+ * @param files - Optional files to attach
12
+ * @returns The message that was sent
13
+ * @throws {InteractionError} If the interaction is too old or fails
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * try {
18
+ * await safeReply(interaction, 'Hello!', true);
19
+ * } catch (err) {
20
+ * if (err instanceof InteractionError && err.reason === 'expired') {
21
+ * this.logger?.warn('Interaction expired', 'command');
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export declare function safeReply(interaction: ChatInputCommandInteraction | ButtonInteraction | ModalSubmitInteraction | ChannelSelectMenuInteraction | StringSelectMenuInteraction, content: string, ephemeral?: boolean, embeds?: EmbedBuilder[], components?: ActionRowBuilder<any>[], files?: (BufferResolvable | Stream | JSONEncodable<APIAttachment> | Attachment | AttachmentBuilder | AttachmentPayload)[]): Promise<Message>;
27
+ /**
28
+ * Safely edits an interaction reply.
29
+ *
30
+ * @param interaction - The interaction to edit
31
+ * @param content - The new message content
32
+ * @param embeds - Optional embeds to include
33
+ * @param components - Optional components to include
34
+ * @returns The edited message
35
+ * @throws {InteractionError} If the interaction is too old or fails
36
+ */
37
+ export declare function safeEdit(interaction: ChatInputCommandInteraction | ButtonInteraction | ModalSubmitInteraction | ChannelSelectMenuInteraction | StringSelectMenuInteraction, content: string, embeds?: EmbedBuilder[], components?: ActionRowBuilder<any>[]): Promise<Message>;