@elara-services/tickets 2.0.4 → 2.1.2
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 +1 -0
- package/index.d.ts +2 -1
- package/index.js +47 -102
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ const { Client } = require("discord.js"),
|
|
|
51
51
|
avatar: "", // 'webhookAvatar' support will be removed in the next major version
|
|
52
52
|
},
|
|
53
53
|
support: {
|
|
54
|
+
canOnlyCloseTickets: true, // If 'true' only roles and users listed below can close tickets. (OR people with 'Manage Server' permission)
|
|
54
55
|
roles: [ // 'supportRoleIds' support will be removed in the next major version
|
|
55
56
|
"123456789"
|
|
56
57
|
],
|
package/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ declare module "@elara-services/tickets" {
|
|
|
12
12
|
ticketOpen?: Pick<MessageOptions, "content" | "embeds">
|
|
13
13
|
appeals?: {
|
|
14
14
|
enabled: boolean;
|
|
15
|
-
|
|
15
|
+
mainserver: {
|
|
16
16
|
id: string;
|
|
17
17
|
checkIfBanned: boolean;
|
|
18
18
|
};
|
|
@@ -42,6 +42,7 @@ declare module "@elara-services/tickets" {
|
|
|
42
42
|
support?: {
|
|
43
43
|
roles?: string[];
|
|
44
44
|
users?: string[];
|
|
45
|
+
canOnlyCloseTickets?: boolean;
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
/** @deprecated Use 'webhook.id' */
|
package/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
const { Collection, WebhookClient, MessageEmbed } = require("discord.js"),
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
2
|
+
{ generate } = require("shortid"),
|
|
3
|
+
{ Interactions: { button, modal }, AES } = require("@elara-services/packages"),
|
|
4
|
+
Webhook = require("discord-hook"),
|
|
5
|
+
de = {
|
|
6
|
+
user: "<:Members:860931214232125450>",
|
|
7
|
+
channel: "<:Channel:841654412509839390>",
|
|
8
|
+
transcript: "<:Log:792290922749624320>"
|
|
9
|
+
};
|
|
11
10
|
|
|
12
11
|
module.exports = class Tickets {
|
|
13
|
-
constructor(options) {
|
|
12
|
+
constructor(options = {}) {
|
|
14
13
|
if (typeof options !== "object") throw new Error(`You didn't provide any data in the constructor, fill it out!`);
|
|
15
14
|
if (!("client" in options) || !("prefix" in options) || !("encryptToken" in options)) throw new Error(`You forgot to fill out either 'client', 'prefix' or 'encryptToken'`)
|
|
16
15
|
this.options = options;
|
|
17
16
|
};
|
|
17
|
+
|
|
18
18
|
get prefix() { return `system:ticket:${this.options.prefix}`; };
|
|
19
19
|
|
|
20
20
|
/** @private */
|
|
@@ -26,6 +26,7 @@ module.exports = class Tickets {
|
|
|
26
26
|
avatar: this.options.webhook?.avatar || this.options.webhookAvatar || "https://cdn.discordapp.com/emojis/818757771310792704.png?v=1"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
|
|
29
30
|
webhook() {
|
|
30
31
|
const { id, token, username, avatar } = this.webhookOptions;
|
|
31
32
|
return new Webhook(`https://discord.com/api/webhooks/${id}/${token}`, { username, avatar_url: avatar });
|
|
@@ -38,7 +39,6 @@ module.exports = class Tickets {
|
|
|
38
39
|
if (int?.isButton?.() || int?.isModalSubmit()) {
|
|
39
40
|
let { guild, channel, member, customId } = int,
|
|
40
41
|
category = guild?.channels?.resolve?.(this.options.ticketCategory || channel?.parentId);
|
|
41
|
-
|
|
42
42
|
if (!guild || !guild.available || !channel || !member || !category) return;
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -53,21 +53,21 @@ module.exports = class Tickets {
|
|
|
53
53
|
};
|
|
54
54
|
switch (customId) {
|
|
55
55
|
case this.prefix: {
|
|
56
|
-
if (this.options.modal?.enabled) {
|
|
57
|
-
return int.showModal(this.modal({
|
|
58
|
-
title: this.options.modal.title,
|
|
59
|
-
components: this.options.modal.questions?.length >= 1 ?
|
|
60
|
-
this.options.modal.questions.slice(0, 5).map(c => ({ type: 1, components: [{ min_length: c.min_length || 10, max_length: c.max_length || 4000, type: 4, style: c.style || 2, label: c.label, value: c.value, placeholder: c.placeholder, required: c.required, custom_id: c.label || `random_${Math.floor(Math.random() * 10000)}` }] })) :
|
|
61
|
-
[]
|
|
62
|
-
})).catch(e => this._debug(e));
|
|
63
|
-
}
|
|
56
|
+
if (this.options.modal?.enabled) return int.showModal(this.modal({ title: this.options.modal.title, components: this.options.modal.questions?.length >= 1 ? this.options.modal.questions.slice(0, 5).map(c => ({ type: 1, components: [{ min_length: c.min_length || 10, max_length: c.max_length || 4000, type: 4, style: c.style || 2, label: c.label, value: c.value, placeholder: c.placeholder, required: c.required, custom_id: c.label || `random_${Math.floor(Math.random() * 10000)}` }] })) : [] })).catch(e => this._debug(e));
|
|
64
57
|
return this.handleCreate({ guild, member, category, send })
|
|
65
58
|
};
|
|
66
59
|
|
|
67
|
-
case `${this.prefix}:close`:
|
|
60
|
+
case `${this.prefix}:close`: {
|
|
61
|
+
if (this.options.support?.canOnlyCloseTickets && !member.permissions.has("MANAGE_GUILD")) {
|
|
62
|
+
let [ support, staffOnly ] = [ this.getSupportIds(), () => send({ ephemeral: true, embeds: [ { author: { name: `Only support staff can close tickets`, iconURL: "https://cdn.discordapp.com/emojis/781955502035697745.gif" }, color: 0xFF0000 } ] }) ];
|
|
63
|
+
if (!support.users?.includes?.(member.id)) return staffOnly();
|
|
64
|
+
if (support.roles?.length && !support.roles.some(c => member.roles.cache.has(c))) return staffOnly()
|
|
65
|
+
}
|
|
66
|
+
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" } }] }] })
|
|
67
|
+
}
|
|
68
68
|
|
|
69
69
|
case `${this.prefix}:modal_submit`: {
|
|
70
|
-
let [embed, fields, split] = [new MessageEmbed().setColor("ORANGE"), [], false];
|
|
70
|
+
let [ embed, fields, split ] = [ new MessageEmbed().setColor("ORANGE").setTimestamp().setTitle(`For Responses`).setFooter({ text: `ID: ${member.id}` }).setAuthor({ name: member.user.username, iconURL: member.user.displayAvatarURL({ dynamic: true }) }), [], false];
|
|
71
71
|
for (const c of int.fields.components) {
|
|
72
72
|
for (const cc of c.components) {
|
|
73
73
|
if (cc.value && cc.customId) {
|
|
@@ -79,31 +79,23 @@ module.exports = class Tickets {
|
|
|
79
79
|
}
|
|
80
80
|
if (embed.length >= 6000 || split) {
|
|
81
81
|
return this.handleCreate({ guild, member, category, send, embeds: fields.map((v, i) => ({
|
|
82
|
-
title: `Form Response: ${v.name}`,
|
|
83
|
-
color: embed.color,
|
|
84
|
-
description: v.value,
|
|
82
|
+
title: `Form Response: ${v.name}`, color: embed.color, description: v.value,
|
|
85
83
|
author: i === 0 ? { name: member.user.username, iconURL: member.user.displayAvatarURL({ dynamic: true }) } : undefined,
|
|
86
84
|
timestamp: fields.length - 1 === i ? new Date() : undefined,
|
|
87
85
|
footer: fields.length - 1 === i ? { text: `ID: ${member.id}` } : undefined
|
|
88
86
|
})) })
|
|
89
87
|
};
|
|
90
|
-
|
|
91
|
-
return this.handleCreate({ guild, member, category, send, embeds: [
|
|
92
|
-
embed
|
|
93
|
-
.setTitle(`Form Responses`)
|
|
94
|
-
.setTimestamp()
|
|
95
|
-
.setAuthor({ name: member.user.username, iconURL: member.user.displayAvatarURL({ dynamic: true }) })
|
|
96
|
-
.setFooter({ text: `ID: ${member.id}` })
|
|
97
|
-
]})
|
|
88
|
+
return this.handleCreate({ guild, member, category, send, embeds: [ embed ]})
|
|
98
89
|
}
|
|
99
90
|
};
|
|
100
91
|
if (customId.startsWith(`${this.prefix}:close:confirm`)) {
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
await send({ ephemeral: true }, true);
|
|
93
|
+
let user = await this.options.client.users.fetch(customId.split("close:confirm:")[1]).catch(() => null);
|
|
94
|
+
if (!user) return send({ content: `❌ I was unable to fetch the user that opened the ticket.` })
|
|
103
95
|
let messages = await this.fetchMessages(channel, 5000);
|
|
104
|
-
if (!messages
|
|
96
|
+
if (!messages?.length) return send({ content: `❌ I was unable to close the ticket, I couldn't fetch the messages in this channel.` })
|
|
105
97
|
let closed = await channel.delete(`${member.user.tag} (${member.id}) closed the ticket.`).catch(e => this._debug(e));
|
|
106
|
-
if (!closed) return send({
|
|
98
|
+
if (!closed) return send({ content: `❌ I was unable to delete the channel & close the ticket.` })
|
|
107
99
|
return this.closeTicket({ channel, guild, user, member, messages });
|
|
108
100
|
}
|
|
109
101
|
};
|
|
@@ -111,10 +103,15 @@ module.exports = class Tickets {
|
|
|
111
103
|
|
|
112
104
|
/** @private */
|
|
113
105
|
async handleCreate({ guild, member, category, send, embeds = [] } = {}) {
|
|
114
|
-
|
|
106
|
+
await send({ ephemeral: true }, true);
|
|
107
|
+
let [ supportIds, permissions, allow, { appeals } ] = [
|
|
108
|
+
this.getSupportIds(), [],
|
|
109
|
+
["ADD_REACTIONS", "ATTACH_FILES", "CREATE_INSTANT_INVITE", "EMBED_LINKS", "READ_MESSAGE_HISTORY", "VIEW_CHANNEL", "USE_EXTERNAL_EMOJIS", "SEND_MESSAGES"],
|
|
110
|
+
this.options ?? {}
|
|
111
|
+
];
|
|
115
112
|
if (supportIds.roles.length) for (const sup of supportIds.roles) {
|
|
116
113
|
let role = guild.roles.resolve(sup);
|
|
117
|
-
if (role)
|
|
114
|
+
if (role) permissions.push({ type: "role", id: sup, allow });
|
|
118
115
|
};
|
|
119
116
|
|
|
120
117
|
if (supportIds.users.length) for (const uId of supportIds.users) {
|
|
@@ -122,11 +119,9 @@ module.exports = class Tickets {
|
|
|
122
119
|
if (e?.stack?.includes?.("Unknown Member")) this.options.support.users = this.options.support.users.filter(c => c !== uId);
|
|
123
120
|
return this._debug(e);
|
|
124
121
|
});
|
|
125
|
-
if (member)
|
|
122
|
+
if (member) permissions.push({ type: "member", id: uId, allow });
|
|
126
123
|
}
|
|
127
|
-
|
|
128
|
-
if (this.options.appeals?.enabled) {
|
|
129
|
-
let appeals = this.options.appeals;
|
|
124
|
+
if (appeals?.enabled) {
|
|
130
125
|
if (appeals.mainserver?.id && appeals.mainserver.checkIfBanned) {
|
|
131
126
|
let server = this.options.client.guilds.resolve(appeals.mainserver.id);
|
|
132
127
|
if (server?.available) {
|
|
@@ -149,14 +144,6 @@ module.exports = class Tickets {
|
|
|
149
144
|
}
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
|
-
let [permissions, allow] = [
|
|
153
|
-
[],
|
|
154
|
-
["ADD_REACTIONS", "ATTACH_FILES", "CREATE_INSTANT_INVITE", "EMBED_LINKS", "READ_MESSAGE_HISTORY", "VIEW_CHANNEL", "USE_EXTERNAL_EMOJIS", "SEND_MESSAGES"]
|
|
155
|
-
];
|
|
156
|
-
if (support.length) for (const sup of support) permissions.push({ type: "role", id: sup, allow });
|
|
157
|
-
if (supportUsers.length) for (const user of supportUsers) permissions.push({ type: "member", id: user, allow });
|
|
158
|
-
|
|
159
|
-
/** @type {import("discord.js").TextChannel} */
|
|
160
147
|
let channel = await guild.channels.create(`${this.options.prefix}-${generate().slice(0, 5).replace(/-|_/g, "")}`, {
|
|
161
148
|
type: "GUILD_TEXT", parent: category, reason: `Ticket created by: @${member.user.tag} (${member.id})`,
|
|
162
149
|
topic: `ID: ${this.code(member.id, "e")}`,
|
|
@@ -167,7 +154,7 @@ module.exports = class Tickets {
|
|
|
167
154
|
...permissions
|
|
168
155
|
]
|
|
169
156
|
}).catch(e => this._debug(e));
|
|
170
|
-
if (!channel) return send({ content:
|
|
157
|
+
if (!channel) return send({ content: `❌ I was unable to create the ticket channel, if this keeps happening contact one of the staff members via their DMs!` });
|
|
171
158
|
let msg = await channel.send({
|
|
172
159
|
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.`,
|
|
173
160
|
embeds: this.options.ticketOpen?.embeds || [{
|
|
@@ -191,24 +178,13 @@ module.exports = class Tickets {
|
|
|
191
178
|
footer: { text: `Ticket ID: ${channel.name.split("-")[1]}` },
|
|
192
179
|
}).send().catch(e => this._debug(e));
|
|
193
180
|
return send({
|
|
194
|
-
embeds: [
|
|
195
|
-
{
|
|
196
|
-
author: { name: `Ticket Created!`, icon_url: `https://cdn.discordapp.com/emojis/476629550797684736.gif` },
|
|
197
|
-
description: channel.toString(),
|
|
198
|
-
color: 0xFF000
|
|
199
|
-
}
|
|
200
|
-
],
|
|
181
|
+
embeds: [ { author: { name: `Ticket Created!`, icon_url: `https://cdn.discordapp.com/emojis/476629550797684736.gif` }, description: channel.toString(), color: 0xFF000 } ],
|
|
201
182
|
components: [ { type: 1, components: [ button({ title: "Go to ticket", url: msg.url }) ] } ]
|
|
202
183
|
})
|
|
203
184
|
}
|
|
204
185
|
|
|
205
186
|
button(options = { style: 3, label: "Create Ticket", emoji: { name: "📩" } }) {
|
|
206
|
-
return button({
|
|
207
|
-
id: options?.id || this.prefix,
|
|
208
|
-
style: options.style || 3,
|
|
209
|
-
title: options.label,
|
|
210
|
-
emoji: options.emoji
|
|
211
|
-
});
|
|
187
|
+
return button({ id: options?.id || this.prefix, style: options.style || 3, title: options.label, emoji: options.emoji });
|
|
212
188
|
};
|
|
213
189
|
|
|
214
190
|
/**
|
|
@@ -217,17 +193,7 @@ module.exports = class Tickets {
|
|
|
217
193
|
* @param {import("@elara-services/packages").Modal['components']} [options.components]
|
|
218
194
|
*/
|
|
219
195
|
modal(options = { title: "", components: [] }) {
|
|
220
|
-
return modal({
|
|
221
|
-
id: `${this.prefix}:modal_submit`,
|
|
222
|
-
title: options?.title || "Create Ticket",
|
|
223
|
-
components: options?.components?.length >= 1 ? options.components : [
|
|
224
|
-
{
|
|
225
|
-
type: 1, components: [
|
|
226
|
-
{ type: 4, min_length: 10, max_length: 4000, custom_id: "message", label: "Content", style: 2, placeholder: "What's the ticket about?", required: true }
|
|
227
|
-
]
|
|
228
|
-
}
|
|
229
|
-
]
|
|
230
|
-
})
|
|
196
|
+
return modal({ id: `${this.prefix}:modal_submit`, title: options?.title || "Create Ticket", components: options?.components?.length >= 1 ? options.components : [ { type: 1, components: [ { type: 4, min_length: 10, max_length: 4000, custom_id: "message", label: "Content", style: 2, placeholder: "What's the ticket about?", required: true } ] } ] })
|
|
231
197
|
}
|
|
232
198
|
|
|
233
199
|
async starterMessage(channelId, options) {
|
|
@@ -235,20 +201,16 @@ module.exports = class Tickets {
|
|
|
235
201
|
if (!channel) return Promise.reject(`No channel found for: ${channelId}`);
|
|
236
202
|
if (!channel.isText()) return Promise.reject(`The channel ID provided isn't a text-based-channel`);
|
|
237
203
|
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})`);
|
|
238
|
-
return channel.send({
|
|
239
|
-
content: options?.content,
|
|
240
|
-
files: options?.attachments,
|
|
241
|
-
embeds: options?.embeds,
|
|
242
|
-
components: options?.components || [{ type: 1, components: [this.button()] }]
|
|
243
|
-
})
|
|
204
|
+
return channel.send({ content: options?.content, files: options?.attachments, embeds: options?.embeds, components: options?.components || [{ type: 1, components: [this.button()] }] })
|
|
244
205
|
.then(() => console.log(`Sent the starter message in ${channel.name} (${channel.id})`))
|
|
245
206
|
};
|
|
246
207
|
|
|
247
208
|
code(id, type = "d") {
|
|
248
209
|
try {
|
|
210
|
+
const aes = new AES(this.options.encryptToken);
|
|
249
211
|
switch (type) {
|
|
250
|
-
case "e": return encrypt(
|
|
251
|
-
case "d": return decrypt(
|
|
212
|
+
case "e": return aes.encrypt(id);
|
|
213
|
+
case "d": return aes.decrypt(id);
|
|
252
214
|
}
|
|
253
215
|
} catch {
|
|
254
216
|
return id;
|
|
@@ -273,13 +235,6 @@ module.exports = class Tickets {
|
|
|
273
235
|
return [...(await channel.messages.fetch({ limit, before, after, around }).catch(() => new Collection())).values()];
|
|
274
236
|
};
|
|
275
237
|
|
|
276
|
-
/**
|
|
277
|
-
* @param {import("discord.js").TextBasedChannel} channel
|
|
278
|
-
* @param {import("discord.js").Message[]} messages
|
|
279
|
-
* @param {string} ticketID
|
|
280
|
-
* @param {string} type
|
|
281
|
-
* @returns {string}
|
|
282
|
-
*/
|
|
283
238
|
displayMessages(channel, messages = [], ticketID, type) {
|
|
284
239
|
let users = [];
|
|
285
240
|
for (const i of messages.values()) {
|
|
@@ -380,15 +335,7 @@ module.exports = class Tickets {
|
|
|
380
335
|
}).catch(e => this._debug(e));
|
|
381
336
|
}).catch(e => this._debug(e));
|
|
382
337
|
};
|
|
383
|
-
|
|
384
|
-
* @typedef {Object} getSupportResponse
|
|
385
|
-
* @property {string[]} [roles]
|
|
386
|
-
* @property {string[]} [users]
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
* @private
|
|
390
|
-
* @returns {getSupportResponse}
|
|
391
|
-
*/
|
|
338
|
+
|
|
392
339
|
getSupportIds() {
|
|
393
340
|
return {
|
|
394
341
|
roles: this.options.support?.roles || this.options.supportRoleIds || [],
|
|
@@ -398,9 +345,7 @@ module.exports = class Tickets {
|
|
|
398
345
|
|
|
399
346
|
/** @private */
|
|
400
347
|
_debug(...args) {
|
|
401
|
-
if (
|
|
402
|
-
console.log(...args);
|
|
348
|
+
if (this.options?.debug) console.log(...args);
|
|
403
349
|
return null;
|
|
404
350
|
}
|
|
405
|
-
|
|
406
351
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elara-services/tickets",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Helper for tickets",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"author": "SUPERCHIEFYT (Elara-Discord-Bots, Elara-Services)",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"optionalDependencies": {
|
|
11
|
-
"discord.js": "13.8.
|
|
11
|
+
"discord.js": "13.8.1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@elara-services/packages": "5.0.0",
|