djs-builder 0.5.31 → 0.5.40
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/.tsbuildinfo +1 -1
- package/dist/discord/builder/system/Pagination.d.ts.map +1 -1
- package/dist/discord/builder/system/Pagination.js +10 -10
- package/dist/discord/builder/system/Pagination.js.map +1 -1
- package/dist/discord/builder/system/collectors.d.ts +27 -0
- package/dist/discord/builder/system/collectors.d.ts.map +1 -0
- package/dist/discord/builder/system/collectors.js +137 -0
- package/dist/discord/builder/system/collectors.js.map +1 -0
- package/dist/discord/events-handler/eventLoader.d.ts +2 -1
- package/dist/discord/events-handler/eventLoader.d.ts.map +1 -1
- package/dist/discord/events-handler/eventLoader.js +23 -0
- package/dist/discord/events-handler/eventLoader.js.map +1 -1
- package/dist/discord/events-handler/login.d.ts.map +1 -1
- package/dist/discord/events-handler/login.js +12 -0
- package/dist/discord/events-handler/login.js.map +1 -1
- package/dist/discord/events-handler/prefixLoader.d.ts +2 -1
- package/dist/discord/events-handler/prefixLoader.d.ts.map +1 -1
- package/dist/discord/events-handler/prefixLoader.js +23 -0
- package/dist/discord/events-handler/prefixLoader.js.map +1 -1
- package/dist/discord/events-handler/slash-register.d.ts.map +1 -1
- package/dist/discord/events-handler/slash-register.js +30 -41
- package/dist/discord/events-handler/slash-register.js.map +1 -1
- package/dist/discord/events-handler/slash-responder.d.ts.map +1 -1
- package/dist/discord/events-handler/slash-responder.js +23 -27
- package/dist/discord/events-handler/slash-responder.js.map +1 -1
- package/dist/discord/events-handler/slashLoader.d.ts +2 -1
- package/dist/discord/events-handler/slashLoader.d.ts.map +1 -1
- package/dist/discord/events-handler/slashLoader.js +23 -0
- package/dist/discord/events-handler/slashLoader.js.map +1 -1
- package/dist/discord/events-handler/starter.js.map +1 -1
- package/dist/discord/games/X-O.d.ts +20 -0
- package/dist/discord/games/X-O.d.ts.map +1 -0
- package/dist/discord/games/X-O.js +166 -0
- package/dist/discord/games/X-O.js.map +1 -0
- package/dist/discord/games/rps.d.ts +21 -0
- package/dist/discord/games/rps.d.ts.map +1 -0
- package/dist/discord/games/rps.js +99 -0
- package/dist/discord/games/rps.js.map +1 -0
- package/dist/discord/types/starter.d.ts +12 -0
- package/dist/discord/types/starter.d.ts.map +1 -1
- package/dist/discord/utils.d.ts +2 -1
- package/dist/discord/utils.d.ts.map +1 -1
- package/dist/discord/utils.js +3 -1
- package/dist/discord/utils.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/lib/discord/builder/system/Pagination.ts +22 -21
- package/lib/discord/builder/system/collectors.ts +197 -0
- package/lib/discord/events-handler/eventLoader.ts +29 -1
- package/lib/discord/events-handler/login.ts +13 -0
- package/lib/discord/events-handler/prefixLoader.ts +31 -2
- package/lib/discord/events-handler/slash-register.ts +32 -46
- package/lib/discord/events-handler/slash-responder.ts +43 -38
- package/lib/discord/events-handler/slashLoader.ts +29 -1
- package/lib/discord/events-handler/starter.ts +4 -1
- package/lib/discord/types/starter.ts +12 -0
- package/lib/discord/utils.ts +3 -1
- package/lib/index.ts +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Message,
|
|
3
|
+
Interaction,
|
|
4
|
+
MessageCollector,
|
|
5
|
+
InteractionCollector,
|
|
6
|
+
CollectorFilter,
|
|
7
|
+
Snowflake,
|
|
8
|
+
ReadonlyCollection,
|
|
9
|
+
CommandInteraction,
|
|
10
|
+
ButtonInteraction,
|
|
11
|
+
MessageReaction,
|
|
12
|
+
ModalSubmitInteraction,
|
|
13
|
+
StringSelectMenuInteraction,
|
|
14
|
+
UserSelectMenuInteraction,
|
|
15
|
+
RoleSelectMenuInteraction,
|
|
16
|
+
ContextMenuCommandInteraction,
|
|
17
|
+
Collector,
|
|
18
|
+
} from 'discord.js';
|
|
19
|
+
|
|
20
|
+
type EventSource = Message | CommandInteraction | ButtonInteraction | MessageReaction | Interaction;
|
|
21
|
+
|
|
22
|
+
const collectorCache: Map<string, any> = new Map();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param sources - The event source(s) (message, interaction, reaction, etc.) to collect from.
|
|
26
|
+
* @param options - Custom options for collection (time, filter, max, idle).
|
|
27
|
+
* @param events - Custom event handlers for `onCollect`, `onEnd`, `onError`, etc.
|
|
28
|
+
*/
|
|
29
|
+
function Djs_Collectors<T extends EventSource>(
|
|
30
|
+
sources: T | T[],
|
|
31
|
+
options: {
|
|
32
|
+
time?: number;
|
|
33
|
+
filter?: CollectorFilter<[T]>;
|
|
34
|
+
max?: number;
|
|
35
|
+
idle?: number;
|
|
36
|
+
log?: boolean;
|
|
37
|
+
throttle?: number;
|
|
38
|
+
uniqueId?: string;
|
|
39
|
+
},
|
|
40
|
+
events: {
|
|
41
|
+
onCollect: (collectedItem: T, meta?: Record<string, any>) => void;
|
|
42
|
+
onEnd?: (collected: ReadonlyCollection<Snowflake, T>, reason: string) => void;
|
|
43
|
+
onError?: (error: Error) => void;
|
|
44
|
+
onDispose?: (disposedItem: T) => void;
|
|
45
|
+
onTimeout?: () => void;
|
|
46
|
+
onStart?: () => void;
|
|
47
|
+
},
|
|
48
|
+
metadata?: Record<string, any>
|
|
49
|
+
): { stop: (reason?: string) => void } {
|
|
50
|
+
const eventSources = Array.isArray(sources) ? sources : [sources];
|
|
51
|
+
const filter: CollectorFilter<[T]> = options.filter || (() => true);
|
|
52
|
+
const time: number = options.time || 60000;
|
|
53
|
+
|
|
54
|
+
if (events.onStart) events.onStart();
|
|
55
|
+
|
|
56
|
+
if (options.uniqueId && collectorCache.has(options.uniqueId)) {
|
|
57
|
+
console.warn(`Collector with unique ID "${options.uniqueId}" is already active. Skipping creation.`);
|
|
58
|
+
return { stop: () => {} };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (options.uniqueId) {
|
|
62
|
+
collectorCache.set(options.uniqueId, { sources: eventSources, time, max: options.max, idle: options.idle });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const collectors: any[] = [];
|
|
66
|
+
|
|
67
|
+
for (const source of eventSources) {
|
|
68
|
+
if (source instanceof Message) {
|
|
69
|
+
const messageCollector = new MessageCollector(source.channel, {
|
|
70
|
+
filter: filter as CollectorFilter<[Message]>,
|
|
71
|
+
time,
|
|
72
|
+
max: options.max,
|
|
73
|
+
idle: options.idle,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
collectors.push(messageCollector); // Store reference
|
|
77
|
+
handleCollector(messageCollector, events, options, metadata);
|
|
78
|
+
|
|
79
|
+
} else if (
|
|
80
|
+
source instanceof CommandInteraction ||
|
|
81
|
+
source instanceof ButtonInteraction ||
|
|
82
|
+
source instanceof StringSelectMenuInteraction ||
|
|
83
|
+
source instanceof UserSelectMenuInteraction ||
|
|
84
|
+
source instanceof RoleSelectMenuInteraction ||
|
|
85
|
+
source instanceof ContextMenuCommandInteraction ||
|
|
86
|
+
source instanceof ModalSubmitInteraction
|
|
87
|
+
) {
|
|
88
|
+
if (!source.client.isReady()) {
|
|
89
|
+
throw new Error('Client is not ready. Please ensure the client is logged in and ready before creating a collector.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const interactionCollector = new InteractionCollector(source.client, {
|
|
93
|
+
filter: filter as CollectorFilter<[Interaction]>,
|
|
94
|
+
time,
|
|
95
|
+
max: options.max,
|
|
96
|
+
idle: options.idle,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
collectors.push(interactionCollector); // Store reference
|
|
100
|
+
handleCollector(interactionCollector, events, options, metadata);
|
|
101
|
+
|
|
102
|
+
} else if (source instanceof MessageReaction) {
|
|
103
|
+
const reactionCollector = source.message.createReactionCollector({
|
|
104
|
+
filter: filter as CollectorFilter<[MessageReaction]>,
|
|
105
|
+
time,
|
|
106
|
+
max: options.max,
|
|
107
|
+
idle: options.idle,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
collectors.push(reactionCollector); // Store reference
|
|
111
|
+
handleCollector(reactionCollector, events, options, metadata);
|
|
112
|
+
|
|
113
|
+
} else {
|
|
114
|
+
throw new Error("Source must be either a Message, Interaction, or Reaction.");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
stop: (reason: string = 'manual') => {
|
|
120
|
+
if (options.uniqueId) {
|
|
121
|
+
const cached = collectorCache.get(options.uniqueId);
|
|
122
|
+
if (cached) {
|
|
123
|
+
collectors.forEach((collector) => collector.stop(reason));
|
|
124
|
+
collectorCache.delete(options.uniqueId);
|
|
125
|
+
console.log(`Collector with unique ID "${options.uniqueId}" has been ended. Reason: ${reason}`);
|
|
126
|
+
} else {
|
|
127
|
+
console.warn(`No collector found with unique ID "${options.uniqueId}".`);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
collectors.forEach((collector) => collector.stop(reason));
|
|
131
|
+
console.log(`Collectors have been ended. Reason: ${reason}`);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generic function to handle a collector's events and lifecycle.
|
|
139
|
+
* @param collector - The specific collector (MessageCollector, InteractionCollector, etc.).
|
|
140
|
+
* @param events - The event handlers for collection lifecycle.
|
|
141
|
+
* @param options - Additional collection options.
|
|
142
|
+
* @param metadata - Optional metadata attached to each collected item.
|
|
143
|
+
*/
|
|
144
|
+
function handleCollector<T>(
|
|
145
|
+
collector: any,
|
|
146
|
+
events: {
|
|
147
|
+
onCollect: (collectedItem: T, meta?: Record<string, any>) => void;
|
|
148
|
+
onEnd?: (collected: ReadonlyCollection<Snowflake, T>, reason: string) => void;
|
|
149
|
+
onError?: (error: Error) => void;
|
|
150
|
+
onDispose?: (disposedItem: T) => void;
|
|
151
|
+
onTimeout?: () => void;
|
|
152
|
+
},
|
|
153
|
+
options: { log?: boolean; throttle?: number; uniqueId?: string },
|
|
154
|
+
metadata?: Record<string, any>
|
|
155
|
+
) {
|
|
156
|
+
let lastCollectedTime = 0;
|
|
157
|
+
|
|
158
|
+
collector.on('collect', (collectedItem: T) => {
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
if (options.throttle && now - lastCollectedTime < options.throttle) {
|
|
161
|
+
if (options.log) console.warn(`Throttled collect for ${collectedItem}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
lastCollectedTime = now;
|
|
165
|
+
|
|
166
|
+
if (options.log) console.log('Collected item:', collectedItem);
|
|
167
|
+
events.onCollect(collectedItem, metadata);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
collector.on('end', (collected: ReadonlyCollection<Snowflake, T>, reason: string) => {
|
|
171
|
+
if (options.log) console.log(`Collector ended. Reason: ${reason}`);
|
|
172
|
+
if (events.onEnd) events.onEnd(collected, reason);
|
|
173
|
+
if (reason === 'time' && events.onTimeout) {
|
|
174
|
+
events.onTimeout();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (options.uniqueId) {
|
|
178
|
+
collectorCache.delete(options.uniqueId);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (events.onError) {
|
|
183
|
+
collector.on('error', (error: Error) => {
|
|
184
|
+
if (options.log) console.error('Collector error:', error);
|
|
185
|
+
(events.onError as (error: Error) => void)(error);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (events.onDispose) {
|
|
190
|
+
collector.on('dispose', (disposedItem: T) => {
|
|
191
|
+
if (options.log) console.log('Disposed item:', disposedItem);
|
|
192
|
+
(events.onDispose as (disposedItem: T) => void)(disposedItem);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { Djs_Collectors };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Collection } from 'discord.js';
|
|
1
|
+
import { Client, Collection } from 'discord.js';
|
|
2
2
|
import { readdir } from 'fs/promises';
|
|
3
3
|
import { resolve, extname } from 'path';
|
|
4
4
|
import { Event } from '../types/utils';
|
|
5
5
|
import { logError, logInfo } from '../functions/logger';
|
|
6
6
|
import { botData } from './login';
|
|
7
|
+
import { watch } from 'fs';
|
|
7
8
|
|
|
8
9
|
const validExtensions = ['.js', '.ts'];
|
|
9
10
|
|
|
@@ -136,3 +137,30 @@ async function withRetry(handler: (...args: any[]) => Promise<void>, retryAttemp
|
|
|
136
137
|
logError('Reached maximum retry attempts.');
|
|
137
138
|
};
|
|
138
139
|
}
|
|
140
|
+
|
|
141
|
+
export function autoEventLoader(client: Client, DEBOUNCE_DELAY: number = 10000, eventsOptions: Events = {}) {
|
|
142
|
+
|
|
143
|
+
const slashPath = botData.get('slashCommandPath') as string;
|
|
144
|
+
const commandPath = resolve(process.cwd(), slashPath);
|
|
145
|
+
|
|
146
|
+
let debounceTimer: NodeJS.Timeout | null = null;
|
|
147
|
+
|
|
148
|
+
const handleReload = async () => {
|
|
149
|
+
if (debounceTimer) {
|
|
150
|
+
clearTimeout(debounceTimer);
|
|
151
|
+
}
|
|
152
|
+
debounceTimer = setTimeout(async () => {
|
|
153
|
+
await eventLoader(client, eventsOptions);
|
|
154
|
+
logInfo('Slash commands successfully reloaded after debounce.');
|
|
155
|
+
}, DEBOUNCE_DELAY);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
watch(commandPath, { recursive: false }, (eventType: any, filename: any) => {
|
|
159
|
+
if (filename && validExtensions.includes(extname(filename))) {
|
|
160
|
+
logInfo(`Detected ${eventType} in ${filename}, waiting for debouncing...`);
|
|
161
|
+
handleReload();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
logInfo('Started watching for changes in slash commands...');
|
|
166
|
+
}
|
|
@@ -7,6 +7,9 @@ import { GatewayIntentBits } from 'discord-api-types/v10';
|
|
|
7
7
|
import { logError, logInfo, logWarning } from '../utils';
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import fs from 'fs';
|
|
10
|
+
import { autoSlashLoader } from './slashLoader';
|
|
11
|
+
import { autoPrefixLoader } from './prefixLoader';
|
|
12
|
+
import { autoEventLoader } from './eventLoader';
|
|
10
13
|
|
|
11
14
|
export const botData = new Collection<string, string | string[]>();
|
|
12
15
|
|
|
@@ -248,5 +251,15 @@ export async function login(djs: Client & { botData?: Collection<string, string
|
|
|
248
251
|
}
|
|
249
252
|
|
|
250
253
|
(djs as any).botData = botData;
|
|
254
|
+
|
|
255
|
+
if (options.slash?.autoLoader?.enable) {
|
|
256
|
+
autoSlashLoader(djs, options.slash?.autoLoader?.DEBOUNCE_DELAY);
|
|
257
|
+
}
|
|
258
|
+
if (options.prefix?.autoLoader?.enable) {
|
|
259
|
+
autoPrefixLoader(djs, options.prefix?.autoLoader?.DEBOUNCE_DELAY);
|
|
260
|
+
}
|
|
261
|
+
if (options.events?.autoLoader?.enable) {
|
|
262
|
+
autoEventLoader(djs, options.events?.autoLoader?.DEBOUNCE_DELAY, options.events);
|
|
263
|
+
}
|
|
251
264
|
});
|
|
252
265
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Collection } from 'discord.js';
|
|
1
|
+
import { Client, Collection } from 'discord.js';
|
|
2
2
|
import { readdir } from 'fs/promises';
|
|
3
3
|
import { join, resolve, extname } from 'path';
|
|
4
4
|
import { commands, aliases, commandNames } from './prefix-register';
|
|
5
5
|
import { readCommands } from './prefix-register';
|
|
6
|
-
import { logError } from '../functions/logger';
|
|
6
|
+
import { logError, logInfo } from '../functions/logger';
|
|
7
7
|
import { botData } from './login';
|
|
8
|
+
import { watch } from 'fs';
|
|
8
9
|
const validExtensions = ['.js', '.ts'];
|
|
9
10
|
|
|
10
11
|
export async function prefixLoader(client: any): Promise<{ commands: Collection<string, any>; success: boolean }> {
|
|
@@ -52,3 +53,31 @@ export async function prefixLoader(client: any): Promise<{ commands: Collection<
|
|
|
52
53
|
return { commands: new Collection(), success: false };
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export function autoPrefixLoader(client: Client, DEBOUNCE_DELAY: number = 10000) {
|
|
59
|
+
|
|
60
|
+
const slashPath = botData.get('slashCommandPath') as string;
|
|
61
|
+
const commandPath = resolve(process.cwd(), slashPath);
|
|
62
|
+
|
|
63
|
+
let debounceTimer: NodeJS.Timeout | null = null;
|
|
64
|
+
|
|
65
|
+
const handleReload = async () => {
|
|
66
|
+
if (debounceTimer) {
|
|
67
|
+
clearTimeout(debounceTimer);
|
|
68
|
+
}
|
|
69
|
+
debounceTimer = setTimeout(async () => {
|
|
70
|
+
await prefixLoader(client);
|
|
71
|
+
logInfo('Slash commands successfully reloaded after debounce.');
|
|
72
|
+
}, DEBOUNCE_DELAY);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
watch(commandPath, { recursive: false }, (eventType: any, filename: any) => {
|
|
76
|
+
if (filename && validExtensions.includes(extname(filename))) {
|
|
77
|
+
logInfo(`Detected ${eventType} in ${filename}, waiting for debouncing...`);
|
|
78
|
+
handleReload();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
logInfo('Started watching for changes in slash commands...');
|
|
83
|
+
}
|
|
@@ -4,7 +4,26 @@ import { Routes } from 'discord-api-types/v10';
|
|
|
4
4
|
import { readdir } from 'fs/promises';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { SlashOptions } from '../types/starter';
|
|
7
|
-
import { logError } from '../functions/logger';
|
|
7
|
+
import { logError } from '../functions/logger';
|
|
8
|
+
|
|
9
|
+
async function loadCommand(filePath: string) {
|
|
10
|
+
try {
|
|
11
|
+
const command = require(filePath);
|
|
12
|
+
if (command.data) {
|
|
13
|
+
command.data.cooldown = command.cooldown && !isNaN(command.cooldown) ? command.cooldown : 0;
|
|
14
|
+
command.data.botPerms = command.botPerms || [];
|
|
15
|
+
command.data.userPerms = command.userPerms || [];
|
|
16
|
+
command.data.owner = command.owner || false;
|
|
17
|
+
command.data.developer = command.developer || false;
|
|
18
|
+
command.data.category = command.category || 'Uncategorized';
|
|
19
|
+
return command;
|
|
20
|
+
}
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
logError(`Error loading command from file: ${filePath}`);
|
|
23
|
+
logError(error.message);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
8
27
|
|
|
9
28
|
export async function registerSlashCommands(client: any, token: string, slash: SlashOptions): Promise<Collection<string, any>> {
|
|
10
29
|
if (!token) {
|
|
@@ -19,74 +38,41 @@ export async function registerSlashCommands(client: any, token: string, slash: S
|
|
|
19
38
|
const dirents = await readdir(resolvedPath, { withFileTypes: true });
|
|
20
39
|
|
|
21
40
|
for (const dirent of dirents) {
|
|
41
|
+
const folderPath = path.join(resolvedPath, dirent.name);
|
|
22
42
|
if (dirent.isDirectory()) {
|
|
23
|
-
const folderPath = path.join(resolvedPath, dirent.name);
|
|
24
43
|
const files = await readdir(folderPath);
|
|
25
|
-
|
|
26
44
|
for (const file of files) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (command.cooldown && !isNaN(command.cooldown)) {
|
|
31
|
-
command.data.cooldown = command.cooldown || 0;
|
|
32
|
-
}
|
|
33
|
-
command.data.botPerms = command.botPerms || [];
|
|
34
|
-
command.data.userPerms = command.userPerms || [];
|
|
35
|
-
command.data.owner = command.owner || false;
|
|
36
|
-
command.data.developer = command.developer || false;
|
|
37
|
-
command.data.category = command.category || 'Uncategorized';
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
slashCommands.set(command.data.name, command);
|
|
41
|
-
}
|
|
42
|
-
} catch (error: any) {
|
|
43
|
-
logError(`Error in file: ${path.join(folderPath, file)}`);
|
|
44
|
-
logError(error.message);
|
|
45
|
+
const command = await loadCommand(path.join(folderPath, file));
|
|
46
|
+
if (command) {
|
|
47
|
+
slashCommands.set(command.data.name, command);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (command.cooldown && !isNaN(command.cooldown)) {
|
|
52
|
-
command.data.cooldown = command.cooldown || 0;
|
|
53
|
-
}
|
|
54
|
-
command.data.botPerms = command.botPerms || [];
|
|
55
|
-
command.data.userPerms = command.userPerms || [];
|
|
56
|
-
command.data.owner = command.owner || false;
|
|
57
|
-
command.data.developer = command.developer || false;
|
|
58
|
-
command.data.category = command.category || 'Uncategorized';
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
slashCommands.set(command.data.name, command);
|
|
62
|
-
}
|
|
63
|
-
} catch (error: any) {
|
|
64
|
-
logError(`Error in file: ${path.join(resolvedPath, dirent.name)}`);
|
|
65
|
-
logError(error.message);
|
|
51
|
+
const command = await loadCommand(path.join(resolvedPath, dirent.name));
|
|
52
|
+
if (command) {
|
|
53
|
+
slashCommands.set(command.data.name, command);
|
|
66
54
|
}
|
|
67
55
|
}
|
|
68
56
|
}
|
|
69
57
|
|
|
58
|
+
const slashCommandArray = Array.from(slashCommands.values()).map(command => command.data.toJSON());
|
|
59
|
+
|
|
70
60
|
if (slash.global && !slash.serverId) {
|
|
71
|
-
|
|
72
|
-
await rest.put(Routes.applicationCommands((client.user?.id || '')), { body: slashCommandArray, headers: { Authorization: `Bot ${token}` } });
|
|
61
|
+
await rest.put(Routes.applicationCommands(client.user?.id || ''), { body: slashCommandArray });
|
|
73
62
|
} else if (!slash.global && slash.serverId) {
|
|
74
63
|
const guild = client.guilds.cache.get(slash.serverId) as Guild | undefined;
|
|
75
64
|
if (guild) {
|
|
76
|
-
|
|
77
|
-
await rest.put(Routes.applicationGuildCommands((client.user?.id || ''), guild.id), { body: slashCommandArray, headers: { Authorization: `Bot ${token}` } });
|
|
65
|
+
await rest.put(Routes.applicationGuildCommands(client.user?.id || '', guild.id), { body: slashCommandArray });
|
|
78
66
|
} else {
|
|
79
67
|
logError(`Guild with ID ${slash.serverId} not found.`);
|
|
80
68
|
}
|
|
81
69
|
} else {
|
|
82
|
-
|
|
83
|
-
await rest.put(Routes.applicationCommands((client.user?.id || '')), { body: slashCommandArray, headers: { Authorization: `Bot ${token}` } });
|
|
70
|
+
await rest.put(Routes.applicationCommands(client.user?.id || ''), { body: slashCommandArray });
|
|
84
71
|
}
|
|
85
72
|
} catch (error: any) {
|
|
86
73
|
logError('Error registering slash commands:');
|
|
87
74
|
logError(error.message);
|
|
88
75
|
console.error(error);
|
|
89
|
-
|
|
90
76
|
}
|
|
91
77
|
|
|
92
78
|
client.slashCommands = slashCommands;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Collection,
|
|
3
|
+
Interaction,
|
|
4
|
+
EmbedBuilder,
|
|
5
|
+
Snowflake,
|
|
6
|
+
PermissionResolvable,
|
|
7
|
+
TextChannel
|
|
8
|
+
} from 'discord.js';
|
|
2
9
|
import { registerSlashCommands } from './slash-register';
|
|
3
10
|
import { SlashOptions } from '../types/starter';
|
|
4
11
|
import { logWarning, logError, logInfo } from '../functions/logger';
|
|
@@ -14,41 +21,47 @@ export async function loadSlash(client: any, token: string, options: SlashOption
|
|
|
14
21
|
const interactionCooldowns: Collection<Snowflake, Collection<string, number>> = new Collection();
|
|
15
22
|
|
|
16
23
|
client.on('interactionCreate', async (interaction: Interaction) => {
|
|
17
|
-
|
|
24
|
+
// Ensure we are handling the correct type of interaction
|
|
25
|
+
if (!interaction.isCommand() && !interaction.isChatInputCommand() && !interaction.isContextMenuCommand()) {
|
|
18
26
|
return;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
const guild = interaction.guild; // Get the guild once for better readability
|
|
30
|
+
const user = interaction.user;
|
|
31
|
+
|
|
32
|
+
if (guild) {
|
|
23
33
|
const botData = new Collection<string, string | string[]>();
|
|
24
34
|
const permissionsArray = botData.get('permissions') as string[] | undefined;
|
|
25
|
-
|
|
26
|
-
if
|
|
35
|
+
|
|
36
|
+
// Check if bot has permission to send messages
|
|
37
|
+
if (!guild.members.me?.permissions.has('SendMessages')) {
|
|
27
38
|
try {
|
|
28
|
-
user.send({ content: "I'm sorry, but I don't have permission to send messages in this server."});
|
|
39
|
+
await user.send({ content: "I'm sorry, but I don't have permission to send messages in this server." });
|
|
29
40
|
} catch (error) {
|
|
30
|
-
return;
|
|
41
|
+
return; // Ignore errors when sending the message
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
|
-
|
|
44
|
+
|
|
45
|
+
// Check for missing permissions
|
|
34
46
|
if (permissionsArray && permissionsArray.length > 0) {
|
|
35
|
-
const missingPermissions = permissionsArray.filter(permission => !
|
|
36
|
-
|
|
47
|
+
const missingPermissions = permissionsArray.filter(permission => !guild.members.me?.permissions.has(permission as PermissionResolvable));
|
|
48
|
+
|
|
37
49
|
if (missingPermissions.length > 0) {
|
|
38
50
|
try {
|
|
39
|
-
user.send({ content
|
|
51
|
+
await user.send({ content: `I'm sorry, but I don't have permission(s): ${missingPermissions.join(', ')} in this server.` });
|
|
40
52
|
} catch (error) {
|
|
41
|
-
return;
|
|
53
|
+
return; // Ignore errors when sending the message
|
|
42
54
|
}
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
|
-
}
|
|
57
|
+
}
|
|
46
58
|
|
|
47
59
|
const commandName = interaction.commandName;
|
|
48
60
|
const command = slashCommands.get(commandName);
|
|
49
61
|
|
|
50
62
|
if (!command) return;
|
|
51
63
|
|
|
64
|
+
// Handle command cooldowns
|
|
52
65
|
if (command.cooldown && !isNaN(command.cooldown)) {
|
|
53
66
|
const userCooldowns = interactionCooldowns.get(interaction.user.id) || new Collection<string, number>();
|
|
54
67
|
const cooldownExpiration = userCooldowns.get(command.data.name);
|
|
@@ -65,37 +78,29 @@ export async function loadSlash(client: any, token: string, options: SlashOption
|
|
|
65
78
|
|
|
66
79
|
try {
|
|
67
80
|
if (command.run) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await command.run(interaction);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else if (command.execute) {
|
|
75
|
-
if (command.execute.length === 2) {
|
|
76
|
-
await command.execute(interaction, client);
|
|
77
|
-
} else {
|
|
78
|
-
await command.execute(interaction);
|
|
79
|
-
}
|
|
81
|
+
await command.run.length === 2 ? command.run(interaction, client) : command.run(interaction);
|
|
82
|
+
} else if (command.execute) {
|
|
83
|
+
await command.execute.length === 2 ? command.execute(interaction, client) : command.execute(interaction);
|
|
80
84
|
} else {
|
|
81
85
|
logWarning(`Command "${command.data.name}" has neither run nor execute method.`);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
88
|
+
// Logging command usage
|
|
89
|
+
if (options.logsId && guild) {
|
|
90
|
+
const channel = guild.channels.cache.get(options.logsId as string) as TextChannel;
|
|
91
|
+
if (channel) {
|
|
92
|
+
const userName = user.username;
|
|
93
|
+
const userId = user.id;
|
|
94
|
+
const serverName = guild.name || 'Unknown Server';
|
|
95
|
+
const serverId = guild.id || 'Unknown ID';
|
|
91
96
|
let messageLink = '';
|
|
92
|
-
|
|
97
|
+
|
|
93
98
|
if (interaction.channel) {
|
|
94
99
|
messageLink = `https://discord.com/channels/${serverId}/${interaction.channel.id}/${interaction.id}`;
|
|
95
100
|
} else {
|
|
96
101
|
messageLink = 'Unknown';
|
|
97
102
|
}
|
|
98
|
-
|
|
103
|
+
|
|
99
104
|
const embedLog = new EmbedBuilder()
|
|
100
105
|
.setColor("Blue")
|
|
101
106
|
.setThumbnail(interaction.client.user?.displayAvatarURL())
|
|
@@ -110,9 +115,9 @@ export async function loadSlash(client: any, token: string, options: SlashOption
|
|
|
110
115
|
{ name: "\u200B", value: "\u200B", inline: true },
|
|
111
116
|
{ name: "⏳ Date:", value: `- <t:${Math.floor(Date.now() / 1000)}:R>`, inline: true }
|
|
112
117
|
);
|
|
113
|
-
|
|
114
|
-
await channel.send({ embeds: [embedLog] });
|
|
115
|
-
|
|
118
|
+
|
|
119
|
+
await channel.send({ embeds: [embedLog] });
|
|
120
|
+
}
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
const executionTime = Date.now() - startExecutionTime;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Collection } from 'discord.js';
|
|
1
|
+
import { Client, Collection } from 'discord.js';
|
|
2
2
|
import { REST } from '@discordjs/rest';
|
|
3
3
|
import { Routes } from 'discord-api-types/v10';
|
|
4
4
|
import { readdir } from 'fs/promises';
|
|
5
5
|
import { resolve, extname } from 'path';
|
|
6
6
|
import { botData } from './login';
|
|
7
7
|
import { logError, logInfo } from '../functions/logger';
|
|
8
|
+
import { watch } from 'fs';
|
|
8
9
|
|
|
9
10
|
const validExtensions = ['.js', '.ts'];
|
|
10
11
|
|
|
@@ -74,3 +75,30 @@ export async function slashLoader(client: any): Promise<{ commands: Collection<s
|
|
|
74
75
|
return { commands: new Collection<string, any>(), success: false };
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
|
|
79
|
+
export function autoSlashLoader(client: Client, DEBOUNCE_DELAY: number = 10000) {
|
|
80
|
+
|
|
81
|
+
const slashPath = botData.get('slashCommandPath') as string;
|
|
82
|
+
const commandPath = resolve(process.cwd(), slashPath);
|
|
83
|
+
|
|
84
|
+
let debounceTimer: NodeJS.Timeout | null = null;
|
|
85
|
+
|
|
86
|
+
const handleReload = async () => {
|
|
87
|
+
if (debounceTimer) {
|
|
88
|
+
clearTimeout(debounceTimer);
|
|
89
|
+
}
|
|
90
|
+
debounceTimer = setTimeout(async () => {
|
|
91
|
+
await slashLoader(client);
|
|
92
|
+
logInfo('Slash commands successfully reloaded after debounce.');
|
|
93
|
+
}, DEBOUNCE_DELAY);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
watch(commandPath, { recursive: false }, (eventType: any, filename: any) => {
|
|
97
|
+
if (filename && validExtensions.includes(extname(filename))) {
|
|
98
|
+
logInfo(`Detected ${eventType} in ${filename}, waiting for debouncing...`);
|
|
99
|
+
handleReload();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
logInfo('Started watching for changes in slash commands...');
|
|
104
|
+
}
|
|
@@ -4,7 +4,7 @@ const wait = require('node:timers/promises').setTimeout;
|
|
|
4
4
|
import { botData, login } from './login';
|
|
5
5
|
import { AntiCrash } from '../functions/anticrash';
|
|
6
6
|
import { loadEvents } from './events';
|
|
7
|
-
import { ShardingManager, ShardClientUtil } from 'discord.js';
|
|
7
|
+
import { MessageCollector, InteractionCollector, ShardingManager, ShardClientUtil } from 'discord.js';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import { logError, logInfo, logSuccess } from '../utils';
|
|
@@ -80,3 +80,6 @@ export class Starter implements StarterInterface {
|
|
|
80
80
|
}, 5000);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
@@ -41,6 +41,10 @@ export interface SlashOptions {
|
|
|
41
41
|
global?: boolean;
|
|
42
42
|
serverId?: string;
|
|
43
43
|
logsId?: string;
|
|
44
|
+
autoLoader?: {
|
|
45
|
+
enable?: boolean;
|
|
46
|
+
DEBOUNCE_DELAY?: number;
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export interface PrefixOptions {
|
|
@@ -54,6 +58,10 @@ export interface PrefixOptions {
|
|
|
54
58
|
}[];
|
|
55
59
|
similarity?: boolean;
|
|
56
60
|
logsId?: string;
|
|
61
|
+
autoLoader?: {
|
|
62
|
+
enable?: boolean;
|
|
63
|
+
DEBOUNCE_DELAY?: number;
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
export interface MongoDbOptions{
|
|
@@ -86,6 +94,10 @@ export interface EventsOptions {
|
|
|
86
94
|
logsId?: string;
|
|
87
95
|
recursive?: boolean;
|
|
88
96
|
eventBlacklist?: string[];
|
|
97
|
+
autoLoader?: {
|
|
98
|
+
enable?: boolean;
|
|
99
|
+
DEBOUNCE_DELAY?: number;
|
|
100
|
+
}
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
export interface Event {
|
package/lib/discord/utils.ts
CHANGED
|
@@ -9,7 +9,9 @@ import { registerSlashCommands } from "./events-handler/slash-register";
|
|
|
9
9
|
import { loadSlash } from "./events-handler/slash-responder";
|
|
10
10
|
import { logError, logInfo, logSuccess, logWarning } from "./functions/logger";
|
|
11
11
|
import { Pagination } from "./builder/system/Pagination";
|
|
12
|
+
import { Djs_Collectors } from "./builder/system/collectors";
|
|
12
13
|
import { loadEvents, withRetry, processEventFile, processDirectory, limitConcurrency, countEventFiles } from "./events-handler/events";
|
|
14
|
+
|
|
13
15
|
export { Starter, ButtonManager, MenuManager, PermissionChecker, slashLoader, prefixLoader, eventLoader, readCommands, loadEvents,
|
|
14
16
|
loadSlash, loadPrefix, handleMessageCreate, processDirectory, countEventFiles, limitConcurrency, withRetry, registerSlashCommands,
|
|
15
|
-
|
|
17
|
+
processEventFile, logError, logInfo, logSuccess, logWarning, Pagination, Djs_Collectors };
|