transcriptify 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/src/generateHtml.d.ts +3 -0
- package/dist/src/generateHtml.js +303 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +252 -0
- package/dist/src/transcript.d.ts +12 -0
- package/dist/src/transcript.js +179 -0
- package/dist/src/types/entities.d.ts +30 -0
- package/dist/src/types/entities.js +2 -0
- package/dist/src/utils/assetManager.d.ts +12 -0
- package/dist/src/utils/assetManager.js +295 -0
- package/dist/src/utils/authors.d.ts +4 -0
- package/dist/src/utils/authors.js +115 -0
- package/dist/src/utils/cache.d.ts +16 -0
- package/dist/src/utils/cache.js +29 -0
- package/dist/src/utils/extractors.d.ts +112 -0
- package/dist/src/utils/extractors.js +223 -0
- package/dist/src/utils/polls.d.ts +2 -0
- package/dist/src/utils/polls.js +91 -0
- package/dist/src/utils/transformer.d.ts +6 -0
- package/dist/src/utils/transformer.js +78 -0
- package/dist/src/utils/user.d.ts +4 -0
- package/dist/src/utils/user.js +56 -0
- package/dist/src/web/client.d.ts +20 -0
- package/dist/src/web/client.js +21 -0
- package/dist/src/web/discord-components/AudioPlayer.d.ts +5 -0
- package/dist/src/web/discord-components/AudioPlayer.js +231 -0
- package/dist/src/web/discord-components/Button.d.ts +3 -0
- package/dist/src/web/discord-components/Button.js +27 -0
- package/dist/src/web/discord-components/ChannelPinnedMessage.d.ts +7 -0
- package/dist/src/web/discord-components/ChannelPinnedMessage.js +11 -0
- package/dist/src/web/discord-components/DateSeperator.d.ts +3 -0
- package/dist/src/web/discord-components/DateSeperator.js +19 -0
- package/dist/src/web/discord-components/Embed.d.ts +2 -0
- package/dist/src/web/discord-components/Embed.js +78 -0
- package/dist/src/web/discord-components/ForwardedMessage.d.ts +2 -0
- package/dist/src/web/discord-components/ForwardedMessage.js +44 -0
- package/dist/src/web/discord-components/Message.d.ts +2 -0
- package/dist/src/web/discord-components/Message.js +543 -0
- package/dist/src/web/discord-components/PinnedMessagesModal.d.ts +6 -0
- package/dist/src/web/discord-components/PinnedMessagesModal.js +119 -0
- package/dist/src/web/discord-components/PinnedMessagesOverview.d.ts +5 -0
- package/dist/src/web/discord-components/PinnedMessagesOverview.js +22 -0
- package/dist/src/web/discord-components/Reply.d.ts +2 -0
- package/dist/src/web/discord-components/Reply.js +42 -0
- package/dist/src/web/discord-components/StickerPreview.d.ts +6 -0
- package/dist/src/web/discord-components/StickerPreview.js +40 -0
- package/dist/src/web/discord-components/ThemeSwitcher.d.ts +2 -0
- package/dist/src/web/discord-components/ThemeSwitcher.js +54 -0
- package/dist/src/web/discord-components/Transcript.d.ts +2 -0
- package/dist/src/web/discord-components/Transcript.js +174 -0
- package/dist/src/web/discord-components/UserJoinMessage.d.ts +3 -0
- package/dist/src/web/discord-components/UserJoinMessage.js +33 -0
- package/dist/src/web/discord-components/VideoPlayer.d.ts +6 -0
- package/dist/src/web/discord-components/VideoPlayer.js +222 -0
- package/dist/src/web/discord-components/icons/ChevronDownIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/ChevronDownIcon.js +7 -0
- package/dist/src/web/discord-components/icons/CloseIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/CloseIcon.js +7 -0
- package/dist/src/web/discord-components/icons/ExternalLinkIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/ExternalLinkIcon.js +7 -0
- package/dist/src/web/discord-components/icons/FileAudioIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/FileAudioIcon.js +7 -0
- package/dist/src/web/discord-components/icons/FileCodeIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/FileCodeIcon.js +7 -0
- package/dist/src/web/discord-components/icons/FileDocumentIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/FileDocumentIcon.js +7 -0
- package/dist/src/web/discord-components/icons/PinIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/PinIcon.js +7 -0
- package/dist/src/web/discord-components/icons/VerifiedIcon.d.ts +1 -0
- package/dist/src/web/discord-components/icons/VerifiedIcon.js +7 -0
- package/dist/src/web/discord-components/index.d.ts +11 -0
- package/dist/src/web/discord-components/index.js +24 -0
- package/dist/src/web/discord-components/messageHelpers.d.ts +8 -0
- package/dist/src/web/discord-components/messageHelpers.js +72 -0
- package/dist/src/web/discord-components/themeColors.d.ts +9 -0
- package/dist/src/web/discord-components/themeColors.js +320 -0
- package/dist/src/web/discord-components/transcriptHelpers.d.ts +19 -0
- package/dist/src/web/discord-components/transcriptHelpers.js +120 -0
- package/dist/src/web/discord-components/types.d.ts +1 -0
- package/dist/src/web/discord-components/types.js +2 -0
- package/dist/src/web/discord-components/utils/date.d.ts +3 -0
- package/dist/src/web/discord-components/utils/date.js +50 -0
- package/dist/src/web/discord-components/utils/markdown.d.ts +11 -0
- package/dist/src/web/discord-components/utils/markdown.js +538 -0
- package/dist/src/web/discord-components/utils/markdownUtils.d.ts +12 -0
- package/dist/src/web/discord-components/utils/markdownUtils.js +140 -0
- package/dist/src/web/helpers/avatarHelpers.d.ts +2 -0
- package/dist/src/web/helpers/avatarHelpers.js +15 -0
- package/dist/src/web/helpers/cdnHelpers.d.ts +5 -0
- package/dist/src/web/helpers/cdnHelpers.js +48 -0
- package/dist/src/web/helpers/contentHelpers.d.ts +9 -0
- package/dist/src/web/helpers/contentHelpers.js +41 -0
- package/dist/src/web/helpers/renderContent.d.ts +2 -0
- package/dist/src/web/helpers/renderContent.js +15 -0
- package/dist/src/web/helpers/scrollHelpers.d.ts +2 -0
- package/dist/src/web/helpers/scrollHelpers.js +31 -0
- package/dist/src/web/helpers/timestampHelpers.d.ts +6 -0
- package/dist/src/web/helpers/timestampHelpers.js +66 -0
- package/dist/src/web/hooks/useMessageContent.d.ts +5 -0
- package/dist/src/web/hooks/useMessageContent.js +37 -0
- package/dist/src/web/index.d.ts +1 -0
- package/dist/src/web/index.js +17 -0
- package/dist/src/web/types/attachment.d.ts +6 -0
- package/dist/src/web/types/attachment.js +2 -0
- package/dist/src/web/types/author.d.ts +14 -0
- package/dist/src/web/types/author.js +2 -0
- package/dist/src/web/types/channel.d.ts +8 -0
- package/dist/src/web/types/channel.js +2 -0
- package/dist/src/web/types/embed.d.ts +52 -0
- package/dist/src/web/types/embed.js +2 -0
- package/dist/src/web/types/interaction.d.ts +8 -0
- package/dist/src/web/types/interaction.js +2 -0
- package/dist/src/web/types/markdown.d.ts +5 -0
- package/dist/src/web/types/markdown.js +2 -0
- package/dist/src/web/types/message.d.ts +73 -0
- package/dist/src/web/types/message.js +2 -0
- package/dist/src/web/types/poll.d.ts +11 -0
- package/dist/src/web/types/poll.js +2 -0
- package/dist/src/web/types/props.d.ts +155 -0
- package/dist/src/web/types/props.js +2 -0
- package/dist/src/web/types/reaction.d.ts +6 -0
- package/dist/src/web/types/reaction.js +2 -0
- package/dist/src/web/types/theme.d.ts +14 -0
- package/dist/src/web/types/theme.js +2 -0
- package/dist/src/web/types/ui.d.ts +10 -0
- package/dist/src/web/types/ui.js +2 -0
- package/dist/types/download.d.ts +12 -0
- package/dist/types/download.js +2 -0
- package/dist/types/exportableTranscript.d.ts +169 -0
- package/dist/types/exportableTranscript.js +2 -0
- package/dist/types/general.d.ts +90 -0
- package/dist/types/general.js +2 -0
- package/package.json +46 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveGuildTagForUser = resolveGuildTagForUser;
|
|
7
|
+
exports.mapMessageToSerializable = mapMessageToSerializable;
|
|
8
|
+
exports.createTranscript = createTranscript;
|
|
9
|
+
exports.generateFromMessages = generateFromMessages;
|
|
10
|
+
const discord_js_1 = require("discord.js");
|
|
11
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const userGuildTagCache = new Map();
|
|
14
|
+
async function resolveGuildTagForUser(user) {
|
|
15
|
+
const cached = userGuildTagCache.get(user.id);
|
|
16
|
+
if (cached)
|
|
17
|
+
return cached;
|
|
18
|
+
if (!user.bot && user.primaryGuild && user.primaryGuild.identityEnabled) {
|
|
19
|
+
const pg = user.primaryGuild;
|
|
20
|
+
const userGuildTag = { name: user.username, iconUrl: `https://cdn.discordapp.com/clan-badges/${pg.identityEnabled}/${pg.badge}.png` };
|
|
21
|
+
userGuildTagCache.set(user.id, userGuildTag);
|
|
22
|
+
return userGuildTag;
|
|
23
|
+
}
|
|
24
|
+
userGuildTagCache.set(user.id, { name: null, iconUrl: null });
|
|
25
|
+
return {
|
|
26
|
+
name: null,
|
|
27
|
+
iconUrl: null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function mapMessageToSerializable(m) {
|
|
31
|
+
const mapEmbed = (e) => ({
|
|
32
|
+
title: e.title ?? null,
|
|
33
|
+
description: e.description ?? null,
|
|
34
|
+
url: e.url ?? null,
|
|
35
|
+
timestamp: e.timestamp ? new Date(e.timestamp).toISOString() : null,
|
|
36
|
+
color: typeof e.hexColor === "string"
|
|
37
|
+
? e.hexColor
|
|
38
|
+
: typeof e.color === "string"
|
|
39
|
+
? e.color
|
|
40
|
+
: typeof e.color === "number"
|
|
41
|
+
? `#${e.color.toString(16).padStart(6, "0")}`
|
|
42
|
+
: null,
|
|
43
|
+
footer: e.footer ? { text: e.footer.text ?? null, iconUrl: e.footer.iconURL ?? null } : null,
|
|
44
|
+
image: e.image ? { url: e.image.url ?? null } : null,
|
|
45
|
+
thumbnail: e.thumbnail ? { url: e.thumbnail.url ?? null } : null,
|
|
46
|
+
author: e.author ? { name: e.author.name ?? null, url: e.author.url ?? null, iconUrl: e.author.iconURL ?? null } : null,
|
|
47
|
+
fields: e.fields ? e.fields.map((f) => ({ name: f.name, value: f.value, inline: !!f.inline })) : undefined
|
|
48
|
+
});
|
|
49
|
+
const attachments = Array.from(m.attachments.values()).map((a) => ({
|
|
50
|
+
id: a.id,
|
|
51
|
+
filename: a.name ?? a.filename ?? "",
|
|
52
|
+
url: a.url,
|
|
53
|
+
proxyURL: a.proxyURL ?? null,
|
|
54
|
+
contentType: a.contentType ?? null,
|
|
55
|
+
size: a.size ?? undefined,
|
|
56
|
+
width: a.width ?? null,
|
|
57
|
+
height: a.height ?? null
|
|
58
|
+
}));
|
|
59
|
+
const referencedMessageId = (m.reference && m.reference.messageId) ?? (m.referencedMessage ? m.referencedMessage.id : null) ?? null;
|
|
60
|
+
let forwarded = null;
|
|
61
|
+
if (referencedMessageId) {
|
|
62
|
+
let refMsg = m.referencedMessage ?? null;
|
|
63
|
+
if (!refMsg) {
|
|
64
|
+
try {
|
|
65
|
+
refMsg = await m.fetchReference();
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
refMsg = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (refMsg) {
|
|
72
|
+
const isDifferentOrigin = refMsg.channelId !== m.channelId || (refMsg.guild?.id ?? null) !== (m.guild?.id ?? null);
|
|
73
|
+
if (isDifferentOrigin) {
|
|
74
|
+
const refAttachments = Array.from(refMsg.attachments.values()).map((a) => ({
|
|
75
|
+
id: a.id,
|
|
76
|
+
filename: a.name ?? a.filename ?? "",
|
|
77
|
+
url: a.url,
|
|
78
|
+
proxyURL: a.proxyURL ?? null,
|
|
79
|
+
contentType: a.contentType ?? null,
|
|
80
|
+
size: a.size ?? undefined,
|
|
81
|
+
width: a.width ?? null,
|
|
82
|
+
height: a.height ?? null
|
|
83
|
+
}));
|
|
84
|
+
const mapEmbed = (e) => ({
|
|
85
|
+
title: e.title ?? null,
|
|
86
|
+
description: e.description ?? null,
|
|
87
|
+
url: e.url ?? null,
|
|
88
|
+
timestamp: e.timestamp ? new Date(e.timestamp).toISOString() : null,
|
|
89
|
+
color: typeof e.hexColor === "string"
|
|
90
|
+
? e.hexColor
|
|
91
|
+
: typeof e.color === "string"
|
|
92
|
+
? e.color
|
|
93
|
+
: typeof e.color === "number"
|
|
94
|
+
? `#${e.color.toString(16).padStart(6, "0")}`
|
|
95
|
+
: null,
|
|
96
|
+
footer: e.footer ? { text: e.footer.text ?? null, iconUrl: e.footer.iconURL ?? null } : null,
|
|
97
|
+
image: e.image ? { url: e.image.url ?? null } : null,
|
|
98
|
+
thumbnail: e.thumbnail ? { url: e.thumbnail.url ?? null } : null,
|
|
99
|
+
author: e.author ? { name: e.author.name ?? null, url: e.author.url ?? null, iconUrl: e.author.iconURL ?? null } : null,
|
|
100
|
+
fields: e.fields ? e.fields.map((f) => ({ name: f.name, value: f.value, inline: !!f.inline })) : undefined
|
|
101
|
+
});
|
|
102
|
+
forwarded = {
|
|
103
|
+
fromMessageId: refMsg.id,
|
|
104
|
+
fromChannelId: refMsg.channelId,
|
|
105
|
+
fromGuildId: refMsg.guild?.id ?? null,
|
|
106
|
+
original: {
|
|
107
|
+
id: refMsg.id,
|
|
108
|
+
content: refMsg.content ?? "",
|
|
109
|
+
author: refMsg.author ? { id: refMsg.author.id, username: refMsg.author.username, tag: refMsg.author.tag ?? null } : null,
|
|
110
|
+
createdAt: refMsg.createdAt ? refMsg.createdAt.toISOString() : null,
|
|
111
|
+
embeds: refMsg.embeds ? refMsg.embeds.map(mapEmbed) : undefined,
|
|
112
|
+
attachments: refAttachments
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const authorId = m.author ? m.author.id : `system:${m.id}`;
|
|
119
|
+
return {
|
|
120
|
+
id: m.id,
|
|
121
|
+
content: m.content ?? (typeof m.type !== "undefined" ? `[System message: type ${m.type}]` : ""),
|
|
122
|
+
author: authorId,
|
|
123
|
+
createdAt: m.createdAt.toISOString(),
|
|
124
|
+
messageType: m.type ?? undefined,
|
|
125
|
+
embeds: m.embeds
|
|
126
|
+
? Array.isArray(m.embeds)
|
|
127
|
+
? m.embeds.map(mapEmbed)
|
|
128
|
+
: typeof m.embeds.values === "function"
|
|
129
|
+
? Array.from(m.embeds.values()).map(mapEmbed)
|
|
130
|
+
: undefined
|
|
131
|
+
: undefined,
|
|
132
|
+
editedAt: m.editedAt ? m.editedAt.toISOString() : null,
|
|
133
|
+
attachments,
|
|
134
|
+
pinned: m.pinned ?? false,
|
|
135
|
+
referencedMessageId: referencedMessageId,
|
|
136
|
+
forwarded: forwarded
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function createTranscript(channel, options = {}) {
|
|
140
|
+
if (!channel.isTextBased())
|
|
141
|
+
throw new TypeError("Provided channel must be text-based");
|
|
142
|
+
let channelMessages = [];
|
|
143
|
+
let lastMessageID;
|
|
144
|
+
const { limit } = options;
|
|
145
|
+
const resolveLimit = typeof limit === "undefined" ? Infinity : limit;
|
|
146
|
+
while (true) {
|
|
147
|
+
const fetchLimitOptions = { limit: 100, before: lastMessageID };
|
|
148
|
+
if (!lastMessageID)
|
|
149
|
+
delete fetchLimitOptions.before;
|
|
150
|
+
const messages = await channel.messages.fetch(fetchLimitOptions);
|
|
151
|
+
channelMessages.push(...messages.values());
|
|
152
|
+
lastMessageID = messages.lastKey();
|
|
153
|
+
if (messages.size < 100)
|
|
154
|
+
break;
|
|
155
|
+
if (channelMessages.length >= resolveLimit)
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (resolveLimit < channelMessages.length)
|
|
159
|
+
channelMessages = channelMessages.slice(0, limit);
|
|
160
|
+
return generateFromMessages(channelMessages.reverse(), channel, options);
|
|
161
|
+
}
|
|
162
|
+
async function generateFromMessages(messages, channel, options = {}) {
|
|
163
|
+
const transformedMessages = messages instanceof discord_js_1.Collection ? Array.from(messages.values()) : messages;
|
|
164
|
+
const messagesSerialized = await Promise.all(transformedMessages.map((m) => mapMessageToSerializable(m)));
|
|
165
|
+
const transcript = {
|
|
166
|
+
meta: {
|
|
167
|
+
channelId: channel.id ?? "unknown",
|
|
168
|
+
channelName: channel.name ?? null,
|
|
169
|
+
guildId: channel.guild?.id ?? null,
|
|
170
|
+
generatedAt: new Date().toISOString(),
|
|
171
|
+
messageCount: messagesSerialized.length
|
|
172
|
+
},
|
|
173
|
+
messages: messagesSerialized
|
|
174
|
+
};
|
|
175
|
+
const fileName = `transcript-${transcript.meta.channelId}-${Date.now()}.json`;
|
|
176
|
+
const outPath = path_1.default.join(process.cwd(), fileName);
|
|
177
|
+
await promises_1.default.writeFile(outPath, JSON.stringify(transcript, null, 2), "utf8");
|
|
178
|
+
return outPath;
|
|
179
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface GuildTag {
|
|
2
|
+
name?: string | null;
|
|
3
|
+
iconUrl?: string | null;
|
|
4
|
+
}
|
|
5
|
+
export interface AuthorData {
|
|
6
|
+
id: string;
|
|
7
|
+
username: string;
|
|
8
|
+
tag?: string;
|
|
9
|
+
guildTag?: GuildTag | null;
|
|
10
|
+
nickname?: string | null;
|
|
11
|
+
avatar?: string | null;
|
|
12
|
+
bot: boolean;
|
|
13
|
+
color?: string | null;
|
|
14
|
+
verified?: boolean | null;
|
|
15
|
+
}
|
|
16
|
+
export interface ForwardedOriginal {
|
|
17
|
+
id: string | null;
|
|
18
|
+
content: string;
|
|
19
|
+
author: string | null;
|
|
20
|
+
createdAt: string | null;
|
|
21
|
+
embeds: any[];
|
|
22
|
+
attachments: any[];
|
|
23
|
+
stickers: any[];
|
|
24
|
+
}
|
|
25
|
+
export interface ForwardedData {
|
|
26
|
+
fromMessageId: string | null;
|
|
27
|
+
fromChannelId: string | null;
|
|
28
|
+
fromGuildId: string | null;
|
|
29
|
+
original: ForwardedOriginal;
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class AssetManager {
|
|
2
|
+
private urlMap;
|
|
3
|
+
private assetsDir;
|
|
4
|
+
private compression?;
|
|
5
|
+
constructor(assetsDir?: string, opts?: {
|
|
6
|
+
compression?: number;
|
|
7
|
+
});
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
downloadAssets(data: unknown): Promise<void>;
|
|
10
|
+
replaceUrls(data: unknown): unknown;
|
|
11
|
+
private extractDiscordUrls;
|
|
12
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AssetManager = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const promises_2 = require("stream/promises");
|
|
11
|
+
const https_1 = __importDefault(require("https"));
|
|
12
|
+
const os_1 = require("os");
|
|
13
|
+
const crypto_1 = require("crypto");
|
|
14
|
+
const sharp_1 = __importDefault(require("sharp"));
|
|
15
|
+
async function downloadFile(url, outputDir, baseName) {
|
|
16
|
+
async function _download(u, tempOut, redirects = 0) {
|
|
17
|
+
return await new Promise((resolve, reject) => {
|
|
18
|
+
const req = https_1.default.get(u, (response) => {
|
|
19
|
+
const code = response.statusCode || 0;
|
|
20
|
+
if (code >= 300 && code < 400 && response.headers.location) {
|
|
21
|
+
if (redirects >= 5) {
|
|
22
|
+
reject(new Error("Too many redirects"));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const loc = response.headers.location;
|
|
26
|
+
const next = loc.startsWith("http") ? loc : new URL(loc, u).toString();
|
|
27
|
+
resolve(_download(next, tempOut, redirects + 1));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (code !== 200) {
|
|
31
|
+
reject(new Error(`HTTP ${code}`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const file = (0, fs_1.createWriteStream)(tempOut);
|
|
35
|
+
(0, promises_2.pipeline)(response, file)
|
|
36
|
+
.then(() => resolve({ temp: tempOut, headers: response.headers }))
|
|
37
|
+
.catch(reject);
|
|
38
|
+
});
|
|
39
|
+
req.on("error", reject);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
if (!(0, fs_1.existsSync)(outputDir))
|
|
44
|
+
await promises_1.default.mkdir(outputDir, { recursive: true });
|
|
45
|
+
const rand = (0, crypto_1.randomBytes)(6).toString("hex");
|
|
46
|
+
const tempOut = path_1.default.join((0, os_1.tmpdir)(), `asset-${Date.now()}-${rand}.tmp`);
|
|
47
|
+
const result = await _download(url, tempOut, 0);
|
|
48
|
+
if (!result)
|
|
49
|
+
return null;
|
|
50
|
+
const headers = result.headers || {};
|
|
51
|
+
const contentType = (headers["content-type"] || "").toString();
|
|
52
|
+
const map = {
|
|
53
|
+
"image/png": ".png",
|
|
54
|
+
"image/jpeg": ".jpg",
|
|
55
|
+
"image/jpg": ".jpg",
|
|
56
|
+
"image/gif": ".gif",
|
|
57
|
+
"image/webp": ".webp",
|
|
58
|
+
"image/svg+xml": ".svg",
|
|
59
|
+
"application/pdf": ".pdf",
|
|
60
|
+
"text/html": ".html",
|
|
61
|
+
"audio/mpeg": ".mp3",
|
|
62
|
+
"audio/mp3": ".mp3",
|
|
63
|
+
"audio/wav": ".wav",
|
|
64
|
+
"video/mp4": ".mp4",
|
|
65
|
+
"video/webm": ".webm"
|
|
66
|
+
};
|
|
67
|
+
let parsedExt = path_1.default.extname(baseName) || "";
|
|
68
|
+
let ext = parsedExt;
|
|
69
|
+
if (!ext) {
|
|
70
|
+
try {
|
|
71
|
+
const u2 = new URL(url);
|
|
72
|
+
const pExt = path_1.default.extname(u2.pathname || "") || "";
|
|
73
|
+
if (pExt)
|
|
74
|
+
parsedExt = pExt;
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
}
|
|
78
|
+
if (!ext && parsedExt) {
|
|
79
|
+
ext = parsedExt;
|
|
80
|
+
}
|
|
81
|
+
if (!ext && contentType) {
|
|
82
|
+
const t = contentType.split(";")[0].trim().toLowerCase();
|
|
83
|
+
if (map[t])
|
|
84
|
+
ext = map[t];
|
|
85
|
+
}
|
|
86
|
+
const baseOnly = parsedExt ? baseName.slice(0, -parsedExt.length) : baseName;
|
|
87
|
+
const encodedName = encodeURIComponent(baseOnly) + (ext || "");
|
|
88
|
+
const finalOut = path_1.default.join(outputDir, encodedName);
|
|
89
|
+
try {
|
|
90
|
+
await promises_1.default.rename(tempOut, finalOut).catch(async () => {
|
|
91
|
+
await promises_1.default.copyFile(tempOut, finalOut);
|
|
92
|
+
await promises_1.default.unlink(tempOut).catch(() => { });
|
|
93
|
+
});
|
|
94
|
+
if (finalOut.includes("%25")) {
|
|
95
|
+
try {
|
|
96
|
+
const alt1 = finalOut.replace(/%25/g, "%");
|
|
97
|
+
if (!(0, fs_1.existsSync)(alt1)) {
|
|
98
|
+
await promises_1.default.copyFile(finalOut, alt1).catch(() => { });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const base = path_1.default.basename(finalOut);
|
|
105
|
+
const dir = path_1.default.dirname(finalOut);
|
|
106
|
+
let decodedBase = base;
|
|
107
|
+
try {
|
|
108
|
+
decodedBase = decodeURIComponent(base);
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
if (decodedBase !== base) {
|
|
112
|
+
const alt2 = path_1.default.join(dir, decodedBase);
|
|
113
|
+
if (!(0, fs_1.existsSync)(alt2)) {
|
|
114
|
+
await promises_1.default.copyFile(finalOut, alt2).catch(() => { });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch { }
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
try {
|
|
122
|
+
await promises_1.default.unlink(tempOut).catch(() => { });
|
|
123
|
+
}
|
|
124
|
+
catch { }
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return finalOut;
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function getAssetType(url) {
|
|
134
|
+
const imageExts = /\.(png|jpg|jpeg|gif|webp|svg|bmp|avif)(\?|$)/i;
|
|
135
|
+
const videoExts = /\.(mp4|mov|webm|avi|mkv|flv|wmv)(\?|$)/i;
|
|
136
|
+
const audioExts = /\.(mp3|wav|ogg|m4a|flac|aac|weba)(\?|$)/i;
|
|
137
|
+
const profileExts = /\/avatars\/|\/user-content\/|\/emojis\//i;
|
|
138
|
+
if (profileExts.test(url))
|
|
139
|
+
return "profiles";
|
|
140
|
+
if (imageExts.test(url))
|
|
141
|
+
return "images";
|
|
142
|
+
if (videoExts.test(url))
|
|
143
|
+
return "videos";
|
|
144
|
+
if (audioExts.test(url))
|
|
145
|
+
return "audio";
|
|
146
|
+
return "files";
|
|
147
|
+
}
|
|
148
|
+
function getFilenameFromUrl(url) {
|
|
149
|
+
try {
|
|
150
|
+
const u = new URL(url);
|
|
151
|
+
const pathname = (u.pathname || "") + (u.search || "");
|
|
152
|
+
const parts = pathname.split("/");
|
|
153
|
+
const filename = parts[parts.length - 1];
|
|
154
|
+
if (filename && filename.length > 0) {
|
|
155
|
+
return filename.split("?")[0] || `file_${Date.now()}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch { }
|
|
159
|
+
return `file_${Date.now()}`;
|
|
160
|
+
}
|
|
161
|
+
function decodeHtmlEntities(value) {
|
|
162
|
+
return value.replace(/&/g, "&").replace(/&/g, "&");
|
|
163
|
+
}
|
|
164
|
+
function normalizeExternalUrl(url) {
|
|
165
|
+
try {
|
|
166
|
+
const newUrl = new URL(url);
|
|
167
|
+
if (/images-ext(?:-[^.]*)?\.discordapp\.net/i.test(newUrl.hostname)) {
|
|
168
|
+
const decoded = decodeURIComponent((newUrl.pathname || "") + (newUrl.search || ""));
|
|
169
|
+
let idx = decoded.lastIndexOf("/https/");
|
|
170
|
+
if (idx === -1)
|
|
171
|
+
idx = decoded.lastIndexOf("/http/");
|
|
172
|
+
if (idx !== -1) {
|
|
173
|
+
let inner = decoded.slice(idx + 1);
|
|
174
|
+
inner = inner.replace(/^https?\//i, (m) => m.replace("/", "://"));
|
|
175
|
+
return inner;
|
|
176
|
+
}
|
|
177
|
+
const find = decoded.match(/https?:\/\/[^\s"'<>]+/i);
|
|
178
|
+
if (find && find[0])
|
|
179
|
+
return find[0];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch { }
|
|
183
|
+
return url;
|
|
184
|
+
}
|
|
185
|
+
class AssetManager {
|
|
186
|
+
urlMap = new Map();
|
|
187
|
+
assetsDir;
|
|
188
|
+
compression;
|
|
189
|
+
constructor(assetsDir = "assets", opts) {
|
|
190
|
+
this.assetsDir = assetsDir;
|
|
191
|
+
this.compression = opts?.compression;
|
|
192
|
+
}
|
|
193
|
+
async initialize() {
|
|
194
|
+
if (!(0, fs_1.existsSync)(this.assetsDir)) {
|
|
195
|
+
await promises_1.default.mkdir(this.assetsDir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async downloadAssets(data) {
|
|
199
|
+
const urls = this.extractDiscordUrls(data);
|
|
200
|
+
for (const url of urls) {
|
|
201
|
+
if (!this.urlMap.has(url)) {
|
|
202
|
+
const cleanedUrl = decodeHtmlEntities(url);
|
|
203
|
+
const type = getAssetType(cleanedUrl);
|
|
204
|
+
const rawFilename = getFilenameFromUrl(cleanedUrl);
|
|
205
|
+
const typeDir = path_1.default.join(this.assetsDir, type);
|
|
206
|
+
if (!(0, fs_1.existsSync)(typeDir)) {
|
|
207
|
+
await promises_1.default.mkdir(typeDir, { recursive: true });
|
|
208
|
+
}
|
|
209
|
+
const downloadedPath = await downloadFile(normalizeExternalUrl(cleanedUrl), typeDir, rawFilename);
|
|
210
|
+
if (downloadedPath) {
|
|
211
|
+
let finalFilename = path_1.default.basename(downloadedPath);
|
|
212
|
+
let finalSavedPath = downloadedPath;
|
|
213
|
+
const imageExts = /\.(png|jpg|jpeg|gif|webp|svg|bmp|avif)(\?|$)/i;
|
|
214
|
+
if (this.compression && this.compression > 0 && imageExts.test(finalFilename)) {
|
|
215
|
+
try {
|
|
216
|
+
const q = Math.max(1, Math.min(100, Math.floor(this.compression)));
|
|
217
|
+
const dirName = path_1.default.dirname(downloadedPath);
|
|
218
|
+
const baseOnlyName = path_1.default.basename(downloadedPath, path_1.default.extname(downloadedPath));
|
|
219
|
+
let tmpOut = path_1.default.join(dirName, `${baseOnlyName}.webp`);
|
|
220
|
+
let uniqueTmpOut = tmpOut;
|
|
221
|
+
let idx = 0;
|
|
222
|
+
while ((0, fs_1.existsSync)(uniqueTmpOut)) {
|
|
223
|
+
idx += 1;
|
|
224
|
+
uniqueTmpOut = path_1.default.join(dirName, `${baseOnlyName}-${idx}.webp`);
|
|
225
|
+
}
|
|
226
|
+
await (0, sharp_1.default)(downloadedPath).toFormat("webp", { quality: q }).toFile(uniqueTmpOut);
|
|
227
|
+
try {
|
|
228
|
+
await promises_1.default.unlink(downloadedPath).catch(() => { });
|
|
229
|
+
}
|
|
230
|
+
catch { }
|
|
231
|
+
finalSavedPath = uniqueTmpOut;
|
|
232
|
+
finalFilename = path_1.default.basename(finalSavedPath);
|
|
233
|
+
}
|
|
234
|
+
catch (_) { }
|
|
235
|
+
}
|
|
236
|
+
const localPath = path_1.default.join("assets", type, finalFilename).replace(/\\/g, "/");
|
|
237
|
+
this.urlMap.set(url, localPath);
|
|
238
|
+
if (cleanedUrl !== url)
|
|
239
|
+
this.urlMap.set(cleanedUrl, localPath);
|
|
240
|
+
const baseUrl = cleanedUrl.split("?")[0];
|
|
241
|
+
if (!this.urlMap.has(baseUrl))
|
|
242
|
+
this.urlMap.set(baseUrl, localPath);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
replaceUrls(data) {
|
|
248
|
+
if (typeof data === "string") {
|
|
249
|
+
let result = data;
|
|
250
|
+
for (const [original, local] of this.urlMap.entries()) {
|
|
251
|
+
const esc = original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
252
|
+
const re = new RegExp(esc + "(?:\\?[^\"'\\s>]*)?", "g");
|
|
253
|
+
result = result.replace(re, local);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
if (Array.isArray(data)) {
|
|
258
|
+
return data.map((item) => this.replaceUrls(item));
|
|
259
|
+
}
|
|
260
|
+
if (data !== null && typeof data === "object") {
|
|
261
|
+
const obj = data;
|
|
262
|
+
const result = {};
|
|
263
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
264
|
+
result[key] = this.replaceUrls(value);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
return data;
|
|
269
|
+
}
|
|
270
|
+
extractDiscordUrls(data, urls = new Set()) {
|
|
271
|
+
if (typeof data === "string") {
|
|
272
|
+
const discordUrlRegex = /https?:\/\/(?:cdn\.discordapp\.com|media\.discordapp\.net|images-ext(?:-[^.]*)?\.discordapp\.net)\/[^\s"<>){}[\]]+/g;
|
|
273
|
+
let match;
|
|
274
|
+
while ((match = discordUrlRegex.exec(data)) !== null) {
|
|
275
|
+
urls.add(match[0]);
|
|
276
|
+
}
|
|
277
|
+
return urls;
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(data)) {
|
|
280
|
+
for (const item of data) {
|
|
281
|
+
this.extractDiscordUrls(item, urls);
|
|
282
|
+
}
|
|
283
|
+
return urls;
|
|
284
|
+
}
|
|
285
|
+
if (data !== null && typeof data === "object") {
|
|
286
|
+
const obj = data;
|
|
287
|
+
for (const value of Object.values(obj)) {
|
|
288
|
+
this.extractDiscordUrls(value, urls);
|
|
289
|
+
}
|
|
290
|
+
return urls;
|
|
291
|
+
}
|
|
292
|
+
return urls;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.AssetManager = AssetManager;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AuthorData, ForwardedOriginal, ForwardedData } from "../types/entities";
|
|
2
|
+
export declare function buildSystemAuthor(messageId: string, messageType?: number): AuthorData;
|
|
3
|
+
export declare function buildForwardedOriginal(message: any): ForwardedOriginal;
|
|
4
|
+
export declare function extractForwarded(message: any): Promise<ForwardedData | null>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSystemAuthor = buildSystemAuthor;
|
|
4
|
+
exports.buildForwardedOriginal = buildForwardedOriginal;
|
|
5
|
+
exports.extractForwarded = extractForwarded;
|
|
6
|
+
function buildSystemAuthor(messageId, messageType) {
|
|
7
|
+
return {
|
|
8
|
+
id: `system:${messageId}`,
|
|
9
|
+
username: `System${messageType ? ` (type ${messageType})` : ""}`,
|
|
10
|
+
bot: false,
|
|
11
|
+
color: null,
|
|
12
|
+
verified: null
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function buildForwardedOriginal(message) {
|
|
16
|
+
const rawEmbeds = Array.isArray(message.embeds)
|
|
17
|
+
? message.embeds
|
|
18
|
+
: typeof message.embeds?.values === "function"
|
|
19
|
+
? Array.from(message.embeds.values())
|
|
20
|
+
: [];
|
|
21
|
+
const rawAttachments = Array.isArray(message.attachments)
|
|
22
|
+
? message.attachments
|
|
23
|
+
: typeof message.attachments?.values === "function"
|
|
24
|
+
? Array.from(message.attachments.values())
|
|
25
|
+
: [];
|
|
26
|
+
const rawStickers = Array.isArray(message.stickers)
|
|
27
|
+
? message.stickers
|
|
28
|
+
: typeof message.stickers?.values === "function"
|
|
29
|
+
? Array.from(message.stickers.values())
|
|
30
|
+
: [];
|
|
31
|
+
return {
|
|
32
|
+
id: message.id ?? null,
|
|
33
|
+
content: message.content ?? "",
|
|
34
|
+
author: message.author?.id ?? null,
|
|
35
|
+
createdAt: message.createdAt ? new Date(message.createdAt).toISOString() : null,
|
|
36
|
+
embeds: rawEmbeds.map((e) => ({
|
|
37
|
+
title: e.title ?? null,
|
|
38
|
+
description: e.description ?? null,
|
|
39
|
+
url: e.url ?? null,
|
|
40
|
+
timestamp: e.timestamp ? new Date(e.timestamp).toISOString() : null,
|
|
41
|
+
color: typeof e.hexColor === "string"
|
|
42
|
+
? e.hexColor
|
|
43
|
+
: typeof e.color === "string"
|
|
44
|
+
? e.color
|
|
45
|
+
: typeof e.color === "number"
|
|
46
|
+
? `#${e.color.toString(16).padStart(6, "0")}`
|
|
47
|
+
: null,
|
|
48
|
+
footer: e.footer ? { text: e.footer.text ?? null, iconUrl: e.footer.iconURL ?? null } : null,
|
|
49
|
+
image: e.image
|
|
50
|
+
? {
|
|
51
|
+
url: e.image.proxyURL ?? e.image.proxy_url ?? e.image.url ?? null,
|
|
52
|
+
width: e.image?.width ?? null,
|
|
53
|
+
height: e.image?.height ?? null
|
|
54
|
+
}
|
|
55
|
+
: null,
|
|
56
|
+
thumbnail: e.thumbnail
|
|
57
|
+
? {
|
|
58
|
+
url: e.thumbnail.proxyURL ?? e.thumbnail.proxy_url ?? e.thumbnail.url ?? null,
|
|
59
|
+
width: e.thumbnail?.width ?? null,
|
|
60
|
+
height: e.thumbnail?.height ?? null
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
author: e.author
|
|
64
|
+
? {
|
|
65
|
+
name: e.author.name ?? null,
|
|
66
|
+
url: e.author.url ?? null,
|
|
67
|
+
iconUrl: e.author.proxyIconURL ?? e.author.proxy_icon_url ?? e.author.iconURL ?? null
|
|
68
|
+
}
|
|
69
|
+
: null,
|
|
70
|
+
fields: Array.isArray(e.fields) ? e.fields.map((f) => ({ name: f.name, value: f.value, inline: !!f.inline })) : undefined
|
|
71
|
+
})),
|
|
72
|
+
attachments: rawAttachments.map((a) => ({
|
|
73
|
+
id: a.id ?? null,
|
|
74
|
+
filename: a.name ?? a.filename ?? null,
|
|
75
|
+
url: a.url ?? null,
|
|
76
|
+
proxyURL: a.proxyURL ?? a.proxy_url ?? null,
|
|
77
|
+
contentType: a.contentType ?? a.content_type ?? null,
|
|
78
|
+
size: a.size ?? undefined,
|
|
79
|
+
width: a.width ?? null,
|
|
80
|
+
height: a.height ?? null
|
|
81
|
+
})),
|
|
82
|
+
stickers: rawStickers.map((s) => ({
|
|
83
|
+
id: s.id ?? null,
|
|
84
|
+
name: s.name ?? null,
|
|
85
|
+
format: s.format ?? s.formatType ?? null,
|
|
86
|
+
url: s.url ?? s.image?.proxyURL ?? null
|
|
87
|
+
}))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function extractForwarded(message) {
|
|
91
|
+
try {
|
|
92
|
+
let source = message.forwarded ?? message.forwardedMessage ?? null;
|
|
93
|
+
if (!source) {
|
|
94
|
+
const snapshots = message.messageSnapshots;
|
|
95
|
+
if (snapshots && typeof snapshots.values === "function") {
|
|
96
|
+
const iterator = snapshots.values();
|
|
97
|
+
const first = iterator.next();
|
|
98
|
+
if (!first.done)
|
|
99
|
+
source = first.value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (source) {
|
|
103
|
+
return {
|
|
104
|
+
fromMessageId: source.id ?? null,
|
|
105
|
+
fromChannelId: source.channelId ?? source.channel?.id ?? null,
|
|
106
|
+
fromGuildId: source.guildId ?? source.guild?.id ?? null,
|
|
107
|
+
original: buildForwardedOriginal(source)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GuildTag } from "../types/entities";
|
|
2
|
+
declare class GuildTagCache {
|
|
3
|
+
private cache;
|
|
4
|
+
get(userId: string): GuildTag | undefined;
|
|
5
|
+
set(userId: string, tag: GuildTag): void;
|
|
6
|
+
has(userId: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
declare class UserColorCache {
|
|
9
|
+
private cache;
|
|
10
|
+
get(key: string): string | null | undefined;
|
|
11
|
+
set(key: string, color: string | null): void;
|
|
12
|
+
has(key: string): boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const guildTagCache: GuildTagCache;
|
|
15
|
+
export declare const userColorCache: UserColorCache;
|
|
16
|
+
export {};
|