discord-message-transcript 1.3.1-dev.3.35 → 1.3.2-dev.0.49
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/assetResolver/base64/imageToBase64.d.ts +2 -0
- package/dist/core/{imageToBase64.js → assetResolver/base64/imageToBase64.js} +18 -4
- package/dist/core/assetResolver/cdn/cdnCustomError.d.ts +16 -0
- package/dist/core/assetResolver/cdn/cdnCustomError.js +28 -0
- package/dist/core/assetResolver/cdn/cdnResolver.d.ts +3 -0
- package/dist/core/assetResolver/cdn/cdnResolver.js +90 -0
- package/dist/core/assetResolver/cdn/cloudinaryCdnResolver.d.ts +1 -0
- package/dist/core/assetResolver/cdn/cloudinaryCdnResolver.js +120 -0
- package/dist/core/assetResolver/cdn/sanitizeFileName.d.ts +1 -0
- package/dist/core/assetResolver/cdn/sanitizeFileName.js +17 -0
- package/dist/core/assetResolver/cdn/uploadCareCdnResolver.d.ts +1 -0
- package/dist/core/assetResolver/cdn/uploadCareCdnResolver.js +137 -0
- package/dist/core/assetResolver/cdn/validateCdnUrl.d.ts +1 -0
- package/dist/core/assetResolver/cdn/validateCdnUrl.js +8 -0
- package/dist/core/assetResolver/contants.d.ts +1 -0
- package/dist/core/assetResolver/contants.js +1 -0
- package/dist/core/assetResolver/index.d.ts +7 -0
- package/dist/core/assetResolver/index.js +22 -0
- package/dist/core/assetResolver/url/authorUrlResolver.d.ts +3 -0
- package/dist/core/assetResolver/url/authorUrlResolver.js +10 -0
- package/dist/core/assetResolver/url/imageUrlResolver.d.ts +4 -0
- package/dist/core/assetResolver/url/imageUrlResolver.js +20 -0
- package/dist/core/assetResolver/url/messageUrlResolver.d.ts +3 -0
- package/dist/core/{urlResolver.js → assetResolver/url/messageUrlResolver.js} +17 -43
- package/dist/core/assetResolver/url/urlResolver.d.ts +3 -0
- package/dist/core/assetResolver/url/urlResolver.js +24 -0
- package/dist/core/{componentToJson.d.ts → discordParser/componentToJson.d.ts} +1 -1
- package/dist/core/discordParser/componentToJson.js +159 -0
- package/dist/core/discordParser/fetchMessages.d.ts +7 -0
- package/dist/core/{fetchMessages.js → discordParser/fetchMessages.js} +1 -1
- package/dist/core/{getMentions.d.ts → discordParser/getMentions.d.ts} +1 -1
- package/dist/core/{getMentions.js → discordParser/getMentions.js} +1 -1
- package/dist/core/discordParser/index.d.ts +4 -0
- package/dist/core/discordParser/index.js +33 -0
- package/dist/core/mappers.d.ts +1 -1
- package/dist/core/mappers.js +1 -1
- package/dist/core/networkSecurity/constants.d.ts +3 -0
- package/dist/core/networkSecurity/constants.js +3 -0
- package/dist/core/networkSecurity/dns.d.ts +2 -0
- package/dist/core/networkSecurity/dns.js +29 -0
- package/dist/core/networkSecurity/index.d.ts +2 -0
- package/dist/core/networkSecurity/index.js +2 -0
- package/dist/core/networkSecurity/ip.d.ts +1 -0
- package/dist/core/networkSecurity/ip.js +110 -0
- package/dist/core/networkSecurity/lookup.d.ts +2 -0
- package/dist/core/networkSecurity/lookup.js +14 -0
- package/dist/core/networkSecurity/urlSafety.d.ts +3 -0
- package/dist/core/networkSecurity/urlSafety.js +71 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +13 -56
- package/dist/renderers/json/json.d.ts +1 -1
- package/dist/renderers/json/json.js +3 -4
- package/dist/types/types.d.ts +36 -2
- package/dist/utils/sleep.d.ts +1 -0
- package/dist/utils/sleep.js +3 -0
- package/package.json +4 -7
- package/dist/core/cdnResolver.d.ts +0 -5
- package/dist/core/cdnResolver.js +0 -210
- package/dist/core/clientManager.d.ts +0 -3
- package/dist/core/clientManager.js +0 -9
- package/dist/core/componentHelpers.d.ts +0 -3
- package/dist/core/componentHelpers.js +0 -175
- package/dist/core/componentToJson.js +0 -145
- package/dist/core/error.d.ts +0 -3
- package/dist/core/error.js +0 -7
- package/dist/core/fetchMessages.d.ts +0 -19
- package/dist/core/imageToBase64.d.ts +0 -1
- package/dist/core/markdown.d.ts +0 -2
- package/dist/core/markdown.js +0 -175
- package/dist/core/urlResolver.d.ts +0 -5
- package/dist/renderers/html/clientRenderer.d.ts +0 -0
- package/dist/renderers/html/clientRenderer.js +0 -73
- package/dist/renderers/html/css.d.ts +0 -11
- package/dist/renderers/html/css.js +0 -663
- package/dist/renderers/html/html copy.d.ts +0 -19
- package/dist/renderers/html/html copy.js +0 -371
- package/dist/renderers/html/html-backup.d.ts +0 -19
- package/dist/renderers/html/html-backup.js +0 -371
- package/dist/renderers/html/html.d.ts +0 -19
- package/dist/renderers/html/html.js +0 -415
- package/dist/renderers/html/html2.d.ts +0 -8
- package/dist/renderers/html/html2.js +0 -233
- package/dist/renderers/html/js.d.ts +0 -4
- package/dist/renderers/html/js.js +0 -174
- package/dist/types/types copy.d.ts +0 -284
- package/dist/types/types copy.js +0 -35
- /package/dist/core/{limiter.d.ts → assetResolver/limiter.d.ts} +0 -0
- /package/dist/core/{limiter.js → assetResolver/limiter.js} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { CreateTranscriptOptions, ConvertTranscriptOptions, TranscriptOptions, ReturnType, CDNOptions, MimeType } from "
|
|
1
|
+
export { CreateTranscriptOptions, ConvertTranscriptOptions, TranscriptOptions, ReturnType, CDNOptions, MimeType } from "@/types";
|
|
2
2
|
export { ReturnFormat, LocalDate, TimeZone } from "discord-message-transcript-base";
|
|
3
|
-
export { setBase64Concurrency, setCDNConcurrency } from '
|
|
3
|
+
export { setBase64Concurrency, setCDNConcurrency } from '@/assetResolver';
|
|
4
4
|
import { TextBasedChannel } from "discord.js";
|
|
5
|
-
import { ConvertTranscriptOptions, CreateTranscriptOptions, OutputType, ReturnType } from "
|
|
5
|
+
import { ConvertTranscriptOptions, CreateTranscriptOptions, OutputType, ReturnType } from "@/types";
|
|
6
6
|
/**
|
|
7
7
|
* Creates a transcript of a Discord channel's messages.
|
|
8
8
|
* Depending on the `returnType` option, this function can return an `AttachmentBuilder`,
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
export { ReturnType } from "
|
|
1
|
+
export { ReturnType } from "@/types";
|
|
2
2
|
export { ReturnFormat } from "discord-message-transcript-base";
|
|
3
|
-
export { setBase64Concurrency, setCDNConcurrency } from '
|
|
3
|
+
export { setBase64Concurrency, setCDNConcurrency } from '@/assetResolver';
|
|
4
4
|
import { AttachmentBuilder } from "discord.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { ReturnType } from "./types/types.js";
|
|
8
|
-
import { output } from "./core/output.js";
|
|
5
|
+
import { ReturnType } from "@/types";
|
|
6
|
+
import { output } from "@/core/output.js";
|
|
9
7
|
import { ReturnTypeBase, ReturnFormat, outputBase, CustomError, CustomWarn } from "discord-message-transcript-base";
|
|
10
|
-
import { returnTypeMapper } from "
|
|
11
|
-
import {
|
|
8
|
+
import { returnTypeMapper } from "@/core/mappers.js";
|
|
9
|
+
import { jsonAssetResolver } from "@/assetResolver";
|
|
10
|
+
import { discordParser } from "./core/discordParser/index.js";
|
|
12
11
|
/**
|
|
13
12
|
* Creates a transcript of a Discord channel's messages.
|
|
14
13
|
* Depending on the `returnType` option, this function can return an `AttachmentBuilder`,
|
|
@@ -55,60 +54,18 @@ export async function createTranscript(channel, options = {}) {
|
|
|
55
54
|
timeZone,
|
|
56
55
|
watermark
|
|
57
56
|
};
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const mentions = {
|
|
61
|
-
channels: new Map(),
|
|
62
|
-
roles: new Map(),
|
|
63
|
-
users: new Map(),
|
|
64
|
-
};
|
|
65
|
-
const fetchMessageParameter = {
|
|
66
|
-
channel: channel,
|
|
67
|
-
options: internalOptions,
|
|
68
|
-
transcriptState: {
|
|
69
|
-
authors: authors,
|
|
70
|
-
mentions: mentions,
|
|
71
|
-
},
|
|
72
|
-
lastMessageId: undefined
|
|
73
|
-
};
|
|
74
|
-
const jsonTranscript = channel.isDMBased() ? new Json(null, channel, internalOptions, options.cdnOptions ?? null, urlCache) : new Json(channel.guild, channel, internalOptions, options.cdnOptions ?? null, urlCache);
|
|
75
|
-
while (true) {
|
|
76
|
-
const { messages, end, newLastMessageId } = await fetchMessages(fetchMessageParameter);
|
|
77
|
-
jsonTranscript.addMessages(messages);
|
|
78
|
-
fetchMessageParameter.lastMessageId = newLastMessageId;
|
|
79
|
-
if (end || (jsonTranscript.getMessages().length >= quantity && quantity != 0)) {
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (quantity > 0 && jsonTranscript.getMessages().length > quantity) {
|
|
84
|
-
jsonTranscript.sliceMessages(quantity);
|
|
85
|
-
}
|
|
86
|
-
if (options.cdnOptions) {
|
|
87
|
-
options.cdnOptions = {
|
|
57
|
+
const cdnOptions = options.cdnOptions ?
|
|
58
|
+
{
|
|
88
59
|
includeAudio: true,
|
|
89
60
|
includeImage: true,
|
|
90
61
|
includeVideo: true,
|
|
91
62
|
includeOthers: true,
|
|
92
63
|
...options.cdnOptions
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
await
|
|
96
|
-
(async () => {
|
|
97
|
-
jsonTranscript.setAuthors(await authorUrlResolver(authors, internalOptions, options.cdnOptions ?? null, urlCache));
|
|
98
|
-
authors.clear();
|
|
99
|
-
})(),
|
|
100
|
-
(() => {
|
|
101
|
-
jsonTranscript.setMentions({ channels: Array.from(mentions.channels.values()), roles: Array.from(mentions.roles.values()), users: Array.from(mentions.users.values()) });
|
|
102
|
-
mentions.channels.clear();
|
|
103
|
-
mentions.roles.clear();
|
|
104
|
-
mentions.users.clear();
|
|
105
|
-
})(),
|
|
106
|
-
(async () => {
|
|
107
|
-
jsonTranscript.setMessages(await messagesUrlResolver(jsonTranscript.getMessages(), internalOptions, options.cdnOptions ?? null, urlCache));
|
|
108
|
-
})()
|
|
109
|
-
]);
|
|
64
|
+
} : null;
|
|
65
|
+
const [jsonTranscript, maps] = await discordParser(channel, internalOptions, cdnOptions);
|
|
66
|
+
await jsonAssetResolver(jsonTranscript, maps, internalOptions, cdnOptions);
|
|
110
67
|
const outputJson = await jsonTranscript.toJson();
|
|
111
|
-
urlCache.clear();
|
|
68
|
+
maps.urlCache.clear();
|
|
112
69
|
const result = await output(outputJson);
|
|
113
70
|
if (!options.returnType || options.returnType == "attachment") {
|
|
114
71
|
if (!(result instanceof Buffer)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
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 "
|
|
3
|
+
import { CDNOptions } from "@/types";
|
|
4
4
|
export declare class Json {
|
|
5
5
|
private guild;
|
|
6
6
|
private channel;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BaseGuildTextChannel, DMChannel } from "discord.js";
|
|
2
|
-
import { urlResolver } from "
|
|
3
|
-
import { resolveImageURL } from "../../../../discord-message-transcript-base/src/core/sanitizer.js";
|
|
2
|
+
import { imageUrlResolver, urlResolver } from "@/assetResolver";
|
|
4
3
|
export class Json {
|
|
5
4
|
guild;
|
|
6
5
|
channel;
|
|
@@ -44,9 +43,9 @@ export class Json {
|
|
|
44
43
|
async toJson() {
|
|
45
44
|
const channel = await this.channel.fetch();
|
|
46
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;
|
|
47
|
-
const safeChannelImg = await
|
|
46
|
+
const safeChannelImg = await imageUrlResolver(channelImg, this.options, true);
|
|
48
47
|
const guild = !channel.isDMBased() ? this.guild : null;
|
|
49
|
-
const guildIcon = guild ? await
|
|
48
|
+
const guildIcon = guild ? await imageUrlResolver(guild.iconURL(), this.options, true) : null;
|
|
50
49
|
const guildJson = !guild ? null : {
|
|
51
50
|
name: guild.name,
|
|
52
51
|
id: guild.id,
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Json } from "@/renderers/json/json.js";
|
|
2
|
+
import { JsonMessageMentionsChannels, JsonMessageMentionsRoles, JsonMessageMentionsUsers, LocalDate, TimeZone, Uploadable, ReturnFormat, TranscriptOptionsBase, JsonAuthor } from "discord-message-transcript-base";
|
|
3
|
+
import { AttachmentBuilder, TextBasedChannel } from "discord.js";
|
|
3
4
|
import Stream from 'stream';
|
|
4
5
|
/**
|
|
5
6
|
* An enum-like object providing the possible return types for the transcript functions.
|
|
@@ -286,3 +287,36 @@ export type CDNOptionsUploadcare = {
|
|
|
286
287
|
*/
|
|
287
288
|
cdnDomain: string;
|
|
288
289
|
};
|
|
290
|
+
/**
|
|
291
|
+
* Result from dns.lookup
|
|
292
|
+
*/
|
|
293
|
+
export type LookupResult = {
|
|
294
|
+
address: string;
|
|
295
|
+
family: 4 | 6;
|
|
296
|
+
};
|
|
297
|
+
export interface safeUrlReturn {
|
|
298
|
+
safe: boolean;
|
|
299
|
+
safeIps: string[];
|
|
300
|
+
url: string;
|
|
301
|
+
}
|
|
302
|
+
export type FetchMessagesContext = {
|
|
303
|
+
channel: TextBasedChannel;
|
|
304
|
+
options: TranscriptOptionsBase;
|
|
305
|
+
transcriptState: TranscriptState;
|
|
306
|
+
lastMessageId: string | undefined;
|
|
307
|
+
};
|
|
308
|
+
export type TranscriptState = {
|
|
309
|
+
authors: Map<string, JsonAuthor>;
|
|
310
|
+
mentions: MapMentions;
|
|
311
|
+
};
|
|
312
|
+
export type ReturnDiscordParser = [
|
|
313
|
+
json: Json,
|
|
314
|
+
maps: Maps
|
|
315
|
+
];
|
|
316
|
+
export type MapAuthors = Map<string, JsonAuthor>;
|
|
317
|
+
export type MapCache = Map<string, Promise<string>>;
|
|
318
|
+
export type Maps = {
|
|
319
|
+
authors: MapAuthors;
|
|
320
|
+
mentions: MapMentions;
|
|
321
|
+
urlCache: MapCache;
|
|
322
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sleep(ms: number): Promise<unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discord-message-transcript",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2-dev.0.49",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,11 +44,8 @@
|
|
|
44
44
|
"url": "https://github.com/HenriqueMairesse/discord-message-transcript/issues"
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://github.com/HenriqueMairesse/discord-message-transcript#readme",
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"typescript": "^5.9.3"
|
|
49
|
-
},
|
|
50
47
|
"dependencies": {
|
|
51
|
-
"discord-message-transcript-base": "1.3.
|
|
48
|
+
"discord-message-transcript-base": "1.3.2-dev.0.49"
|
|
52
49
|
},
|
|
53
50
|
"peerDependencies": {
|
|
54
51
|
"discord.js": ">=14.19.0 <15"
|
|
@@ -57,7 +54,7 @@
|
|
|
57
54
|
"access": "public"
|
|
58
55
|
},
|
|
59
56
|
"scripts": {
|
|
60
|
-
"
|
|
61
|
-
"build": "tsc"
|
|
57
|
+
"clean": "pnpm exec rimraf dist",
|
|
58
|
+
"build": "pnpm run clean && tsc"
|
|
62
59
|
}
|
|
63
60
|
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { CDNOptions } from "../types/types.js";
|
|
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, disableWarnings: boolean): Promise<string>;
|
|
5
|
-
export declare function cloudinaryResolver(url: string, fileName: string, cloudName: string, apiKey: string, apiSecret: string, disableWarnings: boolean): Promise<string>;
|
package/dist/core/cdnResolver.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import https from 'https';
|
|
2
|
-
import http from 'http';
|
|
3
|
-
import { CustomWarn } from "discord-message-transcript-base";
|
|
4
|
-
import crypto from 'crypto';
|
|
5
|
-
import { getCDNLimiter } from "./limiter.js";
|
|
6
|
-
export async function cdnResolver(url, options, cdnOptions) {
|
|
7
|
-
const limit = getCDNLimiter();
|
|
8
|
-
return limit(async () => {
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
const client = url.startsWith('https') ? https : http;
|
|
11
|
-
const request = client.request(url, {
|
|
12
|
-
method: 'HEAD',
|
|
13
|
-
headers: { "User-Agent": "discord-message-transcript" }
|
|
14
|
-
}, async (response) => {
|
|
15
|
-
if (response.statusCode !== 200) {
|
|
16
|
-
response.destroy();
|
|
17
|
-
CustomWarn(`This is not an issue with the package. Using the original URL as fallback instead of uploading to CDN.
|
|
18
|
-
Failed to fetch attachment with status code: ${response.statusCode} from ${url}.`, options.disableWarnings);
|
|
19
|
-
return resolve(url);
|
|
20
|
-
}
|
|
21
|
-
const contentType = response.headers["content-type"];
|
|
22
|
-
const splitContentType = contentType ? contentType?.split('/') : [];
|
|
23
|
-
if (!contentType || splitContentType.length != 2 || splitContentType[0].length == 0 || splitContentType[1].length == 0) {
|
|
24
|
-
response.destroy();
|
|
25
|
-
CustomWarn(`This is not an issue with the package. Using the original URL as fallback instead of uploading to CDN.
|
|
26
|
-
Failed to receive a valid content-type from ${url}.`, options.disableWarnings);
|
|
27
|
-
return resolve(url);
|
|
28
|
-
}
|
|
29
|
-
response.destroy();
|
|
30
|
-
const isImage = contentType.startsWith('image/') && contentType !== 'image/gif';
|
|
31
|
-
const isAudio = contentType.startsWith('audio/');
|
|
32
|
-
const isVideo = contentType.startsWith('video/') || contentType === 'image/gif';
|
|
33
|
-
if ((cdnOptions.includeImage && isImage) ||
|
|
34
|
-
(cdnOptions.includeAudio && isAudio) ||
|
|
35
|
-
(cdnOptions.includeVideo && isVideo) ||
|
|
36
|
-
(cdnOptions.includeOthers && !isAudio && !isImage && !isVideo)) {
|
|
37
|
-
return resolve(await cdnRedirectType(url, options, contentType, cdnOptions));
|
|
38
|
-
}
|
|
39
|
-
return resolve(url);
|
|
40
|
-
});
|
|
41
|
-
request.on('error', (err) => {
|
|
42
|
-
CustomWarn(`This is not an issue with the package. Using the original URL as fallback instead of uploading to CDN.
|
|
43
|
-
Error: ${err.message}`, options.disableWarnings);
|
|
44
|
-
return resolve(url);
|
|
45
|
-
});
|
|
46
|
-
request.setTimeout(15000, () => {
|
|
47
|
-
request.destroy();
|
|
48
|
-
CustomWarn(`This is not an issue with the package. Using the original URL as fallback instead of uploading to CDN.
|
|
49
|
-
Request timeout for ${url}.`, options.disableWarnings);
|
|
50
|
-
resolve(url);
|
|
51
|
-
});
|
|
52
|
-
request.end();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
async function cdnRedirectType(url, options, contentType, cdnOptions) {
|
|
57
|
-
let newUrl;
|
|
58
|
-
switch (cdnOptions.provider) {
|
|
59
|
-
case "CUSTOM": {
|
|
60
|
-
try {
|
|
61
|
-
newUrl = await cdnOptions.resolver(url, contentType, cdnOptions.customData);
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
CustomWarn(`Custom CDN resolver threw an error. Falling back to original URL.
|
|
66
|
-
This is most likely an issue in the custom CDN implementation provided by the user.
|
|
67
|
-
URL: ${url}
|
|
68
|
-
Error: ${error?.message ?? error}`, options.disableWarnings);
|
|
69
|
-
return url;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
case "CLOUDINARY": {
|
|
73
|
-
newUrl = await cloudinaryResolver(url, options.fileName, cdnOptions.cloudName, cdnOptions.apiKey, cdnOptions.apiSecret, options.disableWarnings);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
case "UPLOADCARE": {
|
|
77
|
-
newUrl = await uploadCareResolver(url, cdnOptions.publicKey, cdnOptions.cdnDomain, options.disableWarnings);
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (validateCdnUrl(newUrl, options.disableWarnings))
|
|
82
|
-
return newUrl;
|
|
83
|
-
return url;
|
|
84
|
-
}
|
|
85
|
-
function sleep(ms) {
|
|
86
|
-
return new Promise(r => setTimeout(r, ms));
|
|
87
|
-
}
|
|
88
|
-
export async function uploadCareResolver(url, publicKey, cdnDomain, disableWarnings) {
|
|
89
|
-
try {
|
|
90
|
-
const form = new FormData();
|
|
91
|
-
form.append("pub_key", publicKey);
|
|
92
|
-
form.append("source_url", url);
|
|
93
|
-
form.append("store", "1");
|
|
94
|
-
form.append("check_URL_duplicates", "1");
|
|
95
|
-
form.append("save_URL_duplicates", "1");
|
|
96
|
-
const res = await fetch("https://upload.uploadcare.com/from_url/", {
|
|
97
|
-
method: "POST",
|
|
98
|
-
body: form,
|
|
99
|
-
headers: {
|
|
100
|
-
"User-Agent": "discord-message-transcript"
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
if (!res.ok) {
|
|
104
|
-
switch (res.status) {
|
|
105
|
-
case 400:
|
|
106
|
-
throw new Error(`Uploadcare initial request failed with status code ${res.status} - Request failed input parameters validation.`);
|
|
107
|
-
case 403:
|
|
108
|
-
throw new Error(`Uploadcare initial request failed with status code ${res.status} - Request was not allowed.`);
|
|
109
|
-
case 429:
|
|
110
|
-
throw new Error(`Uploadcare initial request failed with status code ${res.status} - Request was throttled.`);
|
|
111
|
-
default:
|
|
112
|
-
throw new Error(`Uploadcare initial request failed with status code ${res.status}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const json = await res.json();
|
|
116
|
-
if (json.uuid) {
|
|
117
|
-
return `https://${cdnDomain}/${json.uuid}/`;
|
|
118
|
-
}
|
|
119
|
-
let delay = 200;
|
|
120
|
-
let maxDelay = 2000;
|
|
121
|
-
if (json.token) {
|
|
122
|
-
for (let i = 0; i < 10; i++) {
|
|
123
|
-
await sleep(delay);
|
|
124
|
-
delay = Math.min(delay * 2, maxDelay);
|
|
125
|
-
const resToken = await fetch(`https://upload.uploadcare.com/from_url/status/?token=${json.token}&pub_key=${publicKey}`, { headers: { "User-Agent": "discord-message-transcript" } });
|
|
126
|
-
if (!resToken.ok)
|
|
127
|
-
throw new Error(`Uploadcare status failed with status code ${resToken.status}`);
|
|
128
|
-
const jsonToken = await resToken.json();
|
|
129
|
-
if (jsonToken.status === "success" && jsonToken.file_id) {
|
|
130
|
-
return `https://${cdnDomain}/${jsonToken.file_id}/`;
|
|
131
|
-
}
|
|
132
|
-
if (jsonToken.status === "error") {
|
|
133
|
-
throw new Error(jsonToken.error || "Uploadcare failed");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
throw new Error("Uploadcare polling timeout");
|
|
137
|
-
}
|
|
138
|
-
return url;
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
CustomWarn(`Uploadcare CDN upload failed. Using original URL as fallback.
|
|
142
|
-
Check Uploadcare public key, CDN domain, project settings, rate limits, and network access.
|
|
143
|
-
URL: ${url}
|
|
144
|
-
Error: ${error?.message ?? error}`, disableWarnings);
|
|
145
|
-
return url;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
export async function cloudinaryResolver(url, fileName, cloudName, apiKey, apiSecret, disableWarnings) {
|
|
149
|
-
try {
|
|
150
|
-
const paramsToSign = {
|
|
151
|
-
folder: `discord-message-transcript/${fileName}`,
|
|
152
|
-
timestamp: Math.floor(Date.now() / 1000).toString(),
|
|
153
|
-
unique_filename: "true",
|
|
154
|
-
use_filename: "true",
|
|
155
|
-
};
|
|
156
|
-
const stringToSign = Object.keys(paramsToSign).sort().map(k => `${k}=${paramsToSign[k]}`).join("&");
|
|
157
|
-
// signature SHA256
|
|
158
|
-
const signature = crypto
|
|
159
|
-
.createHash("sha256")
|
|
160
|
-
.update(stringToSign + apiSecret)
|
|
161
|
-
.digest("hex");
|
|
162
|
-
const form = new FormData();
|
|
163
|
-
form.append("folder", paramsToSign.folder);
|
|
164
|
-
form.append("file", url);
|
|
165
|
-
form.append("api_key", apiKey);
|
|
166
|
-
form.append("timestamp", paramsToSign.timestamp);
|
|
167
|
-
form.append("signature", signature);
|
|
168
|
-
form.append("use_filename", paramsToSign.use_filename);
|
|
169
|
-
form.append("unique_filename", paramsToSign.unique_filename);
|
|
170
|
-
const res = await fetch(`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`, {
|
|
171
|
-
method: "POST",
|
|
172
|
-
body: form,
|
|
173
|
-
headers: {
|
|
174
|
-
"User-Agent": "discord-message-transcript"
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
if (!res.ok) {
|
|
178
|
-
switch (res.status) {
|
|
179
|
-
case 400:
|
|
180
|
-
throw new Error(`Cloudinary upload failed with status code ${res.status} - Bad request / invalid params.`);
|
|
181
|
-
case 403:
|
|
182
|
-
throw new Error(`Cloudinary upload failed with status code ${res.status} - Invalid credentials or unauthorized.`);
|
|
183
|
-
case 429:
|
|
184
|
-
throw new Error(`Cloudinary upload failed with status code ${res.status} - Rate limited.`);
|
|
185
|
-
default:
|
|
186
|
-
throw new Error(`Cloudinary upload failed with status code ${res.status}.`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
const json = await res.json();
|
|
190
|
-
if (!json.secure_url) {
|
|
191
|
-
throw new Error("Cloudinary response missing secure_url");
|
|
192
|
-
}
|
|
193
|
-
return json.secure_url;
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
CustomWarn(`Failed to upload asset to Cloudinary CDN. Using original URL as fallback.
|
|
197
|
-
Check Cloudinary configuration (cloud name, API key, API secret) and network access.
|
|
198
|
-
URL: ${url}
|
|
199
|
-
Error: ${error?.message ?? error}`, disableWarnings);
|
|
200
|
-
return url;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// Note: for debug use ${JSON.stringify(await res.json())} to understand the error
|
|
204
|
-
function validateCdnUrl(url, disableWarnings) {
|
|
205
|
-
if (url.includes('"') || url.includes('<') || url.includes('>')) {
|
|
206
|
-
CustomWarn(`Unsafe URL received from CDN, using fallback.\nURL: ${url}`, disableWarnings);
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { ComponentType, ButtonStyle, SeparatorSpacingSize } from "discord.js";
|
|
2
|
-
import { JsonComponentType, JsonButtonStyle, JsonSeparatorSpacingSize } from "../types/types";
|
|
3
|
-
import { CustomError } from "./error";
|
|
4
|
-
export function componentsToJson(components, options) {
|
|
5
|
-
return components.filter(component => {
|
|
6
|
-
return !(!options.includeV2Components && component.type != ComponentType.ActionRow);
|
|
7
|
-
}).map(component => {
|
|
8
|
-
switch (component.type) {
|
|
9
|
-
case ComponentType.ActionRow: {
|
|
10
|
-
const actionRowComponents = component.components.filter(c => {
|
|
11
|
-
if (c.type == ComponentType.Button && !options.includeButtons) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
if (c.type != ComponentType.Button && !options.includeComponents) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}).map(c => {
|
|
19
|
-
if (c.type === ComponentType.Button) {
|
|
20
|
-
return {
|
|
21
|
-
type: JsonComponentType.Button,
|
|
22
|
-
style: mapButtonStyle(c.style),
|
|
23
|
-
label: c.label,
|
|
24
|
-
emoji: c.emoji ? {
|
|
25
|
-
id: c.emoji.id ?? null,
|
|
26
|
-
name: c.emoji.name ?? null,
|
|
27
|
-
animated: c.emoji.animated ?? false,
|
|
28
|
-
} : null,
|
|
29
|
-
url: c.url,
|
|
30
|
-
disabled: c.disabled,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
else if (c.type === ComponentType.StringSelect) {
|
|
34
|
-
return {
|
|
35
|
-
type: JsonComponentType.StringSelect,
|
|
36
|
-
placeholder: c.placeholder,
|
|
37
|
-
disabled: c.disabled,
|
|
38
|
-
options: c.options.map(option => ({
|
|
39
|
-
label: option.label,
|
|
40
|
-
description: option.description ?? null,
|
|
41
|
-
emoji: option.emoji ? {
|
|
42
|
-
id: option.emoji.id ?? null,
|
|
43
|
-
name: option.emoji.name ?? null,
|
|
44
|
-
animated: option.emoji.animated ?? false,
|
|
45
|
-
} : null,
|
|
46
|
-
}))
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
return {
|
|
51
|
-
type: JsonComponentType.RoleSelect,
|
|
52
|
-
placeholder: c.placeholder,
|
|
53
|
-
disabled: c.disabled,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
if (actionRowComponents.length > 0) {
|
|
58
|
-
return {
|
|
59
|
-
type: JsonComponentType.ActionRow,
|
|
60
|
-
components: actionRowComponents,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
case ComponentType.Container: {
|
|
68
|
-
const newOptions = { ...options, includeComponents: true, includeButtons: true };
|
|
69
|
-
return {
|
|
70
|
-
type: JsonComponentType.Container,
|
|
71
|
-
components: componentsToJson(component.components, newOptions).filter(isJsonComponentInContainer), // Impossible to send an component that can be used inside and return an component that can't be used inside
|
|
72
|
-
hexAccentColor: component.hexAccentColor,
|
|
73
|
-
spoiler: component.spoiler,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
case ComponentType.File: {
|
|
77
|
-
return {
|
|
78
|
-
type: JsonComponentType.File,
|
|
79
|
-
fileName: component.data.name ?? null,
|
|
80
|
-
size: component.data.size ?? 0,
|
|
81
|
-
url: component.file.url,
|
|
82
|
-
spoiler: component.spoiler,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
case ComponentType.MediaGallery: {
|
|
86
|
-
return {
|
|
87
|
-
type: JsonComponentType.MediaGallery,
|
|
88
|
-
items: component.items.map(item => ({
|
|
89
|
-
media: { url: item.media.url },
|
|
90
|
-
spoiler: item.spoiler,
|
|
91
|
-
})),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
case ComponentType.Section: {
|
|
95
|
-
return {
|
|
96
|
-
type: JsonComponentType.Section,
|
|
97
|
-
accessory: (component.accessory.type === ComponentType.Button ? {
|
|
98
|
-
type: JsonComponentType.Button,
|
|
99
|
-
style: mapButtonStyle(component.accessory.style),
|
|
100
|
-
label: component.accessory.label,
|
|
101
|
-
emoji: component.accessory.emoji ? {
|
|
102
|
-
id: component.accessory.emoji.id ?? null,
|
|
103
|
-
name: component.accessory.emoji.name ?? null,
|
|
104
|
-
animated: component.accessory.emoji.animated ?? false,
|
|
105
|
-
} : null,
|
|
106
|
-
url: component.accessory.url,
|
|
107
|
-
disabled: component.accessory.disabled,
|
|
108
|
-
} : {
|
|
109
|
-
type: JsonComponentType.Thumbnail,
|
|
110
|
-
media: {
|
|
111
|
-
url: component.accessory.media.url,
|
|
112
|
-
},
|
|
113
|
-
spoiler: component.accessory.spoiler,
|
|
114
|
-
}),
|
|
115
|
-
components: component.components.map(c => ({
|
|
116
|
-
type: JsonComponentType.TextDisplay,
|
|
117
|
-
content: c.content,
|
|
118
|
-
})),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
case ComponentType.Separator: {
|
|
122
|
-
return {
|
|
123
|
-
type: JsonComponentType.Separator,
|
|
124
|
-
spacing: mapSeparatorSpacing(component.spacing),
|
|
125
|
-
divider: component.divider,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
case ComponentType.TextDisplay: {
|
|
129
|
-
return {
|
|
130
|
-
type: JsonComponentType.TextDisplay,
|
|
131
|
-
content: component.content,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
default:
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
.filter(c => c != null);
|
|
139
|
-
}
|
|
140
|
-
function mapButtonStyle(style) {
|
|
141
|
-
switch (style) {
|
|
142
|
-
case ButtonStyle.Primary:
|
|
143
|
-
return JsonButtonStyle.Primary;
|
|
144
|
-
case ButtonStyle.Secondary:
|
|
145
|
-
return JsonButtonStyle.Secondary;
|
|
146
|
-
case ButtonStyle.Success:
|
|
147
|
-
return JsonButtonStyle.Success;
|
|
148
|
-
case ButtonStyle.Danger:
|
|
149
|
-
return JsonButtonStyle.Danger;
|
|
150
|
-
case ButtonStyle.Link:
|
|
151
|
-
return JsonButtonStyle.Link;
|
|
152
|
-
case ButtonStyle.Premium:
|
|
153
|
-
return JsonButtonStyle.Premium;
|
|
154
|
-
default:
|
|
155
|
-
throw new CustomError(`Unknow ButtonStyle: ${style}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
function mapSeparatorSpacing(spacing) {
|
|
159
|
-
switch (spacing) {
|
|
160
|
-
case SeparatorSpacingSize.Small:
|
|
161
|
-
return JsonSeparatorSpacingSize.Small;
|
|
162
|
-
case SeparatorSpacingSize.Large:
|
|
163
|
-
return JsonSeparatorSpacingSize.Large;
|
|
164
|
-
default:
|
|
165
|
-
throw new CustomError(`Unknow SeparatorSpacingSize: ${spacing}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function isJsonComponentInContainer(component) {
|
|
169
|
-
return (component.type == JsonComponentType.ActionRow ||
|
|
170
|
-
component.type == JsonComponentType.File ||
|
|
171
|
-
component.type == JsonComponentType.MediaGallery ||
|
|
172
|
-
component.type == JsonComponentType.Section ||
|
|
173
|
-
component.type == JsonComponentType.Separator ||
|
|
174
|
-
component.type == JsonComponentType.TextDisplay);
|
|
175
|
-
}
|