djs-builder 0.5.10 → 0.5.20

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.
@@ -1,321 +1,318 @@
1
1
  import {
2
- ActionRowBuilder,
3
2
  ButtonBuilder,
3
+ ActionRowBuilder,
4
4
  EmbedBuilder,
5
+ Message,
6
+ MessageComponentInteraction,
7
+ MessageActionRowComponentBuilder,
5
8
  ButtonStyle,
6
9
  StringSelectMenuBuilder,
7
- TextChannel,
8
- Message,
9
- Interaction,
10
- ButtonInteraction,
11
- StringSelectMenuInteraction,
12
- } from 'discord.js';
13
-
14
- interface ButtonSettings {
15
- label: string;
16
- style: ButtonStyle;
17
- emoji?: string;
18
- onClick?: () => void;
19
- }
20
-
21
- interface EmbedOptions {
22
- homePage: number;
23
- embeds: EmbedBuilder[];
24
- defaultPage: number;
25
- dynamicUpdate?: (currentPage: number) => EmbedBuilder;
26
- }
27
-
28
- interface TimeoutOptions {
29
- duration: number;
30
- disableComponents: boolean;
31
- onTimeout?: () => void;
32
- }
33
-
34
- interface PaginationOptions {
35
- embedOptions: EmbedOptions;
36
- timeoutOptions?: TimeoutOptions;
37
- restrictToUser?: string | null;
38
- navigationMenu?: boolean;
39
- loopNavigation?: boolean;
40
- buttonSettings?: {
41
- first?: ButtonSettings;
42
- previous?: ButtonSettings;
43
- home?: ButtonSettings;
44
- next?: ButtonSettings;
45
- last?: ButtonSettings;
10
+ StringSelectMenuInteraction
11
+ } from "discord.js";
12
+
13
+ interface PagerOptions {
14
+ firstLast?: boolean;
15
+ deleteOption?: boolean;
16
+ otherComponents?: ActionRowBuilder<MessageActionRowComponentBuilder>[];
17
+ home?: {
18
+ home_Option?: boolean;
19
+ home_page?: number;
46
20
  };
47
- additionalComponents?: ActionRowBuilder<any>[];
48
- attachments?: any[];
49
- content?: string;
50
- channel?: TextChannel;
51
- context?: Interaction;
52
- autoRefresh?: {
53
- interval: number;
54
- updateContent: () => void;
21
+ timeOption?: {
22
+ timeOut?: number;
23
+ disable?: boolean;
24
+ deletes?: boolean;
25
+ custom?: (() => void);
55
26
  };
56
- }
57
-
58
- export class Pagination {
59
- private embedOptions: EmbedOptions;
60
- private timeoutOptions: TimeoutOptions;
61
- private restrictToUser: string | null;
62
- private navigationMenu: boolean;
63
- private loopNavigation: boolean;
64
- private buttonSettings: {
65
- first?: ButtonSettings;
66
- previous?: ButtonSettings;
67
- home?: ButtonSettings;
68
- next?: ButtonSettings;
69
- last?: ButtonSettings;
27
+ user_only?: {
28
+ user_status?: boolean;
29
+ reply?: string;
70
30
  };
71
- private additionalComponents: ActionRowBuilder<any>[];
72
- private attachments: any[];
73
- private content: string | null;
74
- private channel?: TextChannel;
75
- private context?: Interaction;
76
- private components: ActionRowBuilder<any>[];
77
- private message?: Message;
78
- private autoRefresh?: {
79
- interval: number;
80
- updateContent: () => void;
31
+ style?: {
32
+ nextAndBack?: ButtonStyle;
33
+ firstAndLast?: ButtonStyle;
34
+ homeStyle?: ButtonStyle;
35
+ deleteStyle?: ButtonStyle;
81
36
  };
82
-
83
- constructor({
84
- embedOptions,
85
- timeoutOptions = { duration: 60000, disableComponents: true },
86
- restrictToUser = null,
87
- navigationMenu = false,
88
- loopNavigation = true,
89
- buttonSettings = {
90
- first: { label: 'First', style: ButtonStyle.Primary },
91
- previous: { label: 'Previous', style: ButtonStyle.Secondary },
92
- home: { label: 'Home', style: ButtonStyle.Success },
93
- next: { label: 'Next', style: ButtonStyle.Secondary },
94
- last: { label: 'Last', style: ButtonStyle.Primary },
95
- },
96
- additionalComponents = [],
97
- attachments = [],
98
- content = '',
99
- channel,
100
- context,
101
- autoRefresh,
102
- }: PaginationOptions) {
103
- if (navigationMenu && (embedOptions.embeds.length < 1 || embedOptions.embeds.length > 25)) {
104
- throw new Error('The number of embeds must be between 1 and 25 to use the navigation menu.');
105
- }
106
-
107
- this.embedOptions = embedOptions;
108
- this.timeoutOptions = timeoutOptions;
109
- this.restrictToUser = restrictToUser;
110
- this.navigationMenu = navigationMenu;
111
- this.loopNavigation = loopNavigation;
112
- this.buttonSettings = buttonSettings;
113
- this.additionalComponents = additionalComponents;
114
- this.attachments = attachments;
115
- this.content = content;
116
- this.channel = channel;
117
- this.context = context;
118
- this.autoRefresh = autoRefresh;
119
-
120
- this.components = this.createPaginationRows();
121
-
122
- if (this.navigationMenu) {
123
- this.components.push(
124
- new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
125
- new StringSelectMenuBuilder()
126
- .setCustomId('select-djs-builder')
127
- .setPlaceholder('Select a page')
128
- .addOptions(
129
- this.embedOptions.embeds.map((_, index) => ({
130
- label: `Page ${index + 1}`,
131
- value: index.toString(),
132
- }))
133
- )
134
- )
135
- );
136
- }
137
-
138
- if (this.additionalComponents.length > 0) {
139
- this.additionalComponents.forEach((row) => {
140
- if (row.components.length > 5) {
141
- this.splitComponentsIntoRows(row.components).forEach((rowComponents) => {
142
- this.components.push(new ActionRowBuilder().addComponents(rowComponents));
143
- });
144
- } else {
145
- this.components.push(row);
146
- }
147
- });
148
- }
149
-
150
- if (this.autoRefresh) {
151
- setInterval(() => {
152
- this.autoRefresh!.updateContent();
153
- this.message?.edit({
154
- embeds: [this.getEmbed()],
155
- components: this.components,
156
- });
157
- }, this.autoRefresh.interval);
158
- }
37
+ label?: {
38
+ labelNext?: string | null;
39
+ labelBack?: string | null;
40
+ labelFirst?: string | null;
41
+ labelLast?: string | null;
42
+ labelHome?: string | null;
43
+ labelDelete?: string | null;
44
+ };
45
+ emoji?: {
46
+ emojiNext?: string;
47
+ emojiBack?: string;
48
+ emojiFirst?: string;
49
+ emojiLast?: string;
50
+ emojiHome?: string;
51
+ emojiDelete?: string;
52
+ };
53
+ pages?: {
54
+ embeds?: EmbedBuilder[];
55
+ attachment?: (string | Buffer)[];
56
+ content?: string[];
57
+ };
58
+ navigationMenu?: boolean;
59
+ }
60
+
61
+ const CUSTOM_IDS = {
62
+ NEXT: "next-djs-builder",
63
+ BACK: "back-djs-builder",
64
+ FIRST: "first-djs-builder",
65
+ LAST: "last-djs-builder",
66
+ DELETE: "delete-djs-builder",
67
+ HOME: "home-djs-builder",
68
+ SELECT: "select-djs-builder"
69
+ };
70
+
71
+ const VALID_BUTTON_STYLES = [
72
+ ButtonStyle.Primary,
73
+ ButtonStyle.Secondary,
74
+ ButtonStyle.Success,
75
+ ButtonStyle.Danger
76
+ ];
77
+
78
+ const isValidEmoji = (emoji: string | undefined): boolean => {
79
+ if (!emoji) return false;
80
+ const unicodeEmojiPattern = /^[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{2934}\u{2935}\u{3030}]+$/u;
81
+ return unicodeEmojiPattern.test(emoji) || /^\d+$/.test(emoji);
82
+ };
83
+
84
+ export class Pagination {
85
+ private message: Message;
86
+ private options: Required<PagerOptions>;
87
+ private buttons: ButtonBuilder[];
88
+ private components: ActionRowBuilder<MessageActionRowComponentBuilder>[];
89
+ private selectMenus: ActionRowBuilder<StringSelectMenuBuilder>[];
90
+ private naw_page: number = 0;
91
+
92
+
93
+ constructor(message: Message, options: PagerOptions) {
94
+ this.message = message;
95
+ this.options = {
96
+ firstLast: false,
97
+ deleteOption: false,
98
+ otherComponents: [],
99
+ home: { home_Option: false, home_page: 0 },
100
+ timeOption: { timeOut: 86400000, disable: false, deletes: false, custom: undefined },
101
+ user_only: { user_status: false, reply: "You are not the command owner" },
102
+ style: { nextAndBack: ButtonStyle.Primary, firstAndLast: ButtonStyle.Secondary, homeStyle: ButtonStyle.Success, deleteStyle: ButtonStyle.Danger },
103
+ label: {},
104
+ emoji: { emojiNext: "➡", emojiBack: "⬅", emojiFirst: "⏪", emojiLast: "⏩", emojiHome: "🏠", emojiDelete: "🗑" },
105
+ pages: { embeds: [], attachment: [], content: [] },
106
+ navigationMenu: false,
107
+ ...options,
108
+ };
109
+
110
+ this.validateOptions();
111
+ this.buttons = this.createButtons();
112
+ this.components = this.createComponents();
113
+ this.selectMenus = this.createSelectMenus();
159
114
  }
160
-
161
- private createPaginationRows(): ActionRowBuilder<ButtonBuilder>[] {
162
- const rows: ActionRowBuilder<ButtonBuilder>[] = [];
163
- const buttons = [
164
- { id: 'first-djs-builder', settings: this.buttonSettings.first },
165
- { id: 'previous-djs-builder', settings: this.buttonSettings.previous },
166
- { id: 'home-djs-builder', settings: this.buttonSettings.home },
167
- { id: 'next-djs-builder', settings: this.buttonSettings.next },
168
- { id: 'last-djs-builder', settings: this.buttonSettings.last },
169
- ];
170
-
171
- while (buttons.length > 0) {
172
- const row = new ActionRowBuilder<ButtonBuilder>();
173
- for (let i = 0; i < 5 && buttons.length > 0; i++) {
174
- const { id, settings } = buttons.shift()!;
175
- if (settings) {
176
- row.addComponents(this.createButton(id, settings));
177
- }
178
- }
179
- rows.push(row);
180
- }
181
-
182
- return rows;
115
+
116
+ private validateOptions() {
117
+ const { emoji } = this.options;
118
+ if (!Object.values(emoji).every(isValidEmoji)) {
119
+ throw new Error("Invalid emoji format. Must be either Unicode emojis or valid emoji IDs.");
120
+ }
121
+
122
+ const { style } = this.options;
123
+ if (!Object.values(style).every(style => VALID_BUTTON_STYLES.includes(style))) {
124
+ throw new Error("Invalid button style. Must be one of the predefined ButtonStyle values.");
125
+ }
126
+
127
+ if (this.options.deleteOption && this.options.home?.home_Option) {
128
+ throw new Error("You can't use 'home' and 'delete' together.");
129
+ }
130
+
131
+ if (this.options.timeOption?.disable && this.options.timeOption?.deletes) {
132
+ throw new Error("You can't use 'disable' and 'deletes' together.");
133
+ }
183
134
  }
184
-
185
- private splitComponentsIntoRows(components: ButtonBuilder[]): ButtonBuilder[][] {
186
- const rows: ButtonBuilder[][] = [];
187
- while (components.length > 0) {
188
- rows.push(components.splice(0, 5));
189
- }
190
- return rows;
135
+
136
+ private createButtons(): ButtonBuilder[] {
137
+ const {
138
+ nextAndBack,
139
+ firstAndLast,
140
+ homeStyle,
141
+ deleteStyle,
142
+ } = this.options.style;
143
+
144
+ const {
145
+ emojiNext,
146
+ emojiBack,
147
+ emojiFirst,
148
+ emojiLast,
149
+ emojiHome,
150
+ emojiDelete,
151
+ } = this.options.emoji;
152
+
153
+ const next = new ButtonBuilder().setCustomId(CUSTOM_IDS.NEXT).setEmoji(emojiNext!).setStyle(nextAndBack!);
154
+ const back = new ButtonBuilder().setCustomId(CUSTOM_IDS.BACK).setEmoji(emojiBack!).setStyle(nextAndBack!);
155
+ const first = new ButtonBuilder().setCustomId(CUSTOM_IDS.FIRST).setEmoji(emojiFirst!).setStyle(firstAndLast!);
156
+ const last = new ButtonBuilder().setCustomId(CUSTOM_IDS.LAST).setEmoji(emojiLast!).setStyle(firstAndLast!);
157
+ const home = new ButtonBuilder().setCustomId(CUSTOM_IDS.HOME).setEmoji(emojiHome!).setStyle(homeStyle!);
158
+ const deleteBtn = new ButtonBuilder().setCustomId(CUSTOM_IDS.DELETE).setEmoji(emojiDelete!).setStyle(deleteStyle!);
159
+
160
+ const {
161
+ labelNext,
162
+ labelBack,
163
+ labelFirst,
164
+ labelLast,
165
+ labelHome,
166
+ labelDelete,
167
+ } = this.options.label;
168
+
169
+ if (labelNext) next.setLabel(labelNext);
170
+ if (labelBack) back.setLabel(labelBack);
171
+ if (labelFirst) first.setLabel(labelFirst);
172
+ if (labelLast) last.setLabel(labelLast);
173
+ if (labelHome) home.setLabel(labelHome);
174
+ if (labelDelete) deleteBtn.setLabel(labelDelete);
175
+
176
+ const buttons: ButtonBuilder[] = [];
177
+
178
+ if (this.options.firstLast) buttons.push(first);
179
+ buttons.push(back);
180
+ if (this.options.home?.home_Option) buttons.push(home);
181
+ else if (this.options.deleteOption) buttons.push(deleteBtn);
182
+ buttons.push(next);
183
+ if (this.options.firstLast) buttons.push(last);
184
+
185
+ return buttons;
191
186
  }
192
-
193
- private createButton(id: string, settings?: ButtonSettings): ButtonBuilder {
194
- const button = new ButtonBuilder()
195
- .setCustomId(id)
196
- .setLabel(settings?.label ?? '')
197
- .setStyle(settings?.style ?? ButtonStyle.Secondary);
198
-
199
- if (settings?.emoji) {
200
- button.setEmoji(settings.emoji);
187
+
188
+ private createComponents(): ActionRowBuilder<MessageActionRowComponentBuilder>[] {
189
+ const buttonRow = new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(this.buttons);
190
+
191
+ const otherRows = this.options.otherComponents || [];
192
+ const selectMenus = this.options.navigationMenu ? this.createSelectMenus() : [];
193
+
194
+ return [buttonRow, ...selectMenus, ...otherRows];
195
+ }
196
+
197
+ private createSelectMenus(): ActionRowBuilder<StringSelectMenuBuilder>[] {
198
+ const { embeds = [], content = [], attachment = [] } = this.options.pages || {};
199
+ const longest_length = Math.max(embeds.length, content.length, attachment.length);
200
+
201
+ const selectMenus: ActionRowBuilder<StringSelectMenuBuilder>[] = [];
202
+ for (let i = 0; i < longest_length; i += 25) {
203
+ const menuOptions = Array.from({ length: Math.min(25, longest_length - i) }, (_, j) => ({
204
+ label: `Page ${i + j + 1}`,
205
+ value: `${i + j}`
206
+ }));
207
+
208
+ const selectMenu = new StringSelectMenuBuilder()
209
+ .setCustomId(`select_${i}`)
210
+ .setPlaceholder("Choose a page")
211
+ .addOptions(menuOptions);
212
+
213
+ selectMenus.push(new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(selectMenu));
201
214
  }
202
-
203
- return button;
204
- }
205
-
206
- private getEmbed(): EmbedBuilder {
207
- return this.embedOptions.dynamicUpdate
208
- ? this.embedOptions.dynamicUpdate(this.embedOptions.defaultPage)
209
- : this.embedOptions.embeds[this.embedOptions.defaultPage];
210
- }
211
-
212
- private async handleInteraction(interaction: ButtonInteraction | StringSelectMenuInteraction): Promise<void> {
213
- if (this.restrictToUser && interaction.user.id !== this.restrictToUser) {
214
- await interaction.reply({ content: "You can't use these buttons!", ephemeral: true });
215
- return;
215
+
216
+ return selectMenus;
217
+ }
218
+
219
+ public async send() {
220
+ const { embeds = [], content = [], attachment = [] } = this.options.pages || {};
221
+
222
+ if (embeds.length === 0) {
223
+ throw new Error("No embeds provided.");
216
224
  }
217
-
218
- const { customId, values } = interaction as any;
219
-
220
- const buttonAction = this.buttonSettings[customId as keyof typeof this.buttonSettings]?.onClick;
221
- if (buttonAction) {
222
- buttonAction();
225
+
226
+ try {
227
+ const msg = await this.message.reply({
228
+ embeds: [embeds[0]],
229
+ content: content[0] || "",
230
+ files: attachment[0] ? [attachment[0]] : [],
231
+ components: this.components,
232
+ });
233
+
234
+ const collector = this.createCollector(msg);
235
+ collector.on("collect", async (i: MessageComponentInteraction) => this.handleCollect(i));
236
+ collector.on("end", async () => this.handleEnd(msg));
237
+ } catch (error) {
238
+ console.error("Error sending message:", error);
223
239
  }
224
-
225
- switch (customId) {
226
- case 'first-djs-builder':
227
- this.embedOptions.defaultPage = 0;
228
- break;
229
- case 'previous-djs-builder':
230
- this.embedOptions.defaultPage = this.loopNavigation && this.embedOptions.defaultPage === 0
231
- ? this.embedOptions.embeds.length - 1
232
- : Math.max(this.embedOptions.defaultPage - 1, 0);
233
- break;
234
- case 'next-djs-builder':
235
- this.embedOptions.defaultPage = this.loopNavigation && this.embedOptions.defaultPage === this.embedOptions.embeds.length - 1
236
- ? 0
237
- : Math.min(this.embedOptions.defaultPage + 1, this.embedOptions.embeds.length - 1);
240
+ }
241
+
242
+ private createCollector(msg: Message) {
243
+ const filter = this.options.user_only?.user_status
244
+ ? async (i: MessageComponentInteraction) => {
245
+ if (i.user.id !== this.message.author.id) {
246
+ await i.reply({ content: this.options.user_only.reply!, ephemeral: true });
247
+ return false;
248
+ }
249
+ return true;
250
+ }
251
+ : undefined;
252
+
253
+ return msg.createMessageComponentCollector({
254
+ filter,
255
+ time: this.options.timeOption?.timeOut || 86400000,
256
+ });
257
+ }
258
+
259
+ private async handleCollect(i: MessageComponentInteraction) {
260
+ const { embeds = [], content = [], attachment = [] } = this.options.pages || {};
261
+ const num = embeds.length;
262
+
263
+ try {
264
+ if (i.isButton()) {
265
+ switch (i.customId) {
266
+ case CUSTOM_IDS.NEXT:
267
+ this.naw_page = (this.naw_page + 1) % num;
238
268
  break;
239
- case 'last-djs-builder':
240
- this.embedOptions.defaultPage = this.embedOptions.embeds.length - 1;
269
+ case CUSTOM_IDS.BACK:
270
+ this.naw_page = (this.naw_page - 1 + num) % num;
241
271
  break;
242
- case 'home-djs-builder':
243
- this.embedOptions.defaultPage = this.embedOptions.homePage;
272
+ case CUSTOM_IDS.FIRST:
273
+ this.naw_page = 0;
244
274
  break;
245
- case 'select-djs-builder':
246
- this.embedOptions.defaultPage = parseInt(values[0], 10);
275
+ case CUSTOM_IDS.LAST:
276
+ this.naw_page = num - 1;
247
277
  break;
248
- default:
278
+ case CUSTOM_IDS.DELETE:
279
+ await i.message.delete();
280
+ return;
281
+ case CUSTOM_IDS.HOME:
282
+ this.naw_page = this.options.home?.home_page || 0;
249
283
  break;
250
- }
251
-
252
- if (this.message) {
253
- await interaction.deferUpdate();
254
- await this.message.edit({
255
- embeds: [this.getEmbed()],
256
- components: this.components,
257
- content: this.content ?? undefined,
258
- files: this.attachments.length ? this.attachments : undefined,
259
- });
260
- } else if (this.channel) {
261
- this.message = await this.channel.send({
262
- embeds: [this.getEmbed()],
263
- components: this.components,
264
- content: this.content ?? undefined,
265
- files: this.attachments.length ? this.attachments : undefined,
266
- });
267
- this.createCollector(this.message);
268
- } else if (this.context) {
269
- // @ts-ignore
270
- this.message = await this.context.reply({
271
- embeds: [this.getEmbed()],
272
- components: this.components,
273
- content: this.content ?? undefined,
274
- files: this.attachments.length ? this.attachments : undefined,
275
- }) as Message;
276
- // @ts-ignore
277
- this.message = await this.context.fetchReply() as Message;
278
- this.createCollector(this.message);
279
- } else {
280
- throw new Error('You must specify either a context or a channel.');
281
- }
282
- }
283
-
284
- public async send(): Promise<void> {
285
- if (this.context || this.channel) {
286
- // @ts-ignore
287
- await this.handleInteraction({ customId: '', values: [] } as ButtonInteraction);
288
- } else {
289
- throw new Error('You must specify either a context or a channel.');
290
- }
291
- }
292
-
293
- private createCollector(message: Message): void {
294
- const collector = message.createMessageComponentCollector({
295
- filter: (i) => i.isButton() || i.isStringSelectMenu(),
296
- time: this.timeoutOptions.duration,
297
- });
298
-
299
- collector.on('collect', async (interaction) => {
300
- if (interaction.isButton() || interaction.isStringSelectMenu()) {
301
- await this.handleInteraction(interaction);
302
- }
303
- });
304
-
305
- collector.on('end', async () => {
306
- if (this.timeoutOptions.onTimeout) {
307
- this.timeoutOptions.onTimeout();
308
284
  }
309
- if (!this.timeoutOptions.disableComponents) {
310
- await message.edit({ components: [] });
311
- } else {
312
- await message.edit({
313
- // @ts-ignore
314
- components: this.components.map((row) =>
315
- row.components.map((component) => component.setDisabled(true))
316
- ),
317
- });
285
+ } else if (i.isStringSelectMenu()) {
286
+ if (i.customId === "select-djs-builder") {
287
+ this.naw_page = parseInt(i.values[0], 10);
318
288
  }
319
- });
289
+ }
290
+ await i.update({
291
+ embeds: [embeds[this.naw_page]],
292
+ content: content[this.naw_page] || "",
293
+ files: attachment[this.naw_page] ? [attachment[this.naw_page]] : [],
294
+ components: this.createComponents(),
295
+ });
296
+ } catch (error) {
297
+ console.error("Error handling interaction:", error);
298
+ }
299
+ }
300
+
301
+ private async handleEnd(msg: Message) {
302
+ try {
303
+ if (this.options.timeOption?.disable) {
304
+ this.components[0].components.forEach((button) => button.setDisabled(true));
305
+ await msg.edit({ components: this.components });
306
+ }
307
+ if (this.options.timeOption?.deletes) {
308
+ await msg.edit({ components: [] });
309
+ }
310
+ if (typeof this.options.timeOption?.custom === "function") {
311
+ this.options.timeOption.custom();
312
+ }
313
+ } catch (error) {
314
+ console.error("Error handling end of collector:", error);
315
+ }
316
+ }
320
317
  }
321
- }
318
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "djs-builder",
3
- "version": "0.5.10",
3
+ "version": "0.5.20",
4
4
  "description": "Discord.js bot builder. Supports Ts and Js.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",