discord-message-transcript 1.2.0-dev-next.0.19 → 1.2.0-dev-next.0.27
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/dist/core/cdnResolver.d.ts +4 -3
- package/dist/core/cdnResolver.js +28 -17
- package/dist/core/componentToJson.d.ts +3 -3
- package/dist/core/componentToJson.js +8 -9
- package/dist/core/fetchMessages.d.ts +14 -3
- package/dist/core/fetchMessages.js +19 -23
- package/dist/core/urlResolver.d.ts +4 -2
- package/dist/core/urlResolver.js +96 -4
- package/dist/index.js +54 -11
- package/dist/renderers/json/json.d.ts +12 -7
- package/dist/renderers/json/json.js +17 -4
- package/dist/types/types.d.ts +132 -22
- package/dist/types/types.js +18 -0
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CDNOptions } from "../types/types.js";
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
2
|
+
import { TranscriptOptionsBase } from "discord-message-transcript-base";
|
|
3
|
+
export declare function cdnResolver(url: string, options: TranscriptOptionsBase, cdnOptions: CDNOptions): Promise<string>;
|
|
4
|
+
export declare function uploadCareResolver(url: string, publicKey: string, cdnDomain: string): Promise<string>;
|
|
5
|
+
export declare function cloudinaryResolver(url: string, fileName: string, cloudName: string, apiKey: string, apiSecret: string): Promise<string>;
|
package/dist/core/cdnResolver.js
CHANGED
|
@@ -3,7 +3,7 @@ import http from 'http';
|
|
|
3
3
|
import { CustomWarn } from "discord-message-transcript-base";
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { getCDNLimiter } from "./limiter.js";
|
|
6
|
-
export async function cdnResolver(url, cdnOptions) {
|
|
6
|
+
export async function cdnResolver(url, options, cdnOptions) {
|
|
7
7
|
const limit = getCDNLimiter();
|
|
8
8
|
return limit(async () => {
|
|
9
9
|
return new Promise((resolve, reject) => {
|
|
@@ -34,7 +34,7 @@ Failed to receive a valid content-type from ${url}.`);
|
|
|
34
34
|
(cdnOptions.includeAudio && isAudio) ||
|
|
35
35
|
(cdnOptions.includeVideo && isVideo) ||
|
|
36
36
|
(cdnOptions.includeOthers && !isAudio && !isImage && !isVideo)) {
|
|
37
|
-
return resolve(await cdnRedirectType(url, contentType, cdnOptions));
|
|
37
|
+
return resolve(await cdnRedirectType(url, options, contentType, cdnOptions));
|
|
38
38
|
}
|
|
39
39
|
return resolve(url);
|
|
40
40
|
});
|
|
@@ -53,7 +53,7 @@ Request timeout for ${url}.`);
|
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
async function cdnRedirectType(url, contentType, cdnOptions) {
|
|
56
|
+
async function cdnRedirectType(url, options, contentType, cdnOptions) {
|
|
57
57
|
switch (cdnOptions.provider) {
|
|
58
58
|
case "CUSTOM": {
|
|
59
59
|
try {
|
|
@@ -68,18 +68,18 @@ Error: ${error?.message ?? error}`);
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
case "CLOUDINARY": {
|
|
71
|
-
return await cloudinaryResolver(url, cdnOptions.cloudName, cdnOptions.apiKey, cdnOptions.apiSecret);
|
|
71
|
+
return await cloudinaryResolver(url, options.fileName, cdnOptions.cloudName, cdnOptions.apiKey, cdnOptions.apiSecret);
|
|
72
72
|
;
|
|
73
73
|
}
|
|
74
74
|
case "UPLOADCARE": {
|
|
75
|
-
return await uploadCareResolver(url, cdnOptions.publicKey);
|
|
75
|
+
return await uploadCareResolver(url, cdnOptions.publicKey, cdnOptions.cdnDomain);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
function sleep(ms) {
|
|
80
80
|
return new Promise(r => setTimeout(r, ms));
|
|
81
81
|
}
|
|
82
|
-
export async function uploadCareResolver(url, publicKey) {
|
|
82
|
+
export async function uploadCareResolver(url, publicKey, cdnDomain) {
|
|
83
83
|
try {
|
|
84
84
|
const form = new FormData();
|
|
85
85
|
form.append("pub_key", publicKey);
|
|
@@ -108,17 +108,20 @@ export async function uploadCareResolver(url, publicKey) {
|
|
|
108
108
|
}
|
|
109
109
|
const json = await res.json();
|
|
110
110
|
if (json.uuid) {
|
|
111
|
-
return `https
|
|
111
|
+
return `https://${cdnDomain}/${json.uuid}/`;
|
|
112
112
|
}
|
|
113
|
+
let delay = 200;
|
|
114
|
+
let maxDelay = 2000;
|
|
113
115
|
if (json.token) {
|
|
114
116
|
for (let i = 0; i < 10; i++) {
|
|
115
|
-
await sleep(
|
|
117
|
+
await sleep(delay);
|
|
118
|
+
delay = Math.min(delay * 2, maxDelay);
|
|
116
119
|
const resToken = await fetch(`https://upload.uploadcare.com/from_url/status/?token=${json.token}&pub_key=${publicKey}`, { headers: { "User-Agent": "discord-message-transcript" } });
|
|
117
120
|
if (!resToken.ok)
|
|
118
121
|
throw new Error(`Uploadcare status failed with status code ${resToken.status}`);
|
|
119
122
|
const jsonToken = await resToken.json();
|
|
120
123
|
if (jsonToken.status === "success" && jsonToken.file_id) {
|
|
121
|
-
return `https
|
|
124
|
+
return `https://${cdnDomain}/${jsonToken.file_id}/`;
|
|
122
125
|
}
|
|
123
126
|
if (jsonToken.status === "error") {
|
|
124
127
|
throw new Error(jsonToken.error || "Uploadcare failed");
|
|
@@ -130,27 +133,34 @@ export async function uploadCareResolver(url, publicKey) {
|
|
|
130
133
|
}
|
|
131
134
|
catch (error) {
|
|
132
135
|
CustomWarn(`Uploadcare CDN upload failed. Using original URL as fallback.
|
|
133
|
-
Check Uploadcare public key, project settings, rate limits, and network access.
|
|
136
|
+
Check Uploadcare public key, CDN domain, project settings, rate limits, and network access.
|
|
134
137
|
URL: ${url}
|
|
135
138
|
Error: ${error?.message ?? error}`);
|
|
136
139
|
return url;
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
|
-
export async function cloudinaryResolver(url, cloudName, apiKey, apiSecret) {
|
|
142
|
+
export async function cloudinaryResolver(url, fileName, cloudName, apiKey, apiSecret) {
|
|
140
143
|
try {
|
|
141
|
-
const
|
|
144
|
+
const paramsToSign = {
|
|
145
|
+
folder: `discord-message-transcript/${fileName}`,
|
|
146
|
+
timestamp: Math.floor(Date.now() / 1000).toString(),
|
|
147
|
+
unique_filename: "true",
|
|
148
|
+
use_filename: "true",
|
|
149
|
+
};
|
|
150
|
+
const stringToSign = Object.keys(paramsToSign).sort().map(k => `${k}=${paramsToSign[k]}`).join("&");
|
|
142
151
|
// signature SHA1
|
|
143
152
|
const signature = crypto
|
|
144
153
|
.createHash("sha1")
|
|
145
|
-
.update(
|
|
154
|
+
.update(stringToSign + apiSecret)
|
|
146
155
|
.digest("hex");
|
|
147
156
|
const form = new FormData();
|
|
157
|
+
form.append("folder", paramsToSign.folder);
|
|
148
158
|
form.append("file", url);
|
|
149
159
|
form.append("api_key", apiKey);
|
|
150
|
-
form.append("timestamp", timestamp
|
|
160
|
+
form.append("timestamp", paramsToSign.timestamp);
|
|
151
161
|
form.append("signature", signature);
|
|
152
|
-
form.append("use_filename",
|
|
153
|
-
form.append("unique_filename",
|
|
162
|
+
form.append("use_filename", paramsToSign.use_filename);
|
|
163
|
+
form.append("unique_filename", paramsToSign.unique_filename);
|
|
154
164
|
const res = await fetch(`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`, {
|
|
155
165
|
method: "POST",
|
|
156
166
|
body: form,
|
|
@@ -167,7 +177,7 @@ export async function cloudinaryResolver(url, cloudName, apiKey, apiSecret) {
|
|
|
167
177
|
case 429:
|
|
168
178
|
throw new Error(`Cloudinary upload failed with status code ${res.status} - Rate limited.`);
|
|
169
179
|
default:
|
|
170
|
-
throw new Error(`Cloudinary upload failed with status code ${res.status}
|
|
180
|
+
throw new Error(`Cloudinary upload failed with status code ${res.status}.`);
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
183
|
const json = await res.json();
|
|
@@ -184,3 +194,4 @@ Error: ${error?.message ?? error}`);
|
|
|
184
194
|
return url;
|
|
185
195
|
}
|
|
186
196
|
}
|
|
197
|
+
// Note: for debug use ${JSON.stringify(await res.json())} to understand the error
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { TopLevelComponent } from "discord.js";
|
|
2
|
-
import { JsonTopLevelComponent, TranscriptOptionsBase } from "discord-message-transcript-base";
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
2
|
+
import { JsonTopLevelComponent, JsonComponentInContainer, TranscriptOptionsBase } from "discord-message-transcript-base";
|
|
3
|
+
export declare function componentsToJson(components: TopLevelComponent[], options: TranscriptOptionsBase): Promise<JsonTopLevelComponent[]>;
|
|
4
|
+
export declare function isJsonComponentInContainer(component: JsonTopLevelComponent): component is JsonComponentInContainer;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ComponentType } from "discord.js";
|
|
2
2
|
import { mapButtonStyle, mapSelectorType, mapSeparatorSpacing } from "./mappers.js";
|
|
3
3
|
import { JsonComponentType } from "discord-message-transcript-base";
|
|
4
|
-
|
|
5
|
-
export async function componentsToJson(components, options, cdnOptions) {
|
|
4
|
+
export async function componentsToJson(components, options) {
|
|
6
5
|
const processedComponents = await Promise.all(components.filter(component => !(!options.includeV2Components && component.type != ComponentType.ActionRow))
|
|
7
6
|
.map(async (component) => {
|
|
8
7
|
switch (component.type) {
|
|
@@ -54,7 +53,7 @@ export async function componentsToJson(components, options, cdnOptions) {
|
|
|
54
53
|
}
|
|
55
54
|
case ComponentType.Container: {
|
|
56
55
|
const newOptions = { ...options, includeComponents: true, includeButtons: true };
|
|
57
|
-
const componentsJson = await componentsToJson(component.components, newOptions
|
|
56
|
+
const componentsJson = await componentsToJson(component.components, newOptions);
|
|
58
57
|
return {
|
|
59
58
|
type: JsonComponentType.Container,
|
|
60
59
|
components: componentsJson.filter(isJsonComponentInContainer), // Input components that are container-safe must always produce container-safe output.
|
|
@@ -67,17 +66,17 @@ export async function componentsToJson(components, options, cdnOptions) {
|
|
|
67
66
|
type: JsonComponentType.File,
|
|
68
67
|
fileName: component.data.name ?? null,
|
|
69
68
|
size: component.data.size ?? 0,
|
|
70
|
-
url:
|
|
69
|
+
url: component.file.url,
|
|
71
70
|
spoiler: component.spoiler,
|
|
72
71
|
};
|
|
73
72
|
}
|
|
74
73
|
case ComponentType.MediaGallery: {
|
|
75
|
-
const mediaItems =
|
|
74
|
+
const mediaItems = component.items.map(item => {
|
|
76
75
|
return {
|
|
77
|
-
media: { url:
|
|
76
|
+
media: { url: item.media.url },
|
|
78
77
|
spoiler: item.spoiler,
|
|
79
78
|
};
|
|
80
|
-
})
|
|
79
|
+
});
|
|
81
80
|
return {
|
|
82
81
|
type: JsonComponentType.MediaGallery,
|
|
83
82
|
items: mediaItems,
|
|
@@ -99,7 +98,7 @@ export async function componentsToJson(components, options, cdnOptions) {
|
|
|
99
98
|
accessoryJson = {
|
|
100
99
|
type: JsonComponentType.Thumbnail,
|
|
101
100
|
media: {
|
|
102
|
-
url:
|
|
101
|
+
url: component.accessory.media.url,
|
|
103
102
|
},
|
|
104
103
|
spoiler: component.accessory.spoiler,
|
|
105
104
|
};
|
|
@@ -135,7 +134,7 @@ export async function componentsToJson(components, options, cdnOptions) {
|
|
|
135
134
|
}));
|
|
136
135
|
return processedComponents.filter(c => c != null);
|
|
137
136
|
}
|
|
138
|
-
function isJsonComponentInContainer(component) {
|
|
137
|
+
export function isJsonComponentInContainer(component) {
|
|
139
138
|
return (component.type == JsonComponentType.ActionRow ||
|
|
140
139
|
component.type == JsonComponentType.File ||
|
|
141
140
|
component.type == JsonComponentType.MediaGallery ||
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { TextBasedChannel } from "discord.js";
|
|
2
2
|
import { JsonAuthor, JsonMessage, TranscriptOptionsBase } from "discord-message-transcript-base";
|
|
3
|
-
import {
|
|
4
|
-
export declare function fetchMessages(
|
|
3
|
+
import { MapMentions } from "../types/types.js";
|
|
4
|
+
export declare function fetchMessages(ctx: FetchMessagesContext): Promise<{
|
|
5
5
|
messages: JsonMessage[];
|
|
6
6
|
end: boolean;
|
|
7
|
-
|
|
7
|
+
newLastMessageId: string | undefined;
|
|
8
8
|
}>;
|
|
9
|
+
export type FetchMessagesContext = {
|
|
10
|
+
channel: TextBasedChannel;
|
|
11
|
+
options: TranscriptOptionsBase;
|
|
12
|
+
transcriptState: TranscriptState;
|
|
13
|
+
lastMessageId: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
type TranscriptState = {
|
|
16
|
+
authors: Map<string, JsonAuthor>;
|
|
17
|
+
mentions: MapMentions;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -1,45 +1,41 @@
|
|
|
1
1
|
import { EmbedType } from "discord.js";
|
|
2
2
|
import { componentsToJson } from "./componentToJson.js";
|
|
3
|
-
import { urlResolver } from "./urlResolver.js";
|
|
4
3
|
import { getMentions } from "./getMentions.js";
|
|
5
|
-
export async function fetchMessages(
|
|
6
|
-
const
|
|
4
|
+
export async function fetchMessages(ctx) {
|
|
5
|
+
const { channel, options, transcriptState, lastMessageId } = ctx;
|
|
6
|
+
const { authors, mentions } = transcriptState;
|
|
7
|
+
const originalMessages = await channel.messages.fetch({ limit: 100, cache: false, before: lastMessageId });
|
|
7
8
|
const rawMessages = await Promise.all(originalMessages.map(async (message) => {
|
|
8
|
-
const attachments =
|
|
9
|
+
const attachments = message.attachments.map(attachment => {
|
|
9
10
|
return {
|
|
10
11
|
contentType: attachment.contentType,
|
|
11
12
|
name: attachment.name,
|
|
12
13
|
size: attachment.size,
|
|
13
14
|
spoiler: attachment.spoiler,
|
|
14
|
-
url:
|
|
15
|
+
url: attachment.url,
|
|
15
16
|
};
|
|
16
|
-
})
|
|
17
|
+
});
|
|
17
18
|
// This only works because embeds with the type poll_result that are send when a poll end are marked as a message send by the system
|
|
18
|
-
const embeds = message.system && message.embeds.length == 1 && message.embeds[0].data.type == EmbedType.PollResult && !options.includePolls
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
embed.author?.iconURL ? urlResolver(embed.author.iconURL, options, cdnOptions) : Promise.resolve(null),
|
|
22
|
-
embed.thumbnail?.url ? urlResolver(embed.thumbnail.url, options, cdnOptions) : Promise.resolve(null),
|
|
23
|
-
embed.image?.url ? urlResolver(embed.image.url, options, cdnOptions) : Promise.resolve(null),
|
|
24
|
-
embed.footer?.iconURL ? urlResolver(embed.footer.iconURL, options, cdnOptions) : Promise.resolve(null),
|
|
25
|
-
]);
|
|
19
|
+
const embeds = message.system && message.embeds.length == 1 && message.embeds[0].data.type == EmbedType.PollResult && !options.includePolls
|
|
20
|
+
? []
|
|
21
|
+
: message.embeds.map(embed => {
|
|
26
22
|
return {
|
|
27
|
-
author: embed.author ? { name: embed.author.name, url: embed.author.url ?? null, iconURL:
|
|
23
|
+
author: embed.author ? { name: embed.author.name, url: embed.author.url ?? null, iconURL: embed.author?.iconURL ?? null } : null,
|
|
28
24
|
description: embed.description ?? null,
|
|
29
25
|
fields: embed.fields.map(field => ({ inline: field.inline ?? false, name: field.name, value: field.value })),
|
|
30
|
-
footer: embed.footer ? { iconURL:
|
|
26
|
+
footer: embed.footer ? { iconURL: embed.footer?.iconURL ?? null, text: embed.footer.text } : null,
|
|
31
27
|
hexColor: embed.hexColor ?? null,
|
|
32
|
-
image:
|
|
33
|
-
thumbnail:
|
|
28
|
+
image: embed.image?.url ? { url: embed.image.url } : null,
|
|
29
|
+
thumbnail: embed.thumbnail?.url ? { url: embed.thumbnail.url } : null,
|
|
34
30
|
timestamp: embed.timestamp,
|
|
35
31
|
title: embed.title,
|
|
36
32
|
type: embed.data.type ?? "rich",
|
|
37
33
|
url: embed.url,
|
|
38
34
|
};
|
|
39
|
-
})
|
|
35
|
+
});
|
|
40
36
|
if (!authors.has(message.author.id)) {
|
|
41
37
|
authors.set(message.author.id, {
|
|
42
|
-
avatarURL:
|
|
38
|
+
avatarURL: message.author.displayAvatarURL(),
|
|
43
39
|
bot: message.author.bot,
|
|
44
40
|
displayName: message.author.displayName,
|
|
45
41
|
guildTag: message.author.primaryGuild?.tag ?? null,
|
|
@@ -51,7 +47,7 @@ export async function fetchMessages(channel, options, cdnOptions, authors, menti
|
|
|
51
47
|
system: message.author.system,
|
|
52
48
|
});
|
|
53
49
|
}
|
|
54
|
-
const components = await componentsToJson(message.components, options
|
|
50
|
+
const components = await componentsToJson(message.components, options);
|
|
55
51
|
await getMentions(message, mentions);
|
|
56
52
|
return {
|
|
57
53
|
attachments: options.includeAttachments ? attachments : [],
|
|
@@ -89,10 +85,10 @@ export async function fetchMessages(channel, options, cdnOptions, authors, menti
|
|
|
89
85
|
system: message.system,
|
|
90
86
|
};
|
|
91
87
|
}));
|
|
92
|
-
const
|
|
88
|
+
const newLastMessageId = originalMessages.last()?.id;
|
|
93
89
|
const messages = rawMessages.filter(m => !(!options.includeEmpty && m.attachments.length == 0 && m.components.length == 0 && m.content == "" && m.embeds.length == 0 && !m.poll));
|
|
94
90
|
const end = originalMessages.size !== 100;
|
|
95
|
-
return { messages, end,
|
|
91
|
+
return { messages, end, newLastMessageId };
|
|
96
92
|
}
|
|
97
93
|
function formatTimeLeftPoll(timestamp) {
|
|
98
94
|
const now = new Date();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import { TranscriptOptionsBase } from "discord-message-transcript-base";
|
|
1
|
+
import { JsonAuthor, JsonMessage, TranscriptOptionsBase } from "discord-message-transcript-base";
|
|
2
2
|
import { CDNOptions } from "../types/types.js";
|
|
3
|
-
export declare function urlResolver(url: string, options: TranscriptOptionsBase, cdnOptions: CDNOptions | null): Promise<string>;
|
|
3
|
+
export declare function urlResolver(url: string, options: TranscriptOptionsBase, cdnOptions: CDNOptions | null, urlCache: Map<string, Promise<string>>): Promise<string>;
|
|
4
|
+
export declare function messagesUrlResolver(messages: JsonMessage[], options: TranscriptOptionsBase, cdnOptions: CDNOptions | null, urlCache: Map<string, Promise<string>>): Promise<JsonMessage[]>;
|
|
5
|
+
export declare function authorUrlResolver(authors: Map<string, JsonAuthor>, options: TranscriptOptionsBase, cdnOptions: CDNOptions | null, urlCache: Map<string, Promise<string>>): Promise<JsonAuthor[]>;
|
package/dist/core/urlResolver.js
CHANGED
|
@@ -1,9 +1,101 @@
|
|
|
1
|
+
import { JsonComponentType } from "discord-message-transcript-base";
|
|
1
2
|
import { cdnResolver } from "./cdnResolver.js";
|
|
2
3
|
import { imageToBase64 } from "./imageToBase64.js";
|
|
3
|
-
|
|
4
|
+
import { isJsonComponentInContainer } from "./componentToJson.js";
|
|
5
|
+
export async function urlResolver(url, options, cdnOptions, urlCache) {
|
|
6
|
+
if (urlCache.has(url)) {
|
|
7
|
+
const cache = urlCache.get(url);
|
|
8
|
+
if (cache)
|
|
9
|
+
return await cache;
|
|
10
|
+
}
|
|
11
|
+
let returnUrl;
|
|
4
12
|
if (cdnOptions)
|
|
5
|
-
|
|
6
|
-
if (options.saveImages)
|
|
7
|
-
|
|
13
|
+
returnUrl = cdnResolver(url, options, cdnOptions);
|
|
14
|
+
else if (options.saveImages)
|
|
15
|
+
returnUrl = imageToBase64(url);
|
|
16
|
+
if (returnUrl) {
|
|
17
|
+
urlCache.set(url, returnUrl);
|
|
18
|
+
return await returnUrl;
|
|
19
|
+
}
|
|
8
20
|
return url;
|
|
9
21
|
}
|
|
22
|
+
export async function messagesUrlResolver(messages, options, cdnOptions, urlCache) {
|
|
23
|
+
return await Promise.all(messages.map(async (message) => {
|
|
24
|
+
const attachmentsPromise = Promise.all(message.attachments.map(async (attachment) => {
|
|
25
|
+
return {
|
|
26
|
+
...attachment,
|
|
27
|
+
url: await urlResolver(attachment.url, options, cdnOptions, urlCache)
|
|
28
|
+
};
|
|
29
|
+
}));
|
|
30
|
+
const embedsPromise = Promise.all(message.embeds.map(async (embed) => {
|
|
31
|
+
return {
|
|
32
|
+
...embed,
|
|
33
|
+
author: embed.author ? { ...embed.author, iconURL: embed.author.iconURL ? await urlResolver(embed.author.iconURL, options, cdnOptions, urlCache) : null } : null,
|
|
34
|
+
footer: embed.footer ? { ...embed.footer, iconURL: embed.footer.iconURL ? await urlResolver(embed.footer.iconURL, options, cdnOptions, urlCache) : null } : null,
|
|
35
|
+
image: embed.image?.url ? { url: await urlResolver(embed.image.url, options, cdnOptions, urlCache) } : null,
|
|
36
|
+
thumbnail: embed.thumbnail?.url ? { url: await urlResolver(embed.thumbnail.url, options, cdnOptions, urlCache) } : null,
|
|
37
|
+
};
|
|
38
|
+
}));
|
|
39
|
+
async function componentsFunction(components) {
|
|
40
|
+
return Promise.all(components.map(async (component) => {
|
|
41
|
+
if (component.type == JsonComponentType.Section) {
|
|
42
|
+
if (component.accessory.type == JsonComponentType.Thumbnail) {
|
|
43
|
+
return {
|
|
44
|
+
...component,
|
|
45
|
+
accessory: {
|
|
46
|
+
...component.accessory,
|
|
47
|
+
media: {
|
|
48
|
+
url: await urlResolver(component.accessory.media.url, options, cdnOptions, urlCache),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (component.type == JsonComponentType.MediaGallery) {
|
|
55
|
+
return {
|
|
56
|
+
...component,
|
|
57
|
+
items: await Promise.all(component.items.map(async (item) => {
|
|
58
|
+
return {
|
|
59
|
+
...item,
|
|
60
|
+
media: { url: await urlResolver(item.media.url, options, cdnOptions, urlCache) },
|
|
61
|
+
};
|
|
62
|
+
}))
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (component.type == JsonComponentType.File) {
|
|
66
|
+
return {
|
|
67
|
+
...component,
|
|
68
|
+
url: await urlResolver(component.url, options, cdnOptions, urlCache),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (component.type == JsonComponentType.Container) {
|
|
72
|
+
return {
|
|
73
|
+
...component,
|
|
74
|
+
components: (await componentsFunction(component.components)).filter(isJsonComponentInContainer), // Input components that are container-safe must always produce container-safe output.
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return component;
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
const componentsPromise = componentsFunction(message.components);
|
|
81
|
+
const [attachments, embeds, components] = await Promise.all([
|
|
82
|
+
attachmentsPromise,
|
|
83
|
+
embedsPromise,
|
|
84
|
+
componentsPromise
|
|
85
|
+
]);
|
|
86
|
+
return {
|
|
87
|
+
...message,
|
|
88
|
+
attachments: attachments,
|
|
89
|
+
embeds: embeds,
|
|
90
|
+
components: components,
|
|
91
|
+
};
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
export async function authorUrlResolver(authors, options, cdnOptions, urlCache) {
|
|
95
|
+
return await Promise.all(Array.from(authors.values()).map(async (author) => {
|
|
96
|
+
return {
|
|
97
|
+
...author,
|
|
98
|
+
avatarURL: await urlResolver(author.avatarURL, options, cdnOptions, urlCache),
|
|
99
|
+
};
|
|
100
|
+
}));
|
|
101
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import { Json } from "./renderers/json/json.js";
|
|
|
6
6
|
import { fetchMessages } from "./core/fetchMessages.js";
|
|
7
7
|
import { ReturnType } from "./types/types.js";
|
|
8
8
|
import { output } from "./core/output.js";
|
|
9
|
-
import { ReturnTypeBase, ReturnFormat, outputBase, CustomError } from "discord-message-transcript-base";
|
|
9
|
+
import { ReturnTypeBase, ReturnFormat, outputBase, CustomError, CustomWarn } from "discord-message-transcript-base";
|
|
10
10
|
import { returnTypeMapper } from "./core/mappers.js";
|
|
11
|
+
import { authorUrlResolver, messagesUrlResolver } from "./core/urlResolver.js";
|
|
11
12
|
/**
|
|
12
13
|
* Creates a transcript of a Discord channel's messages.
|
|
13
14
|
* Depending on the `returnType` option, this function can return an `AttachmentBuilder`,
|
|
@@ -28,6 +29,11 @@ export async function createTranscript(channel, options = {}) {
|
|
|
28
29
|
const artificialReturnType = options.returnType == ReturnType.Attachment ? ReturnTypeBase.Buffer : options.returnType ? returnTypeMapper(options.returnType) : ReturnTypeBase.Buffer;
|
|
29
30
|
const { fileName = null, includeAttachments = true, includeButtons = true, includeComponents = true, includeEmpty = false, includeEmbeds = true, includePolls = true, includeReactions = true, includeV2Components = true, localDate = 'en-GB', quantity = 0, returnFormat = ReturnFormat.HTML, saveImages = false, selfContained = false, timeZone = 'UTC', watermark = true, } = options;
|
|
30
31
|
const checkedFileName = (fileName ?? `Transcript-${channel.isDMBased() ? "DirectMessage" : channel.name}-${channel.id}`);
|
|
32
|
+
let validQuantity = true;
|
|
33
|
+
if (quantity < 0) {
|
|
34
|
+
CustomWarn("Quantity can't be a negative number, please use 0 for unlimited messages.\nUsing 0 as fallback!");
|
|
35
|
+
validQuantity = false;
|
|
36
|
+
}
|
|
31
37
|
const internalOptions = {
|
|
32
38
|
fileName: checkedFileName,
|
|
33
39
|
includeAttachments,
|
|
@@ -39,7 +45,7 @@ export async function createTranscript(channel, options = {}) {
|
|
|
39
45
|
includeReactions,
|
|
40
46
|
includeV2Components,
|
|
41
47
|
localDate,
|
|
42
|
-
quantity,
|
|
48
|
+
quantity: validQuantity ? quantity : 0,
|
|
43
49
|
returnFormat,
|
|
44
50
|
returnType: artificialReturnType,
|
|
45
51
|
saveImages,
|
|
@@ -47,26 +53,63 @@ export async function createTranscript(channel, options = {}) {
|
|
|
47
53
|
timeZone,
|
|
48
54
|
watermark
|
|
49
55
|
};
|
|
50
|
-
const
|
|
51
|
-
let lastMessageID;
|
|
56
|
+
const urlCache = new Map();
|
|
52
57
|
const authors = new Map();
|
|
53
58
|
const mentions = {
|
|
54
59
|
channels: new Map(),
|
|
55
60
|
roles: new Map(),
|
|
56
61
|
users: new Map(),
|
|
57
62
|
};
|
|
63
|
+
const fetchMessageParameter = {
|
|
64
|
+
channel: channel,
|
|
65
|
+
options: internalOptions,
|
|
66
|
+
transcriptState: {
|
|
67
|
+
authors: authors,
|
|
68
|
+
mentions: mentions,
|
|
69
|
+
},
|
|
70
|
+
lastMessageId: undefined
|
|
71
|
+
};
|
|
72
|
+
const jsonTranscript = channel.isDMBased() ? new Json(null, channel, internalOptions, options.cdnOptions ?? null, urlCache) : new Json(channel.guild, channel, internalOptions, options.cdnOptions ?? null, urlCache);
|
|
58
73
|
while (true) {
|
|
59
|
-
const { messages, end,
|
|
74
|
+
const { messages, end, newLastMessageId } = await fetchMessages(fetchMessageParameter);
|
|
60
75
|
jsonTranscript.addMessages(messages);
|
|
61
|
-
|
|
62
|
-
if (end || (jsonTranscript.
|
|
76
|
+
fetchMessageParameter.lastMessageId = newLastMessageId;
|
|
77
|
+
if (end || (jsonTranscript.getMessages().length >= quantity && quantity != 0)) {
|
|
63
78
|
break;
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
|
-
jsonTranscript.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
if (quantity > 0 && jsonTranscript.getMessages().length > quantity) {
|
|
82
|
+
jsonTranscript.sliceMessages(quantity);
|
|
83
|
+
}
|
|
84
|
+
if (options.cdnOptions) {
|
|
85
|
+
options.cdnOptions = {
|
|
86
|
+
includeAudio: true,
|
|
87
|
+
includeImage: true,
|
|
88
|
+
includeVideo: true,
|
|
89
|
+
includeOthers: true,
|
|
90
|
+
...options.cdnOptions
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (options.cdnOptions || options.saveImages) {
|
|
94
|
+
await Promise.all([
|
|
95
|
+
(async () => {
|
|
96
|
+
jsonTranscript.setAuthors(await authorUrlResolver(authors, internalOptions, options.cdnOptions ?? null, urlCache));
|
|
97
|
+
authors.clear();
|
|
98
|
+
})(),
|
|
99
|
+
(() => {
|
|
100
|
+
jsonTranscript.setMentions({ channels: Array.from(mentions.channels.values()), roles: Array.from(mentions.roles.values()), users: Array.from(mentions.users.values()) });
|
|
101
|
+
mentions.channels.clear();
|
|
102
|
+
mentions.roles.clear();
|
|
103
|
+
mentions.users.clear();
|
|
104
|
+
})(),
|
|
105
|
+
(async () => {
|
|
106
|
+
jsonTranscript.setMessages(await messagesUrlResolver(jsonTranscript.getMessages(), internalOptions, options.cdnOptions ?? null, urlCache));
|
|
107
|
+
})()
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
const outputJson = await jsonTranscript.toJson();
|
|
111
|
+
urlCache.clear();
|
|
112
|
+
const result = await output(outputJson);
|
|
70
113
|
if (!options.returnType || options.returnType == "attachment") {
|
|
71
114
|
if (!(result instanceof Buffer)) {
|
|
72
115
|
throw new CustomError("Expected buffer from output when *attachment* returnType is used.");
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { Guild, TextBasedChannel } from "discord.js";
|
|
2
2
|
import { ArrayMentions, JsonAuthor, JsonMessage, TranscriptOptionsBase, JsonData } from "discord-message-transcript-base";
|
|
3
|
+
import { CDNOptions } from "../../types/types.js";
|
|
3
4
|
export declare class Json {
|
|
4
|
-
guild
|
|
5
|
-
channel
|
|
6
|
-
authors
|
|
7
|
-
messages
|
|
8
|
-
options
|
|
9
|
-
mentions
|
|
10
|
-
|
|
5
|
+
private guild;
|
|
6
|
+
private channel;
|
|
7
|
+
private authors;
|
|
8
|
+
private messages;
|
|
9
|
+
private options;
|
|
10
|
+
private mentions;
|
|
11
|
+
private cdnOptions;
|
|
12
|
+
private urlCache;
|
|
13
|
+
constructor(guild: Guild | null, channel: TextBasedChannel, options: TranscriptOptionsBase, cdnOptions: CDNOptions | null, urlCache: Map<string, Promise<string>>);
|
|
11
14
|
addMessages(messages: JsonMessage[]): void;
|
|
12
15
|
sliceMessages(size: number): void;
|
|
16
|
+
setMessages(messages: JsonMessage[]): void;
|
|
17
|
+
getMessages(): JsonMessage[];
|
|
13
18
|
setAuthors(authors: JsonAuthor[]): void;
|
|
14
19
|
setMentions(mentions: ArrayMentions): void;
|
|
15
20
|
toJson(): Promise<JsonData>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseGuildTextChannel, DMChannel } from "discord.js";
|
|
2
|
+
import { urlResolver } from "../../core/urlResolver.js";
|
|
2
3
|
export class Json {
|
|
3
4
|
guild;
|
|
4
5
|
channel;
|
|
@@ -6,13 +7,17 @@ export class Json {
|
|
|
6
7
|
messages;
|
|
7
8
|
options;
|
|
8
9
|
mentions;
|
|
9
|
-
|
|
10
|
+
cdnOptions;
|
|
11
|
+
urlCache;
|
|
12
|
+
constructor(guild, channel, options, cdnOptions, urlCache) {
|
|
10
13
|
this.guild = guild;
|
|
11
14
|
this.channel = channel;
|
|
12
15
|
this.messages = [];
|
|
13
16
|
this.options = options;
|
|
14
17
|
this.authors = [];
|
|
15
18
|
this.mentions = { channels: [], roles: [], users: [] };
|
|
19
|
+
this.cdnOptions = cdnOptions;
|
|
20
|
+
this.urlCache = urlCache;
|
|
16
21
|
}
|
|
17
22
|
addMessages(messages) {
|
|
18
23
|
this.messages.push(...messages);
|
|
@@ -21,7 +26,13 @@ export class Json {
|
|
|
21
26
|
if (size > this.messages.length || size == 0) {
|
|
22
27
|
return;
|
|
23
28
|
}
|
|
24
|
-
this.messages = this.messages.slice(0, size
|
|
29
|
+
this.messages = this.messages.slice(0, size);
|
|
30
|
+
}
|
|
31
|
+
setMessages(messages) {
|
|
32
|
+
this.messages = messages;
|
|
33
|
+
}
|
|
34
|
+
getMessages() {
|
|
35
|
+
return this.messages;
|
|
25
36
|
}
|
|
26
37
|
setAuthors(authors) {
|
|
27
38
|
this.authors = authors;
|
|
@@ -31,11 +42,13 @@ export class Json {
|
|
|
31
42
|
}
|
|
32
43
|
async toJson() {
|
|
33
44
|
const channel = await this.channel.fetch();
|
|
45
|
+
const channelImg = channel instanceof DMChannel ? channel.recipient?.displayAvatarURL() ?? "cdn.discordapp.com/embed/avatars/4.png" : channel.isDMBased() ? channel.iconURL() ?? (await channel.fetchOwner()).displayAvatarURL() : null;
|
|
34
46
|
const guild = !channel.isDMBased() ? this.guild : null;
|
|
47
|
+
const guildIcon = guild?.iconURL();
|
|
35
48
|
const guildJson = !guild ? null : {
|
|
36
49
|
name: guild.name,
|
|
37
50
|
id: guild.id,
|
|
38
|
-
icon:
|
|
51
|
+
icon: guildIcon ? await urlResolver(guildIcon, this.options, this.cdnOptions, this.urlCache) : null,
|
|
39
52
|
};
|
|
40
53
|
return {
|
|
41
54
|
options: this.options,
|
|
@@ -45,7 +58,7 @@ export class Json {
|
|
|
45
58
|
parent: channel.isDMBased() ? null : (channel.parent ? { name: channel.parent.name, id: channel.parent.id } : null),
|
|
46
59
|
topic: (channel instanceof BaseGuildTextChannel) ? channel.topic : null,
|
|
47
60
|
id: channel.id,
|
|
48
|
-
img:
|
|
61
|
+
img: channelImg ? await urlResolver(channelImg, this.options, this.cdnOptions, this.urlCache) : null,
|
|
49
62
|
},
|
|
50
63
|
authors: this.authors,
|
|
51
64
|
messages: this.messages.reverse(),
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,19 +1,51 @@
|
|
|
1
1
|
import { JsonMessageMentionsChannels, JsonMessageMentionsRoles, JsonMessageMentionsUsers, LocalDate, TimeZone, Uploadable, ReturnFormat } from "discord-message-transcript-base";
|
|
2
2
|
import { AttachmentBuilder } from "discord.js";
|
|
3
3
|
import Stream from 'stream';
|
|
4
|
+
/**
|
|
5
|
+
* An enum-like object providing the possible return types for the transcript functions.
|
|
6
|
+
*/
|
|
4
7
|
export declare const ReturnType: {
|
|
8
|
+
/**
|
|
9
|
+
* Returns a `discord.js` AttachmentBuilder.
|
|
10
|
+
*/
|
|
5
11
|
readonly Attachment: "attachment";
|
|
12
|
+
/**
|
|
13
|
+
* Returns a `Buffer`.
|
|
14
|
+
*/
|
|
6
15
|
readonly Buffer: "buffer";
|
|
16
|
+
/**
|
|
17
|
+
* Returns a `Stream.Readable`.
|
|
18
|
+
* */
|
|
7
19
|
readonly Stream: "stream";
|
|
20
|
+
/**
|
|
21
|
+
* Returns a `string`.
|
|
22
|
+
* */
|
|
8
23
|
readonly String: "string";
|
|
24
|
+
/**
|
|
25
|
+
* Returns an `Uploadable` object with content, contentType, and fileName.
|
|
26
|
+
*/
|
|
9
27
|
readonly Uploadable: "uploadable";
|
|
10
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* The type representing the possible values of the `ReturnType` enum.
|
|
31
|
+
*/
|
|
11
32
|
export type ReturnType = typeof ReturnType[keyof typeof ReturnType];
|
|
33
|
+
/**
|
|
34
|
+
* A conditional type that maps the `ReturnType` string literal to the actual TypeScript type returned by the function.
|
|
35
|
+
* @template T The `ReturnType` literal.
|
|
36
|
+
*/
|
|
12
37
|
export type OutputType<T extends ReturnType> = T extends typeof ReturnType.Buffer ? Buffer : T extends typeof ReturnType.Stream ? Stream : T extends typeof ReturnType.String ? string : T extends typeof ReturnType.Uploadable ? Uploadable : AttachmentBuilder;
|
|
38
|
+
/**
|
|
39
|
+
* Options for creating a transcript, with all properties being optional.
|
|
40
|
+
* @see TranscriptOptions
|
|
41
|
+
*/
|
|
13
42
|
export type CreateTranscriptOptions<T extends ReturnType> = Partial<TranscriptOptions<T>>;
|
|
43
|
+
/**
|
|
44
|
+
* Options for converting a JSON transcript to an HTML transcript.
|
|
45
|
+
*/
|
|
14
46
|
export type ConvertTranscriptOptions<T extends ReturnType> = Partial<{
|
|
15
47
|
/**
|
|
16
|
-
* The type
|
|
48
|
+
* The desired output type for the transcript.
|
|
17
49
|
* - ReturnType.Attachment - The transcript content as a `Attachment`
|
|
18
50
|
* - ReturnType.String - The transcript content as a string.
|
|
19
51
|
* - ReturnType.Buffer - The transcript content as a `Buffer`.
|
|
@@ -23,24 +55,27 @@ export type ConvertTranscriptOptions<T extends ReturnType> = Partial<{
|
|
|
23
55
|
*/
|
|
24
56
|
returnType: T;
|
|
25
57
|
/**
|
|
26
|
-
* Whether the generated HTML should
|
|
58
|
+
* Whether the generated HTML should have its CSS and JS embedded directly in the file.
|
|
27
59
|
* @default false
|
|
28
60
|
*/
|
|
29
61
|
selfContained: boolean;
|
|
30
62
|
/**
|
|
31
|
-
*
|
|
63
|
+
* Whether to include the 'Generated with discord-message-transcript' watermark in the footer.
|
|
32
64
|
* @default true
|
|
33
65
|
*/
|
|
34
66
|
watermark: boolean;
|
|
35
67
|
}>;
|
|
36
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Defines the complete set of options for creating a transcript.
|
|
70
|
+
*/
|
|
71
|
+
export interface TranscriptOptions<T extends ReturnType> {
|
|
37
72
|
/**
|
|
38
|
-
* CDN
|
|
73
|
+
* Configuration for uploading attachments and other assets to a CDN.
|
|
39
74
|
*/
|
|
40
75
|
cdnOptions: CDNOptions;
|
|
41
76
|
/**
|
|
42
|
-
* The name of the file
|
|
43
|
-
*
|
|
77
|
+
* The name of the generated file (without extension).
|
|
78
|
+
* @default `Transcript-channel-name-channel-id`
|
|
44
79
|
*/
|
|
45
80
|
fileName: string;
|
|
46
81
|
/**
|
|
@@ -49,17 +84,17 @@ export interface TranscriptOptions<T extends ReturnType, Other = unknown> {
|
|
|
49
84
|
*/
|
|
50
85
|
includeAttachments: boolean;
|
|
51
86
|
/**
|
|
52
|
-
* Whether to include buttons in the transcript.
|
|
87
|
+
* Whether to include message component buttons in the transcript.
|
|
53
88
|
* @default true
|
|
54
89
|
*/
|
|
55
90
|
includeButtons: boolean;
|
|
56
91
|
/**
|
|
57
|
-
* Whether to include components in the transcript.
|
|
92
|
+
* Whether to include non-button message components (like select menus) in the transcript.
|
|
58
93
|
* @default true
|
|
59
94
|
*/
|
|
60
95
|
includeComponents: boolean;
|
|
61
96
|
/**
|
|
62
|
-
* Whether to include
|
|
97
|
+
* Whether to include messages that have no content.
|
|
63
98
|
* @default false
|
|
64
99
|
*/
|
|
65
100
|
includeEmpty: boolean;
|
|
@@ -74,23 +109,23 @@ export interface TranscriptOptions<T extends ReturnType, Other = unknown> {
|
|
|
74
109
|
*/
|
|
75
110
|
includePolls: boolean;
|
|
76
111
|
/**
|
|
77
|
-
* Whether to include reactions in the transcript.
|
|
112
|
+
* Whether to include message reactions in the transcript.
|
|
78
113
|
* @default true
|
|
79
114
|
*/
|
|
80
115
|
includeReactions: boolean;
|
|
81
116
|
/**
|
|
82
|
-
* Whether to include V2 components
|
|
117
|
+
* Whether to include newer (V2) components like `Container`, `MediaGallery`, etc.
|
|
83
118
|
* @default true
|
|
84
119
|
*/
|
|
85
120
|
includeV2Components: boolean;
|
|
86
121
|
/**
|
|
87
|
-
* The locale to use for formatting dates.
|
|
88
|
-
*
|
|
122
|
+
* The locale to use for formatting dates (e.g., 'en-US', 'pt-BR').
|
|
123
|
+
* Must be a valid BCP 47 language tag.
|
|
89
124
|
* @default 'en-GB'
|
|
90
125
|
*/
|
|
91
126
|
localDate: LocalDate;
|
|
92
127
|
/**
|
|
93
|
-
* The maximum number of messages to fetch. Set to 0 to fetch all messages.
|
|
128
|
+
* The maximum number of messages to fetch. Set to `0` to fetch all messages in the channel.
|
|
94
129
|
* @default 0
|
|
95
130
|
*/
|
|
96
131
|
quantity: number;
|
|
@@ -102,7 +137,7 @@ export interface TranscriptOptions<T extends ReturnType, Other = unknown> {
|
|
|
102
137
|
*/
|
|
103
138
|
returnFormat: ReturnFormat;
|
|
104
139
|
/**
|
|
105
|
-
* The type
|
|
140
|
+
* The desired output type for the transcript.
|
|
106
141
|
* - ReturnType.Attachment - The transcript content as a `Attachment`
|
|
107
142
|
* - ReturnType.String - The transcript content as a string.
|
|
108
143
|
* - ReturnType.Buffer - The transcript content as a `Buffer`.
|
|
@@ -112,53 +147,128 @@ export interface TranscriptOptions<T extends ReturnType, Other = unknown> {
|
|
|
112
147
|
*/
|
|
113
148
|
returnType: T;
|
|
114
149
|
/**
|
|
115
|
-
* Whether to save images
|
|
150
|
+
* Whether to save images as base64 data directly in the transcript.
|
|
151
|
+
* This is an alternative to using a CDN and results in larger file sizes.
|
|
152
|
+
* Will not work if using CDN.
|
|
116
153
|
* @default false
|
|
117
154
|
*/
|
|
118
155
|
saveImages: boolean;
|
|
119
156
|
/**
|
|
120
|
-
* Whether the generated HTML should
|
|
121
|
-
* Only
|
|
157
|
+
* Whether the generated HTML should have its CSS and JS embedded directly in the file.
|
|
158
|
+
* Only applicable if `returnFormat` is `HTML`.
|
|
122
159
|
* @default false
|
|
123
160
|
*/
|
|
124
161
|
selfContained: boolean;
|
|
125
162
|
/**
|
|
126
|
-
* The timezone to use for formatting dates.
|
|
127
|
-
*
|
|
163
|
+
* The timezone to use for formatting dates (e.g., 'UTC', 'America/New_York').
|
|
164
|
+
* Must be a valid IANA time zone name.
|
|
128
165
|
* @default 'UTC'
|
|
129
166
|
*/
|
|
130
167
|
timeZone: TimeZone;
|
|
131
168
|
/**
|
|
132
|
-
*
|
|
169
|
+
* Whether to include the 'Generated with discord-message-transcript' watermark in the footer.
|
|
133
170
|
* @default true
|
|
134
171
|
*/
|
|
135
172
|
watermark: boolean;
|
|
136
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Defines the structure for storing discovered mentions (users, roles, channels) during transcript creation.
|
|
176
|
+
* Uses Maps for efficient lookups.
|
|
177
|
+
*/
|
|
137
178
|
export interface MapMentions {
|
|
138
179
|
channels: Map<string, JsonMessageMentionsChannels>;
|
|
139
180
|
roles: Map<string, JsonMessageMentionsRoles>;
|
|
140
181
|
users: Map<string, JsonMessageMentionsUsers>;
|
|
141
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* A string template type for representing a MIME type (e.g., 'image/png', 'application/json').
|
|
185
|
+
*/
|
|
142
186
|
export type MimeType = `${string}/${string}`;
|
|
187
|
+
/**
|
|
188
|
+
* Base options applicable to all CDN providers.
|
|
189
|
+
*/
|
|
143
190
|
export type CDNBase = Partial<{
|
|
191
|
+
/**
|
|
192
|
+
* Whether to upload audio files to the CDN.
|
|
193
|
+
* @default true
|
|
194
|
+
*/
|
|
144
195
|
includeAudio: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Whether to upload image files (excluding GIFs) to the CDN.
|
|
198
|
+
* @default true
|
|
199
|
+
*/
|
|
145
200
|
includeImage: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* Whether to upload video files (and GIFs) to the CDN.
|
|
203
|
+
* @default true
|
|
204
|
+
*/
|
|
146
205
|
includeVideo: boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Whether to upload any other file types to the CDN.
|
|
208
|
+
* @default true
|
|
209
|
+
*/
|
|
147
210
|
includeOthers: boolean;
|
|
148
211
|
}>;
|
|
212
|
+
/**
|
|
213
|
+
* A discriminated union of all possible CDN configurations.
|
|
214
|
+
*/
|
|
149
215
|
export type CDNOptions = (CDNBase & CDNOptionsCustom<any>) | (CDNBase & CDNOptionsCloudinary) | (CDNBase & CDNOptionsUploadcare);
|
|
216
|
+
/**
|
|
217
|
+
* Configuration for using a custom, user-provided CDN resolver function.
|
|
218
|
+
*/
|
|
150
219
|
export type CDNOptionsCustom<T = unknown> = {
|
|
220
|
+
/** Specifies the use of a custom CDN resolver. */
|
|
151
221
|
provider: "CUSTOM";
|
|
222
|
+
/**
|
|
223
|
+
* An async function that takes a URL and returns a new URL.
|
|
224
|
+
* @param url The original Discord asset URL.
|
|
225
|
+
* @param contentType The MIME type of the asset.
|
|
226
|
+
* @param customData Any additional data you want to pass to your resolver.
|
|
227
|
+
* @returns The new URL of the asset on your CDN.
|
|
228
|
+
*/
|
|
152
229
|
resolver: (url: string, contentType: MimeType | null, customData: T) => Promise<string> | string;
|
|
230
|
+
/**
|
|
231
|
+
* Any custom data you wish to make available within your resolver function.
|
|
232
|
+
*/
|
|
153
233
|
customData: T;
|
|
154
234
|
};
|
|
235
|
+
/**
|
|
236
|
+
* Configuration for using Cloudinary as the CDN.
|
|
237
|
+
*/
|
|
155
238
|
export type CDNOptionsCloudinary = {
|
|
239
|
+
/**
|
|
240
|
+
* Specifies the use of the built-in Cloudinary provider.
|
|
241
|
+
*/
|
|
156
242
|
provider: "CLOUDINARY";
|
|
243
|
+
/**
|
|
244
|
+
* Your Cloudinary cloud name.
|
|
245
|
+
*/
|
|
157
246
|
cloudName: string;
|
|
247
|
+
/**
|
|
248
|
+
* Your Cloudinary API key.
|
|
249
|
+
* */
|
|
158
250
|
apiKey: string;
|
|
251
|
+
/**
|
|
252
|
+
* Your Cloudinary API secret.
|
|
253
|
+
*/
|
|
159
254
|
apiSecret: string;
|
|
160
255
|
};
|
|
256
|
+
/**
|
|
257
|
+
* Configuration for using Uploadcare as the CDN.
|
|
258
|
+
*/
|
|
161
259
|
export type CDNOptionsUploadcare = {
|
|
260
|
+
/**
|
|
261
|
+
* Specifies the use of the built-in Uploadcare provider.
|
|
262
|
+
*/
|
|
162
263
|
provider: "UPLOADCARE";
|
|
264
|
+
/**
|
|
265
|
+
* Your Uploadcare public key.
|
|
266
|
+
*/
|
|
163
267
|
publicKey: string;
|
|
268
|
+
/**
|
|
269
|
+
* Your Uploadcare CDN domain.
|
|
270
|
+
* Example: "aaa111aaa1.ucarecd.net".
|
|
271
|
+
* DO NOT INCLUDE https://
|
|
272
|
+
*/
|
|
273
|
+
cdnDomain: string;
|
|
164
274
|
};
|
package/dist/types/types.js
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An enum-like object providing the possible return types for the transcript functions.
|
|
3
|
+
*/
|
|
1
4
|
export const ReturnType = {
|
|
5
|
+
/**
|
|
6
|
+
* Returns a `discord.js` AttachmentBuilder.
|
|
7
|
+
*/
|
|
2
8
|
Attachment: "attachment",
|
|
9
|
+
/**
|
|
10
|
+
* Returns a `Buffer`.
|
|
11
|
+
*/
|
|
3
12
|
Buffer: "buffer",
|
|
13
|
+
/**
|
|
14
|
+
* Returns a `Stream.Readable`.
|
|
15
|
+
* */
|
|
4
16
|
Stream: "stream",
|
|
17
|
+
/**
|
|
18
|
+
* Returns a `string`.
|
|
19
|
+
* */
|
|
5
20
|
String: "string",
|
|
21
|
+
/**
|
|
22
|
+
* Returns an `Uploadable` object with content, contentType, and fileName.
|
|
23
|
+
*/
|
|
6
24
|
Uploadable: "uploadable"
|
|
7
25
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discord-message-transcript",
|
|
3
|
-
"version": "1.2.0-dev-next.0.
|
|
3
|
+
"version": "1.2.0-dev-next.0.27",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"typescript": "^5.9.3"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"discord-message-transcript-base": "1.2.0-dev-next.0.
|
|
51
|
+
"discord-message-transcript-base": "1.2.0-dev-next.0.27"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"discord.js": ">=14.19.0 <15"
|