discord-html-transcripts-fix 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/downloader/images.js +11 -0
- package/dist/generator/index.js +45 -8
- package/dist/generator/renderers/components/Container.js +10 -4
- package/dist/generator/renderers/components/Media Gallery.js +5 -5
- package/dist/generator/renderers/components/Spacing.js +18 -6
- package/dist/generator/renderers/components/section/Section.js +13 -3
- package/dist/generator/renderers/components/section/SectionAccessory.js +8 -7
- package/dist/generator/renderers/components/section/SectionContent.js +9 -4
- package/dist/generator/renderers/content.js +220 -42
- package/dist/generator/renderers/embed.js +31 -7
- package/dist/generator/renderers/message.js +223 -30
- package/dist/generator/renderers/reply.js +14 -3
- package/dist/generator/renderers/systemMessage.js +62 -9
- package/dist/index.js +25 -3
- package/dist/static/client.js +490 -25
- package/dist/utils/buildProfiles.js +19 -1
- package/dist/utils/utils.js +1 -1
- package/package.json +1 -1
|
@@ -47,8 +47,19 @@ const embed_1 = require("./embed");
|
|
|
47
47
|
const reply_1 = __importDefault(require("./reply"));
|
|
48
48
|
const systemMessage_1 = __importDefault(require("./systemMessage"));
|
|
49
49
|
|
|
50
|
+
const FLAG_CROSSPOSTED = 1 << 0;
|
|
51
|
+
const FLAG_IS_CROSSPOST = 1 << 1;
|
|
50
52
|
const FLAG_SUPPRESS_EMBEDS = 1 << 2;
|
|
53
|
+
const FLAG_SUPPRESS_NOTIFICATIONS = 1 << 12;
|
|
51
54
|
const FLAG_IS_VOICE_MESSAGE = 1 << 13;
|
|
55
|
+
const FLAG_HAS_SNAPSHOT = 1 << 14;
|
|
56
|
+
const ACTIVITY_TYPES = { 1: 'Join', 2: 'Spectate', 3: 'Listen', 5: 'Join Request' };
|
|
57
|
+
|
|
58
|
+
function abbrCount(n) {
|
|
59
|
+
if (n < 1000) return String(n);
|
|
60
|
+
if (n < 1000000) return (n / 1000).toFixed(n < 10000 ? 1 : 0).replace(/\.0$/, '') + 'K';
|
|
61
|
+
return (n / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
62
|
+
}
|
|
52
63
|
|
|
53
64
|
function t(context, key, fallback) {
|
|
54
65
|
const dict = context?.i18n?.[context?.lang] || context?.i18n?.en || {};
|
|
@@ -60,7 +71,18 @@ async function DiscordMessage({ message, context }) {
|
|
|
60
71
|
if (message.system)
|
|
61
72
|
return (0, jsx_runtime_1.jsx)(systemMessage_1.default, { message: message, context: context });
|
|
62
73
|
|
|
63
|
-
const
|
|
74
|
+
const isCrossGuildReply = message.reference && message.reference.guildId !== message.guild?.id;
|
|
75
|
+
|
|
76
|
+
// Data-attrs for client-side filtering (keyword/author/role/date/has-*).
|
|
77
|
+
const memberRoleIds = message.member?.roles?.cache
|
|
78
|
+
? Array.from(message.member.roles.cache.keys()).join(',')
|
|
79
|
+
: '';
|
|
80
|
+
const authorName = (message.member?.nickname || message.author?.displayName || message.author?.username || '').toLowerCase();
|
|
81
|
+
const ts = message.createdAt instanceof Date ? message.createdAt.getTime() : 0;
|
|
82
|
+
const lowerText = (message.content || '').toLowerCase().slice(0, 4096);
|
|
83
|
+
const hasImage = message.attachments && Array.from(message.attachments.values()).some(a => typeof a.contentType === 'string' && a.contentType.startsWith('image/'));
|
|
84
|
+
const hasEmbed = message.embeds && message.embeds.length > 0;
|
|
85
|
+
const hasAttachment = message.attachments && message.attachments.size > 0;
|
|
64
86
|
const stickers = message.stickers && message.stickers.size > 0
|
|
65
87
|
? Array.from(message.stickers.values()).map((s) => ({
|
|
66
88
|
id: s.id,
|
|
@@ -74,6 +96,13 @@ async function DiscordMessage({ message, context }) {
|
|
|
74
96
|
const flags = (typeof message.flags?.bitfield === 'number') ? message.flags.bitfield : 0;
|
|
75
97
|
const suppressEmbeds = !!(flags & FLAG_SUPPRESS_EMBEDS);
|
|
76
98
|
const isVoice = !!(flags & FLAG_IS_VOICE_MESSAGE);
|
|
99
|
+
const isCrosspost = !!(flags & FLAG_IS_CROSSPOST);
|
|
100
|
+
const isCrossposted = !!(flags & FLAG_CROSSPOSTED);
|
|
101
|
+
const isSilent = !!(flags & FLAG_SUPPRESS_NOTIFICATIONS);
|
|
102
|
+
const activity = message.activity ? {
|
|
103
|
+
type: ACTIVITY_TYPES[message.activity.type] || 'Activity',
|
|
104
|
+
partyId: message.activity.partyId,
|
|
105
|
+
} : null;
|
|
77
106
|
|
|
78
107
|
// Forwarded message snapshots (messageSnapshots) — render before the empty body
|
|
79
108
|
const snapshots = Array.isArray(message.messageSnapshots) ? message.messageSnapshots : [];
|
|
@@ -82,19 +111,75 @@ async function DiscordMessage({ message, context }) {
|
|
|
82
111
|
|
|
83
112
|
const editedAtIso = message.editedAt instanceof Date ? message.editedAt.toISOString() : null;
|
|
84
113
|
const pinned = !!message.pinned;
|
|
114
|
+
const verifiedBot = !!message.author?.flags?.has?.(discord_js_1.UserFlags.VerifiedBot);
|
|
115
|
+
|
|
116
|
+
// Forum applied tags — only on the first message of a forum thread (the post itself)
|
|
117
|
+
let appliedTagPills = null;
|
|
118
|
+
if (message.channel?.isThread?.() && message.channel.parent?.availableTags && Array.isArray(message.channel.appliedTags) && message.channel.appliedTags.length > 0) {
|
|
119
|
+
const isFirstInThread = message.id === message.channel.id; // Forum post: thread id === starter message id
|
|
120
|
+
if (isFirstInThread) {
|
|
121
|
+
const available = message.channel.parent.availableTags;
|
|
122
|
+
appliedTagPills = (0, jsx_runtime_1.jsx)("div", {
|
|
123
|
+
className: "dht-applied-tags",
|
|
124
|
+
children: message.channel.appliedTags.map((tagId) => {
|
|
125
|
+
const tag = available.find((t) => t.id === tagId);
|
|
126
|
+
if (!tag) return null;
|
|
127
|
+
return (0, jsx_runtime_1.jsxs)("span", { className: "dht-tag-pill", children: [
|
|
128
|
+
tag.emoji?.name && (0, jsx_runtime_1.jsx)("span", { className: "dht-tag-emoji", children: tag.emoji.name }),
|
|
129
|
+
' ',
|
|
130
|
+
tag.name
|
|
131
|
+
] }, tagId);
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Build a comma-separated badge list for additional flag rendering
|
|
138
|
+
const slashCmdLine = renderSlashCommandLine(message, context);
|
|
139
|
+
|
|
140
|
+
// Resolve author profile data for Skyra slot attributes — without these the
|
|
141
|
+
// <discord-message> web component falls back to a generic display
|
|
142
|
+
const authorMember = message.member;
|
|
143
|
+
const authorDisplay = authorMember?.nickname ?? message.author?.displayName ?? message.author?.username;
|
|
144
|
+
const authorAvatar = authorMember?.displayAvatarURL?.({ size: 64 }) ?? message.author?.displayAvatarURL?.({ size: 64 });
|
|
145
|
+
const authorRoleHex = authorMember?.displayHexColor && authorMember.displayHexColor !== '#000000' ? authorMember.displayHexColor : undefined;
|
|
85
146
|
|
|
86
147
|
return ((0, jsx_runtime_1.jsxs)("discord-message", {
|
|
87
148
|
id: `m-${message.id}`,
|
|
88
149
|
timestamp: message.createdAt,
|
|
89
150
|
edited: message.editedAt !== null,
|
|
90
|
-
server: isCrosspost
|
|
151
|
+
server: (isCrossGuildReply || isCrosspost || isCrossposted) ? true : undefined,
|
|
91
152
|
highlight: message.mentions.everyone || pinned,
|
|
92
153
|
profile: message.author.id,
|
|
154
|
+
author: authorDisplay,
|
|
155
|
+
avatar: authorAvatar,
|
|
156
|
+
roleColor: authorRoleHex,
|
|
157
|
+
bot: message.author?.bot && !message.webhookId ? true : undefined,
|
|
158
|
+
verified: verifiedBot ? true : undefined,
|
|
159
|
+
className: pinned ? 'dht-msg dht-msg-pinned' : 'dht-msg',
|
|
160
|
+
"data-author-id": message.author.id,
|
|
161
|
+
"data-author-name": authorName,
|
|
162
|
+
"data-roles": memberRoleIds,
|
|
163
|
+
"data-timestamp": ts ? String(ts) : undefined,
|
|
164
|
+
"data-text": lowerText,
|
|
165
|
+
"data-pinned": pinned ? 'true' : undefined,
|
|
166
|
+
"data-has-image": hasImage ? 'true' : undefined,
|
|
167
|
+
"data-has-embed": hasEmbed ? 'true' : undefined,
|
|
168
|
+
"data-has-attachment": hasAttachment ? 'true' : undefined,
|
|
93
169
|
children: [
|
|
94
|
-
pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned", children: "📌" }),
|
|
170
|
+
pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned message", children: "📌" }),
|
|
171
|
+
appliedTagPills,
|
|
95
172
|
(0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }),
|
|
96
173
|
message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", { slot: "reply", profile: message.interaction.user.id, command: '/' + message.interaction.commandName })),
|
|
97
|
-
|
|
174
|
+
slashCmdLine,
|
|
175
|
+
isApp && (0, jsx_runtime_1.jsxs)("span", { className: "dht-app-badge", "data-i18n": "appBadge", children: [t(context, 'appBadge', 'APP'), verifiedBot && (0, jsx_runtime_1.jsx)("span", { className: "dht-verified-check", title: "Verified Bot", children: '✓' })] }),
|
|
176
|
+
isCrossposted && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Published from announcement channel", children: '📣 Published' }),
|
|
177
|
+
isCrosspost && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Follows another channel", children: '📡 Follow' }),
|
|
178
|
+
isSilent && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-silent", title: "Silent message (no notification)", children: '🔕 Silent' }),
|
|
179
|
+
activity && (0, jsx_runtime_1.jsxs)("div", { className: "dht-activity", children: [
|
|
180
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-activity-icon", children: '🎮' }),
|
|
181
|
+
(0, jsx_runtime_1.jsxs)("span", { children: [activity.type, ' invite', activity.partyId ? ` · Party ${activity.partyId.slice(-6)}` : ''] })
|
|
182
|
+
] }),
|
|
98
183
|
editedAtIso && (0, jsx_runtime_1.jsx)("span", { className: "dht-edit-marker", title: editedAtIso, "data-edit-iso": editedAtIso, "data-i18n": "edited", "data-i18n-params": JSON.stringify({ time: editedAtIso }), children: '(' + t(context, 'edited', 'edited') + ')' }),
|
|
99
184
|
Array.isArray(message.editHistory) && message.editHistory.length > 0 && renderEditHistory(message.editHistory, context),
|
|
100
185
|
snapshots.length > 0 && renderSnapshots(snapshots, context),
|
|
@@ -109,13 +194,26 @@ async function DiscordMessage({ message, context }) {
|
|
|
109
194
|
message.reactions.cache.size > 0 && ((0, jsx_runtime_1.jsx)("discord-reactions", {
|
|
110
195
|
slot: "reactions",
|
|
111
196
|
children: Array.from(message.reactions.cache.values()).map((reaction, id) => {
|
|
112
|
-
const
|
|
197
|
+
const total = reaction.count ?? 0;
|
|
198
|
+
const burstCount = reaction.count_details?.burst || reaction.burst_count || 0;
|
|
199
|
+
const burst = burstCount > 0;
|
|
200
|
+
const burstColors = Array.isArray(reaction.burst_colors) && reaction.burst_colors.length
|
|
201
|
+
? reaction.burst_colors.filter((c) => typeof c === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(c))
|
|
202
|
+
: [];
|
|
203
|
+
const title = burst
|
|
204
|
+
? `${total} reactions (${burstCount} super)`
|
|
205
|
+
: `${total} reaction${total !== 1 ? 's' : ''}`;
|
|
206
|
+
const style = burst && burstColors.length
|
|
207
|
+
? { boxShadow: `inset 0 0 0 2px ${burstColors[0]}` }
|
|
208
|
+
: undefined;
|
|
113
209
|
return ((0, jsx_runtime_1.jsx)("discord-reaction", {
|
|
114
210
|
name: reaction.emoji?.name || ':unknown:',
|
|
115
211
|
emoji: (0, utils_1.parseDiscordEmoji)(reaction.emoji) || undefined,
|
|
116
|
-
count:
|
|
212
|
+
count: abbrCount(total),
|
|
213
|
+
title,
|
|
117
214
|
"data-burst": burst ? 'true' : undefined,
|
|
118
|
-
className: burst ? 'dht-reaction--burst' : undefined
|
|
215
|
+
className: burst ? 'dht-reaction--burst' : undefined,
|
|
216
|
+
style,
|
|
119
217
|
}, `${message.id}r${id}`));
|
|
120
218
|
})
|
|
121
219
|
})),
|
|
@@ -147,12 +245,25 @@ function renderThread(thread, context) {
|
|
|
147
245
|
function renderStickers(stickers) {
|
|
148
246
|
return (0, jsx_runtime_1.jsx)("div", {
|
|
149
247
|
className: "dht-stickers",
|
|
150
|
-
children: stickers.map((s) =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
248
|
+
children: stickers.map((s) => {
|
|
249
|
+
// Discord sticker formats: 1=PNG, 2=APNG (animated png), 3=Lottie (JSON), 4=GIF
|
|
250
|
+
const format = s.format;
|
|
251
|
+
const baseUrl = `https://media.discordapp.net/stickers/${s.id}`;
|
|
252
|
+
if (format === 3) {
|
|
253
|
+
// Lottie — render as iframe to discord's lottie player CDN page, or fallback link
|
|
254
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-sticker dht-sticker--lottie", title: s.name || '', children: [
|
|
255
|
+
(0, jsx_runtime_1.jsx)("div", { className: "dht-sticker-placeholder", children: '🎞️' }),
|
|
256
|
+
(0, jsx_runtime_1.jsxs)("span", { className: "dht-sticker-name", children: [s.name || 'sticker', ' (Lottie)'] })
|
|
257
|
+
] }, s.id);
|
|
258
|
+
}
|
|
259
|
+
if (format === 4) {
|
|
260
|
+
return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.gif`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker" }, s.id);
|
|
261
|
+
}
|
|
262
|
+
if (format === 2) {
|
|
263
|
+
return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.png`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker dht-sticker--apng" }, s.id);
|
|
264
|
+
}
|
|
265
|
+
return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.png`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker" }, s.id);
|
|
266
|
+
})
|
|
156
267
|
});
|
|
157
268
|
}
|
|
158
269
|
|
|
@@ -160,7 +271,8 @@ function renderPoll(poll, context) {
|
|
|
160
271
|
let totalVotes = 0;
|
|
161
272
|
const answers = poll.answers ? Array.from(poll.answers.values()) : [];
|
|
162
273
|
for (const a of answers) totalVotes += a.voteCount || 0;
|
|
163
|
-
const
|
|
274
|
+
const locale = context?.lang === 'de' ? 'de-DE' : 'en-US';
|
|
275
|
+
const expires = poll.expiresAt instanceof Date ? poll.expiresAt.toLocaleString(locale) : '';
|
|
164
276
|
|
|
165
277
|
return (0, jsx_runtime_1.jsxs)("div", {
|
|
166
278
|
className: "dht-poll",
|
|
@@ -187,51 +299,132 @@ function renderPoll(poll, context) {
|
|
|
187
299
|
}
|
|
188
300
|
|
|
189
301
|
function renderVoiceIndicator(message, context) {
|
|
190
|
-
|
|
302
|
+
// FIX: discord.js Collection.find takes a predicate but on Map it conflicts; iterate values explicitly.
|
|
303
|
+
let audio = null;
|
|
304
|
+
if (message.attachments && typeof message.attachments.values === 'function') {
|
|
305
|
+
for (const a of message.attachments.values()) {
|
|
306
|
+
if (typeof a.contentType === 'string' && a.contentType.startsWith('audio/')) { audio = a; break; }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
191
309
|
const duration = audio?.duration_secs || audio?.durationSecs;
|
|
192
310
|
const waveformB64 = audio?.waveform;
|
|
193
|
-
let
|
|
194
|
-
|
|
311
|
+
let waveformRects = null;
|
|
312
|
+
let waveformWidth = 0;
|
|
313
|
+
if (typeof waveformB64 === 'string' && waveformB64.length > 0 && waveformB64.length < 16384) {
|
|
195
314
|
try {
|
|
196
315
|
const buf = Buffer.from(waveformB64, 'base64');
|
|
197
316
|
const bars = Array.from(buf).slice(0, 100);
|
|
198
317
|
const max = bars.length ? Math.max(1, ...bars) : 1;
|
|
199
|
-
|
|
200
|
-
|
|
318
|
+
waveformWidth = bars.length * 3;
|
|
319
|
+
waveformRects = bars.map((b, i) => {
|
|
201
320
|
const h = Math.max(2, (b / max) * 20);
|
|
202
|
-
return
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
} catch (_e) {}
|
|
321
|
+
return (0, jsx_runtime_1.jsx)("rect", { x: i * 3, y: (22 - h) / 2, width: 2, height: h, rx: 1, fill: "#5865F2" }, i);
|
|
322
|
+
});
|
|
323
|
+
} catch (_e) { waveformRects = null; }
|
|
206
324
|
}
|
|
207
325
|
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-voice", children: [
|
|
208
326
|
(0, jsx_runtime_1.jsx)("span", { className: "dht-voice-icon", children: '🎤' }),
|
|
209
327
|
(0, jsx_runtime_1.jsx)("span", { className: "dht-voice-label", "data-i18n": "voiceMessage", children: t(context, 'voiceMessage', 'Voice message') }),
|
|
210
|
-
|
|
328
|
+
waveformRects && (0, jsx_runtime_1.jsx)("svg", {
|
|
329
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
330
|
+
viewBox: `0 0 ${waveformWidth} 22`,
|
|
331
|
+
width: waveformWidth,
|
|
332
|
+
height: 22,
|
|
333
|
+
className: "dht-voice-wave",
|
|
334
|
+
children: waveformRects,
|
|
335
|
+
}),
|
|
211
336
|
duration ? (0, jsx_runtime_1.jsxs)("span", { className: "dht-voice-dur", children: [Math.round(duration), 's'] }) : null,
|
|
212
337
|
] });
|
|
213
338
|
}
|
|
214
339
|
|
|
215
|
-
function
|
|
340
|
+
function toArray(maybeColl) {
|
|
341
|
+
if (!maybeColl) return [];
|
|
342
|
+
if (Array.isArray(maybeColl)) return maybeColl;
|
|
343
|
+
if (typeof maybeColl.values === 'function') return Array.from(maybeColl.values());
|
|
344
|
+
if (typeof maybeColl === 'object') return Object.values(maybeColl);
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function renderSnapshots(snapshots, context, depth = 0) {
|
|
349
|
+
const arr = toArray(snapshots);
|
|
350
|
+
if (arr.length === 0 || depth >= 5) return null; // hard-cap at 5 levels
|
|
216
351
|
return (0, jsx_runtime_1.jsx)("div", {
|
|
217
352
|
className: "dht-forwarded-wrap",
|
|
218
|
-
children:
|
|
353
|
+
children: arr.map((snap, i) => {
|
|
219
354
|
const m = snap.message || snap;
|
|
220
355
|
const author = m?.author?.displayName || m?.author?.username || 'Unknown';
|
|
221
356
|
const text = m?.content || '';
|
|
222
|
-
|
|
357
|
+
const attachments = toArray(m?.attachments);
|
|
358
|
+
const nested = toArray(m?.messageSnapshots);
|
|
359
|
+
const embeds = toArray(m?.embeds);
|
|
360
|
+
const stickers = toArray(m?.stickers).map((s) => ({
|
|
361
|
+
id: s.id,
|
|
362
|
+
name: s.name,
|
|
363
|
+
url: s.url || `https://media.discordapp.net/stickers/${s.id}.png`,
|
|
364
|
+
format: s.format,
|
|
365
|
+
}));
|
|
366
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded", "data-depth": depth, children: [
|
|
223
367
|
(0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-head", children: [
|
|
224
|
-
(0, jsx_runtime_1.jsx)("span", { className: "dht-forwarded-icon", children: '
|
|
368
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-forwarded-icon", children: '↪' }),
|
|
225
369
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "forwardedFrom", children: t(context, 'forwardedFrom', 'Forwarded from') }),
|
|
226
370
|
(0, jsx_runtime_1.jsx)("strong", { children: ' ' + author }),
|
|
227
371
|
] }),
|
|
228
|
-
text && (0, jsx_runtime_1.jsx)("div", { className: "dht-forwarded-body", children: (0, jsx_runtime_1.jsx)(content_1.default, { content: text, context: Object.assign({}, context, { type: content_1.RenderType.
|
|
229
|
-
|
|
372
|
+
text && (0, jsx_runtime_1.jsx)("div", { className: "dht-forwarded-body", children: (0, jsx_runtime_1.jsx)(content_1.default, { content: text, context: Object.assign({}, context, { type: content_1.RenderType.NORMAL }) }) }),
|
|
373
|
+
attachments.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-attachments", children: [
|
|
374
|
+
(0, jsx_runtime_1.jsxs)("strong", { children: ['📎 ', attachments.length, ' attachment', attachments.length !== 1 ? 's' : '', ':'] }),
|
|
375
|
+
(0, jsx_runtime_1.jsx)("ul", { children: attachments.map((a, ai) => {
|
|
376
|
+
const safeUrl = (0, utils_1.safeHref)(a.url);
|
|
377
|
+
return (0, jsx_runtime_1.jsxs)("li", { children: [
|
|
378
|
+
a.contentType?.startsWith?.('image/') && a.url
|
|
379
|
+
? (0, jsx_runtime_1.jsx)("img", { src: safeUrl !== '#' ? safeUrl : '', alt: a.name || '', style: { maxWidth: '200px', borderRadius: '4px', display: 'block', margin: '4px 0' } })
|
|
380
|
+
: (0, jsx_runtime_1.jsxs)("a", { href: safeUrl, target: "_blank", rel: "noreferrer", children: [a.name || 'attachment', a.size ? ` (${Math.round(a.size / 1024)} KB)` : ''] })
|
|
381
|
+
] }, ai);
|
|
382
|
+
}) })
|
|
383
|
+
] }),
|
|
384
|
+
stickers.length > 0 && renderStickers(stickers),
|
|
385
|
+
embeds.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-embeds", children: ['📑 ', embeds.length, ' embed', embeds.length !== 1 ? 's' : ''] }),
|
|
386
|
+
nested.length > 0 && renderSnapshots(nested, context, depth + 1),
|
|
230
387
|
] }, i);
|
|
231
388
|
})
|
|
232
389
|
});
|
|
233
390
|
}
|
|
234
391
|
|
|
392
|
+
function renderSlashCommandLine(message, context) {
|
|
393
|
+
const interaction = message?.interaction;
|
|
394
|
+
const metadata = message?.interactionMetadata;
|
|
395
|
+
const optsRaw = message?.interactionOptions || interaction?.options;
|
|
396
|
+
// Build command path: /cmd group? subcommand? plus key:value options
|
|
397
|
+
if (!optsRaw && !metadata) return null;
|
|
398
|
+
|
|
399
|
+
const opts = Array.isArray(optsRaw) ? optsRaw : [];
|
|
400
|
+
let cmdParts = [interaction?.commandName || '?'];
|
|
401
|
+
let valueOpts = opts;
|
|
402
|
+
|
|
403
|
+
// Detect subcommand groups: nested option structure { type:2/1 (group/subcommand), name, options }
|
|
404
|
+
for (let i = 0; i < 2; i++) {
|
|
405
|
+
if (valueOpts.length === 1 && valueOpts[0] && (valueOpts[0].type === 1 || valueOpts[0].type === 2)) {
|
|
406
|
+
cmdParts.push(valueOpts[0].name);
|
|
407
|
+
valueOpts = Array.isArray(valueOpts[0].options) ? valueOpts[0].options : [];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Filter out autocomplete-only `focused` options
|
|
411
|
+
valueOpts = valueOpts.filter((o) => o && !o.focused);
|
|
412
|
+
|
|
413
|
+
if (!valueOpts.length && cmdParts.length === 1 && !metadata) return null;
|
|
414
|
+
|
|
415
|
+
const triggeredBy = metadata?.user || interaction?.user;
|
|
416
|
+
|
|
417
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-slash-options", children: [
|
|
418
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-slash-cmd-name", children: '/' + cmdParts.filter(Boolean).join(' ') }),
|
|
419
|
+
valueOpts.map((o, i) => (0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt", children: [
|
|
420
|
+
(0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt-name", children: [String(o.name), ':'] }),
|
|
421
|
+
' ',
|
|
422
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-slash-opt-val", children: String(o.value ?? '') })
|
|
423
|
+
] }, i)),
|
|
424
|
+
triggeredBy && (0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt", children: ['by ', (0, jsx_runtime_1.jsx)("strong", { children: triggeredBy.displayName || triggeredBy.username || triggeredBy.id })] })
|
|
425
|
+
] });
|
|
426
|
+
}
|
|
427
|
+
|
|
235
428
|
function renderEditHistory(history, context) {
|
|
236
429
|
// Optional feature — host bot may attach `message.editHistory: [{content, editedAt}, …]`
|
|
237
430
|
return (0, jsx_runtime_1.jsxs)("details", { className: "dht-edit-history", children: [
|
|
@@ -33,7 +33,12 @@ async function MessageReply({ message, context }) {
|
|
|
33
33
|
return (0, jsx_runtime_1.jsx)("discord-reply", { slot: "reply", children: "Message could not be loaded." });
|
|
34
34
|
|
|
35
35
|
const isCrossPost = referencedMessage.reference && referencedMessage.reference.guildId !== message.guild?.id;
|
|
36
|
-
const isCommand = referencedMessage.interaction
|
|
36
|
+
const isCommand = !!referencedMessage.interaction;
|
|
37
|
+
// Special-case: replying to a forwarded-only / Components-V2-only message has empty content
|
|
38
|
+
const hasContent = !!referencedMessage.content;
|
|
39
|
+
const hasSnapshot = Array.isArray(referencedMessage.messageSnapshots) && referencedMessage.messageSnapshots.length > 0;
|
|
40
|
+
const hasComponents = Array.isArray(referencedMessage.components) && referencedMessage.components.length > 0;
|
|
41
|
+
const hasSticker = referencedMessage.stickers?.size > 0;
|
|
37
42
|
const author = referencedMessage.member?.nickname ?? referencedMessage.author.displayName ?? referencedMessage.author.username;
|
|
38
43
|
const avatar = referencedMessage.author.avatarURL?.({ size: 32 }) ?? undefined;
|
|
39
44
|
const roleColor = referencedMessage.member?.displayHexColor ?? undefined;
|
|
@@ -51,11 +56,17 @@ async function MessageReply({ message, context }) {
|
|
|
51
56
|
op: op,
|
|
52
57
|
server: isCrossPost ?? undefined,
|
|
53
58
|
command: isCommand,
|
|
54
|
-
children:
|
|
59
|
+
children: hasContent
|
|
55
60
|
? (0, jsx_runtime_1.jsx)("span", { "data-goto": referencedMessage.id, className: "reply-inline", children: (0, jsx_runtime_1.jsx)(content_1.default, { content: referencedMessage.content, context: Object.assign({}, context, { type: content_1.RenderType.REPLY }) }) })
|
|
56
61
|
: isCommand
|
|
57
62
|
? (0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see command." })
|
|
58
|
-
:
|
|
63
|
+
: hasSnapshot
|
|
64
|
+
? (0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see forwarded message." })
|
|
65
|
+
: hasComponents
|
|
66
|
+
? (0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see message." })
|
|
67
|
+
: hasSticker
|
|
68
|
+
? (0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see sticker." })
|
|
69
|
+
: (0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see attachment." })
|
|
59
70
|
}));
|
|
60
71
|
}
|
|
61
72
|
//# sourceMappingURL=reply.js.map
|
|
@@ -23,13 +23,15 @@ async function SystemMessage({ message, context }) {
|
|
|
23
23
|
case discord_js_1.MessageType.UserJoin:
|
|
24
24
|
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "join", children: (0, jsx_runtime_1.jsx)(JoinMessage, { member: message.member, fallbackUser: author, seed: message.id, context: ctx }) }, message.id));
|
|
25
25
|
|
|
26
|
-
case discord_js_1.MessageType.ChannelPinnedMessage:
|
|
26
|
+
case discord_js_1.MessageType.ChannelPinnedMessage: {
|
|
27
|
+
const refId = message.reference?.messageId;
|
|
27
28
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "pin", children: [
|
|
28
29
|
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
29
30
|
' ',
|
|
30
31
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "pinned", children: t(ctx, 'pinned', 'pinned a message to this channel.') }),
|
|
31
|
-
(0, jsx_runtime_1.jsx)("i", { "data-goto":
|
|
32
|
+
refId ? (0, jsx_runtime_1.jsx)("i", { "data-goto": refId, className: "dht-jump", role: "button", tabIndex: 0, children: " (jump)" }) : null,
|
|
32
33
|
] }, message.id));
|
|
34
|
+
}
|
|
33
35
|
|
|
34
36
|
case discord_js_1.MessageType.GuildBoost:
|
|
35
37
|
case discord_js_1.MessageType.GuildBoostTier1:
|
|
@@ -42,14 +44,19 @@ async function SystemMessage({ message, context }) {
|
|
|
42
44
|
] }, message.id));
|
|
43
45
|
|
|
44
46
|
case discord_js_1.MessageType.ThreadStarterMessage:
|
|
45
|
-
case discord_js_1.MessageType.ThreadCreated:
|
|
47
|
+
case discord_js_1.MessageType.ThreadCreated: {
|
|
48
|
+
const refId = message.reference?.messageId;
|
|
49
|
+
const label = message.content || 'New thread';
|
|
46
50
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "thread", children: [
|
|
47
51
|
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
48
52
|
' ',
|
|
49
53
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "threadStarted", children: t(ctx, 'threadStarted', 'started a thread:') }),
|
|
50
54
|
' ',
|
|
51
|
-
|
|
55
|
+
refId
|
|
56
|
+
? (0, jsx_runtime_1.jsx)("i", { "data-goto": refId, className: "dht-jump", role: "button", tabIndex: 0, children: label })
|
|
57
|
+
: (0, jsx_runtime_1.jsx)("i", { children: label }),
|
|
52
58
|
] }, message.id));
|
|
59
|
+
}
|
|
53
60
|
|
|
54
61
|
case discord_js_1.MessageType.ChannelNameChange:
|
|
55
62
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
@@ -86,12 +93,17 @@ async function SystemMessage({ message, context }) {
|
|
|
86
93
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "startedCall", children: t(ctx, 'startedCall', 'started a call.') }),
|
|
87
94
|
] }, message.id));
|
|
88
95
|
|
|
89
|
-
case discord_js_1.MessageType.ChannelFollowAdd:
|
|
96
|
+
case discord_js_1.MessageType.ChannelFollowAdd: {
|
|
97
|
+
const followText = t(ctx, 'addedFollow', 'added {target} to follow this channel.');
|
|
98
|
+
const parts = followText.split('{target}');
|
|
90
99
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
91
100
|
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
92
101
|
' ',
|
|
93
|
-
|
|
102
|
+
parts[0],
|
|
103
|
+
(0, jsx_runtime_1.jsxs)("span", { className: "dht-mention dht-mention--channel", children: ['#', message.content || 'channel'] }),
|
|
104
|
+
parts[1] || '',
|
|
94
105
|
] }, message.id));
|
|
106
|
+
}
|
|
95
107
|
|
|
96
108
|
case discord_js_1.MessageType.RecipientRemove:
|
|
97
109
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "leave", children: [
|
|
@@ -101,16 +113,57 @@ async function SystemMessage({ message, context }) {
|
|
|
101
113
|
] }, message.id));
|
|
102
114
|
|
|
103
115
|
case discord_js_1.MessageType.AutoModerationAction: {
|
|
104
|
-
// Discord packs detail into the first embed: matched_keyword,
|
|
116
|
+
// Discord packs detail into the first embed: rule_name, matched_keyword, action_type, channel_id
|
|
105
117
|
const e = message.embeds?.[0];
|
|
106
|
-
const
|
|
118
|
+
const fields = Array.isArray(e?.fields) ? e.fields : [];
|
|
119
|
+
const ruleName = fields.find((f) => /rule_name|rule name/i.test(f.name))?.value || e?.title || '';
|
|
120
|
+
const matchedKeyword = fields.find((f) => /matched_keyword|keyword/i.test(f.name))?.value || '';
|
|
121
|
+
const action = fields.find((f) => /action_type|action/i.test(f.name))?.value || '';
|
|
107
122
|
const blocked = e?.description || '';
|
|
123
|
+
const channelId = fields.find((f) => /channel/i.test(f.name))?.value;
|
|
108
124
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
109
125
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "autoModBlocked", children: t(ctx, 'autoModBlocked', 'AutoMod blocked a message.') }),
|
|
110
|
-
ruleName && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · ', (0,
|
|
126
|
+
ruleName && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · Rule: ', (0, jsx_runtime_1.jsx)("strong", { children: ruleName })] }),
|
|
127
|
+
action && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · Action: ', action] }),
|
|
128
|
+
matchedKeyword && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · Matched: ', (0, jsx_runtime_1.jsx)("code", { children: matchedKeyword })] }),
|
|
111
129
|
blocked && (0, jsx_runtime_1.jsx)("blockquote", { className: "dht-automod-content", children: blocked }),
|
|
130
|
+
channelId && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · In <#', channelId, '>'] }),
|
|
112
131
|
] }, message.id));
|
|
113
132
|
}
|
|
133
|
+
case discord_js_1.MessageType.StageStart:
|
|
134
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
135
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
136
|
+
' started a stage: ',
|
|
137
|
+
(0, jsx_runtime_1.jsx)("strong", { children: message.content || '' })
|
|
138
|
+
] }, message.id));
|
|
139
|
+
case discord_js_1.MessageType.StageEnd:
|
|
140
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "leave", children: [
|
|
141
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
142
|
+
' ended the stage.'
|
|
143
|
+
] }, message.id));
|
|
144
|
+
case discord_js_1.MessageType.StageSpeaker:
|
|
145
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
146
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
147
|
+
' is now a speaker.'
|
|
148
|
+
] }, message.id));
|
|
149
|
+
case discord_js_1.MessageType.StageRaiseHand:
|
|
150
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
151
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
152
|
+
' requested to speak.'
|
|
153
|
+
] }, message.id));
|
|
154
|
+
case discord_js_1.MessageType.StageTopic:
|
|
155
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
156
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
157
|
+
' changed the stage topic: ',
|
|
158
|
+
(0, jsx_runtime_1.jsx)("strong", { children: message.content || '' })
|
|
159
|
+
] }, message.id));
|
|
160
|
+
case discord_js_1.MessageType.GuildApplicationPremiumSubscription:
|
|
161
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
162
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
163
|
+
' subscribed ',
|
|
164
|
+
(0, jsx_runtime_1.jsx)("strong", { children: message.applicationId ? `app ${message.applicationId}` : 'an app' }),
|
|
165
|
+
' to this server.'
|
|
166
|
+
] }, message.id));
|
|
114
167
|
|
|
115
168
|
case discord_js_1.MessageType.RoleSubscriptionPurchase: {
|
|
116
169
|
const roleName = message.roleSubscriptionData?.tierName || message.content || 'a role';
|
package/dist/index.js
CHANGED
|
@@ -44,11 +44,33 @@ async function generateFromMessages(messages, channel, options = {}) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Pre-build resolution maps from messages so mentions resolve to a name even
|
|
48
|
+
// when the user/role/channel isn't in client cache and can't be fetched.
|
|
49
|
+
const userIndex = new Map();
|
|
50
|
+
const roleIndex = new Map();
|
|
51
|
+
const channelIndex = new Map();
|
|
52
|
+
for (const m of transformedMessages) {
|
|
53
|
+
if (m.author) userIndex.set(m.author.id, m.author);
|
|
54
|
+
if (m.member?.user) userIndex.set(m.member.user.id, m.member.user);
|
|
55
|
+
if (m.interaction?.user) userIndex.set(m.interaction.user.id, m.interaction.user);
|
|
56
|
+
if (m.mentions) {
|
|
57
|
+
if (m.mentions.users) for (const [id, u] of m.mentions.users) userIndex.set(id, u);
|
|
58
|
+
if (m.mentions.roles) for (const [id, r] of m.mentions.roles) roleIndex.set(id, r);
|
|
59
|
+
if (m.mentions.channels) for (const [id, c] of m.mentions.channels) channelIndex.set(id, c);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!channel.isDMBased() && channel.guild) {
|
|
63
|
+
// pre-warm every role we can see (cheap — cache only)
|
|
64
|
+
for (const [id, r] of channel.guild.roles.cache) roleIndex.set(id, r);
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
const callbacks = Object.assign({
|
|
48
68
|
resolveImageSrc,
|
|
49
|
-
resolveChannel: async (id) => channel.client.channels.fetch(id).catch(() => null),
|
|
50
|
-
resolveUser: async (id) => channel.client.users.fetch(id).catch(() => null),
|
|
51
|
-
resolveRole: channel.isDMBased()
|
|
69
|
+
resolveChannel: async (id) => channelIndex.get(id) || channel.client.channels.fetch(id).catch(() => null),
|
|
70
|
+
resolveUser: async (id) => userIndex.get(id) || channel.client.users.fetch(id).catch(() => null),
|
|
71
|
+
resolveRole: channel.isDMBased()
|
|
72
|
+
? () => null
|
|
73
|
+
: async (id) => roleIndex.get(id) || channel.guild?.roles.fetch(id).catch(() => null),
|
|
52
74
|
}, options.callbacks ?? {});
|
|
53
75
|
|
|
54
76
|
const rendered = await (0, generator_1.default)({
|