@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 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
- mainServer: {
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
- { encrypt, decrypt } = require("aes256"),
3
- { generate } = require("shortid"),
4
- { Interactions: { button, modal } } = require("@elara-services/packages"),
5
- Webhook = require("discord-hook"),
6
- de = {
7
- user: "<:Members:860931214232125450>",
8
- channel: "<:Channel:841654412509839390>",
9
- transcript: "<:Log:792290922749624320>"
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`: 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" } }] }] })
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
- 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);
102
- if (!user) return send({ content: `❌ I was unable to fetch the user that opened the ticket.`, ephemeral: true })
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 || !messages.length) return send({ ephemeral: true, content: `❌ I was unable to close the ticket, I couldn't fetch the messages in this channel.` })
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({ ephemeral: true, content: `${emojis.x} I was unable to delete the channel & close the ticket.` })
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
- let [support, supportUsers, supportIds] = [[], [], this.getSupportIds()];
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) support.push(sup);
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) supportUsers.push(uId);
122
+ if (member) permissions.push({ type: "member", id: uId, allow });
126
123
  }
127
- await send({ ephemeral: true }, true);
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: `${emojis.x} I was unable to create the ticket channel, if this keeps happening contact one of the staff members via their DMs!` });
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(this.options.encryptToken, id);
251
- case "d": return decrypt(this.options.encryptToken, id);
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 (!this.options?.debug) return null;
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.0.4",
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.0"
11
+ "discord.js": "13.8.1"
12
12
  },
13
13
  "dependencies": {
14
14
  "@elara-services/packages": "5.0.0",