discord-message-transcript-base 1.2.0 → 1.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.
@@ -97,8 +97,14 @@ document.addEventListener('DOMContentLoaded', () => {
97
97
  const authorName = repliedToAuthor?.member?.displayName ?? repliedToAuthor?.displayName ?? 'Unknown';
98
98
  const authorColor = repliedToAuthor?.member?.displayHexColor ?? 'inherit';
99
99
 
100
- const authorNameSpan = `<span style="color: ${authorColor};">${authorName}</span>`;
101
- replyTextDiv.innerHTML = authorNameSpan + " " + content;
100
+ replyTextDiv.innerHTML = "";
101
+
102
+ const nameSpan = document.createElement("span");
103
+ nameSpan.style.color = authorColor;
104
+ nameSpan.textContent = authorName;
105
+
106
+ replyTextDiv.appendChild(nameSpan);
107
+ replyTextDiv.appendChild(document.createTextNode(" " + content));
102
108
  }
103
109
  }
104
110
  });
@@ -1,4 +1,4 @@
1
1
  export declare class CustomError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
- export declare function CustomWarn(message: string): void;
4
+ export declare function CustomWarn(message: string, disableWarnings: boolean): void;
@@ -5,6 +5,8 @@ export class CustomError extends Error {
5
5
  Object.setPrototypeOf(this, CustomError.prototype);
6
6
  }
7
7
  }
