@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.
- package/LICENSE +675 -0
- package/README.md +77 -0
- package/dist/classes/Command.class.d.ts +169 -0
- package/dist/classes/Command.class.js +156 -0
- package/dist/classes/CommandManager.class.d.ts +69 -0
- package/dist/classes/CommandManager.class.js +149 -0
- package/dist/classes/DiscordHandler.class.d.ts +241 -0
- package/dist/classes/DiscordHandler.class.js +222 -0
- package/dist/classes/InteractionError.class.d.ts +8 -0
- package/dist/classes/InteractionError.class.js +11 -0
- package/dist/classes/ModalManager.class.d.ts +154 -0
- package/dist/classes/ModalManager.class.js +205 -0
- package/dist/classes/SubcommandGroup.class.d.ts +127 -0
- package/dist/classes/SubcommandGroup.class.js +156 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/types/button.d.ts +2 -0
- package/dist/types/button.js +1 -0
- package/dist/types/channel.d.ts +2 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/logger.d.ts +37 -0
- package/dist/types/logger.js +1 -0
- package/dist/types/modal.d.ts +77 -0
- package/dist/types/modal.js +1 -0
- package/dist/types/permission.d.ts +2 -0
- package/dist/types/permission.js +1 -0
- package/dist/utils/PaginatedEmbed.class.d.ts +25 -0
- package/dist/utils/PaginatedEmbed.class.js +102 -0
- package/dist/utils/TToolboxLogger.class.d.ts +176 -0
- package/dist/utils/TToolboxLogger.class.js +252 -0
- package/dist/utils/cooldown.d.ts +13 -0
- package/dist/utils/cooldown.js +43 -0
- package/dist/utils/editAndReply.d.ts +37 -0
- package/dist/utils/editAndReply.js +85 -0
- package/dist/utils/embeds.d.ts +55 -0
- package/dist/utils/embeds.js +94 -0
- package/dist/utils/formatting.d.ts +44 -0
- package/dist/utils/formatting.js +87 -0
- package/dist/utils/miliseconds.d.ts +10 -0
- package/dist/utils/miliseconds.js +11 -0
- package/dist/utils/permissions.d.ts +8 -0
- package/dist/utils/permissions.js +24 -0
- package/dist/utils/slashCommandOptions.d.ts +8 -0
- package/dist/utils/slashCommandOptions.js +11 -0
- 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>;
|