@elara-services/tickets 1.0.0
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/README.md +46 -0
- package/index.d.ts +42 -0
- package/index.js +222 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Elara Services: Tickets
|
|
2
|
+
|
|
3
|
+
This is a customizable ticket system that uses interactions and discord.js
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Getting Started
|
|
7
|
+
```js
|
|
8
|
+
const { Client } = require("discord.js"),
|
|
9
|
+
Tickets = require(`@elara-services/tickets`),
|
|
10
|
+
client = new Client({ intents: [ "GUILDS" ] }),
|
|
11
|
+
tickets = new Tickets({
|
|
12
|
+
client,
|
|
13
|
+
prefix: "support", // This is what is used for interactions (buttons) and the start of the channel name
|
|
14
|
+
webhookId: "WEBHOOK ID HERE",
|
|
15
|
+
webhookToken: "WEBHOOK TOKEN HERE",
|
|
16
|
+
encryptToken: "ASB!@#$%^&*(B", // This is to encrypt/decrypt the user IDs in the channel topic, to avoid non-staff from seeing who's ticket it is
|
|
17
|
+
supportRoleIds: [
|
|
18
|
+
"12345678", // Add the support role ids here
|
|
19
|
+
],
|
|
20
|
+
supportUserIds: [
|
|
21
|
+
`12345678`, // Add the support user ids here
|
|
22
|
+
],
|
|
23
|
+
webhookUsername: "WEBHOOK USERNAME HERE",
|
|
24
|
+
webhookAvatar: "WEBHOOK AVATAR URL HERE",
|
|
25
|
+
ticketOpen: {
|
|
26
|
+
content: "", // The content of the ticket message once it gets created, use "%user%" or "%server%" for the user mention or server name
|
|
27
|
+
embeds: [], // View https://discord.com/developers/docs/resources/channel#embed-object
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
client.on("interactionCreate", (int) => tickets.run(int))
|
|
32
|
+
|
|
33
|
+
client.on("ready", () => {
|
|
34
|
+
console.log(`Client is ready`);
|
|
35
|
+
// Use it as "node bot.js --starter" or just create a command in your bot to manage the starter message
|
|
36
|
+
if (process.argv.find(c => c === "--starter")) {
|
|
37
|
+
return tickets.starterMessage(`HELP OR SUPPORT CHANNEL ID HERE`, {
|
|
38
|
+
embeds: [
|
|
39
|
+
{ title: "Support Tickets", description: `Click the button below to create a support ticket!`, color: 0xFF000 }
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
client.login("BOT TOKEN HERE")
|
|
46
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare module "@elara-services/tickets" {
|
|
2
|
+
|
|
3
|
+
import { Client, MessageOptions, GuildMember, Guild, User, TextBasedChannel, Message, Interaction } from "discord.js";
|
|
4
|
+
import Webhook from "discord-hook";
|
|
5
|
+
|
|
6
|
+
interface TicketOptions {
|
|
7
|
+
client: Client;
|
|
8
|
+
prefix: string;
|
|
9
|
+
encryptToken: string;
|
|
10
|
+
webhookId?: string;
|
|
11
|
+
webhookToken?: string;
|
|
12
|
+
webhookUsername?: string;
|
|
13
|
+
webhookAvatar?: string;
|
|
14
|
+
supportRoleIds?: string[];
|
|
15
|
+
supportUserIds?: string[];
|
|
16
|
+
ticketOpen?: Pick<MessageOptions, "content" | "embeds">
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class Tickets {
|
|
20
|
+
public constructor(options: TicketOptions);
|
|
21
|
+
public options: TicketOptions;
|
|
22
|
+
public prefix: string;
|
|
23
|
+
public webhook(): typeof Webhook.prototype;
|
|
24
|
+
public button(options: { style: 1 | 2 | 3 | 4 | 5 | number, id?: string, label?: string, emoji?: { name?: string, id?: string } }): { type: number, custom_id: string, style: number, label?: string, emoji?: { name?: string, id?: string } }
|
|
25
|
+
|
|
26
|
+
public fetchMessages(channel: TextBasedChannel, limit?: number, before?: string, after?: string, around?: string): Promise<Array<Message>>
|
|
27
|
+
public displayMessages(channel: TextBasedChannel, messages: Array<Message>, ticketID: string, type: string): string;
|
|
28
|
+
public closeTicket(options: {
|
|
29
|
+
member: GuildMember,
|
|
30
|
+
guild: Guild,
|
|
31
|
+
user: User,
|
|
32
|
+
messages: Array<Message>,
|
|
33
|
+
channel: TextBasedChannel
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
|
|
36
|
+
public code(id: string, type: string): string;
|
|
37
|
+
public run(int: Interaction): Promise<void>;
|
|
38
|
+
public starterMessage(channelId: string, options?: Pick<MessageOptions, "embeds" | "content" | "components" | "attachments">): Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export = Tickets;
|
|
42
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const { Collection, WebhookClient } = require("discord.js"),
|
|
2
|
+
{ encrypt, decrypt } = require("aes256"),
|
|
3
|
+
{ generate } = require("shortid"),
|
|
4
|
+
Webhook = require("discord-hook");
|
|
5
|
+
|
|
6
|
+
module.exports = class Tickets {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
};
|
|
10
|
+
get prefix() { return `system:ticket:${this.options.prefix}`; };
|
|
11
|
+
webhook() {
|
|
12
|
+
return new Webhook(`https://discord.com/api/webhooks/${this.options.webhookId}/${this.options.webhookToken}`, {
|
|
13
|
+
username: this.options.webhookUsername || "Tickets",
|
|
14
|
+
avatar_url: this.options.webhookAvatar || "https://cdn.discordapp.com/emojis/818757771310792704.png?v=1",
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async fetchMessages(channel, limit = 50, before, after, around) {
|
|
19
|
+
if (limit && limit > 100) {
|
|
20
|
+
let logs = [];
|
|
21
|
+
const get = async (_before, _after) => {
|
|
22
|
+
const messages = [ ...(await channel.messages.fetch({ limit: 100, before: _before || undefined, after: _after || undefined }).catch(() => new Collection())).values() ];
|
|
23
|
+
if (limit <= messages.length) {
|
|
24
|
+
return (_after ? messages.slice(messages.length - limit, messages.length).map((message) => message).concat(logs) : logs.concat(messages.slice(0, limit).map((message) => message)));
|
|
25
|
+
}
|
|
26
|
+
limit -= messages.length;
|
|
27
|
+
logs = (_after ? messages.map((message) => message).concat(logs) : logs.concat(messages.map((message) => message)));
|
|
28
|
+
if (messages.length < 100) return logs;
|
|
29
|
+
return get((_before || !_after) && messages[messages.length - 1].id, _after && messages[0].id);
|
|
30
|
+
};
|
|
31
|
+
return get(before, after);
|
|
32
|
+
}
|
|
33
|
+
return [ ...(await channel.messages.fetch({ limit, before, after, around }).catch(() => new Collection())).values() ];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
displayMessages(channel, messages = [], ticketID, type) {
|
|
37
|
+
let users = [];
|
|
38
|
+
for (const i of messages.values()) {
|
|
39
|
+
let f = users.find(c => c.user.id === i.author.id);
|
|
40
|
+
if (f) f.count++; else users.push({ user: i.author, count: 1 });
|
|
41
|
+
};
|
|
42
|
+
return [`<discord-messages>`, `<discord-message author="${type} Ticket: ${ticketID}" bot="true" verified avatar="https://cdn.discordapp.com/emojis/847397714677334066.png?v=1" role-color="#1da1f2">Total Messages: ${users.map(c => c.count).reduce((a, b) => a + b, 0).toLocaleString()}<br>${users.map(c => `<discord-mention type="role" color="${channel.guild?.members?.cache?.get(c.user.id)?.displayColor ? channel.guild?.members?.cache?.get(c.user.id)?.displayHexColor : `#ffffff`}">${c.user.tag}</discord-mention> (${c.user.id})`).join("<br>")}</discord-message></discord-messages><discord-messages>`,
|
|
43
|
+
...messages.map(message => {
|
|
44
|
+
let str = [
|
|
45
|
+
`<discord-message${message.author.bot ? ` bot="true"` : ""} timestamp="${message.createdAt.toLocaleString("en-US", { timeZone: "America/Los_Angeles" })} (PST/PDT) | ${message.createdAt.toLocaleString("en-GB", { timeZone: "Europe/London" })} (BST/British Time)" author="${message.author.username}" avatar=${message.author.displayAvatarURL({ dynamic: true, format: "png" })} role-color="${channel.guild?.members?.cache?.get(message.author.id)?.displayColor ? channel.guild?.members?.cache?.get(message.author.id)?.displayHexColor : `#ffffff`}">`
|
|
46
|
+
];
|
|
47
|
+
if (message.content) {
|
|
48
|
+
let content = message.content;
|
|
49
|
+
if (message.mentions.users.size) for (const user of message.mentions.users.values()) content = content.replace(new RegExp(`<@!?${user.id}>`, "g"), `<discord-mention type="role" color="${message.guild?.members?.cache?.get?.(user?.id)?.displayHexColor ?? "#ffffff"}">${user.tag}</discord-mention>`);
|
|
50
|
+
if (message.mentions.channels.size) for (const channel of message.mentions.channels.values()) content = content.replace(new RegExp(channel.toString(), "g"), `<discord-mention type="channel">${channel.name}</discord-mention>`);
|
|
51
|
+
if (message.mentions.roles.size) for (const role of message.mentions.roles.values()) content = content.replace(new RegExp(role.toString(), "g"), `<discord-mention type="role" color="${role.hexColor}">${role.name}</discord-mention>`)
|
|
52
|
+
str.push(content)
|
|
53
|
+
};
|
|
54
|
+
if (message.attachments.size) str.push(message.attachments.map((c) => `<a href="${c.proxyURL}">${c.name}</a>`).join("<br>"))
|
|
55
|
+
str.push(`<br><br><code style="background-color: #36393e; color: white;">ID: ${message.id}</code>`)
|
|
56
|
+
return [...str, `</discord-message>`].join(" ");
|
|
57
|
+
}),
|
|
58
|
+
"</discord-messages>"].join(" ");
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
async closeTicket({ member, messages, channel, guild, user } = {}) {
|
|
62
|
+
if (!this.options.webhookId || !this.options.webhookToken) return;
|
|
63
|
+
let embeds = [
|
|
64
|
+
{
|
|
65
|
+
author: { name: guild.name, icon_url: guild.iconURL({ dynamic: true }) },
|
|
66
|
+
title: "Ticket: Closed",
|
|
67
|
+
description: `▫️User: ${user.toString()} \`@${user.tag}\` (${user.id})\n▫️Closed By: ${member.toString()} (${member.id})\n▫️Channel: \`#${channel.name}\` (${channel.id})`,
|
|
68
|
+
color: 0xFF0000,
|
|
69
|
+
timestamp: new Date(),
|
|
70
|
+
footer: { text: `Ticket ID: ${channel.name.split("-")[1]}` },
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
new WebhookClient({ id: this.options.webhookId, token: this.options.webhookToken })
|
|
74
|
+
.send({
|
|
75
|
+
username: this.options.webhookUsername || "Tickets",
|
|
76
|
+
avatarURL: this.options.webhookAvatar || "https://cdn.discordapp.com/emojis/818757771310792704.png?v=1",
|
|
77
|
+
embeds,
|
|
78
|
+
files: [ { name: "transcript.txt", attachment: Buffer.from(this.displayMessages(channel, messages.reverse(), channel.name.split("-")[1], this.options.prefix)) } ]
|
|
79
|
+
})
|
|
80
|
+
.then(m => {
|
|
81
|
+
let components = [{ type: 2, style: 5, label: "Transcript", emoji: { name: "📝" }, url: `https://my.elara.services/tickets?url=${Array.isArray(m.attachments) ? m.attachments?.[0]?.url : m.attachments instanceof Collection ? m.attachments?.first?.()?.url : "URL_NOT_FOUND" ?? "URL_NOT_FOUND"}` }];
|
|
82
|
+
|
|
83
|
+
this.webhook()
|
|
84
|
+
.embeds(embeds)
|
|
85
|
+
.button({ type: 1, components })
|
|
86
|
+
.edit(m.id)
|
|
87
|
+
.catch(() => null);
|
|
88
|
+
if (user) user.send({
|
|
89
|
+
embeds: [{
|
|
90
|
+
author: { name: guild.name, icon_url: guild.iconURL({ dynamic: true }) },
|
|
91
|
+
title: `Ticket: Closed`,
|
|
92
|
+
color: 0xFF0000,
|
|
93
|
+
timestamp: new Date(),
|
|
94
|
+
footer: { text: `Ticket ID: ${channel.name.split("-")[1]}` }
|
|
95
|
+
}],
|
|
96
|
+
components: [{ type: 1, components }]
|
|
97
|
+
}).catch(() => null)
|
|
98
|
+
})
|
|
99
|
+
.catch((e) => console.log(e));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
async run(int) {
|
|
103
|
+
if (int?.isButton?.()) {
|
|
104
|
+
let { guild, channel, member, customId } = int,
|
|
105
|
+
category = guild?.channels?.resolve?.(channel?.parentId),
|
|
106
|
+
[ support, supportUsers ] = [ [], [] ];
|
|
107
|
+
if (!guild || !guild.available || !channel || !member || !category) return;
|
|
108
|
+
if (this.options?.supportRoleIds?.length) for (const sup of this.options.supportRoleIds) {
|
|
109
|
+
let role = guild.roles.resolve(sup);
|
|
110
|
+
if (role) support.push(sup);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (this.options?.supportUserIds?.length) for (const uId of this.options.supportUserIds) {
|
|
114
|
+
let member = guild.members.resolve(uId) || await guild.members.fetch(uId).catch((e) => {
|
|
115
|
+
if (e?.stack?.includes?.("Unknown Member")) this.options.supportUserIds = this.options.supportUserIds.filter(c => c !== uId);
|
|
116
|
+
return null;
|
|
117
|
+
});
|
|
118
|
+
if (member) supportUsers.push(uId);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* @param {import("discord.js").InteractionDeferReplyOptions|import("discord.js").InteractionReplyOptions} options
|
|
122
|
+
* @param {boolean} edit
|
|
123
|
+
* @param {boolean} defer
|
|
124
|
+
*/
|
|
125
|
+
const send = async (options = {}, defer = false) => {
|
|
126
|
+
if (defer) return int.deferReply(options).catch(() => null);
|
|
127
|
+
if (int.replied || int.deferred) return int.editReply(options).catch(() => {});
|
|
128
|
+
return int.reply(options).catch(() => null);
|
|
129
|
+
};
|
|
130
|
+
switch (customId) {
|
|
131
|
+
case this.prefix: {
|
|
132
|
+
await send({ ephemeral: true }, true);
|
|
133
|
+
let [ permissions, allow ] = [
|
|
134
|
+
[],
|
|
135
|
+
[ "ADD_REACTIONS", "ATTACH_FILES", "CREATE_INSTANT_INVITE", "EMBED_LINKS", "READ_MESSAGE_HISTORY", "VIEW_CHANNEL", "USE_EXTERNAL_EMOJIS", "SEND_MESSAGES" ]
|
|
136
|
+
];
|
|
137
|
+
if (support.length) for (const sup of support) permissions.push({ type: "role", id: sup, allow });
|
|
138
|
+
if (supportUsers.length) for (const user of supportUsers) permissions.push({ type: "member", id: user, allow });
|
|
139
|
+
|
|
140
|
+
/** @type {import("discord.js").TextChannel} */
|
|
141
|
+
let channel = await guild.channels.create(`${this.options.prefix}-${generate().slice(0, 5).replace(/-|_/g, "")}`, {
|
|
142
|
+
type: "GUILD_TEXT", parent: category, reason: `Ticket created by: @${member.user.tag} (${member.id})`,
|
|
143
|
+
topic: `ID: ${this.code(member.id, "e")}`,
|
|
144
|
+
permissionOverwrites: [
|
|
145
|
+
{ type: "member", id: this.options.client.user.id, allow: ["ADD_REACTIONS", "ATTACH_FILES", "SEND_MESSAGES", "READ_MESSAGE_HISTORY", "EMBED_LINKS", "USE_EXTERNAL_EMOJIS", "VIEW_CHANNEL", "MENTION_EVERYONE"] },
|
|
146
|
+
{ type: "member", id: member.id, allow: ["ADD_REACTIONS", "ATTACH_FILES", "SEND_MESSAGES", "READ_MESSAGE_HISTORY", "EMBED_LINKS", "USE_EXTERNAL_EMOJIS", "VIEW_CHANNEL"], deny: ["MENTION_EVERYONE"] },
|
|
147
|
+
{ type: "role", id: guild.id, deny: ["VIEW_CHANNEL"] },
|
|
148
|
+
...permissions
|
|
149
|
+
]
|
|
150
|
+
}).catch((err) => { console.log(err); return null; });
|
|
151
|
+
if (!channel) return send({ content: `${emojis.x} I was unable to create the ticket channel, if this keeps happening contact one of the staff members via their DMs!` });
|
|
152
|
+
let msg = await channel.send({
|
|
153
|
+
content: this.options.ticketOpen?.content?.replace?.(/%user%/gi, member.user.toString())?.replace?.(/%server%/gi, guild.name) || `${member.user.toString()} 👋 Hello, please explain what you need help with.`,
|
|
154
|
+
embeds: this.options.ticketOpen?.embeds || [{
|
|
155
|
+
author: { name: guild.name, icon_url: guild.iconURL({ dynamic: true }) },
|
|
156
|
+
title: `Support will be with you shortly`,
|
|
157
|
+
color: 0xF50DE3,
|
|
158
|
+
timestamp: new Date(),
|
|
159
|
+
footer: { text: `To close this ticket press the button below.` }
|
|
160
|
+
}],
|
|
161
|
+
components: [{ type: 1, components: [{ type: 2, custom_id: `${this.prefix}:close`, label: "Close Ticket", style: 4, emoji: { name: "🔒" } }] }]
|
|
162
|
+
}).catch(() => null);
|
|
163
|
+
if (!msg) return null
|
|
164
|
+
this.webhook()
|
|
165
|
+
.embed({
|
|
166
|
+
author: { name: guild.name, icon_url: guild.iconURL({ dynamic: true }) },
|
|
167
|
+
title: "Ticket: Opened",
|
|
168
|
+
description: `▫️User: ${member.user.toString()} \`@${member.user.tag}\` (${member.id})\n▫️Channel: \`#${channel.name}\` (${channel.id})`,
|
|
169
|
+
color: 0xFF000,
|
|
170
|
+
timestamp: new Date(),
|
|
171
|
+
footer: { text: `Ticket ID: ${channel.name.split("-")[1]}` },
|
|
172
|
+
}).send().catch((e) => console.log(e));
|
|
173
|
+
return send({ content: `✅ Ticket created: ${channel.toString()}`, components: [
|
|
174
|
+
{ type: 1, components: [
|
|
175
|
+
{ type: 2, style: 5, url: msg.url, label: "Go to ticket" }
|
|
176
|
+
] }
|
|
177
|
+
] })
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
case `${this.prefix}:close`: return send({ ephemeral: true, content: `🤔 Are you sure you want to close this ticket?`, components: [{ type: 1, components: [{ type: 2, custom_id: `${this.prefix}:close:confirm:${this.code(channel.topic?.split?.("ID: ")?.[1])}`, label: "Yes close the ticket", style: 4, emoji: { id: "807031399563264030" } }] }] })
|
|
181
|
+
};
|
|
182
|
+
if (customId.startsWith(`${this.prefix}:close:confirm`)) {
|
|
183
|
+
let user = this.options.client.users.resolve(customId.split("close:confirm:")[1]) ?? await this.options.client.users.fetch(customId.split("close:confirm:")[1]).catch(() => null);
|
|
184
|
+
if (!user) return send({ content: `❌ I was unable to fetch the user that opened the ticket.`, ephemeral: true })
|
|
185
|
+
let messages = await this.fetchMessages(channel, 5000);
|
|
186
|
+
if (!messages || !messages.length) return send({ ephemeral: true, content: `❌ I was unable to close the ticket, I couldn't fetch the messages in this channel.` })
|
|
187
|
+
let closed = await channel.delete(`${member.user.tag} (${member.id}) closed the ticket.`).catch(() => null);
|
|
188
|
+
if (!closed) return send({ ephemeral: true, content: `${emojis.x} I was unable to delete the channel & close the ticket.` })
|
|
189
|
+
return this.closeTicket({ channel, guild, user, member, messages });
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
async starterMessage(channelId, options) {
|
|
195
|
+
let channel = this.options.client.channels.resolve(channelId);
|
|
196
|
+
if (!channel) return Promise.reject(`No channel found for: ${channelId}`);
|
|
197
|
+
if (!channel.isText()) return Promise.reject(`The channel ID provided isn't a text-based-channel`);
|
|
198
|
+
if (!channel.permissionsFor?.(this.options.client.user.id)?.has?.([ "VIEW_CHANNEL", "SEND_MESSAGES", "EMBED_LINKS", "ATTACH_FILES", "READ_MESSAGE_HISTORY" ])) return Promise.reject(`I'm missing permissions in ${channel.name} (${channelId})`);
|
|
199
|
+
return channel.send({
|
|
200
|
+
content: options?.content,
|
|
201
|
+
files: options?.attachments,
|
|
202
|
+
embeds: options?.embeds,
|
|
203
|
+
components: options?.components || [ { type: 1, components: [ this.button() ] } ]
|
|
204
|
+
})
|
|
205
|
+
.then(() => console.log(`Sent the starter message in ${channel.name} (${channel.id})`))
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
button(options = { style: 3, label: "Create Ticket", emoji: { name: "📩" } }) {
|
|
209
|
+
return { type: 2, custom_id: options?.id || this.prefix, style: options.style || 3, label: options.label, emoji: options.emoji };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
code(id, type = "d") {
|
|
213
|
+
try {
|
|
214
|
+
switch (type) {
|
|
215
|
+
case "e": return encrypt(this.options.encryptToken, id);
|
|
216
|
+
case "d": return decrypt(this.options.encryptToken, id);
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
return id;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elara-services/tickets",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Helper for tickets",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"typings": "index.d.ts",
|
|
8
|
+
"author": "SUPERCHIEFYT (Elara-Discord-Bots, Elara-Services)",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"optionalDependencies": {
|
|
11
|
+
"discord.js": "13.8.0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"aes256": "1.1.0",
|
|
15
|
+
"discord-hook": "github:elara-bots/discord-hook#v2",
|
|
16
|
+
"shortid": "2.2.16"
|
|
17
|
+
}
|
|
18
|
+
}
|