@elara-services/tickets 4.1.1 → 4.3.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 CHANGED
@@ -44,6 +44,7 @@ const { Client } = require("discord.js"),
44
44
  ]
45
45
  },
46
46
  webhook: { // [OPTIONAL]
47
+ channelId: "", // The channel ID to use for the logs (can be used instead of using a webhook id, token and threadId)
47
48
  id: "", // The webhook ID for this ticket's open and closed logs.
48
49
  token: "", // The webhook Token for this ticket's open and closed logs.
49
50
  username: "Webhook Username Here", // The webhook username for this ticket's logs.
package/index.d.ts CHANGED
@@ -20,6 +20,7 @@ declare module "@elara-services/tickets" {
20
20
  encryptToken: string;
21
21
  lang?: Langs;
22
22
  debug?: boolean;
23
+ suppressPatreon?: boolean;
23
24
  appeals?: {
24
25
  enabled?: boolean;
25
26
  sendBanResults?: boolean;
@@ -37,6 +38,7 @@ declare module "@elara-services/tickets" {
37
38
  questions?: TicketModalQuestion[]
38
39
  };
39
40
  webhook?: {
41
+ channelId?: string;
40
42
  id?: string;
41
43
  token?: string;
42
44
  threadId?: string;
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { emitPackageMessage, log } = require("@elara-services/utils");
1
2
  const pack = require("./package.json");
2
3
  const { version } = require("discord.js");
3
4
 
@@ -7,6 +8,9 @@ module.exports = (() => {
7
8
  }
8
9
  let [major] = version.split(".");
9
10
  if (["13", "14"].includes(major)) {
11
+ emitPackageMessage(`${pack.name} - thanks`, () => {
12
+ log(`[${pack.name}, v${pack.version}]: Thanks for using the package!`, `Please support the packages via ${pack.funding.map((c) => c.url).join(" OR ")}`);
13
+ });
10
14
  return require(`./lib/v${major}`);
11
15
  }
12
16
  throw new Error(`[${pack.name}, v${pack.version}]: The discord.js version you're using isn't supported by this package. (currently supported: v13, v14)`);
@@ -32,6 +32,7 @@ module.exports = {
32
32
  NO_CHANNEL_CREATE: `${x} I was unable to create the ticket channel, if this keeps happening contact one of the staff members via their DMs!`,
33
33
  NO_CHANNEL_DELETE: `${x} I was unable to delete the channel and close the ticket.`,
34
34
  NO_BAN_PERMS_USER: `${x} You need (Ban Members) in this server to complete this action!`,
35
+ TRANSCRIPT_VIEW: `View Transcript`,
35
36
  TICKET_BLOCKED: `${x} You can't create tickets, if you believe this is a mistake contact one of the staff members.`,
36
37
  FORM_RESPONSES: "Form Responses",
37
38
  TOTAL_MESSAGES: "Total Messages",
package/lib/base.js CHANGED
@@ -1,10 +1,15 @@
1
- const { getWebhookInfo, displayMessages, de, webhook, getString } = require("./util");
1
+ const { getWebhookInfo, displayMessages, de, getString, generateHTMLPage } = require("./util");
2
2
  const {
3
3
  Interactions: { button },
4
+ ButtonStyle,
4
5
  } = require("@elara-services/packages");
5
- const { is, status, isV13 } = require("@elara-services/utils");
6
+ const { is, status, isV13, embedComment, log } = require("@elara-services/utils");
6
7
  const { WebhookClient } = require("discord.js");
8
+ const { name, version, funding } = require("../package.json");
7
9
  const required = ["client", "prefix", "encryptToken"];
10
+ const moment = require("moment");
11
+
12
+ let showed = false;
8
13
 
9
14
  module.exports = class Tickets {
10
15
  /**
@@ -20,6 +25,10 @@ module.exports = class Tickets {
20
25
  }
21
26
  }
22
27
  this.options = options;
28
+ if (!showed && options.suppressPatreon !== true) {
29
+ log(`[${name}, ${version}]: Thanks for using the package, if you want to support the packages created: ${funding.map((c) => c.url).join(" or ")}`);
30
+ showed = true;
31
+ }
23
32
  }
24
33
 
25
34
  get prefix() {
@@ -140,6 +149,36 @@ module.exports = class Tickets {
140
149
  emoji: options.emoji,
141
150
  });
142
151
  }
152
+
153
+ /**
154
+ *
155
+ * @param {import("discord.js").ButtonInteraction} int
156
+ */
157
+ async handleTicketButton(int) {
158
+ await int.deferUpdate().catch(() => null);
159
+ const msg = await int.message.fetch(true).catch(() => null);
160
+ const file = msg?.attachments.find((c) => c.name.endsWith(".txt"));
161
+ if (!file) {
162
+ return int
163
+ .followUp({
164
+ ...embedComment(`Unable to find the .txt file.`),
165
+ ephemeral: true,
166
+ })
167
+ .catch(() => null);
168
+ }
169
+ const components = int.message.components.slice(0, 1);
170
+ components.push({
171
+ type: 1,
172
+ components: [
173
+ button({
174
+ title: `${this.str("TRANSCRIPT_VIEW")} (${moment().format("MM DD YYYY h:m:s")})`,
175
+ emoji: { id: "1059556038761787433" },
176
+ url: `https://view.elara.workers.dev/tickets?url=${file.url}`,
177
+ }),
178
+ ],
179
+ });
180
+ return int.editReply({ components }).catch(console.log);
181
+ }
143
182
  /**
144
183
  * @param {object} opts
145
184
  * @param {import("discord.js").GuildMember} opts.member
@@ -149,7 +188,7 @@ module.exports = class Tickets {
149
188
  * @param {string} opts.reason
150
189
  */
151
190
  async closeTicket({ member, messages, channel, guild, user, reason } = {}) {
152
- const { id, token, username, avatar: avatarURL, threadId } = this.webhookOptions;
191
+ const { id, token, username, avatar: avatarURL, threadId } = await this.webhookOptions;
153
192
  if (!id || !token) {
154
193
  return;
155
194
  }
@@ -187,24 +226,26 @@ module.exports = class Tickets {
187
226
  name: `${this.str("TRANSCRIPT")}.txt`,
188
227
  attachment: Buffer.from(displayMessages(channel, messages.reverse(), channel.name.split("-")[1], this.options.prefix, (name) => this.str(name, this?.options?.lang))),
189
228
  },
229
+ {
230
+ name: `${this.str("TRANSCRIPT")}.html`,
231
+ attachment: Buffer.from(generateHTMLPage(channel, messages.reverse(), channel.name.split("-")[1], this.options.prefix, (name) => this.str(name, this?.options?.lang))),
232
+ },
190
233
  ],
191
- })
192
- .then((m) => {
193
- let components = [
234
+ components: [
194
235
  {
195
- type: 2,
196
- style: 5,
197
- label: this.str("TRANSCRIPT"),
198
- emoji: { id: "792290922749624320" },
199
- url: `https://view.elara.workers.dev/tickets?url=${Array.isArray(m.attachments) ? m.attachments?.[0]?.url : m.attachments?.first?.()?.url ?? "URL_NOT_FOUND"}`,
236
+ type: 1,
237
+ components: [
238
+ button({
239
+ id: `transcript`,
240
+ label: this.str("TRANSCRIPT"),
241
+ emoji: { id: `792290922749624320` },
242
+ style: ButtonStyle.SECONDARY,
243
+ }),
244
+ ],
200
245
  },
201
- ];
202
- embeds[0].description += `\n${de.transcript} ${this.str("TRANSCRIPT")}: [${this.str("VIEW_HERE")}](${components[0].url})`;
203
- webhook(this.webhookOptions)
204
- .embeds(embeds)
205
- .button({ type: 1, components })
206
- .edit(m.id)
207
- .catch((e) => this._debug(e));
246
+ ],
247
+ })
248
+ .then((m) => {
208
249
  if (user) {
209
250
  user.send({
210
251
  embeds: [
@@ -229,7 +270,26 @@ module.exports = class Tickets {
229
270
  },
230
271
  },
231
272
  ],
232
- components: [{ type: 1, components }],
273
+ components: [
274
+ {
275
+ type: 1,
276
+ components: [
277
+ {
278
+ type: 2,
279
+ style: 5,
280
+ label: this.str("TRANSCRIPT"),
281
+ emoji: { id: "792290922749624320" },
282
+ url: `https://view.elara.workers.dev/tickets?url=${m.attachments?.find?.((c) => c.filename.includes(".txt"))?.url}`,
283
+ },
284
+ ],
285
+ },
286
+ ],
287
+ files: [
288
+ {
289
+ name: `${this.str("TRANSCRIPT")}.html`,
290
+ attachment: Buffer.from(generateHTMLPage(channel, messages.reverse(), channel.name.split("-")[1], this.options.prefix, (name) => this.str(name, this?.options?.lang))),
291
+ },
292
+ ],
233
293
  }).catch((e) => this._debug(e));
234
294
  }
235
295
  })
package/lib/html.js ADDED
@@ -0,0 +1,68 @@
1
+ exports.discordView = function (messages) {
2
+ return `<!DOCTYPE html>
3
+ <head>
4
+ <title>Discord Viewer</title>
5
+ <link href="https://cdn.discordapp.com/emojis/880708306761564220.png" rel="icon" />
6
+ <script>
7
+ window.onload = _ => {
8
+ let doc = document.getElementById("messages");
9
+ if (doc.innerHTML.includes('new_timestamp="')) {
10
+ let matches = doc.innerHTML.match(new RegExp(\`new_timestamp=".*?"\`, "gi"))
11
+ if (matches) {
12
+ for (const match of matches) {
13
+ document.getElementById("messages").innerHTML = doc.innerHTML.replace(match, \`timestamp="\${new Date(match.split("new_timestamp=\\"")[1].replace('"', "")).toLocaleString()}"\`)
14
+ }
15
+ }
16
+ }
17
+ setTimeout(_ => hidePreloader(), 1000)
18
+ }
19
+ function hidePreloader() {
20
+ document.getElementById("load").style.transitionDuration = "0.5s"
21
+ document.getElementById("load").style.pointerEvents = "none"
22
+ document.getElementById("load").style.opacity = 0
23
+ document.getElementById("load").style.zIndex = -1
24
+ }
25
+ </script>
26
+ <script type="module" src="https://unpkg.com/@skyra/discord-components-core" async></script>
27
+ <style>
28
+ /* Preloader styles. */
29
+ #load {
30
+ position: fixed;
31
+ top: 0;
32
+ left: 0;
33
+ right: 0;
34
+ bottom: 0;
35
+ background: black;
36
+ z-index: 9999;
37
+ cursor: progress;
38
+ }
39
+
40
+ /* Preloader image. */
41
+ #icon {
42
+ width: 200px;
43
+ height: 200px;
44
+ position: absolute;
45
+ background-color: transparent;
46
+ left: 50%;
47
+ top: 50%;
48
+ background-image: url(https://cdn.discordapp.com/emojis/634127148696862753.gif?v=1);
49
+ background-repeat: no-repeat;
50
+ background-position: center;
51
+ margin: -100px 0 0 -100px;
52
+ }
53
+ </style>
54
+ </head>
55
+
56
+ <body style="background: #36393e;">
57
+ <div id="load">
58
+ <div id="icon"></div>
59
+ <br>
60
+ <h3 style="min-height: 110%; display: flex; justify-content: center; align-items: center;">Loading, one moment please.</h3>
61
+ </div>
62
+ <discord-messages id="messages">${messages}</discord-messages>
63
+ <br>
64
+ <br>
65
+ </body>
66
+
67
+ </html>`;
68
+ };
package/lib/util.js CHANGED
@@ -3,9 +3,10 @@ const {
3
3
  Interactions: { modal },
4
4
  } = require("@elara-services/packages"),
5
5
  { Collection, version } = require("discord.js"),
6
- { DiscordWebhook: Webhook } = require("@elara-services/webhooks"),
6
+ { DiscordWebhook: Webhook, Webhook: BotHook } = require("@elara-services/webhooks"),
7
7
  defLang = require("../languages/en-US"),
8
8
  pack = require("../package.json");
9
+ const { discordView } = require("./html");
9
10
 
10
11
  exports.getString = (name, lang = "en-US") => {
11
12
  if (!lang) {
@@ -205,6 +206,13 @@ exports.displayMessages = (channel, messages = [], ticketID, type, str) => {
205
206
  ].join(" ");
206
207
  };
207
208
 
209
+ /**
210
+ * @description Generates an HTML page string to use.
211
+ */
212
+ exports.generateHTMLPage = (channel, messages = [], ticketID, type, str) => {
213
+ return discordView(exports.displayMessages(channel, messages, ticketID, type, str));
214
+ };
215
+
208
216
  exports.defs = {
209
217
  modals: {
210
218
  reason: (customId, str) =>
@@ -336,7 +344,22 @@ exports.webhook = (options) => {
336
344
  });
337
345
  };
338
346
 
339
- exports.getWebhookInfo = (options, username = "Tickets") => {
347
+ /**
348
+ * @param {import("@elara-services/tickets").TicketOptions} options
349
+ * @param {string} username
350
+ */
351
+ exports.getWebhookInfo = async (options, username = "Tickets") => {
352
+ if (options.webhook?.channelId) {
353
+ const channel = options.client.channels.resolve(options.webhook.channelId);
354
+ if (channel) {
355
+ const hook = await new BotHook(options.client.token).fetch(channel.isThread() ? channel.parentId : channel.id);
356
+ if (hook) {
357
+ options.webhook.id = hook.id;
358
+ options.webhook.token = hook.token;
359
+ options.webhook.threadId = channel.isThread() ? channel.id : undefined;
360
+ }
361
+ }
362
+ }
340
363
  return {
341
364
  id: options.webhook?.id,
342
365
  token: options.webhook?.token,
package/lib/v13.js CHANGED
@@ -5,7 +5,7 @@ const {
5
5
  } = require("discord.js"),
6
6
  base = require("./base"),
7
7
  { de, code, fetchMessages, hasTicket, embed, webhook, getAppealServer, perms, defs } = require("./util"),
8
- { generate, parser, discord } = require("@elara-services/utils"),
8
+ { generate, parser, discord, is } = require("@elara-services/utils"),
9
9
  {
10
10
  Interactions: { button },
11
11
  } = require("@elara-services/packages");
@@ -32,6 +32,9 @@ module.exports = class Tickets extends base {
32
32
  }
33
33
  }
34
34
  if (int.isButton() || int.isModalSubmit()) {
35
+ if (int.customId.startsWith("transcript")) {
36
+ return this.handleTicketButton(int);
37
+ }
35
38
  let { guild, channel, member, customId } = int,
36
39
  category = guild?.channels?.resolve?.(this.options.ticket?.category || channel?.parentId);
37
40
  if (!guild?.available || !channel || !member || !category) {
@@ -111,9 +114,25 @@ module.exports = class Tickets extends base {
111
114
  }
112
115
  }
113
116
  }
117
+ const hasEmbeds = this.options.ticket?.close?.confirm?.embeds;
114
118
  let embs = await Promise.all(
115
- (
116
- this.options.ticket?.close?.confirm?.embeds || [
119
+ (hasEmbeds.length
120
+ ? hasEmbeds
121
+ : [
122
+ embed(undefined, {
123
+ description: this.str("TICKET_CLOSE_CONFIRM"),
124
+ title: `INFO`,
125
+ color: 0xff000,
126
+ guild,
127
+ str: (name) => this.str(name, this?.options?.lang),
128
+ }),
129
+ ]
130
+ ).map((c) => parser(c, { guild, member, user: member.user })),
131
+ );
132
+ const content = this.options.ticket?.close?.confirm?.content || ``;
133
+ if (!is.array(embs) && !is.string(content)) {
134
+ embs = [
135
+ await parser(
117
136
  embed(undefined, {
118
137
  description: this.str("TICKET_CLOSE_CONFIRM"),
119
138
  title: `INFO`,
@@ -121,10 +140,14 @@ module.exports = class Tickets extends base {
121
140
  guild,
122
141
  str: (name) => this.str(name, this?.options?.lang),
123
142
  }),
124
- ]
125
- ).map((c) => parser(c, { guild, member, user: member.user })),
126
- );
127
- const content = this.options.ticket?.close?.confirm?.content || ``;
143
+ {
144
+ guild,
145
+ member,
146
+ user: member.user,
147
+ },
148
+ ),
149
+ ];
150
+ }
128
151
  return send({
129
152
  ephemeral: true,
130
153
  content,
@@ -510,8 +533,9 @@ module.exports = class Tickets extends base {
510
533
  .catch((e) => this._debug(e));
511
534
  }
512
535
  }
513
- if (this.webhookOptions.id && this.webhookOptions.token) {
514
- webhook(this.webhookOptions)
536
+ const webOpt = await this.webhookOptions;
537
+ if (webOpt.id && webOpt.token) {
538
+ webhook(webOpt)
515
539
  .embed(
516
540
  embed(`${de.user} ${this.str("USER")}: ${member.user.toString()} \`@${member.user.tag}\` (${member.id})\n${de.channel} ${this.str("CHANNEL")}: \`#${channel.name}\` (${channel.id})`, {
517
541
  title: this.str("OPEN_TICKET_TITLE"),
package/lib/v14.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { EmbedBuilder: MessageEmbed, InteractionType, ChannelType, ComponentType } = require("discord.js"),
2
2
  base = require("./base"),
3
3
  { de, code, fetchMessages, hasTicket, embed, webhook, getAppealServer, perms, defs } = require("./util"),
4
- { generate, parser, discord } = require("@elara-services/utils"),
4
+ { generate, parser, discord, is } = require("@elara-services/utils"),
5
5
  {
6
6
  Interactions: { button },
7
7
  } = require("@elara-services/packages");
@@ -28,6 +28,9 @@ module.exports = class Tickets extends base {
28
28
  }
29
29
  }
30
30
  if (int.isButton() || int.type === InteractionType.ModalSubmit) {
31
+ if (int.customId.startsWith("transcript")) {
32
+ return this.handleTicketButton(int);
33
+ }
31
34
  let { guild, channel, member, customId } = int,
32
35
  category = guild?.channels?.resolve?.(this.options.ticket?.category || channel?.parentId);
33
36
  if (!guild?.available || !channel || !member || !category) {
@@ -107,15 +110,44 @@ module.exports = class Tickets extends base {
107
110
  }
108
111
  }
109
112
  }
113
+ const hasEmbeds = this.options.ticket?.close?.confirm?.embeds;
114
+ let embs = await Promise.all(
115
+ (hasEmbeds.length
116
+ ? hasEmbeds
117
+ : [
118
+ embed(undefined, {
119
+ description: this.str("TICKET_CLOSE_CONFIRM"),
120
+ title: `INFO`,
121
+ color: 0xff000,
122
+ guild,
123
+ str: (name) => this.str(name, this?.options?.lang),
124
+ }),
125
+ ]
126
+ ).map((c) => parser(c, { guild, member, user: member.user })),
127
+ );
128
+ const content = this.options.ticket?.close?.confirm?.content || ``;
129
+ if (!is.array(embs) && !is.string(content)) {
130
+ embs = [
131
+ await parser(
132
+ embed(undefined, {
133
+ description: this.str("TICKET_CLOSE_CONFIRM"),
134
+ title: `INFO`,
135
+ color: 0xff000,
136
+ guild,
137
+ str: (name) => this.str(name, this?.options?.lang),
138
+ }),
139
+ {
140
+ guild,
141
+ member,
142
+ user: member.user,
143
+ },
144
+ ),
145
+ ];
146
+ }
110
147
  return send({
111
148
  ephemeral: true,
112
- embeds: [
113
- embed(this.str("TICKET_CLOSE_CONFIRM"), {
114
- color: 0xff000,
115
- guild,
116
- str: (name) => this.str(name, this?.options?.lang),
117
- }),
118
- ],
149
+ content,
150
+ embeds: [embs],
119
151
  components: [
120
152
  {
121
153
  type: 1,
@@ -124,7 +156,7 @@ module.exports = class Tickets extends base {
124
156
  title: this.str("TICKET_CLOSE_CONFIRM_BUTTON"),
125
157
  style: 3,
126
158
  emoji: { id: "807031399563264030" },
127
- id: `${this.prefix}:close:confirm:${code(channel.topic?.split?.(`ID: `)?.[1], "d", this.options.encryptToken)}${this.options?.ticket?.closeReason ? `:modal_submit` : ""}`,
159
+ id: `${this.prefix}:close:confirm:${code(channel.topic?.split?.("ID: ")?.[1], "d", this.options.encryptToken)}${this.options?.ticket?.closeReason ? `:modal_submit` : ""}`,
128
160
  }),
129
161
  ],
130
162
  },
@@ -506,8 +538,9 @@ module.exports = class Tickets extends base {
506
538
  .catch((e) => this._debug(e));
507
539
  }
508
540
  }
509
- if (this.webhookOptions.id && this.webhookOptions.token) {
510
- webhook(this.webhookOptions)
541
+ const webOpt = await this.webhookOptions;
542
+ if (webOpt.id && webOpt.token) {
543
+ webhook(webOpt)
511
544
  .embed(
512
545
  embed(`${de.user} ${this.str("USER")}: ${member.user.toString()} \`@${member.user.tag}\` (${member.id})\n${de.channel} ${this.str("CHANNEL")}: \`#${channel.name}\` (${channel.id})`, {
513
546
  title: this.str("OPEN_TICKET_TITLE"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elara-services/tickets",
3
- "version": "4.1.1",
3
+ "version": "4.3.0",
4
4
  "description": "Helper for tickets",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -11,15 +11,25 @@
11
11
  "repository": {
12
12
  "url": "https://github.com/elara-bots/npm/tree/main/tickets"
13
13
  },
14
+ "funding": [
15
+ {
16
+ "type": "patreon",
17
+ "url": "https://patreon.com/elaraservices"
18
+ },
19
+ {
20
+ "type": "paypal",
21
+ "url": "https://paypal.me/superchiefyt"
22
+ }
23
+ ],
14
24
  "scripts": {
15
25
  "lint": "eslint .",
16
26
  "pf": "prettier --write **/**/*.js",
17
27
  "pc": "prettier --check **/**/*.js"
18
28
  },
19
29
  "dependencies": {
20
- "@elara-services/packages": "^6.0.8",
21
- "@elara-services/utils": "^1.3.1",
22
- "@elara-services/webhooks": "^2.1.10"
30
+ "@elara-services/packages": "^6.0.9",
31
+ "@elara-services/utils": "^1.4.0",
32
+ "@elara-services/webhooks": "^2.1.18"
23
33
  },
24
34
  "devDependencies": {
25
35
  "eslint": "8.20.0",