@elara-services/tickets 2.4.0 → 3.1.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/.eslintrc.json +20 -0
- package/README.md +54 -50
- package/index.d.ts +10 -14
- package/index.js +12 -418
- package/lib/base.js +80 -0
- package/lib/util.js +143 -0
- package/lib/v13.js +218 -0
- package/lib/v14.js +263 -0
- package/package.json +6 -3
package/lib/v14.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const { EmbedBuilder: MessageEmbed, InteractionType, PermissionFlagsBits, ChannelType } = require("discord.js"),
|
|
2
|
+
base = require("./base"),
|
|
3
|
+
{ de, code, fetchMessages, hasTicket, embed, webhook, getAppealServer } = require("./util"),
|
|
4
|
+
{ generate } = require("shortid"),
|
|
5
|
+
{ Interactions: { button, modal } } = require("@elara-services/packages");
|
|
6
|
+
|
|
7
|
+
module.exports = class Tickets extends base {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
super(options);
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* @param {import("discord.js").Interaction} int
|
|
13
|
+
*/
|
|
14
|
+
async run(int) {
|
|
15
|
+
if (!int) return;
|
|
16
|
+
if (int.isButton() || int.type === InteractionType.ModalSubmit) {
|
|
17
|
+
let { guild, channel, member, customId } = int,
|
|
18
|
+
category = guild?.channels?.resolve?.(this.options.ticket?.category || this.options.ticketCategory || channel?.parentId);
|
|
19
|
+
if (!guild?.available || !channel || !member || !category) return;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import("discord.js").InteractionDeferReplyOptions|import("discord.js").InteractionReplyOptions} options
|
|
23
|
+
* @param {boolean} edit
|
|
24
|
+
* @param {boolean} defer
|
|
25
|
+
*/
|
|
26
|
+
const send = async (options = {}, defer = false) => {
|
|
27
|
+
if (defer) return int.deferReply(options).catch(e => this._debug(e));
|
|
28
|
+
if (int.replied || int.deferred) return int.editReply(options).catch(e => this._debug(e));
|
|
29
|
+
return int.reply(options).catch(e => this._debug(e));
|
|
30
|
+
};
|
|
31
|
+
switch (customId) {
|
|
32
|
+
case this.prefix: {
|
|
33
|
+
if (this.options?.ticket?.limitOnePerUser && hasTicket({ userId: member.id, guild, token: this.options.encryptToken, prefix: this.options.prefix })) return send({ embeds: [ embed(`❌ You can only create one (${this.options.prefix}) ticket at a time.`, { guild }) ], ephemeral: true })
|
|
34
|
+
if (this.options.support?.ignore?.length) {
|
|
35
|
+
if (this.options.support.ignore.some(c => member.roles?.cache?.has?.(c))) return send({ embeds: [embed(`❌ You can no longer create tickets, if you believe this is a mistake contact one of the staff members.`)], ephemeral: true });
|
|
36
|
+
}
|
|
37
|
+
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));
|
|
38
|
+
return this.handleCreate({ guild, member, category, send })
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
case `${this.prefix}:close`: {
|
|
42
|
+
if (this.options.support?.canOnlyCloseTickets && !member.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
|
43
|
+
if (!this.getSupportIds.users?.includes?.(member.id)) {
|
|
44
|
+
if (!this.getSupportIds.roles?.some?.(c => member.roles?.cache?.has?.(c))) return send({ ephemeral: true, embeds: [{ author: { name: `Only support staff can close tickets`, iconURL: "https://cdn.discordapp.com/emojis/781955502035697745.gif" }, color: 0xFF0000 }] })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return send({
|
|
48
|
+
ephemeral: true,
|
|
49
|
+
embeds: [ embed(`🤔 Are you sure you want to close the ticket?`, { color: 0xFF000, guild }) ],
|
|
50
|
+
components: [
|
|
51
|
+
{ type: 1, components: [
|
|
52
|
+
button({ title: "Yes, close the ticket!", style: 3, emoji: { id: "807031399563264030" }, id: `${this.prefix}:close:confirm:${code(channel.topic?.split?.("ID: ")?.[1], "d", this.options.encryptToken)}${this.options?.ticket?.closeReason ? `:modal_submit` : ""}` })
|
|
53
|
+
]
|
|
54
|
+
}]
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case `${this.prefix}:modal_submit`: {
|
|
59
|
+
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];
|
|
60
|
+
for (const c of int.fields.components) {
|
|
61
|
+
for (const cc of c.components) {
|
|
62
|
+
if (cc.value && cc.customId) {
|
|
63
|
+
fields.push({ name: cc.customId, value: cc.value });
|
|
64
|
+
if (cc.value.length <= 1024) embed.addFields({ name: cc.customId, value: cc.value });
|
|
65
|
+
else split = true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (embed.length >= 6000 || split) {
|
|
70
|
+
return this.handleCreate({
|
|
71
|
+
guild, member, category, send, embeds: fields.map((v, i) => ({
|
|
72
|
+
title: `Form Response: ${v.name}`, color: embed.color, description: v.value,
|
|
73
|
+
author: i === 0 ? { name: member.user.username, iconURL: member.user.displayAvatarURL({ dynamic: true }) } : undefined,
|
|
74
|
+
timestamp: fields.length - 1 === i ? new Date() : undefined,
|
|
75
|
+
footer: fields.length - 1 === i ? { text: `ID: ${member.id}` } : undefined
|
|
76
|
+
}))
|
|
77
|
+
})
|
|
78
|
+
};
|
|
79
|
+
return this.handleCreate({ guild, member, category, send, embeds: [embed] })
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
if (customId.startsWith(`${this.prefix}:close:confirm`)) {
|
|
83
|
+
if (customId.includes(`:modal_submit`)) {
|
|
84
|
+
return int.showModal(modal({
|
|
85
|
+
id: `${customId.split(":modal_submit")[0]}:modal_complete`,
|
|
86
|
+
title: "Reason",
|
|
87
|
+
components: [ { type: 1, components: [ { type: 4, custom_id: "reason", label: "Reason", style: 2, min_length: 1, max_length: 1024, required: true, placeholder: "What's the reason for closing the ticket?" } ] } ]
|
|
88
|
+
}))
|
|
89
|
+
}
|
|
90
|
+
await send({ ephemeral: true }, true);
|
|
91
|
+
let reason = "No Reason Provided";
|
|
92
|
+
if (int.type === InteractionType.ModalSubmit) reason = int.fields.getTextInputValue("reason")
|
|
93
|
+
let user = await this.options.client.users.fetch(customId.split("close:confirm:")[1].replace(/:modal_complete/gi, "")).catch(e => this._debug(e));
|
|
94
|
+
if (!user) return send({ embeds: [ embed(`❌ I was unable to fetch the user that opened the ticket.`) ] });
|
|
95
|
+
let messages = await fetchMessages(channel, 5000);
|
|
96
|
+
if (!messages?.length) return send({ embeds: [ embed(`❌ I was unable to close the ticket, I couldn't fetch the messages in this channel.`) ] });
|
|
97
|
+
let closed = await channel.delete(`${member.user.tag} (${member.id}) closed the ticket.`).catch(e => this._debug(e));
|
|
98
|
+
if (!closed) return send({ embeds: [ embed(`❌ I was unable to delete the channel and close the ticket.`) ] });
|
|
99
|
+
return this.closeTicket({ channel, guild, user, member, messages, reason });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (customId.startsWith("unban:")) {
|
|
103
|
+
if (!int.memberPermissions?.has?.(PermissionFlagsBits.BanMembers)) return send({ embeds: [ embed(`❌ You need (Ban Members) in this server to complete this action!`) ], ephemeral: true });
|
|
104
|
+
let server = getAppealServer(this.options);
|
|
105
|
+
if (!server) return send({ embeds: [embed(`❌ I was unable to find the appeal server!`)], ephemeral: true });
|
|
106
|
+
let mod = server.members.resolve(member.id) || await server.members.fetch(member.id).catch(e => this._debug(e));
|
|
107
|
+
if (!mod) return send({ embeds: [embed(`❌ I was unable to find you in ${server.name}!`)], ephemeral: true });
|
|
108
|
+
if (!mod.permissions?.has?.(PermissionFlagsBits.BanMembers)) return send({ embeds: [embed(`❌ You need (Ban Members) in ${server.name} to complete this action!`)], ephemeral: true });
|
|
109
|
+
return int.showModal(modal({
|
|
110
|
+
id: `unban_modal:${customId.split(":")[1]}`,
|
|
111
|
+
title: `Unban From ${server.name}`,
|
|
112
|
+
components: [ { type: 1, components: [ { type: 4, label: "Reason", custom_id: "reason", style: 2, min_length: 1, max_length: 512, required: true, value: `No Reason Provided | By: ${member.user.tag} (${member.id})` } ] } ]
|
|
113
|
+
})).catch(e => this._debug(e));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (customId.startsWith("unban_modal:")) {
|
|
117
|
+
await send({ ephemeral: true }, true);
|
|
118
|
+
const [ , id ] = customId.split(":");
|
|
119
|
+
if (!int.memberPermissions?.has?.(PermissionFlagsBits.BanMembers)) return send({ embeds: [ embed(`❌ You need (Ban Members) in this server to complete this action!`)] });
|
|
120
|
+
let server = getAppealServer(this.options);
|
|
121
|
+
if (!server) return send({ embeds: [embed(`❌ I was unable to find the appeal server!`)] });
|
|
122
|
+
let mod = server.members.resolve(member.id) || await server.members.fetch(member.id).catch(e => this._debug(e));
|
|
123
|
+
if (!mod) return send({ embeds: [embed(`❌ I was unable to find you in ${server.name}!`)] });
|
|
124
|
+
if (!mod.permissions?.has?.(PermissionFlagsBits.BanMembers)) return send({ embeds: [embed(`❌ You need (Ban Members) in ${server.name} to complete this action!`)] });
|
|
125
|
+
let isBanned = await server.bans.fetch({ user: id, force: true }).catch(e => this._debug(e));
|
|
126
|
+
if (!isBanned) return send({ embeds: [embed(`❌ User (<@${id}>) isn't banned in the main server!`)] });
|
|
127
|
+
return server.bans.remove(id, int.fields.getTextInputValue("reason") ?? `No Reason Provided | By: ${member.user.tag} (${member.id})`)
|
|
128
|
+
.then(() => {
|
|
129
|
+
int.message.edit({ components: [ { type: 1, components: [ button({ title: "Unbanned!", style: 3, id: "_ _", disabled: true, emoji: { id: `476629550797684736` } }) ] } ] }).catch(e => this._debug(e));
|
|
130
|
+
send({ embeds: [embed(`✅ Successfully unbanned <@${id}> from ${server.name}!`)] })
|
|
131
|
+
})
|
|
132
|
+
.catch(e => send({ embeds: [ { title: "ERROR", fields: [ { name: "\u200b", value: `❌ Unable to unban <@${id} from ${server.name}!` } ], description: `\`\`\`js\n${e.message ?? e.stack}\`\`\``, color: 0xFF0000 } ] }))
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** @private */
|
|
138
|
+
async handleCreate({ guild, member, category, send, embeds = [] } = {}) {
|
|
139
|
+
await send({ ephemeral: true }, true);
|
|
140
|
+
let [ permissions, allow, { appeals }, sendBanReason ] = [
|
|
141
|
+
[],
|
|
142
|
+
[
|
|
143
|
+
PermissionFlagsBits.AddReactions,
|
|
144
|
+
PermissionFlagsBits.AttachFiles,
|
|
145
|
+
PermissionFlagsBits.CreateInstantInvite,
|
|
146
|
+
PermissionFlagsBits.EmbedLinks,
|
|
147
|
+
PermissionFlagsBits.ReadMessageHistory,
|
|
148
|
+
PermissionFlagsBits.ViewChannel,
|
|
149
|
+
PermissionFlagsBits.UseExternalEmojis,
|
|
150
|
+
PermissionFlagsBits.SendMessages
|
|
151
|
+
],
|
|
152
|
+
this.options ?? {},
|
|
153
|
+
null
|
|
154
|
+
];
|
|
155
|
+
if (this.getSupportIds.roles.length) for (const sup of this.getSupportIds.roles) {
|
|
156
|
+
let role = guild.roles.resolve(sup);
|
|
157
|
+
if (role) permissions.push({ type: "role", id: sup, allow });
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (this.getSupportIds.users.length) for (const uId of this.getSupportIds.users) {
|
|
161
|
+
let member = guild.members.resolve(uId) || await guild.members.fetch(uId).catch((e) => {
|
|
162
|
+
if (e?.stack?.includes?.("Unknown Member")) this.options.support.users = this.options.support.users.filter(c => c !== uId);
|
|
163
|
+
return this._debug(e);
|
|
164
|
+
});
|
|
165
|
+
if (member) permissions.push({ type: "member", id: uId, allow });
|
|
166
|
+
}
|
|
167
|
+
if (appeals?.enabled) {
|
|
168
|
+
let server = getAppealServer(this.options);
|
|
169
|
+
if (server) {
|
|
170
|
+
let ban = await server.bans.fetch({ user: member.id, force: true }).catch(e => this._debug(e));
|
|
171
|
+
if (!ban) return send(
|
|
172
|
+
typeof appeals.embeds?.not_banned === "object" ?
|
|
173
|
+
appeals.embeds.not_banned :
|
|
174
|
+
{ embeds: [ embed(`❌ You can't open this ticket due to you not being banned in the main server!`, { guild, color: 0xFF0000 }) ] }
|
|
175
|
+
);
|
|
176
|
+
sendBanReason = {
|
|
177
|
+
embeds: [ embed(ban?.reason ?? "No Reason Provided", { title: "Ban Reason", guild: server }) ],
|
|
178
|
+
components: [
|
|
179
|
+
{
|
|
180
|
+
type: 1, components: [
|
|
181
|
+
button({ id: `unban:${member.id}`, style: 4, title: "Unban", emoji: { name: "🔒" } })
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let channel = await guild.channels.create({
|
|
189
|
+
name: `${this.options.prefix}-${generate().slice(0, 5).replace(/-|_/g, "")}`,
|
|
190
|
+
type: ChannelType.GuildText,
|
|
191
|
+
parent: category,
|
|
192
|
+
reason: `Ticket created by: @${member.user.tag} (${member.id})`,
|
|
193
|
+
topic: `ID: ${code(member.id, "e", this.options.encryptToken)}`,
|
|
194
|
+
permissionOverwrites: [
|
|
195
|
+
{
|
|
196
|
+
type: "member",
|
|
197
|
+
id: this.options.client.user.id,
|
|
198
|
+
allow: [
|
|
199
|
+
PermissionFlagsBits.AddReactions,
|
|
200
|
+
PermissionFlagsBits.AttachFiles,
|
|
201
|
+
PermissionFlagsBits.SendMessages,
|
|
202
|
+
PermissionFlagsBits.ReadMessageHistory,
|
|
203
|
+
PermissionFlagsBits.EmbedLinks,
|
|
204
|
+
PermissionFlagsBits.UseExternalEmojis,
|
|
205
|
+
PermissionFlagsBits.ViewChannel,
|
|
206
|
+
PermissionFlagsBits.MentionEveryone
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
type: "member",
|
|
211
|
+
id: member.id,
|
|
212
|
+
allow: [
|
|
213
|
+
PermissionFlagsBits.AddReactions,
|
|
214
|
+
PermissionFlagsBits.AttachFiles,
|
|
215
|
+
PermissionFlagsBits.SendMessages,
|
|
216
|
+
PermissionFlagsBits.ReadMessageHistory,
|
|
217
|
+
PermissionFlagsBits.EmbedLinks,
|
|
218
|
+
PermissionFlagsBits.UseExternalEmojis,
|
|
219
|
+
PermissionFlagsBits.ViewChannel
|
|
220
|
+
],
|
|
221
|
+
deny: [ PermissionFlagsBits.MentionEveryone ]
|
|
222
|
+
},
|
|
223
|
+
{ type: "role", id: guild.id, deny: [ PermissionFlagsBits.ViewChannel ] },
|
|
224
|
+
...permissions
|
|
225
|
+
]
|
|
226
|
+
}).catch(e => this._debug(e));
|
|
227
|
+
if (!channel) return send({ embeds: [embed(`❌ I was unable to create the ticket channel, if this keeps happening contact one of the staff members via their DMs!`)] });
|
|
228
|
+
let msg = await channel.send({
|
|
229
|
+
content: (this.options.ticket?.open || 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.`,
|
|
230
|
+
embeds: (this.options.ticket?.open || this.options.ticketOpen)?.embeds || [ embed(undefined, { title: `Support will be with you shortly.`, color: 0xF50DE3, guild, footer: { text: `To close the ticket, press the button below` } }) ],
|
|
231
|
+
components: [ { type: 1, components: [ button({ id: `${this.prefix}:close`, title: `Close Ticket`, style: 4, emoji: { name: "🔒" } }) ] }]
|
|
232
|
+
}).catch(e => this._debug(e));
|
|
233
|
+
if (!msg) return null;
|
|
234
|
+
if (sendBanReason) await channel.send(sendBanReason).catch(e => this._debug(e));
|
|
235
|
+
if (embeds?.length <= 10) for await (const embed of embeds) await channel.send({ embeds: [embed] }).catch(e => this._debug(e));
|
|
236
|
+
if (this.webhookOptions.id && this.webhookOptions.token) webhook(this.webhookOptions)
|
|
237
|
+
.embed(embed(`${de.user} User: ${member.user.toString()} \`@${member.user.tag}\` (${member.id})\n${de.channel} Channel: \`#${channel.name}\` (${channel.id})`, {
|
|
238
|
+
title: `TIcket: Opened`,
|
|
239
|
+
color: 0xFF000,
|
|
240
|
+
footer: { text: `Ticket ID: ${channel.name.split("-")[1]}` },
|
|
241
|
+
guild
|
|
242
|
+
})).send().catch(e => this._debug(e));
|
|
243
|
+
return send({
|
|
244
|
+
embeds: [ embed(channel.toString(), { color: 0xFF000, author: { name: `Ticket Created!`, icon_url: `https://cdn.discordapp.com/emojis/476629550797684736.gif` } }) ],
|
|
245
|
+
components: [{ type: 1, components: [button({ title: "Go to ticket", url: msg.url })] }]
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async starterMessage(channelId, options) {
|
|
250
|
+
let channel = this.options.client.channels.resolve(channelId);
|
|
251
|
+
if (!channel) return Promise.reject(`No channel found for: ${channelId}`);
|
|
252
|
+
if (!channel.isText()) return Promise.reject(`The channel ID provided isn't a text-based-channel`);
|
|
253
|
+
if (!channel.permissionsFor?.(this.options.client.user.id)?.has?.([
|
|
254
|
+
PermissionFlagsBits.ViewChannel,
|
|
255
|
+
PermissionFlagsBits.SendMessages,
|
|
256
|
+
PermissionFlagsBits.AttachFiles,
|
|
257
|
+
PermissionFlagsBits.EmbedLinks,
|
|
258
|
+
PermissionFlagsBits.ReadMessageHistory
|
|
259
|
+
])) return Promise.reject(`I'm missing permissions in ${channel.name} (${channelId})`);
|
|
260
|
+
return channel.send({ content: options?.content, files: options?.attachments, embeds: options?.embeds, components: options?.components || [{ type: 1, components: [this.button()] }] })
|
|
261
|
+
.then(() => console.log(`Sent the starter message in ${channel.name} (${channel.id})`))
|
|
262
|
+
};
|
|
263
|
+
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elara-services/tickets",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Helper for tickets",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"typings": "index.d.ts",
|
|
8
8
|
"author": "SUPERCHIEFYT (Elara-Discord-Bots, Elara-Services)",
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"scripts": {
|
|
11
|
+
"lint": "eslint ."
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@elara-services/packages": "5.0.0",
|
|
15
15
|
"discord-hook": "2.0.0",
|
|
16
16
|
"shortid": "2.2.16"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"eslint": "8.20.0"
|
|
17
20
|
}
|
|
18
21
|
}
|