discord-html-transcripts-fix 1.4.0 → 1.7.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/generator/index.js +51 -19
- package/dist/generator/renderers/components/Container.js +12 -4
- package/dist/generator/renderers/components/section/SectionContent.js +7 -3
- package/dist/generator/renderers/message.js +108 -32
- package/dist/generator/renderers/systemMessage.js +52 -24
- package/dist/generator/transcript.js +29 -9
- package/dist/static/client.js +372 -180
- package/dist/types.d.ts +14 -0
- package/dist/utils/buildProfiles.js +19 -10
- package/package.json +17 -29
package/dist/generator/index.js
CHANGED
|
@@ -134,7 +134,6 @@ async function render(_a) {
|
|
|
134
134
|
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.searchBarStyles } }),
|
|
135
135
|
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.lightboxStyles } }),
|
|
136
136
|
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.tocStyles } }),
|
|
137
|
-
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.i18nStyles } }),
|
|
138
137
|
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.dateSeparatorStyles } }),
|
|
139
138
|
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.markdownStyles } }),
|
|
140
139
|
(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.filterPanelStyles } }),
|
|
@@ -155,16 +154,13 @@ async function render(_a) {
|
|
|
155
154
|
] }),
|
|
156
155
|
(0, jsx_runtime_1.jsxs)("body", { style: { margin: 0, minHeight: '100vh' }, children: [
|
|
157
156
|
(0, jsx_runtime_1.jsx)(transcript_1.default, Object.assign({ messages, channel, callbacks }, options, renderContext)),
|
|
158
|
-
(
|
|
157
|
+
renderStatsFooter(stats, lang, i18n, options.statsFooter),
|
|
159
158
|
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.mentionPopup } }),
|
|
160
|
-
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.backToTop } }),
|
|
161
159
|
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.searchBar } }),
|
|
162
160
|
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.lightbox } }),
|
|
163
161
|
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.toc } }),
|
|
164
|
-
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.i18nSwitcher } }),
|
|
165
162
|
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.filterPanel } }),
|
|
166
163
|
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.codeCopyButton } }),
|
|
167
|
-
(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.permalinkButton } }),
|
|
168
164
|
options.hydrate && (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.revealSpoiler } }),
|
|
169
165
|
] })
|
|
170
166
|
] });
|
|
@@ -200,27 +196,63 @@ function formatSpan(ms, lang) {
|
|
|
200
196
|
if (mins > 0) return `${mins}m`;
|
|
201
197
|
return '<1m';
|
|
202
198
|
}
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
// Renders the bottom stats footer. Caller-controlled via options.statsFooter:
|
|
200
|
+
// false → no footer
|
|
201
|
+
// { enabled: false } → no footer
|
|
202
|
+
// { template: '{messages} ...' } → render with custom template
|
|
203
|
+
// Placeholders: {messages} {participants}
|
|
204
|
+
// {images} {from} {to} {span}
|
|
205
|
+
// Default: en template "X messages · Y participants · Z images · from → to · span"
|
|
206
|
+
function renderStatsFooter(stats, lang, i18n, opts) {
|
|
207
|
+
if (opts === false) return null;
|
|
208
|
+
const config = (opts && typeof opts === 'object') ? opts : {};
|
|
209
|
+
if (config.enabled === false) return null;
|
|
210
|
+
|
|
205
211
|
const locale = lang === 'de' ? 'de-DE' : 'en-US';
|
|
206
|
-
const fmtDate = (ms) => new Date(ms).toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' });
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
const fmtDate = (ms) => ms ? new Date(ms).toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' }) : '';
|
|
213
|
+
const span = stats.spanMs ? formatSpan(stats.spanMs) : '';
|
|
214
|
+
|
|
215
|
+
const values = {
|
|
216
|
+
messages: String(stats.messageCount),
|
|
217
|
+
participants: String(stats.participantCount),
|
|
218
|
+
images: String(stats.imageCount),
|
|
219
|
+
from: fmtDate(stats.firstTs),
|
|
220
|
+
to: fmtDate(stats.lastTs),
|
|
221
|
+
span,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (typeof config.template === 'string' && config.template.length > 0) {
|
|
225
|
+
const rendered = config.template.replace(/\{(messages|participants|images|from|to|span)\}/g, (_, k) => values[k] || '');
|
|
226
|
+
return (0, jsx_runtime_1.jsx)("footer", { className: "dht-stats", children: rendered });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const dict = (i18n && i18n[lang]) || (i18n && i18n.en) || {};
|
|
230
|
+
const parts = [
|
|
231
|
+
(dict.statsMessages || '{n} messages').replace('{n}', values.messages),
|
|
232
|
+
(dict.statsParticipants || '{n} participants').replace('{n}', values.participants),
|
|
233
|
+
(dict.statsImages || '{n} images').replace('{n}', values.images),
|
|
234
|
+
];
|
|
235
|
+
if (values.from && values.to) parts.push(values.from + ' → ' + values.to);
|
|
236
|
+
if (values.span) parts.push(values.span);
|
|
237
|
+
return (0, jsx_runtime_1.jsx)("footer", { className: "dht-stats", children: parts.join(' · ') });
|
|
216
238
|
}
|
|
217
239
|
|
|
218
240
|
const extraStyles = `
|
|
219
241
|
/* Ensure the host establishes a positioning context for the absolute pin indicator,
|
|
220
242
|
and clamp it inside the message box on narrow viewports. */
|
|
243
|
+
/* Action-row buttons — Discord-like horizontal spacing */
|
|
244
|
+
discord-action-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:8px 0}
|
|
245
|
+
.dht-v2-container discord-action-row,.dht-section-content discord-action-row{margin:6px 0}
|
|
246
|
+
/* V2 container — Discord-like spacing inside block flow */
|
|
247
|
+
.dht-v2-container .dht-heading,.dht-section-content .dht-heading{margin:8px 0 4px}
|
|
248
|
+
.dht-v2-container .dht-heading:first-child,.dht-section-content .dht-heading:first-child{margin-top:0}
|
|
249
|
+
.dht-v2-container .dht-v2-separator,.dht-section-content .dht-v2-separator{margin:8px 0}
|
|
250
|
+
.dht-v2-container strong,.dht-section-content strong{display:inline;font-weight:700;color:#fff}
|
|
251
|
+
.dht-v2-container .dht-mention,.dht-section-content .dht-mention{display:inline-block;vertical-align:baseline}
|
|
252
|
+
/* System-message author — clickable like a user mention */
|
|
253
|
+
.dht-sys-author{cursor:pointer;font-style:normal;font-weight:500}
|
|
254
|
+
.dht-sys-author:hover{text-decoration:underline}
|
|
221
255
|
.dht-msg-pinned{position:relative}
|
|
222
|
-
.dht-pinned-indicator{position:absolute;top:4px;left:4px;font-size:12px;opacity:.75;pointer-events:none}
|
|
223
|
-
@media (max-width:640px){.dht-pinned-indicator{position:static;display:inline-block;margin-right:4px}}
|
|
224
256
|
.dht-app-badge,.dht-popup-badge{background:#5865F2;color:#fff;font-size:10px;padding:1px 6px;border-radius:4px;margin-left:6px;vertical-align:middle;font-weight:600}
|
|
225
257
|
.dht-edit-marker{color:#949ba4;font-size:11px;margin-left:4px;cursor:help}
|
|
226
258
|
.dht-suppressed{color:#6e727a;font-size:11px;font-style:italic}
|
|
@@ -19,12 +19,17 @@ function DiscordContainer({ children, accentColor, spoiler }) {
|
|
|
19
19
|
// string can't smuggle CSS into the inline borderLeft.
|
|
20
20
|
const raw = colorToHex(accentColor);
|
|
21
21
|
const hex = raw ? (0, utils_1.safeColor)(raw, null) : null;
|
|
22
|
+
// FIX: previous layout was display:flex;flex-direction:column;gap:8px which
|
|
23
|
+
// forced every direct child — including inline <strong>, <br>, mention
|
|
24
|
+
// <span> and text nodes — to become its own vertical flex item. That broke
|
|
25
|
+
// sentences across multiple lines and made role mentions fill the entire
|
|
26
|
+
// row. Use normal block flow instead so inline content lays out the way
|
|
27
|
+
// Discord renders it.
|
|
22
28
|
const style = {
|
|
23
|
-
display: '
|
|
29
|
+
display: 'block',
|
|
24
30
|
width: '100%',
|
|
25
31
|
maxWidth: '500px',
|
|
26
32
|
minWidth: 0,
|
|
27
|
-
flexDirection: 'column',
|
|
28
33
|
backgroundColor: '#3f4248',
|
|
29
34
|
padding: '16px',
|
|
30
35
|
borderLeft: hex ? `4px solid ${hex}` : '1px solid #4f5359',
|
|
@@ -34,10 +39,13 @@ function DiscordContainer({ children, accentColor, spoiler }) {
|
|
|
34
39
|
marginTop: '2px',
|
|
35
40
|
marginBottom: '2px',
|
|
36
41
|
borderRadius: '10px',
|
|
37
|
-
gap: '8px',
|
|
38
42
|
boxSizing: 'border-box',
|
|
43
|
+
lineHeight: 1.4,
|
|
44
|
+
color: '#dbdee1',
|
|
45
|
+
wordBreak: 'break-word',
|
|
46
|
+
overflowWrap: 'anywhere',
|
|
39
47
|
};
|
|
40
|
-
const node = (0, jsx_runtime_1.jsx)("div", { style, children });
|
|
48
|
+
const node = (0, jsx_runtime_1.jsx)("div", { className: "dht-v2-container", style, children });
|
|
41
49
|
if (spoiler) {
|
|
42
50
|
return (0, jsx_runtime_1.jsx)("span", { className: "discord-spoiler", children: node });
|
|
43
51
|
}
|
|
@@ -3,12 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
4
|
function SectionContent({ children }) {
|
|
5
5
|
return ((0, jsx_runtime_1.jsx)("div", {
|
|
6
|
+
className: "dht-section-content",
|
|
6
7
|
style: {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
// Block flow so inline content (mentions, strong, br, text) flows
|
|
9
|
+
// naturally inside the section instead of becoming flex rows.
|
|
10
|
+
display: 'block',
|
|
9
11
|
flex: 1,
|
|
10
|
-
minWidth: 0,
|
|
12
|
+
minWidth: 0,
|
|
11
13
|
overflowWrap: 'anywhere',
|
|
14
|
+
wordBreak: 'break-word',
|
|
15
|
+
lineHeight: 1.4,
|
|
12
16
|
},
|
|
13
17
|
children
|
|
14
18
|
}));
|
|
@@ -80,9 +80,11 @@ async function DiscordMessage({ message, context }) {
|
|
|
80
80
|
const authorName = (message.member?.nickname || message.author?.displayName || message.author?.username || '').toLowerCase();
|
|
81
81
|
const ts = message.createdAt instanceof Date ? message.createdAt.getTime() : 0;
|
|
82
82
|
const lowerText = (message.content || '').toLowerCase().slice(0, 4096);
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
83
|
+
const mediaFlags = detectMediaFlags(message);
|
|
84
|
+
const hasImage = mediaFlags.hasImage;
|
|
85
|
+
const hasEmbed = mediaFlags.hasEmbed;
|
|
86
|
+
const hasAttachment = mediaFlags.hasAttachment;
|
|
87
|
+
const hasComponentV2 = mediaFlags.hasComponentV2;
|
|
86
88
|
const stickers = message.stickers && message.stickers.size > 0
|
|
87
89
|
? Array.from(message.stickers.values()).map((s) => ({
|
|
88
90
|
id: s.id,
|
|
@@ -107,11 +109,8 @@ async function DiscordMessage({ message, context }) {
|
|
|
107
109
|
// Forwarded message snapshots (messageSnapshots) — render before the empty body
|
|
108
110
|
const snapshots = Array.isArray(message.messageSnapshots) ? message.messageSnapshots : [];
|
|
109
111
|
|
|
110
|
-
const isApp = !!(message.author?.bot && message.applicationId);
|
|
111
|
-
|
|
112
112
|
const editedAtIso = message.editedAt instanceof Date ? message.editedAt.toISOString() : null;
|
|
113
113
|
const pinned = !!message.pinned;
|
|
114
|
-
const verifiedBot = !!message.author?.flags?.has?.(discord_js_1.UserFlags.VerifiedBot);
|
|
115
114
|
|
|
116
115
|
// Forum applied tags — only on the first message of a forum thread (the post itself)
|
|
117
116
|
let appliedTagPills = null;
|
|
@@ -134,8 +133,10 @@ async function DiscordMessage({ message, context }) {
|
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
//
|
|
138
|
-
|
|
136
|
+
// Slash command interaction data is attached to <discord-command> as
|
|
137
|
+
// data attributes so the popup can show parameters Discord-style. We
|
|
138
|
+
// no longer render a separate "/cmd opt:val by user" line.
|
|
139
|
+
const slashData = buildSlashCommandData(message);
|
|
139
140
|
|
|
140
141
|
return ((0, jsx_runtime_1.jsxs)("discord-message", {
|
|
141
142
|
id: `m-${message.id}`,
|
|
@@ -154,13 +155,20 @@ async function DiscordMessage({ message, context }) {
|
|
|
154
155
|
"data-has-image": hasImage ? 'true' : undefined,
|
|
155
156
|
"data-has-embed": hasEmbed ? 'true' : undefined,
|
|
156
157
|
"data-has-attachment": hasAttachment ? 'true' : undefined,
|
|
158
|
+
"data-has-component-v2": hasComponentV2 ? 'true' : undefined,
|
|
157
159
|
children: [
|
|
158
|
-
pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned message", children: "📌" }),
|
|
159
160
|
appliedTagPills,
|
|
160
161
|
(0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }),
|
|
161
|
-
message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", {
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", {
|
|
163
|
+
slot: "reply",
|
|
164
|
+
profile: message.interaction.user.id,
|
|
165
|
+
command: (slashData && slashData.cmd) || ('/' + message.interaction.commandName),
|
|
166
|
+
className: "dht-slash-clickable",
|
|
167
|
+
"data-slash-cmd": slashData ? slashData.cmd : ('/' + message.interaction.commandName),
|
|
168
|
+
"data-slash-by": slashData ? slashData.by : '',
|
|
169
|
+
"data-slash-by-id": slashData ? slashData.byId : message.interaction.user.id,
|
|
170
|
+
"data-slash-options": JSON.stringify((slashData && slashData.opts) || []),
|
|
171
|
+
})),
|
|
164
172
|
isCrossposted && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Published from announcement channel", children: '📣 Published' }),
|
|
165
173
|
isCrosspost && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Follows another channel", children: '📡 Follow' }),
|
|
166
174
|
isSilent && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-silent", title: "Silent message (no notification)", children: '🔕 Silent' }),
|
|
@@ -377,40 +385,108 @@ function renderSnapshots(snapshots, context, depth = 0) {
|
|
|
377
385
|
});
|
|
378
386
|
}
|
|
379
387
|
|
|
380
|
-
|
|
388
|
+
const COMPONENT_TYPE_ACTION_ROW = 1;
|
|
389
|
+
const COMPONENT_TYPE_SECTION = 9;
|
|
390
|
+
const COMPONENT_TYPE_THUMBNAIL = 11;
|
|
391
|
+
const COMPONENT_TYPE_MEDIA_GALLERY = 12;
|
|
392
|
+
const COMPONENT_TYPE_FILE = 13;
|
|
393
|
+
const COMPONENT_TYPE_CONTAINER = 17;
|
|
394
|
+
const FLAG_IS_COMPONENTS_V2 = 1 << 15;
|
|
395
|
+
|
|
396
|
+
// Walks message.embeds and message.components to determine whether the message
|
|
397
|
+
// carries media or V2 containers — used to drive the filter checkboxes
|
|
398
|
+
// (data-has-image, data-has-attachment, data-has-embed, data-has-component-v2).
|
|
399
|
+
function detectMediaFlags(message) {
|
|
400
|
+
const out = { hasImage: false, hasAttachment: false, hasEmbed: false, hasComponentV2: false };
|
|
401
|
+
|
|
402
|
+
// Direct attachments
|
|
403
|
+
if (message.attachments && typeof message.attachments.values === 'function') {
|
|
404
|
+
for (const a of message.attachments.values()) {
|
|
405
|
+
out.hasAttachment = true;
|
|
406
|
+
const ct = typeof a.contentType === 'string' ? a.contentType : '';
|
|
407
|
+
if (ct.startsWith('image/') || ct.startsWith('video/')) out.hasImage = true;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Classic embeds
|
|
412
|
+
if (Array.isArray(message.embeds)) {
|
|
413
|
+
for (const e of message.embeds) {
|
|
414
|
+
if (!e) continue;
|
|
415
|
+
out.hasEmbed = true;
|
|
416
|
+
if (e.image || e.thumbnail) out.hasImage = true;
|
|
417
|
+
if (e.video && (e.video.url || e.video.proxyURL || e.video.proxy_url)) out.hasImage = true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Components V2 — message-level flag and recursive walk
|
|
422
|
+
const flags = (typeof message.flags?.bitfield === 'number') ? message.flags.bitfield : 0;
|
|
423
|
+
if (flags & FLAG_IS_COMPONENTS_V2) out.hasComponentV2 = true;
|
|
424
|
+
|
|
425
|
+
const walk = (comp) => {
|
|
426
|
+
if (!comp || typeof comp.type !== 'number') return;
|
|
427
|
+
switch (comp.type) {
|
|
428
|
+
case COMPONENT_TYPE_CONTAINER:
|
|
429
|
+
out.hasComponentV2 = true;
|
|
430
|
+
if (Array.isArray(comp.components)) for (const c of comp.components) walk(c);
|
|
431
|
+
break;
|
|
432
|
+
case COMPONENT_TYPE_SECTION:
|
|
433
|
+
if (Array.isArray(comp.components)) for (const c of comp.components) walk(c);
|
|
434
|
+
if (comp.accessory) walk(comp.accessory);
|
|
435
|
+
break;
|
|
436
|
+
case COMPONENT_TYPE_ACTION_ROW:
|
|
437
|
+
if (Array.isArray(comp.components)) for (const c of comp.components) walk(c);
|
|
438
|
+
break;
|
|
439
|
+
case COMPONENT_TYPE_MEDIA_GALLERY:
|
|
440
|
+
if (Array.isArray(comp.items) && comp.items.length > 0) out.hasImage = true;
|
|
441
|
+
break;
|
|
442
|
+
case COMPONENT_TYPE_THUMBNAIL:
|
|
443
|
+
if (comp.media && (comp.media.url || comp.media.proxy_url || comp.media.proxyURL)) out.hasImage = true;
|
|
444
|
+
break;
|
|
445
|
+
case COMPONENT_TYPE_FILE:
|
|
446
|
+
out.hasAttachment = true;
|
|
447
|
+
if (comp.file && typeof comp.file.url === 'string' && /\.(png|jpe?g|gif|webp|bmp|svg|mp4|webm|mov)(\?|$)/i.test(comp.file.url)) out.hasImage = true;
|
|
448
|
+
break;
|
|
449
|
+
default:
|
|
450
|
+
if (Array.isArray(comp.components)) for (const c of comp.components) walk(c);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
if (Array.isArray(message.components)) for (const c of message.components) walk(c);
|
|
455
|
+
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function buildSlashCommandData(message) {
|
|
381
460
|
const interaction = message?.interaction;
|
|
382
461
|
const metadata = message?.interactionMetadata;
|
|
383
462
|
const optsRaw = message?.interactionOptions || interaction?.options;
|
|
384
|
-
|
|
385
|
-
if (!optsRaw && !metadata) return null;
|
|
463
|
+
if (!interaction && !metadata) return null;
|
|
386
464
|
|
|
387
|
-
|
|
388
|
-
let
|
|
389
|
-
let valueOpts = opts;
|
|
465
|
+
let cmdParts = [interaction?.commandName || metadata?.name || '?'];
|
|
466
|
+
let valueOpts = Array.isArray(optsRaw) ? optsRaw : [];
|
|
390
467
|
|
|
391
|
-
//
|
|
468
|
+
// Walk subcommand-group → subcommand nesting.
|
|
392
469
|
for (let i = 0; i < 2; i++) {
|
|
393
470
|
if (valueOpts.length === 1 && valueOpts[0] && (valueOpts[0].type === 1 || valueOpts[0].type === 2)) {
|
|
394
471
|
cmdParts.push(valueOpts[0].name);
|
|
395
472
|
valueOpts = Array.isArray(valueOpts[0].options) ? valueOpts[0].options : [];
|
|
396
473
|
}
|
|
397
474
|
}
|
|
398
|
-
// Filter out autocomplete-only `focused` options
|
|
399
475
|
valueOpts = valueOpts.filter((o) => o && !o.focused);
|
|
400
476
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
477
|
+
const triggeredBy = metadata?.user || interaction?.user || null;
|
|
478
|
+
const opts = valueOpts.map((o) => ({
|
|
479
|
+
name: String(o.name ?? ''),
|
|
480
|
+
value: o.value == null ? '' : String(o.value),
|
|
481
|
+
type: typeof o.type === 'number' ? o.type : null,
|
|
482
|
+
}));
|
|
404
483
|
|
|
405
|
-
return
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
] });
|
|
484
|
+
return {
|
|
485
|
+
cmd: '/' + cmdParts.filter(Boolean).join(' '),
|
|
486
|
+
opts,
|
|
487
|
+
by: triggeredBy ? (triggeredBy.displayName || triggeredBy.username || triggeredBy.id || '') : '',
|
|
488
|
+
byId: triggeredBy ? (triggeredBy.id || '') : '',
|
|
489
|
+
};
|
|
414
490
|
}
|
|
415
491
|
|
|
416
492
|
function renderEditHistory(history, context) {
|
|
@@ -16,6 +16,7 @@ async function SystemMessage({ message, context }) {
|
|
|
16
16
|
const author = message.author;
|
|
17
17
|
const authorName = author?.displayName ?? author?.username ?? 'Unknown';
|
|
18
18
|
const color = message.member?.roles?.color?.hexColor;
|
|
19
|
+
const authorId = author?.id;
|
|
19
20
|
const ctx = context || {};
|
|
20
21
|
|
|
21
22
|
switch (message.type) {
|
|
@@ -25,12 +26,31 @@ async function SystemMessage({ message, context }) {
|
|
|
25
26
|
|
|
26
27
|
case discord_js_1.MessageType.ChannelPinnedMessage: {
|
|
27
28
|
const refId = message.reference?.messageId;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Discord renders this as: "{user} pinned {a message} to this channel."
|
|
30
|
+
// where "a message" is the clickable jump link. The i18n key `pinned`
|
|
31
|
+
// accepts a `{message}` placeholder; if absent (legacy dict), we fall
|
|
32
|
+
// back to flat text + a trailing " (jump)".
|
|
33
|
+
const template = t(ctx, 'pinned', 'pinned {message} to this channel.');
|
|
34
|
+
const linkLabel = t(ctx, 'pinnedLink', 'a message');
|
|
35
|
+
const linkEl = refId
|
|
36
|
+
? (0, jsx_runtime_1.jsx)("i", { "data-goto": refId, className: "dht-jump", role: "button", tabIndex: 0, children: linkLabel })
|
|
37
|
+
: (0, jsx_runtime_1.jsx)("strong", { children: linkLabel });
|
|
38
|
+
const parts = template.includes('{message}') ? template.split('{message}') : null;
|
|
39
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "pin", children: parts
|
|
40
|
+
? [
|
|
41
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
42
|
+
' ',
|
|
43
|
+
parts[0],
|
|
44
|
+
linkEl,
|
|
45
|
+
parts[1],
|
|
46
|
+
]
|
|
47
|
+
: [
|
|
48
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
49
|
+
' ',
|
|
50
|
+
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "pinned", children: template }),
|
|
51
|
+
refId ? (0, jsx_runtime_1.jsx)("i", { "data-goto": refId, className: "dht-jump", role: "button", tabIndex: 0, children: " (jump)" }) : null,
|
|
52
|
+
]
|
|
53
|
+
}, message.id));
|
|
34
54
|
}
|
|
35
55
|
|
|
36
56
|
case discord_js_1.MessageType.GuildBoost:
|
|
@@ -38,7 +58,7 @@ async function SystemMessage({ message, context }) {
|
|
|
38
58
|
case discord_js_1.MessageType.GuildBoostTier2:
|
|
39
59
|
case discord_js_1.MessageType.GuildBoostTier3:
|
|
40
60
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
41
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
61
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
42
62
|
' ',
|
|
43
63
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "boosted", children: t(ctx, 'boosted', 'boosted the server!') }),
|
|
44
64
|
] }, message.id));
|
|
@@ -48,7 +68,7 @@ async function SystemMessage({ message, context }) {
|
|
|
48
68
|
const refId = message.reference?.messageId;
|
|
49
69
|
const label = message.content || 'New thread';
|
|
50
70
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "thread", children: [
|
|
51
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
71
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
52
72
|
' ',
|
|
53
73
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "threadStarted", children: t(ctx, 'threadStarted', 'started a thread:') }),
|
|
54
74
|
' ',
|
|
@@ -60,7 +80,7 @@ async function SystemMessage({ message, context }) {
|
|
|
60
80
|
|
|
61
81
|
case discord_js_1.MessageType.ChannelNameChange:
|
|
62
82
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
63
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
83
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
64
84
|
' ',
|
|
65
85
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "changedChannelName", children: t(ctx, 'changedChannelName', 'changed the channel name:') }),
|
|
66
86
|
' ',
|
|
@@ -69,7 +89,7 @@ async function SystemMessage({ message, context }) {
|
|
|
69
89
|
|
|
70
90
|
case discord_js_1.MessageType.ChannelIconChange:
|
|
71
91
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
72
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
92
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
73
93
|
' ',
|
|
74
94
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "changedChannelIcon", children: t(ctx, 'changedChannelIcon', 'changed the channel icon.') }),
|
|
75
95
|
] }, message.id));
|
|
@@ -78,7 +98,7 @@ async function SystemMessage({ message, context }) {
|
|
|
78
98
|
case discord_js_1.MessageType.ContextMenuCommand: {
|
|
79
99
|
const cmd = message.interaction?.commandName ? '/' + message.interaction.commandName : 'a command';
|
|
80
100
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
81
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
101
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
82
102
|
' ',
|
|
83
103
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "usedCommand", children: t(ctx, 'usedCommand', 'used') }),
|
|
84
104
|
' ',
|
|
@@ -88,7 +108,7 @@ async function SystemMessage({ message, context }) {
|
|
|
88
108
|
|
|
89
109
|
case discord_js_1.MessageType.Call:
|
|
90
110
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
91
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
111
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
92
112
|
' ',
|
|
93
113
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "startedCall", children: t(ctx, 'startedCall', 'started a call.') }),
|
|
94
114
|
] }, message.id));
|
|
@@ -97,7 +117,7 @@ async function SystemMessage({ message, context }) {
|
|
|
97
117
|
const followText = t(ctx, 'addedFollow', 'added {target} to follow this channel.');
|
|
98
118
|
const parts = followText.split('{target}');
|
|
99
119
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
100
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
120
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
101
121
|
' ',
|
|
102
122
|
parts[0],
|
|
103
123
|
(0, jsx_runtime_1.jsxs)("span", { className: "dht-mention dht-mention--channel", children: ['#', message.content || 'channel'] }),
|
|
@@ -107,7 +127,7 @@ async function SystemMessage({ message, context }) {
|
|
|
107
127
|
|
|
108
128
|
case discord_js_1.MessageType.RecipientRemove:
|
|
109
129
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "leave", children: [
|
|
110
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
130
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
111
131
|
' ',
|
|
112
132
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "left", children: t(ctx, 'left', 'left.') }),
|
|
113
133
|
] }, message.id));
|
|
@@ -132,34 +152,34 @@ async function SystemMessage({ message, context }) {
|
|
|
132
152
|
}
|
|
133
153
|
case discord_js_1.MessageType.StageStart:
|
|
134
154
|
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 }),
|
|
155
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
136
156
|
' started a stage: ',
|
|
137
157
|
(0, jsx_runtime_1.jsx)("strong", { children: message.content || '' })
|
|
138
158
|
] }, message.id));
|
|
139
159
|
case discord_js_1.MessageType.StageEnd:
|
|
140
160
|
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 }),
|
|
161
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
142
162
|
' ended the stage.'
|
|
143
163
|
] }, message.id));
|
|
144
164
|
case discord_js_1.MessageType.StageSpeaker:
|
|
145
165
|
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 }),
|
|
166
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
147
167
|
' is now a speaker.'
|
|
148
168
|
] }, message.id));
|
|
149
169
|
case discord_js_1.MessageType.StageRaiseHand:
|
|
150
170
|
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 }),
|
|
171
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
152
172
|
' requested to speak.'
|
|
153
173
|
] }, message.id));
|
|
154
174
|
case discord_js_1.MessageType.StageTopic:
|
|
155
175
|
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 }),
|
|
176
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
157
177
|
' changed the stage topic: ',
|
|
158
178
|
(0, jsx_runtime_1.jsx)("strong", { children: message.content || '' })
|
|
159
179
|
] }, message.id));
|
|
160
180
|
case discord_js_1.MessageType.GuildApplicationPremiumSubscription:
|
|
161
181
|
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 }),
|
|
182
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
163
183
|
' subscribed ',
|
|
164
184
|
(0, jsx_runtime_1.jsx)("strong", { children: message.applicationId ? `app ${message.applicationId}` : 'an app' }),
|
|
165
185
|
' to this server.'
|
|
@@ -168,7 +188,7 @@ async function SystemMessage({ message, context }) {
|
|
|
168
188
|
case discord_js_1.MessageType.RoleSubscriptionPurchase: {
|
|
169
189
|
const roleName = message.roleSubscriptionData?.tierName || message.content || 'a role';
|
|
170
190
|
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
171
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }),
|
|
191
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
172
192
|
' ',
|
|
173
193
|
t(ctx, 'roleSubPurchased', 'subscribed to {role}!').replace('{role}', roleName),
|
|
174
194
|
] }, message.id));
|
|
@@ -193,9 +213,16 @@ async function SystemMessage({ message, context }) {
|
|
|
193
213
|
}
|
|
194
214
|
}
|
|
195
215
|
|
|
196
|
-
function Highlight({ children, color }) {
|
|
216
|
+
function Highlight({ children, color, userId }) {
|
|
197
217
|
const safe = typeof color === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(color) ? color : 'white';
|
|
198
|
-
|
|
218
|
+
const props = { style: { color: safe } };
|
|
219
|
+
// Wire up the user popup the same way mentions and message authors do.
|
|
220
|
+
if (typeof userId === 'string' && userId) {
|
|
221
|
+
props.className = 'dht-sys-author';
|
|
222
|
+
props['data-mention-type'] = 'user';
|
|
223
|
+
props['data-mention-id'] = userId;
|
|
224
|
+
}
|
|
225
|
+
return (0, jsx_runtime_1.jsx)("i", Object.assign({}, props, { children }));
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
const allJoinMessages = [
|
|
@@ -252,11 +279,12 @@ function pickJoinMessage(seed) {
|
|
|
252
279
|
|
|
253
280
|
function JoinMessage({ member, fallbackUser, seed, context }) {
|
|
254
281
|
const template = pickJoinMessage(seed);
|
|
282
|
+
const uid = member?.id || fallbackUser?.id;
|
|
255
283
|
return template
|
|
256
284
|
.split('{user}')
|
|
257
285
|
.flatMap((item, i) => [
|
|
258
286
|
item,
|
|
259
|
-
(0, jsx_runtime_1.jsx)(Highlight, { color: member?.roles?.color?.hexColor, children: member?.nickname ?? fallbackUser.displayName ?? fallbackUser.username }, i),
|
|
287
|
+
(0, jsx_runtime_1.jsx)(Highlight, { color: member?.roles?.color?.hexColor, userId: uid, children: member?.nickname ?? fallbackUser.displayName ?? fallbackUser.username }, i),
|
|
260
288
|
])
|
|
261
289
|
.slice(0, -1);
|
|
262
290
|
}
|
|
@@ -48,15 +48,35 @@ async function DiscordMessages(_a) {
|
|
|
48
48
|
dateSeps[idx] && (0, jsx_runtime_1.jsx)("div", { className: "dht-date-sep", children: dateSeps[idx] }, `sep-${idx}`),
|
|
49
49
|
(0, jsx_runtime_1.jsx)(message_1.default, { message: message, context: Object.assign({ messages, channel, callbacks }, options) }, message.id)
|
|
50
50
|
]),
|
|
51
|
-
(
|
|
52
|
-
options.footerText
|
|
53
|
-
? options.footerText.replaceAll('{number}', messages.length.toString()).replaceAll('{s}', messages.length > 1 ? 's' : '')
|
|
54
|
-
: `Exported ${messages.length} message${messages.length > 1 ? 's' : ''}.`,
|
|
55
|
-
' ',
|
|
56
|
-
options.poweredBy
|
|
57
|
-
? (0, jsx_runtime_1.jsxs)("span", { style: { textAlign: 'center' }, children: ["Powered by ", (0, jsx_runtime_1.jsx)("a", { href: "https://github.com/ItzDerock/discord-html-transcripts", style: { color: 'lightblue' }, children: "discord-html-transcripts" }), "."] })
|
|
58
|
-
: null
|
|
59
|
-
] })
|
|
51
|
+
renderLegacyFooter(messages, options),
|
|
60
52
|
] }));
|
|
61
53
|
}
|
|
54
|
+
|
|
55
|
+
// Legacy "Exported X message(s). Powered by …" line. The new stats footer
|
|
56
|
+
// (see renderStatsFooter in generator/index.js) already shows the message
|
|
57
|
+
// count, so this line is suppressed by default and only renders when:
|
|
58
|
+
// - options.statsFooter === false (caller opted out of the new footer), or
|
|
59
|
+
// - options.footerText is explicitly set to a string, or
|
|
60
|
+
// - options.poweredBy === true (caller wants the credit link).
|
|
61
|
+
function renderLegacyFooter(messages, options) {
|
|
62
|
+
const statsDisabled = options.statsFooter === false ||
|
|
63
|
+
(options.statsFooter && options.statsFooter.enabled === false);
|
|
64
|
+
const customText = (typeof options.footerText === 'string' && options.footerText.length > 0)
|
|
65
|
+
? options.footerText.replaceAll('{number}', messages.length.toString()).replaceAll('{s}', messages.length > 1 ? 's' : '')
|
|
66
|
+
: null;
|
|
67
|
+
const wantsPowered = !!options.poweredBy;
|
|
68
|
+
// Default render only if stats footer is off AND no custom text was set.
|
|
69
|
+
const defaultText = statsDisabled && !customText
|
|
70
|
+
? `Exported ${messages.length} message${messages.length > 1 ? 's' : ''}.`
|
|
71
|
+
: null;
|
|
72
|
+
const shown = customText || defaultText;
|
|
73
|
+
if (!shown && !wantsPowered) return null;
|
|
74
|
+
return (0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', width: '100%' }, children: [
|
|
75
|
+
shown,
|
|
76
|
+
shown && wantsPowered ? ' ' : null,
|
|
77
|
+
wantsPowered
|
|
78
|
+
? (0, jsx_runtime_1.jsxs)("span", { style: { textAlign: 'center' }, children: ["Powered by ", (0, jsx_runtime_1.jsx)("a", { href: "https://github.com/ItzDerock/discord-html-transcripts", style: { color: 'lightblue' }, children: "discord-html-transcripts" }), "."] })
|
|
79
|
+
: null
|
|
80
|
+
] });
|
|
81
|
+
}
|
|
62
82
|
//# sourceMappingURL=transcript.js.map
|