djs-next 0.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.

Potentially problematic release.


This version of djs-next might be problematic. Click here for more details.

@@ -0,0 +1,53 @@
1
+ export const tsTemplates = {
2
+ index: `import { DJSNextClient, GatewayIntentBits } from 'djs-next';
3
+ import 'dotenv/config';
4
+
5
+ const client = new DJSNextClient({
6
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
7
+ commandsDir: './src/commands',
8
+ eventsDir: './src/events',
9
+ componentsDir: './src/components',
10
+ tasksDir: './src/tasks',
11
+ clientId: process.env.CLIENT_ID
12
+ });
13
+
14
+ client.start(process.env.DISCORD_TOKEN!);
15
+ `,
16
+
17
+ ping: `import { FileCommand } from 'djs-next';
18
+
19
+ export default {
20
+ description: 'Replies with the actual bot latency!',
21
+ execute: async (interaction, client) => {
22
+ const sent = await interaction.reply({ content: 'Pinging...', withResponse: true });
23
+ const msg = sent.resource?.message || await interaction.fetchReply();
24
+ await interaction.editReply(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${msg.createdTimestamp - interaction.createdTimestamp}ms\\\`\`);
25
+ },
26
+ executeText: async (message, args, client) => {
27
+ const sent = await message.reply('Pinging...');
28
+ await sent.edit(\`Pong! 🏓\\nWebsocket Latency: \\\`\${client.ws.ping}ms\\\`\\nAPI Latency: \\\`\${sent.createdTimestamp - message.createdTimestamp}ms\\\`\`);
29
+ }
30
+ } as FileCommand;
31
+ `,
32
+
33
+ ready: `import { DJSNextEvent, Events } from 'djs-next';
34
+
35
+ export const event: DJSNextEvent<Events.ClientReady> = {
36
+ name: Events.ClientReady,
37
+ once: true,
38
+ execute: (client) => {
39
+ console.log('Ready! Logged in as ' + client.user?.tag);
40
+ }
41
+ };
42
+ `,
43
+
44
+ healthcheck: `import { FileTask } from 'djs-next';
45
+
46
+ export const task: FileTask = {
47
+ interval: 60000,
48
+ execute: async (client) => {
49
+ console.log('[Task] Healthcheck completed.');
50
+ }
51
+ };
52
+ `
53
+ };
@@ -0,0 +1,8 @@
1
+ import { DJSNextClient } from '../client.js';
2
+
3
+ describe('DJSNextClient', () => {
4
+ it('should instantiate without throwing', () => {
5
+ const client = new DJSNextClient({ intents: [] });
6
+ expect(client).toBeDefined();
7
+ });
8
+ });
package/src/types.ts ADDED
@@ -0,0 +1,94 @@
1
+ import {
2
+ ClientOptions,
3
+ ClientEvents,
4
+ ChatInputCommandInteraction,
5
+ Client,
6
+ ApplicationCommandOptionData,
7
+ PermissionResolvable,
8
+ AutocompleteInteraction,
9
+ MessageComponentInteraction,
10
+ ModalSubmitInteraction,
11
+ Interaction,
12
+ Message
13
+ } from 'discord.js';
14
+
15
+ export interface CooldownAdapter {
16
+ get(commandId: string, userId: string): Promise<number | null> | number | null;
17
+ set(commandId: string, userId: string, expirationTime: number): Promise<void> | void;
18
+ }
19
+
20
+ export interface DJSNextConfig {
21
+ devGuildId?: string;
22
+ errorLogChannelId?: string;
23
+ responses?: {
24
+ developerOnly?: string | null;
25
+ guildOnly?: string | null;
26
+ cooldown?: string | null;
27
+ missingPerms?: string | null;
28
+ errorBoundary?: string | null;
29
+ };
30
+ locales?: string[];
31
+ defaultLocale?: string;
32
+ directories?: {
33
+ commands?: string;
34
+ events?: string;
35
+ components?: string;
36
+ tasks?: string;
37
+ locales?: string;
38
+ };
39
+ cooldownAdapter?: CooldownAdapter;
40
+ }
41
+
42
+ export interface DJSNextClientOptions extends ClientOptions {
43
+ commandsDir?: string;
44
+ eventsDir?: string;
45
+ componentsDir?: string;
46
+ tasksDir?: string;
47
+ clientId?: string;
48
+ guildId?: string;
49
+ developers?: string[];
50
+ prefixes?: string[] | string;
51
+ enableSlashCommands?: boolean;
52
+ enableTextCommands?: boolean;
53
+ enableMentionPrefix?: boolean | string[];
54
+ enableNoPrefix?: boolean | string[];
55
+ middleware?: (interaction: Interaction | Message, client: Client) => Promise<boolean> | boolean;
56
+ config?: DJSNextConfig;
57
+ db?: any;
58
+ }
59
+
60
+ export interface FileTask<DB = any> {
61
+ filepath?: string;
62
+ interval: number;
63
+ execute: (client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
64
+ }
65
+
66
+ export interface FileCommand<DB = any> {
67
+ filepath?: string;
68
+ description: string;
69
+ options?: ApplicationCommandOptionData[];
70
+ cooldown?: number;
71
+ userPermissions?: PermissionResolvable[];
72
+ botPermissions?: PermissionResolvable[];
73
+ developerOnly?: boolean;
74
+ guildOnly?: boolean;
75
+ aliases?: string[];
76
+ preconditions?: string[];
77
+ execute?: (interaction: ChatInputCommandInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
78
+ executeText?: (message: Message, args: string[], client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
79
+ autocomplete?: (interaction: AutocompleteInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }) => Promise<void> | void;
80
+ }
81
+
82
+ export interface FileComponent<DB = any> {
83
+ filepath?: string;
84
+ customId?: string;
85
+ preconditions?: string[];
86
+ execute: (interaction: MessageComponentInteraction | ModalSubmitInteraction, client: Client & { db: DB; t: Function; config: DJSNextConfig }, params?: Record<string, string>) => Promise<void> | void;
87
+ }
88
+
89
+ export interface Event<K extends keyof ClientEvents = keyof ClientEvents, DB = any> {
90
+ filepath?: string;
91
+ name: K;
92
+ once?: boolean;
93
+ execute: (client: Client & { db: DB; t: Function; config: DJSNextConfig }, ...args: ClientEvents[K]) => Promise<void> | void;
94
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ ActionRowBuilder,
3
+ ButtonBuilder,
4
+ ButtonStyle,
5
+ CommandInteraction,
6
+ EmbedBuilder,
7
+ Message,
8
+ ComponentType
9
+ } from 'discord.js';
10
+
11
+ export class PaginationBuilder {
12
+ private pages: EmbedBuilder[] = [];
13
+ private timeout: number = 60000;
14
+
15
+ constructor(pages?: EmbedBuilder[]) {
16
+ if (pages) this.pages = pages;
17
+ }
18
+
19
+ public addPage(embed: EmbedBuilder): this {
20
+ this.pages.push(embed);
21
+ return this;
22
+ }
23
+
24
+ public setPages(pages: EmbedBuilder[]): this {
25
+ this.pages = pages;
26
+ return this;
27
+ }
28
+
29
+ public setTimeout(ms: number): this {
30
+ this.timeout = ms;
31
+ return this;
32
+ }
33
+
34
+ public async build(target: CommandInteraction | Message): Promise<Message | null> {
35
+ if (this.pages.length === 0) throw new Error('[djs-next] PaginationBuilder requires at least one page.');
36
+
37
+ if (this.pages.length === 1) {
38
+ if (target instanceof CommandInteraction) {
39
+ if (target.deferred || target.replied) {
40
+ return await target.editReply({ embeds: [this.pages[0]] }) as Message;
41
+ }
42
+ return await target.reply({ embeds: [this.pages[0]], fetchReply: true }) as Message;
43
+ } else {
44
+ return await target.reply({ embeds: [this.pages[0]] });
45
+ }
46
+ }
47
+
48
+ let currentPage = 0;
49
+
50
+ const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
51
+ new ButtonBuilder().setCustomId('prev').setLabel('◀').setStyle(ButtonStyle.Primary).setDisabled(true),
52
+ new ButtonBuilder().setCustomId('page').setLabel(`1 / ${this.pages.length}`).setStyle(ButtonStyle.Secondary).setDisabled(true),
53
+ new ButtonBuilder().setCustomId('next').setLabel('▶').setStyle(ButtonStyle.Primary)
54
+ );
55
+
56
+ let replyMsg: Message;
57
+ if (target instanceof CommandInteraction) {
58
+ if (target.deferred || target.replied) {
59
+ replyMsg = await target.editReply({ embeds: [this.pages[0]], components: [row] }) as Message;
60
+ } else {
61
+ replyMsg = await target.reply({ embeds: [this.pages[0]], components: [row], fetchReply: true }) as Message;
62
+ }
63
+ } else {
64
+ replyMsg = await target.reply({ embeds: [this.pages[0]], components: [row] });
65
+ }
66
+
67
+ const userId = target instanceof CommandInteraction ? target.user.id : target.author.id;
68
+
69
+ const collector = replyMsg.createMessageComponentCollector({
70
+ componentType: ComponentType.Button,
71
+ time: this.timeout,
72
+ filter: i => i.user.id === userId
73
+ });
74
+
75
+ collector.on('collect', async i => {
76
+ if (i.customId === 'prev') currentPage--;
77
+ else if (i.customId === 'next') currentPage++;
78
+
79
+ const newRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
80
+ new ButtonBuilder().setCustomId('prev').setLabel('◀').setStyle(ButtonStyle.Primary).setDisabled(currentPage === 0),
81
+ new ButtonBuilder().setCustomId('page').setLabel(`${currentPage + 1} / ${this.pages.length}`).setStyle(ButtonStyle.Secondary).setDisabled(true),
82
+ new ButtonBuilder().setCustomId('next').setLabel('▶').setStyle(ButtonStyle.Primary).setDisabled(currentPage === this.pages.length - 1)
83
+ );
84
+
85
+ await i.update({ embeds: [this.pages[currentPage]], components: [newRow] });
86
+ });
87
+
88
+ collector.on('end', () => {
89
+ replyMsg.edit({ components: [] }).catch(() => null);
90
+ });
91
+
92
+ return replyMsg;
93
+ }
94
+ }
@@ -0,0 +1,27 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { pathToFileURL } from 'url';
4
+ import { DJSNextConfig } from '../types.js';
5
+
6
+ export async function loadConfig(): Promise<DJSNextConfig> {
7
+ const exts = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'];
8
+ const cwd = process.cwd();
9
+
10
+ for (const ext of exts) {
11
+ const configPath = path.join(cwd, `djs-next.config${ext}`);
12
+ if (fs.existsSync(configPath)) {
13
+ try {
14
+ const configModule = await import(pathToFileURL(configPath).href);
15
+ return configModule.default || configModule;
16
+ } catch (err) {
17
+ console.error(`[djs-next] Error loading config file ${configPath}:`, err);
18
+ return {};
19
+ }
20
+ }
21
+ }
22
+ return {};
23
+ }
24
+
25
+ export function defineConfig(config: DJSNextConfig): DJSNextConfig {
26
+ return config;
27
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ let localesCache: Record<string, Record<string, any>> = {};
5
+ let defaultLoc = 'en';
6
+
7
+ export function loadLocales(localesDir: string, defaultLocale?: string) {
8
+ if (defaultLocale) defaultLoc = defaultLocale;
9
+
10
+ if (!fs.existsSync(localesDir)) return;
11
+
12
+ const files = fs.readdirSync(localesDir);
13
+ for (const file of files) {
14
+ if (file.endsWith('.json')) {
15
+ const lang = file.replace('.json', '');
16
+ const content = fs.readFileSync(path.join(localesDir, file), 'utf8');
17
+ try {
18
+ localesCache[lang] = JSON.parse(content);
19
+ } catch (e) {
20
+ console.error(`[djs-next] Failed to parse locale file: ${file}`, e);
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ export function translate(key: string, locale: string = defaultLoc, variables?: Record<string, string | number>): string {
27
+ // Fallback to default locale if the requested one is not found
28
+ const dict = localesCache[locale] || localesCache[defaultLoc] || {};
29
+
30
+ const keys = key.split('.');
31
+ let value: any = dict;
32
+
33
+ for (const k of keys) {
34
+ if (value && typeof value === 'object') {
35
+ value = value[k];
36
+ } else {
37
+ value = undefined;
38
+ break;
39
+ }
40
+ }
41
+
42
+ if (typeof value !== 'string') {
43
+ return key; // return the key itself if string not found
44
+ }
45
+
46
+ if (variables) {
47
+ for (const [varKey, varValue] of Object.entries(variables)) {
48
+ value = value.replace(new RegExp(`{{s*${varKey}s*}}`, 'g'), String(varValue));
49
+ }
50
+ }
51
+
52
+ return value;
53
+ }
54
+
55
+ export function getLocalesCache() {
56
+ return localesCache;
57
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ ActionRowBuilder,
3
+ ButtonBuilder,
4
+ ButtonStyle,
5
+ CommandInteraction,
6
+ EmbedBuilder,
7
+ Message,
8
+ MessageComponentInteraction
9
+ } from 'discord.js';
10
+
11
+ export async function paginate(
12
+ context: Message | CommandInteraction | MessageComponentInteraction,
13
+ pages: EmbedBuilder[],
14
+ time: number = 60000
15
+ ) {
16
+ const isMessage = 'author' in context;
17
+
18
+ if (!isMessage) {
19
+ if (!context.deferred && !context.replied) {
20
+ await context.deferReply();
21
+ }
22
+ }
23
+
24
+ if (pages.length === 1) {
25
+ if (isMessage) {
26
+ return context.reply({ embeds: [pages[0]], components: [] });
27
+ } else {
28
+ return context.editReply({ embeds: [pages[0]], components: [] });
29
+ }
30
+ }
31
+
32
+ let index = 0;
33
+
34
+ const prevButton = new ButtonBuilder()
35
+ .setCustomId('djs_prev')
36
+ .setLabel('Previous')
37
+ .setStyle(ButtonStyle.Primary)
38
+ .setDisabled(true);
39
+
40
+ const nextButton = new ButtonBuilder()
41
+ .setCustomId('djs_next')
42
+ .setLabel('Next')
43
+ .setStyle(ButtonStyle.Primary);
44
+
45
+ const row = new ActionRowBuilder<ButtonBuilder>().addComponents(prevButton, nextButton);
46
+
47
+ let message: Message;
48
+ if (isMessage) {
49
+ message = await context.reply({
50
+ embeds: [pages[index]],
51
+ components: [row],
52
+ });
53
+ } else {
54
+ message = await context.editReply({
55
+ embeds: [pages[index]],
56
+ components: [row],
57
+ });
58
+ }
59
+
60
+ const collector = message.createMessageComponentCollector({
61
+ filter: (i) => i.user.id === (isMessage ? context.author.id : context.user.id),
62
+ time
63
+ });
64
+
65
+ collector.on('collect', async (i: MessageComponentInteraction) => {
66
+ if (i.customId === 'djs_prev') {
67
+ index = index > 0 ? index - 1 : index;
68
+ } else if (i.customId === 'djs_next') {
69
+ index = index < pages.length - 1 ? index + 1 : index;
70
+ }
71
+
72
+ prevButton.setDisabled(index === 0);
73
+ nextButton.setDisabled(index === pages.length - 1);
74
+
75
+ await i.update({
76
+ embeds: [pages[index]],
77
+ components: [new ActionRowBuilder<ButtonBuilder>().addComponents(prevButton, nextButton)]
78
+ });
79
+ });
80
+
81
+ collector.on('end', async () => {
82
+ prevButton.setDisabled(true);
83
+ nextButton.setDisabled(true);
84
+ if (message.editable) {
85
+ await message.edit({
86
+ components: [new ActionRowBuilder<ButtonBuilder>().addComponents(prevButton, nextButton)]
87
+ }).catch(() => {});
88
+ }
89
+ });
90
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ ActionRowBuilder,
3
+ ButtonBuilder,
4
+ ButtonStyle,
5
+ CommandInteraction,
6
+ EmbedBuilder,
7
+ Message,
8
+ MessageComponentInteraction
9
+ } from 'discord.js';
10
+
11
+ /**
12
+ * Sends a confirmation prompt (Yes/No) to the user.
13
+ * @param context The message or interaction context.
14
+ * @param content The text or embed to display in the prompt.
15
+ * @param time Time to wait for a response in milliseconds.
16
+ * @returns Boolean indicating true for Yes, false for No, or null if timed out.
17
+ */
18
+ export async function confirmPrompt(
19
+ context: Message | CommandInteraction | MessageComponentInteraction,
20
+ content: string | EmbedBuilder,
21
+ time: number = 30000
22
+ ): Promise<boolean | null> {
23
+ const isMessage = 'author' in context;
24
+
25
+ if (!isMessage) {
26
+ if (!context.deferred && !context.replied) {
27
+ await context.deferReply();
28
+ }
29
+ }
30
+
31
+ const yesButton = new ButtonBuilder()
32
+ .setCustomId('djs_prompt_yes')
33
+ .setLabel('Yes')
34
+ .setStyle(ButtonStyle.Success);
35
+
36
+ const noButton = new ButtonBuilder()
37
+ .setCustomId('djs_prompt_no')
38
+ .setLabel('No')
39
+ .setStyle(ButtonStyle.Danger);
40
+
41
+ const row = new ActionRowBuilder<ButtonBuilder>().addComponents(yesButton, noButton);
42
+
43
+ const payload: any = { components: [row] };
44
+ if (typeof content === 'string') {
45
+ payload.content = content;
46
+ } else {
47
+ payload.embeds = [content];
48
+ }
49
+
50
+ let message: Message;
51
+ if (isMessage) {
52
+ message = await context.reply(payload);
53
+ } else {
54
+ message = await context.editReply(payload);
55
+ }
56
+
57
+ try {
58
+ const interaction = await message.awaitMessageComponent({
59
+ filter: (i) => i.user.id === (isMessage ? context.author.id : context.user.id),
60
+ time
61
+ });
62
+
63
+ if (interaction.customId === 'djs_prompt_yes') {
64
+ await interaction.update({ components: [] });
65
+ return true;
66
+ } else {
67
+ await interaction.update({ components: [] });
68
+ return false;
69
+ }
70
+ } catch (error) {
71
+ if (message.editable) {
72
+ await message.edit({ components: [] }).catch(() => {});
73
+ }
74
+ return null; // Timed out
75
+ }
76
+ }
@@ -0,0 +1,3 @@
1
+ const { MessagePayload, Message } = require('discord.js');
2
+ const payload = MessagePayload.create({ flags: { bitfield: 0 } }, "Pong!");
3
+ console.log(payload.resolveBody());
package/test_reply.js ADDED
@@ -0,0 +1,4 @@
1
+ const { Client, MessagePayload } = require('discord.js');
2
+ const client = new Client({ intents: [] });
3
+ const payload = MessagePayload.create(client, { components: [], flags: 32768 });
4
+ console.log(payload.resolveBody());
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "declaration": true,
11
+ "outDir": "./dist",
12
+ "types": ["node"],
13
+ "ignoreDeprecations": "6.0"
14
+ },
15
+ "include": ["src/**/*"]
16
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'], // Build for CommonJS and ES Modules
6
+ dts: true, // Generate declaration file (.d.ts)
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ });