discord-html-transcripts-fix 1.2.0 → 1.4.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 +219 -42
- package/dist/generator/renderers/embed.js +31 -7
- package/dist/generator/renderers/message.js +211 -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 +496 -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,63 @@ 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);
|
|
85
139
|
|
|
86
140
|
return ((0, jsx_runtime_1.jsxs)("discord-message", {
|
|
87
141
|
id: `m-${message.id}`,
|
|
88
142
|
timestamp: message.createdAt,
|
|
89
143
|
edited: message.editedAt !== null,
|
|
90
|
-
server: isCrosspost
|
|
144
|
+
server: (isCrossGuildReply || isCrosspost || isCrossposted) ? true : undefined,
|
|
91
145
|
highlight: message.mentions.everyone || pinned,
|
|
92
146
|
profile: message.author.id,
|
|
147
|
+
className: pinned ? 'dht-msg dht-msg-pinned' : 'dht-msg',
|
|
148
|
+
"data-author-id": message.author.id,
|
|
149
|
+
"data-author-name": authorName,
|
|
150
|
+
"data-roles": memberRoleIds,
|
|
151
|
+
"data-timestamp": ts ? String(ts) : undefined,
|
|
152
|
+
"data-text": lowerText,
|
|
153
|
+
"data-pinned": pinned ? 'true' : undefined,
|
|
154
|
+
"data-has-image": hasImage ? 'true' : undefined,
|
|
155
|
+
"data-has-embed": hasEmbed ? 'true' : undefined,
|
|
156
|
+
"data-has-attachment": hasAttachment ? 'true' : undefined,
|
|
93
157
|
children: [
|
|
94
|
-
pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned", children: "📌" }),
|
|
158
|
+
pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned message", children: "📌" }),
|
|
159
|
+
appliedTagPills,
|
|
95
160
|
(0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }),
|
|
96
161
|
message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", { slot: "reply", profile: message.interaction.user.id, command: '/' + message.interaction.commandName })),
|
|
97
|
-
|
|
162
|
+
slashCmdLine,
|
|
163
|
+
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: '✓' })] }),
|
|
164
|
+
isCrossposted && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Published from announcement channel", children: '📣 Published' }),
|
|
165
|
+
isCrosspost && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Follows another channel", children: '📡 Follow' }),
|
|
166
|
+
isSilent && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-silent", title: "Silent message (no notification)", children: '🔕 Silent' }),
|
|
167
|
+
activity && (0, jsx_runtime_1.jsxs)("div", { className: "dht-activity", children: [
|
|
168
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-activity-icon", children: '🎮' }),
|
|
169
|
+
(0, jsx_runtime_1.jsxs)("span", { children: [activity.type, ' invite', activity.partyId ? ` · Party ${activity.partyId.slice(-6)}` : ''] })
|
|
170
|
+
] }),
|
|
98
171
|
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
172
|
Array.isArray(message.editHistory) && message.editHistory.length > 0 && renderEditHistory(message.editHistory, context),
|
|
100
173
|
snapshots.length > 0 && renderSnapshots(snapshots, context),
|
|
@@ -109,13 +182,26 @@ async function DiscordMessage({ message, context }) {
|
|
|
109
182
|
message.reactions.cache.size > 0 && ((0, jsx_runtime_1.jsx)("discord-reactions", {
|
|
110
183
|
slot: "reactions",
|
|
111
184
|
children: Array.from(message.reactions.cache.values()).map((reaction, id) => {
|
|
112
|
-
const
|
|
185
|
+
const total = reaction.count ?? 0;
|
|
186
|
+
const burstCount = reaction.count_details?.burst || reaction.burst_count || 0;
|
|
187
|
+
const burst = burstCount > 0;
|
|
188
|
+
const burstColors = Array.isArray(reaction.burst_colors) && reaction.burst_colors.length
|
|
189
|
+
? reaction.burst_colors.filter((c) => typeof c === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(c))
|
|
190
|
+
: [];
|
|
191
|
+
const title = burst
|
|
192
|
+
? `${total} reactions (${burstCount} super)`
|
|
193
|
+
: `${total} reaction${total !== 1 ? 's' : ''}`;
|
|
194
|
+
const style = burst && burstColors.length
|
|
195
|
+
? { boxShadow: `inset 0 0 0 2px ${burstColors[0]}` }
|
|
196
|
+
: undefined;
|
|
113
197
|
return ((0, jsx_runtime_1.jsx)("discord-reaction", {
|
|
114
198
|
name: reaction.emoji?.name || ':unknown:',
|
|
115
199
|
emoji: (0, utils_1.parseDiscordEmoji)(reaction.emoji) || undefined,
|
|
116
|
-
count:
|
|
200
|
+
count: abbrCount(total),
|
|
201
|
+
title,
|
|
117
202
|
"data-burst": burst ? 'true' : undefined,
|
|
118
|
-
className: burst ? 'dht-reaction--burst' : undefined
|
|
203
|
+
className: burst ? 'dht-reaction--burst' : undefined,
|
|
204
|
+
style,
|
|
119
205
|
}, `${message.id}r${id}`));
|
|
120
206
|
})
|
|
121
207
|
})),
|
|
@@ -147,12 +233,25 @@ function renderThread(thread, context) {
|
|
|
147
233
|
function renderStickers(stickers) {
|
|
148
234
|
return (0, jsx_runtime_1.jsx)("div", {
|
|
149
235
|
className: "dht-stickers",
|
|
150
|
-
children: stickers.map((s) =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
236
|
+
children: stickers.map((s) => {
|
|
237
|
+
// Discord sticker formats: 1=PNG, 2=APNG (animated png), 3=Lottie (JSON), 4=GIF
|
|
238
|
+
const format = s.format;
|
|
239
|
+
const baseUrl = `https://media.discordapp.net/stickers/${s.id}`;
|
|
240
|
+
if (format === 3) {
|
|
241
|
+
// Lottie — render as iframe to discord's lottie player CDN page, or fallback link
|
|
242
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-sticker dht-sticker--lottie", title: s.name || '', children: [
|
|
243
|
+
(0, jsx_runtime_1.jsx)("div", { className: "dht-sticker-placeholder", children: '🎞️' }),
|
|
244
|
+
(0, jsx_runtime_1.jsxs)("span", { className: "dht-sticker-name", children: [s.name || 'sticker', ' (Lottie)'] })
|
|
245
|
+
] }, s.id);
|
|
246
|
+
}
|
|
247
|
+
if (format === 4) {
|
|
248
|
+
return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.gif`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker" }, s.id);
|
|
249
|
+
}
|
|
250
|
+
if (format === 2) {
|
|
251
|
+
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);
|
|
252
|
+
}
|
|
253
|
+
return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.png`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker" }, s.id);
|
|
254
|
+
})
|
|
156
255
|
});
|
|
157
256
|
}
|
|
158
257
|
|
|
@@ -160,7 +259,8 @@ function renderPoll(poll, context) {
|
|
|
160
259
|
let totalVotes = 0;
|
|
161
260
|
const answers = poll.answers ? Array.from(poll.answers.values()) : [];
|
|
162
261
|
for (const a of answers) totalVotes += a.voteCount || 0;
|
|
163
|
-
const
|
|
262
|
+
const locale = context?.lang === 'de' ? 'de-DE' : 'en-US';
|
|
263
|
+
const expires = poll.expiresAt instanceof Date ? poll.expiresAt.toLocaleString(locale) : '';
|
|
164
264
|
|
|
165
265
|
return (0, jsx_runtime_1.jsxs)("div", {
|
|
166
266
|
className: "dht-poll",
|
|
@@ -187,51 +287,132 @@ function renderPoll(poll, context) {
|
|
|
187
287
|
}
|
|
188
288
|
|
|
189
289
|
function renderVoiceIndicator(message, context) {
|
|
190
|
-
|
|
290
|
+
// FIX: discord.js Collection.find takes a predicate but on Map it conflicts; iterate values explicitly.
|
|
291
|
+
let audio = null;
|
|
292
|
+
if (message.attachments && typeof message.attachments.values === 'function') {
|
|
293
|
+
for (const a of message.attachments.values()) {
|
|
294
|
+
if (typeof a.contentType === 'string' && a.contentType.startsWith('audio/')) { audio = a; break; }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
191
297
|
const duration = audio?.duration_secs || audio?.durationSecs;
|
|
192
298
|
const waveformB64 = audio?.waveform;
|
|
193
|
-
let
|
|
194
|
-
|
|
299
|
+
let waveformRects = null;
|
|
300
|
+
let waveformWidth = 0;
|
|
301
|
+
if (typeof waveformB64 === 'string' && waveformB64.length > 0 && waveformB64.length < 16384) {
|
|
195
302
|
try {
|
|
196
303
|
const buf = Buffer.from(waveformB64, 'base64');
|
|
197
304
|
const bars = Array.from(buf).slice(0, 100);
|
|
198
305
|
const max = bars.length ? Math.max(1, ...bars) : 1;
|
|
199
|
-
|
|
200
|
-
|
|
306
|
+
waveformWidth = bars.length * 3;
|
|
307
|
+
waveformRects = bars.map((b, i) => {
|
|
201
308
|
const h = Math.max(2, (b / max) * 20);
|
|
202
|
-
return
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
} catch (_e) {}
|
|
309
|
+
return (0, jsx_runtime_1.jsx)("rect", { x: i * 3, y: (22 - h) / 2, width: 2, height: h, rx: 1, fill: "#5865F2" }, i);
|
|
310
|
+
});
|
|
311
|
+
} catch (_e) { waveformRects = null; }
|
|
206
312
|
}
|
|
207
313
|
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-voice", children: [
|
|
208
314
|
(0, jsx_runtime_1.jsx)("span", { className: "dht-voice-icon", children: '🎤' }),
|
|
209
315
|
(0, jsx_runtime_1.jsx)("span", { className: "dht-voice-label", "data-i18n": "voiceMessage", children: t(context, 'voiceMessage', 'Voice message') }),
|
|
210
|
-
|
|
316
|
+
waveformRects && (0, jsx_runtime_1.jsx)("svg", {
|
|
317
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
318
|
+
viewBox: `0 0 ${waveformWidth} 22`,
|
|
319
|
+
width: waveformWidth,
|
|
320
|
+
height: 22,
|
|
321
|
+
className: "dht-voice-wave",
|
|
322
|
+
children: waveformRects,
|
|
323
|
+
}),
|
|
211
324
|
duration ? (0, jsx_runtime_1.jsxs)("span", { className: "dht-voice-dur", children: [Math.round(duration), 's'] }) : null,
|
|
212
325
|
] });
|
|
213
326
|
}
|
|
214
327
|
|
|
215
|
-
function
|
|
328
|
+
function toArray(maybeColl) {
|
|
329
|
+
if (!maybeColl) return [];
|
|
330
|
+
if (Array.isArray(maybeColl)) return maybeColl;
|
|
331
|
+
if (typeof maybeColl.values === 'function') return Array.from(maybeColl.values());
|
|
332
|
+
if (typeof maybeColl === 'object') return Object.values(maybeColl);
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function renderSnapshots(snapshots, context, depth = 0) {
|
|
337
|
+
const arr = toArray(snapshots);
|
|
338
|
+
if (arr.length === 0 || depth >= 5) return null; // hard-cap at 5 levels
|
|
216
339
|
return (0, jsx_runtime_1.jsx)("div", {
|
|
217
340
|
className: "dht-forwarded-wrap",
|
|
218
|
-
children:
|
|
341
|
+
children: arr.map((snap, i) => {
|
|
219
342
|
const m = snap.message || snap;
|
|
220
343
|
const author = m?.author?.displayName || m?.author?.username || 'Unknown';
|
|
221
344
|
const text = m?.content || '';
|
|
222
|
-
|
|
345
|
+
const attachments = toArray(m?.attachments);
|
|
346
|
+
const nested = toArray(m?.messageSnapshots);
|
|
347
|
+
const embeds = toArray(m?.embeds);
|
|
348
|
+
const stickers = toArray(m?.stickers).map((s) => ({
|
|
349
|
+
id: s.id,
|
|
350
|
+
name: s.name,
|
|
351
|
+
url: s.url || `https://media.discordapp.net/stickers/${s.id}.png`,
|
|
352
|
+
format: s.format,
|
|
353
|
+
}));
|
|
354
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded", "data-depth": depth, children: [
|
|
223
355
|
(0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-head", children: [
|
|
224
|
-
(0, jsx_runtime_1.jsx)("span", { className: "dht-forwarded-icon", children: '
|
|
356
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-forwarded-icon", children: '↪' }),
|
|
225
357
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "forwardedFrom", children: t(context, 'forwardedFrom', 'Forwarded from') }),
|
|
226
358
|
(0, jsx_runtime_1.jsx)("strong", { children: ' ' + author }),
|
|
227
359
|
] }),
|
|
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
|
-
|
|
360
|
+
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 }) }) }),
|
|
361
|
+
attachments.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-attachments", children: [
|
|
362
|
+
(0, jsx_runtime_1.jsxs)("strong", { children: ['📎 ', attachments.length, ' attachment', attachments.length !== 1 ? 's' : '', ':'] }),
|
|
363
|
+
(0, jsx_runtime_1.jsx)("ul", { children: attachments.map((a, ai) => {
|
|
364
|
+
const safeUrl = (0, utils_1.safeHref)(a.url);
|
|
365
|
+
return (0, jsx_runtime_1.jsxs)("li", { children: [
|
|
366
|
+
a.contentType?.startsWith?.('image/') && a.url
|
|
367
|
+
? (0, jsx_runtime_1.jsx)("img", { src: safeUrl !== '#' ? safeUrl : '', alt: a.name || '', style: { maxWidth: '200px', borderRadius: '4px', display: 'block', margin: '4px 0' } })
|
|
368
|
+
: (0, jsx_runtime_1.jsxs)("a", { href: safeUrl, target: "_blank", rel: "noreferrer", children: [a.name || 'attachment', a.size ? ` (${Math.round(a.size / 1024)} KB)` : ''] })
|
|
369
|
+
] }, ai);
|
|
370
|
+
}) })
|
|
371
|
+
] }),
|
|
372
|
+
stickers.length > 0 && renderStickers(stickers),
|
|
373
|
+
embeds.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-embeds", children: ['📑 ', embeds.length, ' embed', embeds.length !== 1 ? 's' : ''] }),
|
|
374
|
+
nested.length > 0 && renderSnapshots(nested, context, depth + 1),
|
|
230
375
|
] }, i);
|
|
231
376
|
})
|
|
232
377
|
});
|
|
233
378
|
}
|
|
234
379
|
|
|
380
|
+
function renderSlashCommandLine(message, context) {
|
|
381
|
+
const interaction = message?.interaction;
|
|
382
|
+
const metadata = message?.interactionMetadata;
|
|
383
|
+
const optsRaw = message?.interactionOptions || interaction?.options;
|
|
384
|
+
// Build command path: /cmd group? subcommand? plus key:value options
|
|
385
|
+
if (!optsRaw && !metadata) return null;
|
|
386
|
+
|
|
387
|
+
const opts = Array.isArray(optsRaw) ? optsRaw : [];
|
|
388
|
+
let cmdParts = [interaction?.commandName || '?'];
|
|
389
|
+
let valueOpts = opts;
|
|
390
|
+
|
|
391
|
+
// Detect subcommand groups: nested option structure { type:2/1 (group/subcommand), name, options }
|
|
392
|
+
for (let i = 0; i < 2; i++) {
|
|
393
|
+
if (valueOpts.length === 1 && valueOpts[0] && (valueOpts[0].type === 1 || valueOpts[0].type === 2)) {
|
|
394
|
+
cmdParts.push(valueOpts[0].name);
|
|
395
|
+
valueOpts = Array.isArray(valueOpts[0].options) ? valueOpts[0].options : [];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Filter out autocomplete-only `focused` options
|
|
399
|
+
valueOpts = valueOpts.filter((o) => o && !o.focused);
|
|
400
|
+
|
|
401
|
+
if (!valueOpts.length && cmdParts.length === 1 && !metadata) return null;
|
|
402
|
+
|
|
403
|
+
const triggeredBy = metadata?.user || interaction?.user;
|
|
404
|
+
|
|
405
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-slash-options", children: [
|
|
406
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-slash-cmd-name", children: '/' + cmdParts.filter(Boolean).join(' ') }),
|
|
407
|
+
valueOpts.map((o, i) => (0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt", children: [
|
|
408
|
+
(0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt-name", children: [String(o.name), ':'] }),
|
|
409
|
+
' ',
|
|
410
|
+
(0, jsx_runtime_1.jsx)("span", { className: "dht-slash-opt-val", children: String(o.value ?? '') })
|
|
411
|
+
] }, i)),
|
|
412
|
+
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 })] })
|
|
413
|
+
] });
|
|
414
|
+
}
|
|
415
|
+
|
|
235
416
|
function renderEditHistory(history, context) {
|
|
236
417
|
// Optional feature — host bot may attach `message.editHistory: [{content, editedAt}, …]`
|
|
237
418
|
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)({
|