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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/src/generateHtml.d.ts +3 -0
  4. package/dist/src/generateHtml.js +303 -0
  5. package/dist/src/index.d.ts +5 -0
  6. package/dist/src/index.js +252 -0
  7. package/dist/src/transcript.d.ts +12 -0
  8. package/dist/src/transcript.js +179 -0
  9. package/dist/src/types/entities.d.ts +30 -0
  10. package/dist/src/types/entities.js +2 -0
  11. package/dist/src/utils/assetManager.d.ts +12 -0
  12. package/dist/src/utils/assetManager.js +295 -0
  13. package/dist/src/utils/authors.d.ts +4 -0
  14. package/dist/src/utils/authors.js +115 -0
  15. package/dist/src/utils/cache.d.ts +16 -0
  16. package/dist/src/utils/cache.js +29 -0
  17. package/dist/src/utils/extractors.d.ts +112 -0
  18. package/dist/src/utils/extractors.js +223 -0
  19. package/dist/src/utils/polls.d.ts +2 -0
  20. package/dist/src/utils/polls.js +91 -0
  21. package/dist/src/utils/transformer.d.ts +6 -0
  22. package/dist/src/utils/transformer.js +78 -0
  23. package/dist/src/utils/user.d.ts +4 -0
  24. package/dist/src/utils/user.js +56 -0
  25. package/dist/src/web/client.d.ts +20 -0
  26. package/dist/src/web/client.js +21 -0
  27. package/dist/src/web/discord-components/AudioPlayer.d.ts +5 -0
  28. package/dist/src/web/discord-components/AudioPlayer.js +231 -0
  29. package/dist/src/web/discord-components/Button.d.ts +3 -0
  30. package/dist/src/web/discord-components/Button.js +27 -0
  31. package/dist/src/web/discord-components/ChannelPinnedMessage.d.ts +7 -0
  32. package/dist/src/web/discord-components/ChannelPinnedMessage.js +11 -0
  33. package/dist/src/web/discord-components/DateSeperator.d.ts +3 -0
  34. package/dist/src/web/discord-components/DateSeperator.js +19 -0
  35. package/dist/src/web/discord-components/Embed.d.ts +2 -0
  36. package/dist/src/web/discord-components/Embed.js +78 -0
  37. package/dist/src/web/discord-components/ForwardedMessage.d.ts +2 -0
  38. package/dist/src/web/discord-components/ForwardedMessage.js +44 -0
  39. package/dist/src/web/discord-components/Message.d.ts +2 -0
  40. package/dist/src/web/discord-components/Message.js +543 -0
  41. package/dist/src/web/discord-components/PinnedMessagesModal.d.ts +6 -0
  42. package/dist/src/web/discord-components/PinnedMessagesModal.js +119 -0
  43. package/dist/src/web/discord-components/PinnedMessagesOverview.d.ts +5 -0
  44. package/dist/src/web/discord-components/PinnedMessagesOverview.js +22 -0
  45. package/dist/src/web/discord-components/Reply.d.ts +2 -0
  46. package/dist/src/web/discord-components/Reply.js +42 -0
  47. package/dist/src/web/discord-components/StickerPreview.d.ts +6 -0
  48. package/dist/src/web/discord-components/StickerPreview.js +40 -0
  49. package/dist/src/web/discord-components/ThemeSwitcher.d.ts +2 -0
  50. package/dist/src/web/discord-components/ThemeSwitcher.js +54 -0
  51. package/dist/src/web/discord-components/Transcript.d.ts +2 -0
  52. package/dist/src/web/discord-components/Transcript.js +174 -0
  53. package/dist/src/web/discord-components/UserJoinMessage.d.ts +3 -0
  54. package/dist/src/web/discord-components/UserJoinMessage.js +33 -0
  55. package/dist/src/web/discord-components/VideoPlayer.d.ts +6 -0
  56. package/dist/src/web/discord-components/VideoPlayer.js +222 -0
  57. package/dist/src/web/discord-components/icons/ChevronDownIcon.d.ts +1 -0
  58. package/dist/src/web/discord-components/icons/ChevronDownIcon.js +7 -0
  59. package/dist/src/web/discord-components/icons/CloseIcon.d.ts +1 -0
  60. package/dist/src/web/discord-components/icons/CloseIcon.js +7 -0
  61. package/dist/src/web/discord-components/icons/ExternalLinkIcon.d.ts +1 -0
  62. package/dist/src/web/discord-components/icons/ExternalLinkIcon.js +7 -0
  63. package/dist/src/web/discord-components/icons/FileAudioIcon.d.ts +1 -0
  64. package/dist/src/web/discord-components/icons/FileAudioIcon.js +7 -0
  65. package/dist/src/web/discord-components/icons/FileCodeIcon.d.ts +1 -0
  66. package/dist/src/web/discord-components/icons/FileCodeIcon.js +7 -0
  67. package/dist/src/web/discord-components/icons/FileDocumentIcon.d.ts +1 -0
  68. package/dist/src/web/discord-components/icons/FileDocumentIcon.js +7 -0
  69. package/dist/src/web/discord-components/icons/PinIcon.d.ts +1 -0
  70. package/dist/src/web/discord-components/icons/PinIcon.js +7 -0
  71. package/dist/src/web/discord-components/icons/VerifiedIcon.d.ts +1 -0
  72. package/dist/src/web/discord-components/icons/VerifiedIcon.js +7 -0
  73. package/dist/src/web/discord-components/index.d.ts +11 -0
  74. package/dist/src/web/discord-components/index.js +24 -0
  75. package/dist/src/web/discord-components/messageHelpers.d.ts +8 -0
  76. package/dist/src/web/discord-components/messageHelpers.js +72 -0
  77. package/dist/src/web/discord-components/themeColors.d.ts +9 -0
  78. package/dist/src/web/discord-components/themeColors.js +320 -0
  79. package/dist/src/web/discord-components/transcriptHelpers.d.ts +19 -0
  80. package/dist/src/web/discord-components/transcriptHelpers.js +120 -0
  81. package/dist/src/web/discord-components/types.d.ts +1 -0
  82. package/dist/src/web/discord-components/types.js +2 -0
  83. package/dist/src/web/discord-components/utils/date.d.ts +3 -0
  84. package/dist/src/web/discord-components/utils/date.js +50 -0
  85. package/dist/src/web/discord-components/utils/markdown.d.ts +11 -0
  86. package/dist/src/web/discord-components/utils/markdown.js +538 -0
  87. package/dist/src/web/discord-components/utils/markdownUtils.d.ts +12 -0
  88. package/dist/src/web/discord-components/utils/markdownUtils.js +140 -0
  89. package/dist/src/web/helpers/avatarHelpers.d.ts +2 -0
  90. package/dist/src/web/helpers/avatarHelpers.js +15 -0
  91. package/dist/src/web/helpers/cdnHelpers.d.ts +5 -0
  92. package/dist/src/web/helpers/cdnHelpers.js +48 -0
  93. package/dist/src/web/helpers/contentHelpers.d.ts +9 -0
  94. package/dist/src/web/helpers/contentHelpers.js +41 -0
  95. package/dist/src/web/helpers/renderContent.d.ts +2 -0
  96. package/dist/src/web/helpers/renderContent.js +15 -0
  97. package/dist/src/web/helpers/scrollHelpers.d.ts +2 -0
  98. package/dist/src/web/helpers/scrollHelpers.js +31 -0
  99. package/dist/src/web/helpers/timestampHelpers.d.ts +6 -0
  100. package/dist/src/web/helpers/timestampHelpers.js +66 -0
  101. package/dist/src/web/hooks/useMessageContent.d.ts +5 -0
  102. package/dist/src/web/hooks/useMessageContent.js +37 -0
  103. package/dist/src/web/index.d.ts +1 -0
  104. package/dist/src/web/index.js +17 -0
  105. package/dist/src/web/types/attachment.d.ts +6 -0
  106. package/dist/src/web/types/attachment.js +2 -0
  107. package/dist/src/web/types/author.d.ts +14 -0
  108. package/dist/src/web/types/author.js +2 -0
  109. package/dist/src/web/types/channel.d.ts +8 -0
  110. package/dist/src/web/types/channel.js +2 -0
  111. package/dist/src/web/types/embed.d.ts +52 -0
  112. package/dist/src/web/types/embed.js +2 -0
  113. package/dist/src/web/types/interaction.d.ts +8 -0
  114. package/dist/src/web/types/interaction.js +2 -0
  115. package/dist/src/web/types/markdown.d.ts +5 -0
  116. package/dist/src/web/types/markdown.js +2 -0
  117. package/dist/src/web/types/message.d.ts +73 -0
  118. package/dist/src/web/types/message.js +2 -0
  119. package/dist/src/web/types/poll.d.ts +11 -0
  120. package/dist/src/web/types/poll.js +2 -0
  121. package/dist/src/web/types/props.d.ts +155 -0
  122. package/dist/src/web/types/props.js +2 -0
  123. package/dist/src/web/types/reaction.d.ts +6 -0
  124. package/dist/src/web/types/reaction.js +2 -0
  125. package/dist/src/web/types/theme.d.ts +14 -0
  126. package/dist/src/web/types/theme.js +2 -0
  127. package/dist/src/web/types/ui.d.ts +10 -0
  128. package/dist/src/web/types/ui.js +2 -0
  129. package/dist/types/download.d.ts +12 -0
  130. package/dist/types/download.js +2 -0
  131. package/dist/types/exportableTranscript.d.ts +169 -0
  132. package/dist/types/exportableTranscript.js +2 -0
  133. package/dist/types/general.d.ts +90 -0
  134. package/dist/types/general.js +2 -0
  135. 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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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(/&amp;/g, "&").replace(/&#38;/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 {};