discord-html-transcripts-fix 1.3.0 → 1.6.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/content.js +16 -17
- package/dist/generator/renderers/message.js +108 -44
- package/dist/generator/renderers/systemMessage.js +52 -24
- package/dist/generator/transcript.js +29 -9
- package/dist/static/client.js +376 -178
- 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
|
}));
|
|
@@ -195,62 +195,61 @@ async function renderASTNode(node, context) {
|
|
|
195
195
|
const id = node.id;
|
|
196
196
|
if (typeof id === 'string' && /^[a-z][a-z-]*$/.test(id)) {
|
|
197
197
|
const label = GUIDE_LABELS[id] || id;
|
|
198
|
-
return (0, jsx_runtime_1.
|
|
198
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--channel", children: '#' + label });
|
|
199
199
|
}
|
|
200
|
-
const channel = await context.callbacks.resolveChannel(id).catch(() => null);
|
|
201
|
-
const name = channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `unknown-${id.slice(-6)}`;
|
|
202
|
-
return (0, jsx_runtime_1.
|
|
200
|
+
const channel = await Promise.resolve(context.callbacks.resolveChannel(id)).catch(() => null);
|
|
201
|
+
const name = channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `unknown-${(id || '').slice(-6)}`;
|
|
202
|
+
return (0, jsx_runtime_1.jsx)("span", {
|
|
203
203
|
className: "dht-mention dht-mention--channel",
|
|
204
204
|
"data-mention-type": "channel",
|
|
205
205
|
"data-mention-id": id,
|
|
206
|
-
children:
|
|
206
|
+
children: '#' + name
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
case 'role': {
|
|
211
211
|
const id = node.id;
|
|
212
|
-
const role = await context.callbacks.resolveRole(id).catch(() => null);
|
|
213
|
-
const name = role?.name ?? `role-${id.slice(-6)}`;
|
|
212
|
+
const role = await Promise.resolve(context.callbacks.resolveRole(id)).catch(() => null);
|
|
213
|
+
const name = role?.name ?? `role-${(id || '').slice(-6)}`;
|
|
214
214
|
const color = role?.hexColor && role.hexColor !== '#000000' ? (0, utils_1.safeColor)(role.hexColor, undefined) : undefined;
|
|
215
215
|
const bg = color ? hexToRgba(color, 0.18) : undefined;
|
|
216
216
|
const style = color ? { color, backgroundColor: bg, borderColor: hexToRgba(color, 0.45) } : undefined;
|
|
217
|
-
return (0, jsx_runtime_1.
|
|
217
|
+
return (0, jsx_runtime_1.jsx)("span", {
|
|
218
218
|
className: "dht-mention dht-mention--role",
|
|
219
219
|
"data-mention-type": "role",
|
|
220
220
|
"data-mention-id": id,
|
|
221
221
|
style,
|
|
222
|
-
children:
|
|
222
|
+
children: '@' + name
|
|
223
223
|
});
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
case 'user': {
|
|
227
227
|
const id = node.id;
|
|
228
|
-
const user = await context.callbacks.resolveUser(id).catch(() => null);
|
|
229
|
-
const name = user ? (user.displayName ?? user.username ?? `user-${id.slice(-6)}`) : `user-${id.slice(-6)}`;
|
|
230
|
-
return (0, jsx_runtime_1.
|
|
228
|
+
const user = await Promise.resolve(context.callbacks.resolveUser(id)).catch(() => null);
|
|
229
|
+
const name = user ? (user.displayName ?? user.username ?? `user-${(id || '').slice(-6)}`) : `user-${(id || '').slice(-6)}`;
|
|
230
|
+
return (0, jsx_runtime_1.jsx)("span", {
|
|
231
231
|
className: "dht-mention dht-mention--user",
|
|
232
232
|
"data-mention-type": "user",
|
|
233
233
|
"data-mention-id": id,
|
|
234
|
-
children:
|
|
234
|
+
children: '@' + name
|
|
235
235
|
});
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
case 'slashCommand':
|
|
239
239
|
case 'command': {
|
|
240
240
|
const name = node.name || node.content || 'command';
|
|
241
|
-
return (0, jsx_runtime_1.
|
|
241
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--command", children: '/' + String(name) });
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
case 'guildNavigation': {
|
|
245
|
-
// discord-markdown-parser emits this for <id:guide> etc.
|
|
246
245
|
const id = node.id || node.content || 'guide';
|
|
247
246
|
const label = GUIDE_LABELS[id] || id;
|
|
248
|
-
return (0, jsx_runtime_1.
|
|
247
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--channel", children: '#' + label });
|
|
249
248
|
}
|
|
250
249
|
|
|
251
250
|
case 'here':
|
|
252
251
|
case 'everyone':
|
|
253
|
-
return (0, jsx_runtime_1.
|
|
252
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--role dht-mention--highlight", children: '@' + type });
|
|
254
253
|
|
|
255
254
|
case 'codeBlock':
|
|
256
255
|
if (context.type !== RenderType.REPLY) {
|
|
@@ -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,15 +133,10 @@ async function DiscordMessage({ message, context }) {
|
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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;
|
|
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);
|
|
146
140
|
|
|
147
141
|
return ((0, jsx_runtime_1.jsxs)("discord-message", {
|
|
148
142
|
id: `m-${message.id}`,
|
|
@@ -151,11 +145,6 @@ async function DiscordMessage({ message, context }) {
|
|
|
151
145
|
server: (isCrossGuildReply || isCrosspost || isCrossposted) ? true : undefined,
|
|
152
146
|
highlight: message.mentions.everyone || pinned,
|
|
153
147
|
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
148
|
className: pinned ? 'dht-msg dht-msg-pinned' : 'dht-msg',
|
|
160
149
|
"data-author-id": message.author.id,
|
|
161
150
|
"data-author-name": authorName,
|
|
@@ -166,13 +155,20 @@ async function DiscordMessage({ message, context }) {
|
|
|
166
155
|
"data-has-image": hasImage ? 'true' : undefined,
|
|
167
156
|
"data-has-embed": hasEmbed ? 'true' : undefined,
|
|
168
157
|
"data-has-attachment": hasAttachment ? 'true' : undefined,
|
|
158
|
+
"data-has-component-v2": hasComponentV2 ? 'true' : undefined,
|
|
169
159
|
children: [
|
|
170
|
-
pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned message", children: "📌" }),
|
|
171
160
|
appliedTagPills,
|
|
172
161
|
(0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }),
|
|
173
|
-
message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", {
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
})),
|
|
176
172
|
isCrossposted && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Published from announcement channel", children: '📣 Published' }),
|
|
177
173
|
isCrosspost && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Follows another channel", children: '📡 Follow' }),
|
|
178
174
|
isSilent && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-silent", title: "Silent message (no notification)", children: '🔕 Silent' }),
|
|
@@ -389,40 +385,108 @@ function renderSnapshots(snapshots, context, depth = 0) {
|
|
|
389
385
|
});
|
|
390
386
|
}
|
|
391
387
|
|
|
392
|
-
|
|
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) {
|
|
393
460
|
const interaction = message?.interaction;
|
|
394
461
|
const metadata = message?.interactionMetadata;
|
|
395
462
|
const optsRaw = message?.interactionOptions || interaction?.options;
|
|
396
|
-
|
|
397
|
-
if (!optsRaw && !metadata) return null;
|
|
463
|
+
if (!interaction && !metadata) return null;
|
|
398
464
|
|
|
399
|
-
|
|
400
|
-
let
|
|
401
|
-
let valueOpts = opts;
|
|
465
|
+
let cmdParts = [interaction?.commandName || metadata?.name || '?'];
|
|
466
|
+
let valueOpts = Array.isArray(optsRaw) ? optsRaw : [];
|
|
402
467
|
|
|
403
|
-
//
|
|
468
|
+
// Walk subcommand-group → subcommand nesting.
|
|
404
469
|
for (let i = 0; i < 2; i++) {
|
|
405
470
|
if (valueOpts.length === 1 && valueOpts[0] && (valueOpts[0].type === 1 || valueOpts[0].type === 2)) {
|
|
406
471
|
cmdParts.push(valueOpts[0].name);
|
|
407
472
|
valueOpts = Array.isArray(valueOpts[0].options) ? valueOpts[0].options : [];
|
|
408
473
|
}
|
|
409
474
|
}
|
|
410
|
-
// Filter out autocomplete-only `focused` options
|
|
411
475
|
valueOpts = valueOpts.filter((o) => o && !o.focused);
|
|
412
476
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
+
}));
|
|
416
483
|
|
|
417
|
-
return
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
] });
|
|
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
|
+
};
|
|
426
490
|
}
|
|
427
491
|
|
|
428
492
|
function renderEditHistory(history, context) {
|