8
- export function CustomWarn(message) {
8
+ export function CustomWarn(message, disableWarnings) {
9
+ if (disableWarnings)
10
+ return;
9
11
  console.warn(`[discord-message-transcript] Warning: ${message}`);
10
12
  }
@@ -1 +1,9 @@
1
+ import { hexColor, JsonAttachment, LookupResult, TranscriptOptionsBase } from "../types/types.js";
2
+ export declare const FALLBACK_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
1
3
  export declare function sanitize(text: string): string;
4
+ export declare function isValidHexColor(colorInput: string, canReturnNull: false): hexColor;
5
+ export declare function isValidHexColor(colorInput: string | null, canReturnNull: true): hexColor | null;
6
+ export declare function resolveImageURL(url: string, options: TranscriptOptionsBase, canReturnNull: false, attachments?: JsonAttachment[]): Promise<string>;
7
+ export declare function resolveImageURL(url: string | null, options: TranscriptOptionsBase, canReturnNull: true, attachments?: JsonAttachment[]): Promise<string | null>;
8
+ export declare function isSafeForHTML(url: string, options: TranscriptOptionsBase): Promise<boolean>;
9
+ export declare function resolveAllIps(host: string): Promise<LookupResult[]>;
@@ -1,3 +1,10 @@
1
+ import net from "node:net";
2
+ import { CustomWarn } from "./customMessages.js";
3
+ import { Resolver } from "dns/promises";
4
+ export const FALLBACK_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
5
+ const DNS_SERVERS = ["1.1.1.1", "8.8.8.8"];
6
+ const DNS_LOOKUP_TIMEOUT = 5000;
7
+ const TRUSTED_DISCORD_HOSTS = ["discordapp.com", "discordapp.net"];
1
8
  export function sanitize(text) {
2
9
  return text
3
10
  .replace(/&/g, "&amp;")
@@ -6,3 +13,146 @@ export function sanitize(text) {
6
13
  .replace(/"/g, "&quot;")
7
14
  .replace(/'/g, "&#39;");
8
15
  }
16
+ export function isValidHexColor(colorInput, canReturnNull) {
17
+ if (!colorInput)
18
+ return null;
19
+ const hexColorRegex = /^#([A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}([A-Fa-f0-9]{2})?)$/;
20
+ let color = colorInput.trim();
21
+ if (/^[A-Fa-f0-9]+$/.test(color)) {
22
+ color = "#" + color;
23
+ }
24
+ if (hexColorRegex.test(color)) {
25
+ return color;
26
+ }
27
+ if (canReturnNull) {
28
+ return null;
29
+ }
30
+ return "#000000"; // Falback to a default hexColor if can't be null
31
+ }
32
+ export async function resolveImageURL(url, options, canReturnNull, attachments) {
33
+ if (!url)
34
+ return null;
35
+ // Resolve attachment:// references to actual attachment URL
36
+ if (url.startsWith("attachment://")) {
37
+ const name = url.slice("attachment://".length).trim();
38
+ const found = attachments?.find(a => a.name === name);
39
+ if (found && await isSafeForHTML(found.url, options))
40
+ return found.url;
41
+ return FALLBACK_PIXEL;
42
+ }
43
+ if (await isSafeForHTML(url, options))
44
+ return url;
45
+ if (canReturnNull)
46
+ return null;
47
+ return FALLBACK_PIXEL;
48
+ }
49
+ export async function isSafeForHTML(url, options) {
50
+ const { safeMode, disableWarnings } = options;
51
+ if (!safeMode)
52
+ return true;
53
+ let u;
54
+ try {
55
+ u = new URL(url);
56
+ }
57
+ catch {
58
+ CustomWarn(`Unsafe URL rejected: Invalid URL format\nURL: ${url}`, disableWarnings);
59
+ return false;
60
+ }
61
+ const host = u.hostname.toLowerCase();
62
+ // If is from discord accept
63
+ if (isTrustedDiscordHost(host))
64
+ return true;
65
+ // Don't accept if isn't https or http
66
+ if (!["http:", "https:"].includes(u.protocol)) {
67
+ CustomWarn(`Unsafe URL rejected: Invalid protocol "${u.protocol}"\nURL: ${url}`, disableWarnings);
68
+ return false;
69
+ }
70
+ if (u.username || u.password) {
71
+ CustomWarn(`Unsafe URL rejected: Contains username or password\nURL: ${url}`, disableWarnings);
72
+ return false;
73
+ }
74
+ if (u.port && !["80", "443", ""].includes(u.port)) {
75
+ CustomWarn(`Unsafe URL rejected: Invalid port "${u.port}"\nURL: ${url}`, disableWarnings);
76
+ return false;
77
+ }
78
+ // Block localhost and loopback addresses (SSRF protection)
79
+ if (host === "localhost" ||
80
+ host === "127.0.0.1" ||
81
+ host.startsWith("0.")) {
82
+ CustomWarn(`Unsafe URL rejected: Blacklisted host "${host}"\nURL: ${url}`, disableWarnings);
83
+ return false;
84
+ }
85
+ let ips;
86
+ try {
87
+ ips = await resolveAllIps(host);
88
+ }
89
+ catch (e) {
90
+ CustomWarn(`Unsafe URL rejected: DNS lookup failed or timed out for host "${host}". Error: ${e.message}\nURL: ${url}`, disableWarnings);
91
+ return false;
92
+ }
93
+ // Block private/internal network IPs (SSRF protection)
94
+ for (const ip of ips) {
95
+ if (isPrivateIp(ip.address)) {
96
+ CustomWarn(`Unsafe URL rejected: Private IP address "${ip.address}" resolved for host "${host}"\nURL: ${url}`, disableWarnings);
97
+ return false;
98
+ }
99
+ }
100
+ const path = u.pathname.toLowerCase();
101
+ // External SVGs can execute scripts → allow only from Discord CDN
102
+ if (path.endsWith(".svg")) {
103
+ CustomWarn(`Unsafe URL rejected: External SVG not from Discord CDN\nURL: ${url}`, disableWarnings);
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+ export async function resolveAllIps(host) {
109
+ const resolver = new Resolver();
110
+ resolver.setServers(DNS_SERVERS);
111
+ const lookupPromise = (async () => {
112
+ const results = [];
113
+ const [v4, v6] = await Promise.allSettled([
114
+ resolver.resolve4(host),
115
+ resolver.resolve6(host)
116
+ ]);
117
+ if (v4.status === "fulfilled") {
118
+ for (const ip of v4.value) {
119
+ results.push({ address: ip, family: 4 });
120
+ }
121
+ }
122
+ if (v6.status === "fulfilled") {
123
+ for (const ip of v6.value) {
124
+ results.push({ address: ip, family: 6 });
125
+ }
126
+ }
127
+ if (results.length === 0) {
128
+ throw new Error(`No DNS records found for ${host}`);
129
+ }
130
+ return results;
131
+ })();
132
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`DNS timeout for ${host}`)), DNS_LOOKUP_TIMEOUT));
133
+ return Promise.race([lookupPromise, timeoutPromise]);
134
+ }
135
+ function isPrivateIp(ip) {
136
+ if (!net.isIP(ip))
137
+ return true;
138
+ // Detects private IPv6 ranges
139
+ if (ip.includes(":")) {
140
+ return (ip === "::1" ||
141
+ ip.startsWith("fc") ||
142
+ ip.startsWith("fd") ||
143
+ ip.startsWith("fe80"));
144
+ }
145
+ const parts = ip.split(".").map(Number);
146
+ // Detects private IPv4 ranges
147
+ return (parts[0] === 10 ||
148
+ parts[0] === 127 ||
149
+ (parts[0] === 192 && parts[1] === 168) ||
150
+ (parts[0] === 169 && parts[1] === 254) ||
151
+ (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31));
152
+ }
153
+ function isTrustedDiscordHost(host) {
154
+ host = host.toLowerCase();
155
+ return TRUSTED_DISCORD_HOSTS.some(trusted => {
156
+ return host === trusted || host.endsWith("." + trusted);
157
+ });
158
+ }
@@ -55,19 +55,19 @@ export class Html {
55
55
  <div style="display: flex; flex-direction: column; justify-content: center; gap: 1.25rem;">
56
56
  ${guild ? `<div id="guild" class="line">
57
57
  <h4>Guild: </h4>
58
- <h4 style="font-weight: normal;">${guild.name}</h4>
58
+ <h4 style="font-weight: normal;">${sanitize(guild.name)}</h4>
59
59
  </div>` : ""}
60
60
  ${channel.parent ? `<div id="category" class="line">
61
61
  <h4>Category: </h4>
62
- <h4 style="font-weight: normal;">${channel.parent.name}</h4>
62
+ <h4 style="font-weight: normal;">${sanitize(channel.parent.name)}</h4>
63
63
  </div>` : ""}
64
64
  <div id="channel" class="line">
65
65
  <h4>Channel: </h4>
66
- <h4 style="font-weight: normal;">${channel.name}</h4>
66
+ <h4 style="font-weight: normal;">${sanitize(channel.name)}</h4>
67
67
  </div>
68
68
  ${channel.topic ? `<div id="topic" class="line">
69
69
  <h4>Topic: </h4>
70
- <h4 style="font-weight: normal;">${channel.topic}</h4>
70
+ <h4 style="font-weight: normal;">${sanitize(channel.topic)}</h4>
71
71
  </div>` : ""}
72
72
  </div>
73
73
  </div>
@@ -77,9 +77,9 @@ export class Html {
77
77
  return (await Promise.all(this.data.messages.map(async (message) => {
78
78
  const date = new Date(message.createdTimestamp);
79
79
  return `
80
- <div class="messageDiv" id="${message.id}" data-author-id="${message.authorId}">
80
+ <div class="messageDiv" id="${sanitize(message.id)}" data-author-id="${sanitize(message.authorId)}">
81
81
  ${message.references && message.references.messageId ?
82
- `<div class="messageReply" data-id="${message.references.messageId}">
82
+ `<div class="messageReply" data-id="${sanitize(message.references.messageId)}">
83
83
  <svg class="messageReplySvg"><use href="#reply-icon"></use></svg>
84
84
  <img class="messageReplyImg" src="">
85
85
  <div class="replyBadges"></div>
@@ -126,7 +126,7 @@ export class Html {
126
126
  <head>
127
127
  <meta charset="UTF-8" />
128
128
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
129
- <title>${options.fileName}</title>
129
+ <title>${sanitize(options.fileName)}</title>
130
130
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/atom-one-dark.min.css">
131
131
  <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script>
132
132
  ${options.selfContained ? `<style>${cssContent}</style>` : `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/discord-message-transcript-base@${packageJson.version}/dist/assets/style.css">`}
@@ -223,13 +223,13 @@ export class Html {
223
223
  <div class="embedHeaderLeft">
224
224
  ${embed.author ? `
225
225
  <div class="embedHeaderLeftAuthor">
226
- ${embed.author.iconURL ? `<img class="embedHeaderLeftAuthorImg" src="${sanitize(embed.author.iconURL)}">` : ""}
226
+ ${embed.author.iconURL ? `<img class="embedHeaderLeftAuthorImg" src="${embed.author.iconURL}">` : ""}
227
227
  ${embedAuthor}
228
228
  </div>` : ""}
229
229
  ${embedTitle}
230
230
  ${embed.description ? `<div class="embedDescription">${markdownToHTML(embed.description, this.data.mentions, message.mentions, this.dateFormat)}</div>` : ""}
231
231
  </div>
232
- ${embed.thumbnail ? `<img class="embedHeaderThumbnail" src="${sanitize(embed.thumbnail.url)}">` : ""}
232
+ ${embed.thumbnail ? `<img class="embedHeaderThumbnail" src="${embed.thumbnail.url}">` : ""}
233
233
  </div>` : ""}
234
234
  ${embed.fields && embed.fields.length > 0 ? `
235
235
  <div class="embedFields">
@@ -241,11 +241,11 @@ export class Html {
241
241
  </div>` : ""}
242
242
  ${embed.image ? `
243
243
  <div class="embedImage">
244
- <img src="${sanitize(embed.image.url)}">
244
+ <img src="${embed.image.url}">
245
245
  </div>` : ""}
246
246
  ${embed.footer || embed.timestamp ? `
247
247
  <div class="embedFooter">
248
- ${embed.footer?.iconURL ? `<img class="embedFooterImg" src="${sanitize(embed.footer.iconURL)}">` : ""}
248
+ ${embed.footer?.iconURL ? `<img class="embedFooterImg" src="${embed.footer.iconURL}">` : ""}
249
249
  ${embed.footer?.text || embed.timestamp ? `<p class="embedFooterText">${embed.footer?.text ? sanitize(embed.footer.text) : ''}${embed.footer?.text && embed.timestamp ? ' | ' : ''}${embed.timestamp ? this.dateFormat.format(new Date(embed.timestamp)) : ''}</p>` : ""}
250
250
  </div>` : ""}
251
251
  </div>
@@ -256,13 +256,23 @@ export class Html {
256
256
  return attachments.map(attachment => {
257
257
  let html = "";
258
258
  if (attachment.contentType?.startsWith('image/')) {
259
- html = `<img class="attachmentImage" src="${sanitize(attachment.url)}">`;
259
+ html = `<img class="attachmentImage" src="${attachment.url}">`;
260
260
  }
261
261
  else if (attachment.contentType?.startsWith('video/')) {
262
- html = `<video class="attachmentVideo" controls src="${sanitize(attachment.url)}"></video>`;
262
+ if (attachment.url == "") {
263
+ html = `<video class="attachmentVideo" controls src="${attachment.url}"></video>`;
264
+ }
265
+ else {
266
+ html = `<div class="attachmentVideoUnavailable">Video unavailable</div>`;
267
+ }
263
268
  }
264
269
  else if (attachment.contentType?.startsWith('audio/')) {
265
- html = `<audio class="attachmentAudio" controls src="${sanitize(attachment.url)}"></audio>`;
270
+ if (attachment.url == "") {
271
+ html = `<audio class="attachmentAudio" controls src="${attachment.url}"></audio>`;
272
+ }
273
+ else {
274
+ html = `<div class="attachmentAudioUnavailable">Audio unavailable</div>`;
275
+ }
266
276
  }
267
277
  else {
268
278
  let fileSize = attachment.size / 1024;
@@ -277,9 +287,13 @@ export class Html {
277
287
  <p class="attachmentFileName">${attachment.name ? sanitize(attachment.name) : 'attachment'}</p>
278
288
  <div class="attachmentFileSize">${fileSize.toFixed(2)} ${COUNT_UNIT[count]}</div>
279
289
  </div>
280
- <a class="attachmentDownload" href="${sanitize(attachment.url)}" target="_blank">
290
+ ${attachment.url != "" ?
291
+ `<a class="attachmentDownload" href="${attachment.url}" target="_blank">
292
+ <svg class="attachmentDownloadIcon"><use href="#download-icon"></use></svg>
293
+ </a>` :
294
+ `<span class="attachmentDownload">
281
295
  <svg class="attachmentDownloadIcon"><use href="#download-icon"></use></svg>
282
- </a>
296
+ </span>`}
283
297
  </div>
284
298
  `;
285
299
  }
@@ -302,7 +316,7 @@ export class Html {
302
316
  }
303
317
  case JsonComponentType.Container: {
304
318
  const html = `
305
- <div class="container" style="${component.hexAccentColor ? `border-left-color: ${component.hexAccentColor}` : ''}">
319
+ <div class="container" style="border-left-color: ${component.hexAccentColor}">
306
320
  ${this.componentBuilder(message, component.components)}
307
321
  </div>
308
322
  `;
@@ -321,9 +335,13 @@ export class Html {
321
335
  <p class="attachmentFileName">${component.fileName ? sanitize(component.fileName) : 'file'}</p>
322
336
  <div class="attachmentFileSize">${fileSize.toFixed(2)} ${COUNT_UNIT[count]}</div>
323
337
  </div>
324
- <a class="attachmentDownload" href="${component.url ? sanitize(component.url) : ''}" target="_blank">
338
+ ${component.url != "" ?
339
+ `<a class="attachmentDownload" href="${component.url}" target="_blank">
340
+ <svg class="attachmentDownloadIcon"><use href="#download-icon"></use></svg>
341
+ </a>` :
342
+ `<span class="attachmentDownload">
325
343
  <svg class="attachmentDownloadIcon"><use href="#download-icon"></use></svg>
326
- </a>
344
+ </span>`}
327
345
  </div>
328
346
  `;
329
347
  return this.spoilerAttachmentBuilder(component.spoiler, html);
@@ -334,7 +352,7 @@ export class Html {
334
352
  ${component.items.map(image => {
335
353
  return `
336
354
  <div class="mediaGalleryItem">
337
- ${this.spoilerAttachmentBuilder(image.spoiler, `<img class="mediaGalleryImg" src="${sanitize(image.media.url)}">`)}
355
+ ${this.spoilerAttachmentBuilder(image.spoiler, `<img class="mediaGalleryImg" src="${image.media.url}">`)}
338
356
  </div>
339
357
  `;
340
358
  }).join("")}
@@ -350,7 +368,7 @@ export class Html {
350
368
  <div class="sectionRight">
351
369
  ${component.accessory.type == JsonComponentType.Button ? this.buttonBuilder(component.accessory)
352
370
  : component.accessory.type == JsonComponentType.Thumbnail ? this.spoilerAttachmentBuilder(component.accessory.spoiler, `
353
- <img class="sectionThumbnail" src="${sanitize(component.accessory.media.url)}">
371
+ <img class="sectionThumbnail" src="${component.accessory.media.url}">
354
372
  `) : ""}
355
373
  </div>
356
374
  </div>
@@ -99,8 +99,14 @@ document.addEventListener('DOMContentLoaded', () => {
99
99
  const authorName = repliedToAuthor?.member?.displayName ?? repliedToAuthor?.displayName ?? 'Unknown';
100
100
  const authorColor = repliedToAuthor?.member?.displayHexColor ?? 'inherit';
101
101
 
102
- const authorNameSpan = \`<span style="color: \${authorColor};">\${authorName}</span>\`;
103
- replyTextDiv.innerHTML = authorNameSpan + " " + content;
102
+ replyTextDiv.innerHTML = "";
103
+
104
+ const nameSpan = document.createElement("span");
105
+ nameSpan.style.color = authorColor;
106
+ nameSpan.textContent = authorName;
107
+
108
+ replyTextDiv.appendChild(nameSpan);
109
+ replyTextDiv.appendChild(document.createTextNode(" " + content));
104
110
  }
105
111
  }
106
112
  });
@@ -15,6 +15,17 @@ export type JsonTopLevelComponent = JsonActionRow | JsonButtonComponent | JsonSe
15
15
  * A union of all V2 component types.
16
16
  */
17
17
  export type JsonV2Component = JsonContainerComponent | JsonFileComponent | JsonMediaGalleryComponent | JsonSectionComponent | JsonSeparatorComponent | JsonTextDisplayComponent | JsonThumbnailComponent;
18
+ /**
19
+ * A hexColor type
20
+ */
21
+ export type hexColor = `#${string}`;
22
+ /**
23
+ * Result from dns.lookup
24
+ */
25
+ export type LookupResult = {
26
+ address: string;
27
+ family: 4 | 6;
28
+ };
18
29
  /**
19
30
  * A union of all possible timestamp styles for formatting dates and times in Discord.
20
31
  */
@@ -91,6 +102,12 @@ export interface TranscriptOptionsBase {
91
102
  * The name of the generated file.
92
103
  */
93
104
  fileName: string;
105
+ /**
106
+ * Disable all warnings to keep console output clean.
107
+ * ⚠️ Can hide issues like unsafe URLs or fallback usage.
108
+ * @default false
109
+ */
110
+ disableWarnings: boolean;
94
111
  /**
95
112
  * Whether to include attachments.
96
113
  */
@@ -139,6 +156,14 @@ export interface TranscriptOptionsBase {
139
156
  * The type of the returned value (buffer, string, etc.).
140
157
  */
141
158
  returnType: ReturnTypeBase;
159
+ /**
160
+ * Enables safe mode, blocking potentially unsafe URLs and content.
161
+ * Prevents suspicious links, images, or HTML from being included in the final transcript.
162
+ *
163
+ * ⚠️ Disabling may allow unsafe content to appear in the transcript.
164
+ * @default true
165
+ */
166
+ safeMode: boolean;
142
167
  /**
143
168
  * Whether to save images as base64.
144
169
  */
@@ -161,6 +186,7 @@ export interface TranscriptOptionsBase {
161
186
  */
162
187
  export interface TranscriptOptionsParse {
163
188
  fileName: string;
189
+ disableWarnings: boolean;
164
190
  includeAttachments: boolean;
165
191
  includeButtons: boolean;
166
192
  includeComponents: boolean;
@@ -173,6 +199,7 @@ export interface TranscriptOptionsParse {
173
199
  quantity: number;
174
200
  returnFormat: ReturnFormat;
175
201
  returnType: ReturnTypeParse;
202
+ safeMode: boolean;
176
203
  saveImages: boolean;
177
204
  selfContained: boolean;
178
205
  timeZone: TimeZone;
@@ -288,7 +315,7 @@ export interface JsonAuthor {
288
315
  /**
289
316
  * The member's display color in hex format.
290
317
  */
291
- displayHexColor: string;
318
+ displayHexColor: hexColor;
292
319
  /**
293
320
  * The member's display name in the guild.
294
321
  */
@@ -339,7 +366,7 @@ export interface JsonContainerComponent {
339
366
  /**
340
367
  * The accent color of the container's border.
341
368
  */
342
- hexAccentColor: string | null;
369
+ hexAccentColor: hexColor;
343
370
  /**
344
371
  * Whether the container's content is a spoiler.
345
372
  */
@@ -453,7 +480,7 @@ export interface JsonEmbed {
453
480
  iconURL: string | null;
454
481
  text: string;
455
482
  } | null;
456
- hexColor: string | null;
483
+ hexColor: hexColor | null;
457
484
  image: {
458
485
  url: string;
459
486
  } | null;
@@ -548,7 +575,7 @@ export interface JsonMessageMentionsChannels {
548
575
  export interface JsonMessageMentionsRoles {
549
576
  id: string;
550
577
  name: string;
551
- color: string;
578
+ color: hexColor;
552
579
  }
553
580
  /**
554
581
  * A JSON-serializable representation of a user mention.
@@ -556,7 +583,7 @@ export interface JsonMessageMentionsRoles {
556
583
  export interface JsonMessageMentionsUsers {
557
584
  id: string;
558
585
  name: string;
559
- color: string | null;
586
+ color: hexColor | null;
560
587
  }
561
588
  /**
562
589
  * A JSON-serializable representation of a poll.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discord-message-transcript-base",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",