discord-html-transcripts-fix 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.default = DiscordMessage;
40
40
  const jsx_runtime_1 = require("react/jsx-runtime");
41
+ const discord_js_1 = require("discord.js");
41
42
  const utils_1 = require("../../utils/utils");
42
43
  const attachment_1 = require("./attachment");
43
44
  const components_1 = __importDefault(require("./components"));
@@ -45,12 +46,43 @@ const content_1 = __importStar(require("./content"));
45
46
  const embed_1 = require("./embed");
46
47
  const reply_1 = __importDefault(require("./reply"));
47
48
  const systemMessage_1 = __importDefault(require("./systemMessage"));
48
- async function DiscordMessage({ message, context, }) {
49
- var _a;
49
+
50
+ const FLAG_CROSSPOSTED = 1 << 0;
51
+ const FLAG_IS_CROSSPOST = 1 << 1;
52
+ const FLAG_SUPPRESS_EMBEDS = 1 << 2;
53
+ const FLAG_SUPPRESS_NOTIFICATIONS = 1 << 12;
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
+ }
63
+
64
+ function t(context, key, fallback) {
65
+ const dict = context?.i18n?.[context?.lang] || context?.i18n?.en || {};
66
+ return dict[key] || fallback || key;
67
+ }
68
+
69
+ async function DiscordMessage({ message, context }) {
50
70
  try {
51
71
  if (message.system)
52
- return (0, jsx_runtime_1.jsx)(systemMessage_1.default, { message: message });
53
- const isCrosspost = message.reference && message.reference.guildId !== ((_a = message.guild) === null || _a === void 0 ? void 0 : _a.id);
72
+ return (0, jsx_runtime_1.jsx)(systemMessage_1.default, { message: message, context: context });
73
+
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;
54
86
  const stickers = message.stickers && message.stickers.size > 0
55
87
  ? Array.from(message.stickers.values()).map((s) => ({
56
88
  id: s.id,
@@ -60,76 +92,350 @@ async function DiscordMessage({ message, context, }) {
60
92
  }))
61
93
  : [];
62
94
  const poll = (message.poll && (message.poll.question || (message.poll.answers && message.poll.answers.size))) ? message.poll : null;
63
- return ((0, jsx_runtime_1.jsxs)("discord-message", { id: `m-${message.id}`, timestamp: message.createdAt, edited: message.editedAt !== null, server: isCrosspost !== null && isCrosspost !== void 0 ? isCrosspost : undefined, highlight: message.mentions.everyone, profile: message.author.id, children: [(0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }), message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", { slot: "reply", profile: message.interaction.user.id, command: '/' + message.interaction.commandName })), message.content && ((0, jsx_runtime_1.jsx)(content_1.default, { content: message.content, context: Object.assign(Object.assign({}, context), { type: message.webhookId ? content_1.RenderType.WEBHOOK : content_1.RenderType.NORMAL }) })), (0, jsx_runtime_1.jsx)(attachment_1.Attachments, { message: message, context: context }), stickers.length > 0 && renderStickers(stickers), poll && renderPoll(poll), message.embeds.map((embed, id) => ((0, jsx_runtime_1.jsx)(embed_1.DiscordEmbed, { embed: embed, context: Object.assign(Object.assign({}, context), { index: id, message }) }, id))), message.components.length > 0 && ((0, jsx_runtime_1.jsx)("discord-attachments", { slot: "components", children: message.components.map((component, id) => ((0, jsx_runtime_1.jsx)(components_1.default, { id: id, component: component, context: context }, id))) })), message.reactions.cache.size > 0 && ((0, jsx_runtime_1.jsx)("discord-reactions", { slot: "reactions", children: Array.from(message.reactions.cache.values()).map((reaction, id) => {
64
- var _a, _b;
65
- return ((0, jsx_runtime_1.jsx)("discord-reaction", { name: ((_a = reaction.emoji) === null || _a === void 0 ? void 0 : _a.name) || ':unknown:', emoji: (0, utils_1.parseDiscordEmoji)(reaction.emoji) || undefined, count: (_b = reaction.count) !== null && _b !== void 0 ? _b : 0 }, `${message.id}r${id}`));
66
- }) })), message.hasThread && message.thread && ((0, jsx_runtime_1.jsx)("discord-thread", { slot: "thread", name: message.thread.name, cta: message.thread.messageCount
67
- ? `${message.thread.messageCount} Message${message.thread.messageCount > 1 ? 's' : ''}`
68
- : 'View Thread', children: message.thread.lastMessage ? ((0, jsx_runtime_1.jsx)("discord-thread-message", { profile: message.thread.lastMessage.author.id, children: (0, jsx_runtime_1.jsx)(content_1.default, { content: message.thread.lastMessage.content.length > 128
69
- ? message.thread.lastMessage.content.substring(0, 125) + '...'
70
- : message.thread.lastMessage.content, context: Object.assign(Object.assign({}, context), { type: content_1.RenderType.REPLY }) }) })) : (`Thread messages not saved.`) }))] }, message.id));
71
- }
72
- catch (err) {
73
- console.warn('[discord-html-transcripts-fix] Failed to render message', message && message.id, err && err.message ? err.message : err);
74
- const safeAuthor = (message && message.author && (message.author.displayName || message.author.username)) || 'Unknown';
75
- return ((0, jsx_runtime_1.jsxs)("discord-message", { id: `m-${message && message.id}`, children: [(0, jsx_runtime_1.jsx)("span", { style: { opacity: 0.6 }, children: `[Nachricht von ${safeAuthor} konnte nicht gerendert werden]` })] }, message && message.id));
95
+
96
+ const flags = (typeof message.flags?.bitfield === 'number') ? message.flags.bitfield : 0;
97
+ const suppressEmbeds = !!(flags & FLAG_SUPPRESS_EMBEDS);
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;
106
+
107
+ // Forwarded message snapshots (messageSnapshots) — render before the empty body
108
+ const snapshots = Array.isArray(message.messageSnapshots) ? message.messageSnapshots : [];
109
+
110
+ const isApp = !!(message.author?.bot && message.applicationId);
111
+
112
+ const editedAtIso = message.editedAt instanceof Date ? message.editedAt.toISOString() : null;
113
+ const pinned = !!message.pinned;
114
+ const verifiedBot = !!message.author?.flags?.has?.(discord_js_1.UserFlags.VerifiedBot);
115
+
116
+ // Forum applied tags — only on the first message of a forum thread (the post itself)
117
+ let appliedTagPills = null;
118
+ if (message.channel?.isThread?.() && message.channel.parent?.availableTags && Array.isArray(message.channel.appliedTags) && message.channel.appliedTags.length > 0) {
119
+ const isFirstInThread = message.id === message.channel.id; // Forum post: thread id === starter message id
120
+ if (isFirstInThread) {
121
+ const available = message.channel.parent.availableTags;
122
+ appliedTagPills = (0, jsx_runtime_1.jsx)("div", {
123
+ className: "dht-applied-tags",
124
+ children: message.channel.appliedTags.map((tagId) => {
125
+ const tag = available.find((t) => t.id === tagId);
126
+ if (!tag) return null;
127
+ return (0, jsx_runtime_1.jsxs)("span", { className: "dht-tag-pill", children: [
128
+ tag.emoji?.name && (0, jsx_runtime_1.jsx)("span", { className: "dht-tag-emoji", children: tag.emoji.name }),
129
+ ' ',
130
+ tag.name
131
+ ] }, tagId);
132
+ })
133
+ });
134
+ }
135
+ }
136
+
137
+ // Build a comma-separated badge list for additional flag rendering
138
+ const slashCmdLine = renderSlashCommandLine(message, context);
139
+
140
+ // Resolve author profile data for Skyra slot attributes — without these the
141
+ // <discord-message> web component falls back to a generic display
142
+ const authorMember = message.member;
143
+ const authorDisplay = authorMember?.nickname ?? message.author?.displayName ?? message.author?.username;
144
+ const authorAvatar = authorMember?.displayAvatarURL?.({ size: 64 }) ?? message.author?.displayAvatarURL?.({ size: 64 });
145
+ const authorRoleHex = authorMember?.displayHexColor && authorMember.displayHexColor !== '#000000' ? authorMember.displayHexColor : undefined;
146
+
147
+ return ((0, jsx_runtime_1.jsxs)("discord-message", {
148
+ id: `m-${message.id}`,
149
+ timestamp: message.createdAt,
150
+ edited: message.editedAt !== null,
151
+ server: (isCrossGuildReply || isCrosspost || isCrossposted) ? true : undefined,
152
+ highlight: message.mentions.everyone || pinned,
153
+ profile: message.author.id,
154
+ author: authorDisplay,
155
+ avatar: authorAvatar,
156
+ roleColor: authorRoleHex,
157
+ bot: message.author?.bot && !message.webhookId ? true : undefined,
158
+ verified: verifiedBot ? true : undefined,
159
+ className: pinned ? 'dht-msg dht-msg-pinned' : 'dht-msg',
160
+ "data-author-id": message.author.id,
161
+ "data-author-name": authorName,
162
+ "data-roles": memberRoleIds,
163
+ "data-timestamp": ts ? String(ts) : undefined,
164
+ "data-text": lowerText,
165
+ "data-pinned": pinned ? 'true' : undefined,
166
+ "data-has-image": hasImage ? 'true' : undefined,
167
+ "data-has-embed": hasEmbed ? 'true' : undefined,
168
+ "data-has-attachment": hasAttachment ? 'true' : undefined,
169
+ children: [
170
+ pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned message", children: "📌" }),
171
+ appliedTagPills,
172
+ (0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }),
173
+ message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", { slot: "reply", profile: message.interaction.user.id, command: '/' + message.interaction.commandName })),
174
+ slashCmdLine,
175
+ isApp && (0, jsx_runtime_1.jsxs)("span", { className: "dht-app-badge", "data-i18n": "appBadge", children: [t(context, 'appBadge', 'APP'), verifiedBot && (0, jsx_runtime_1.jsx)("span", { className: "dht-verified-check", title: "Verified Bot", children: '✓' })] }),
176
+ isCrossposted && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Published from announcement channel", children: '📣 Published' }),
177
+ isCrosspost && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-crosspost", title: "Follows another channel", children: '📡 Follow' }),
178
+ isSilent && (0, jsx_runtime_1.jsx)("span", { className: "dht-badge dht-badge-silent", title: "Silent message (no notification)", children: '🔕 Silent' }),
179
+ activity && (0, jsx_runtime_1.jsxs)("div", { className: "dht-activity", children: [
180
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-activity-icon", children: '🎮' }),
181
+ (0, jsx_runtime_1.jsxs)("span", { children: [activity.type, ' invite', activity.partyId ? ` · Party ${activity.partyId.slice(-6)}` : ''] })
182
+ ] }),
183
+ editedAtIso && (0, jsx_runtime_1.jsx)("span", { className: "dht-edit-marker", title: editedAtIso, "data-edit-iso": editedAtIso, "data-i18n": "edited", "data-i18n-params": JSON.stringify({ time: editedAtIso }), children: '(' + t(context, 'edited', 'edited') + ')' }),
184
+ Array.isArray(message.editHistory) && message.editHistory.length > 0 && renderEditHistory(message.editHistory, context),
185
+ snapshots.length > 0 && renderSnapshots(snapshots, context),
186
+ message.content && ((0, jsx_runtime_1.jsx)(content_1.default, { content: message.content, context: Object.assign({}, context, { type: message.webhookId ? content_1.RenderType.WEBHOOK : content_1.RenderType.NORMAL }) })),
187
+ (0, jsx_runtime_1.jsx)(attachment_1.Attachments, { message: message, context: context }),
188
+ isVoice && renderVoiceIndicator(message, context),
189
+ stickers.length > 0 && renderStickers(stickers),
190
+ poll && renderPoll(poll, context),
191
+ !suppressEmbeds && message.embeds.map((embed, id) => ((0, jsx_runtime_1.jsx)(embed_1.DiscordEmbed, { embed: embed, context: Object.assign({}, context, { index: id, message }) }, id))),
192
+ suppressEmbeds && message.embeds.length > 0 && (0, jsx_runtime_1.jsx)("span", { className: "dht-suppressed", "data-i18n": "suppressedEmbeds", children: t(context, 'suppressedEmbeds', '(embeds hidden)') }),
193
+ message.components.length > 0 && ((0, jsx_runtime_1.jsx)("discord-attachments", { slot: "components", children: message.components.map((component, id) => ((0, jsx_runtime_1.jsx)(components_1.default, { id: id, component: component, context: context }, id))) })),
194
+ message.reactions.cache.size > 0 && ((0, jsx_runtime_1.jsx)("discord-reactions", {
195
+ slot: "reactions",
196
+ children: Array.from(message.reactions.cache.values()).map((reaction, id) => {
197
+ const total = reaction.count ?? 0;
198
+ const burstCount = reaction.count_details?.burst || reaction.burst_count || 0;
199
+ const burst = burstCount > 0;
200
+ const burstColors = Array.isArray(reaction.burst_colors) && reaction.burst_colors.length
201
+ ? reaction.burst_colors.filter((c) => typeof c === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(c))
202
+ : [];
203
+ const title = burst
204
+ ? `${total} reactions (${burstCount} super)`
205
+ : `${total} reaction${total !== 1 ? 's' : ''}`;
206
+ const style = burst && burstColors.length
207
+ ? { boxShadow: `inset 0 0 0 2px ${burstColors[0]}` }
208
+ : undefined;
209
+ return ((0, jsx_runtime_1.jsx)("discord-reaction", {
210
+ name: reaction.emoji?.name || ':unknown:',
211
+ emoji: (0, utils_1.parseDiscordEmoji)(reaction.emoji) || undefined,
212
+ count: abbrCount(total),
213
+ title,
214
+ "data-burst": burst ? 'true' : undefined,
215
+ className: burst ? 'dht-reaction--burst' : undefined,
216
+ style,
217
+ }, `${message.id}r${id}`));
218
+ })
219
+ })),
220
+ message.hasThread && message.thread && renderThread(message.thread, context),
221
+ ]
222
+ }, message.id));
223
+ } catch (err) {
224
+ console.warn('[discord-html-transcripts-fix] Render failed', message?.id, err?.message || err);
225
+ const safeAuthor = (message?.author?.displayName || message?.author?.username) || 'Unknown';
226
+ return ((0, jsx_runtime_1.jsx)("discord-message", { id: `m-${message?.id}`, children: (0, jsx_runtime_1.jsx)("span", { style: { opacity: 0.6 }, children: `[message from ${safeAuthor} failed to render]` }) }, message?.id));
76
227
  }
77
228
  }
78
229
 
230
+ function renderThread(thread, context) {
231
+ const cta = thread.messageCount
232
+ ? `${thread.messageCount} Message${thread.messageCount > 1 ? 's' : ''}`
233
+ : 'View Thread';
234
+ const badges = [];
235
+ if (thread.archived) badges.push((0, jsx_runtime_1.jsx)("span", { className: "dht-thread-badge dht-thread-badge--archived", "data-i18n": "threadArchived", children: t(context, 'threadArchived', 'Archived') }, 'a'));
236
+ if (thread.locked) badges.push((0, jsx_runtime_1.jsx)("span", { className: "dht-thread-badge dht-thread-badge--locked", "data-i18n": "threadLocked", children: t(context, 'threadLocked', 'Locked') }, 'l'));
237
+ return (0, jsx_runtime_1.jsxs)("discord-thread", { slot: "thread", name: thread.name, cta, children: [
238
+ badges,
239
+ thread.lastMessage
240
+ ? (0, jsx_runtime_1.jsx)("discord-thread-message", { profile: thread.lastMessage.author.id, children: (0, jsx_runtime_1.jsx)(content_1.default, { content: thread.lastMessage.content.length > 128 ? thread.lastMessage.content.substring(0, 125) + '...' : thread.lastMessage.content, context: Object.assign({}, context, { type: content_1.RenderType.REPLY }) }) })
241
+ : 'Thread messages not saved.'
242
+ ] });
243
+ }
244
+
79
245
  function renderStickers(stickers) {
80
246
  return (0, jsx_runtime_1.jsx)("div", {
81
247
  className: "dht-stickers",
82
- style: { display: 'flex', gap: '8px', flexWrap: 'wrap', marginTop: '4px' },
83
- children: stickers.map((s) => (0, jsx_runtime_1.jsx)("img", {
84
- src: s.url,
85
- alt: s.name || 'sticker',
86
- title: s.name || '',
87
- style: { width: '160px', height: '160px', borderRadius: '8px', objectFit: 'contain', background: 'rgba(255,255,255,0.02)' }
88
- }, s.id))
248
+ children: stickers.map((s) => {
249
+ // Discord sticker formats: 1=PNG, 2=APNG (animated png), 3=Lottie (JSON), 4=GIF
250
+ const format = s.format;
251
+ const baseUrl = `https://media.discordapp.net/stickers/${s.id}`;
252
+ if (format === 3) {
253
+ // Lottie — render as iframe to discord's lottie player CDN page, or fallback link
254
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-sticker dht-sticker--lottie", title: s.name || '', children: [
255
+ (0, jsx_runtime_1.jsx)("div", { className: "dht-sticker-placeholder", children: '🎞️' }),
256
+ (0, jsx_runtime_1.jsxs)("span", { className: "dht-sticker-name", children: [s.name || 'sticker', ' (Lottie)'] })
257
+ ] }, s.id);
258
+ }
259
+ if (format === 4) {
260
+ return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.gif`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker" }, s.id);
261
+ }
262
+ if (format === 2) {
263
+ return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.png`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker dht-sticker--apng" }, s.id);
264
+ }
265
+ return (0, jsx_runtime_1.jsx)("img", { src: s.url || `${baseUrl}.png`, alt: s.name || 'sticker', title: s.name || '', className: "dht-sticker" }, s.id);
266
+ })
89
267
  });
90
268
  }
91
269
 
92
- function renderPoll(poll) {
270
+ function renderPoll(poll, context) {
93
271
  let totalVotes = 0;
94
272
  const answers = poll.answers ? Array.from(poll.answers.values()) : [];
95
273
  for (const a of answers) totalVotes += a.voteCount || 0;
96
- const expires = poll.expiresAt instanceof Date ? poll.expiresAt.toLocaleString('de-DE') : '';
274
+ const locale = context?.lang === 'de' ? 'de-DE' : 'en-US';
275
+ const expires = poll.expiresAt instanceof Date ? poll.expiresAt.toLocaleString(locale) : '';
97
276
 
98
277
  return (0, jsx_runtime_1.jsxs)("div", {
99
278
  className: "dht-poll",
100
- style: {
101
- border: '1px solid #2b2d31', background: '#1e1f22', borderRadius: '8px',
102
- padding: '12px 14px', marginTop: '6px', maxWidth: '480px'
103
- },
104
279
  children: [
105
- (0, jsx_runtime_1.jsxs)("div", { style: { fontWeight: 700, marginBottom: '8px', fontSize: '15px' }, children: ['📊 ', poll.question && poll.question.text ? poll.question.text : 'Poll'] }),
280
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-q", children: ['📊 ', poll.question?.text || 'Poll'] }),
106
281
  (0, jsx_runtime_1.jsx)("div", {
107
- style: { display: 'flex', flexDirection: 'column', gap: '6px' },
282
+ className: "dht-poll-answers",
108
283
  children: answers.map((a, i) => {
109
- const text = (a.text || a.poll_media?.text || a.media?.text || a.answer_id || `Option ${i + 1}`);
284
+ const text = a.text ?? a.media?.text ?? `Option ${i + 1}`;
110
285
  const votes = a.voteCount || 0;
111
286
  const pct = totalVotes > 0 ? Math.round((votes / totalVotes) * 100) : 0;
112
- return (0, jsx_runtime_1.jsxs)("div", {
113
- style: { background: '#2b2d31', borderRadius: '6px', padding: '6px 10px', position: 'relative', overflow: 'hidden' },
114
- children: [
115
- (0, jsx_runtime_1.jsx)("div", { style: {
116
- position: 'absolute', top: 0, left: 0, height: '100%',
117
- width: pct + '%', background: 'rgba(88,101,242,0.25)', zIndex: 0
118
- }}),
119
- (0, jsx_runtime_1.jsxs)("div", { style: { position: 'relative', zIndex: 1, display: 'flex', justifyContent: 'space-between', gap: '8px' },
120
- children: [
121
- (0, jsx_runtime_1.jsx)("span", { children: String(text) }),
122
- (0, jsx_runtime_1.jsxs)("span", { style: { color: '#b5bac1', fontSize: '12px', whiteSpace: 'nowrap' }, children: [votes, ' (', pct, '%)'] })
123
- ]
124
- })
125
- ]
126
- }, i);
287
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-a", children: [
288
+ (0, jsx_runtime_1.jsx)("div", { className: "dht-poll-bar", style: { width: pct + '%' } }),
289
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-line", children: [
290
+ (0, jsx_runtime_1.jsx)("span", { children: String(text) }),
291
+ (0, jsx_runtime_1.jsxs)("span", { className: "dht-poll-pct", children: [votes, ' (', pct, '%)'] })
292
+ ] })
293
+ ] }, i);
127
294
  })
128
295
  }),
129
- (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '8px', fontSize: '11px', color: '#949ba4' },
130
- children: [totalVotes, ' Stimme', totalVotes !== 1 ? 'n' : '', expires ? ' · endet ' + expires : '']
131
- })
296
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-foot", children: [totalVotes, ' votes', expires ? ' · ends ' + expires : ''] })
132
297
  ]
133
298
  });
134
299
  }
135
- //# sourceMappingURL=message.js.map
300
+
301
+ function renderVoiceIndicator(message, context) {
302
+ // FIX: discord.js Collection.find takes a predicate but on Map it conflicts; iterate values explicitly.
303
+ let audio = null;
304
+ if (message.attachments && typeof message.attachments.values === 'function') {
305
+ for (const a of message.attachments.values()) {
306
+ if (typeof a.contentType === 'string' && a.contentType.startsWith('audio/')) { audio = a; break; }
307
+ }
308
+ }
309
+ const duration = audio?.duration_secs || audio?.durationSecs;
310
+ const waveformB64 = audio?.waveform;
311
+ let waveformRects = null;
312
+ let waveformWidth = 0;
313
+ if (typeof waveformB64 === 'string' && waveformB64.length > 0 && waveformB64.length < 16384) {
314
+ try {
315
+ const buf = Buffer.from(waveformB64, 'base64');
316
+ const bars = Array.from(buf).slice(0, 100);
317
+ const max = bars.length ? Math.max(1, ...bars) : 1;
318
+ waveformWidth = bars.length * 3;
319
+ waveformRects = bars.map((b, i) => {
320
+ const h = Math.max(2, (b / max) * 20);
321
+ return (0, jsx_runtime_1.jsx)("rect", { x: i * 3, y: (22 - h) / 2, width: 2, height: h, rx: 1, fill: "#5865F2" }, i);
322
+ });
323
+ } catch (_e) { waveformRects = null; }
324
+ }
325
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-voice", children: [
326
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-voice-icon", children: '🎤' }),
327
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-voice-label", "data-i18n": "voiceMessage", children: t(context, 'voiceMessage', 'Voice message') }),
328
+ waveformRects && (0, jsx_runtime_1.jsx)("svg", {
329
+ xmlns: "http://www.w3.org/2000/svg",
330
+ viewBox: `0 0 ${waveformWidth} 22`,
331
+ width: waveformWidth,
332
+ height: 22,
333
+ className: "dht-voice-wave",
334
+ children: waveformRects,
335
+ }),
336
+ duration ? (0, jsx_runtime_1.jsxs)("span", { className: "dht-voice-dur", children: [Math.round(duration), 's'] }) : null,
337
+ ] });
338
+ }
339
+
340
+ function toArray(maybeColl) {
341
+ if (!maybeColl) return [];
342
+ if (Array.isArray(maybeColl)) return maybeColl;
343
+ if (typeof maybeColl.values === 'function') return Array.from(maybeColl.values());
344
+ if (typeof maybeColl === 'object') return Object.values(maybeColl);
345
+ return [];
346
+ }
347
+
348
+ function renderSnapshots(snapshots, context, depth = 0) {
349
+ const arr = toArray(snapshots);
350
+ if (arr.length === 0 || depth >= 5) return null; // hard-cap at 5 levels
351
+ return (0, jsx_runtime_1.jsx)("div", {
352
+ className: "dht-forwarded-wrap",
353
+ children: arr.map((snap, i) => {
354
+ const m = snap.message || snap;
355
+ const author = m?.author?.displayName || m?.author?.username || 'Unknown';
356
+ const text = m?.content || '';
357
+ const attachments = toArray(m?.attachments);
358
+ const nested = toArray(m?.messageSnapshots);
359
+ const embeds = toArray(m?.embeds);
360
+ const stickers = toArray(m?.stickers).map((s) => ({
361
+ id: s.id,
362
+ name: s.name,
363
+ url: s.url || `https://media.discordapp.net/stickers/${s.id}.png`,
364
+ format: s.format,
365
+ }));
366
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded", "data-depth": depth, children: [
367
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-head", children: [
368
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-forwarded-icon", children: '↪' }),
369
+ (0, jsx_runtime_1.jsx)("span", { "data-i18n": "forwardedFrom", children: t(context, 'forwardedFrom', 'Forwarded from') }),
370
+ (0, jsx_runtime_1.jsx)("strong", { children: ' ' + author }),
371
+ ] }),
372
+ text && (0, jsx_runtime_1.jsx)("div", { className: "dht-forwarded-body", children: (0, jsx_runtime_1.jsx)(content_1.default, { content: text, context: Object.assign({}, context, { type: content_1.RenderType.NORMAL }) }) }),
373
+ attachments.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-attachments", children: [
374
+ (0, jsx_runtime_1.jsxs)("strong", { children: ['📎 ', attachments.length, ' attachment', attachments.length !== 1 ? 's' : '', ':'] }),
375
+ (0, jsx_runtime_1.jsx)("ul", { children: attachments.map((a, ai) => {
376
+ const safeUrl = (0, utils_1.safeHref)(a.url);
377
+ return (0, jsx_runtime_1.jsxs)("li", { children: [
378
+ a.contentType?.startsWith?.('image/') && a.url
379
+ ? (0, jsx_runtime_1.jsx)("img", { src: safeUrl !== '#' ? safeUrl : '', alt: a.name || '', style: { maxWidth: '200px', borderRadius: '4px', display: 'block', margin: '4px 0' } })
380
+ : (0, jsx_runtime_1.jsxs)("a", { href: safeUrl, target: "_blank", rel: "noreferrer", children: [a.name || 'attachment', a.size ? ` (${Math.round(a.size / 1024)} KB)` : ''] })
381
+ ] }, ai);
382
+ }) })
383
+ ] }),
384
+ stickers.length > 0 && renderStickers(stickers),
385
+ embeds.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-embeds", children: ['📑 ', embeds.length, ' embed', embeds.length !== 1 ? 's' : ''] }),
386
+ nested.length > 0 && renderSnapshots(nested, context, depth + 1),
387
+ ] }, i);
388
+ })
389
+ });
390
+ }
391
+
392
+ function renderSlashCommandLine(message, context) {
393
+ const interaction = message?.interaction;
394
+ const metadata = message?.interactionMetadata;
395
+ const optsRaw = message?.interactionOptions || interaction?.options;
396
+ // Build command path: /cmd group? subcommand? plus key:value options
397
+ if (!optsRaw && !metadata) return null;
398
+
399
+ const opts = Array.isArray(optsRaw) ? optsRaw : [];
400
+ let cmdParts = [interaction?.commandName || '?'];
401
+ let valueOpts = opts;
402
+
403
+ // Detect subcommand groups: nested option structure { type:2/1 (group/subcommand), name, options }
404
+ for (let i = 0; i < 2; i++) {
405
+ if (valueOpts.length === 1 && valueOpts[0] && (valueOpts[0].type === 1 || valueOpts[0].type === 2)) {
406
+ cmdParts.push(valueOpts[0].name);
407
+ valueOpts = Array.isArray(valueOpts[0].options) ? valueOpts[0].options : [];
408
+ }
409
+ }
410
+ // Filter out autocomplete-only `focused` options
411
+ valueOpts = valueOpts.filter((o) => o && !o.focused);
412
+
413
+ if (!valueOpts.length && cmdParts.length === 1 && !metadata) return null;
414
+
415
+ const triggeredBy = metadata?.user || interaction?.user;
416
+
417
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-slash-options", children: [
418
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-slash-cmd-name", children: '/' + cmdParts.filter(Boolean).join(' ') }),
419
+ valueOpts.map((o, i) => (0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt", children: [
420
+ (0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt-name", children: [String(o.name), ':'] }),
421
+ ' ',
422
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-slash-opt-val", children: String(o.value ?? '') })
423
+ ] }, i)),
424
+ triggeredBy && (0, jsx_runtime_1.jsxs)("span", { className: "dht-slash-opt", children: ['by ', (0, jsx_runtime_1.jsx)("strong", { children: triggeredBy.displayName || triggeredBy.username || triggeredBy.id })] })
425
+ ] });
426
+ }
427
+
428
+ function renderEditHistory(history, context) {
429
+ // Optional feature — host bot may attach `message.editHistory: [{content, editedAt}, …]`
430
+ return (0, jsx_runtime_1.jsxs)("details", { className: "dht-edit-history", children: [
431
+ (0, jsx_runtime_1.jsx)("summary", { "data-i18n": "editHistoryTitle", children: t(context, 'editHistoryTitle', 'Edit history') }),
432
+ (0, jsx_runtime_1.jsx)("ol", { children: history.map((entry, i) => {
433
+ const when = entry.editedAt instanceof Date ? entry.editedAt.toISOString() : (typeof entry.editedAt === 'string' ? entry.editedAt : '');
434
+ return (0, jsx_runtime_1.jsxs)("li", { children: [
435
+ when && (0, jsx_runtime_1.jsxs)("time", { dateTime: when, children: [t(context, 'editHistoryAt', 'at'), ' ', when] }),
436
+ (0, jsx_runtime_1.jsx)("div", { children: String(entry.content || '') })
437
+ ] }, i);
438
+ }) })
439
+ ] });
440
+ }
441
+ //# sourceMappingURL=message.js.map
@@ -1,53 +1,72 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
2
  var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
3
+ var ownKeys = function(o){ownKeys = Object.getOwnPropertyNames || function (o){var ar=[];for (var k in o) if (Object.prototype.hasOwnProperty.call(o,k)) ar[ar.length]=k;return ar;};return ownKeys(o);};
4
+ return function (mod){if (mod && mod.__esModule) return mod;var result={};if (mod != null) for (var k=ownKeys(mod), i=0; i<k.length; i++) if (k[i] !== "default") Object.defineProperty(result,k[i],{get:function(){return mod[k[i]];}});Object.defineProperty(result,"default",{value:mod,enumerable:true});return result;};
34
5
  })();
35
6
  Object.defineProperty(exports, "__esModule", { value: true });
36
7
  exports.default = MessageReply;
37
8
  const jsx_runtime_1 = require("react/jsx-runtime");
38
9
  const discord_js_1 = require("discord.js");
39
10
  const content_1 = __importStar(require("./content"));
11
+
12
+ function t(context, key, fallback) {
13
+ const dict = context?.i18n?.[context?.lang] || context?.i18n?.en || {};
14
+ return dict[key] || fallback || key;
15
+ }
16
+
40
17
  async function MessageReply({ message, context }) {
41
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
42
- if (!message.reference)
43
- return null;
44
- if (message.reference.guildId !== ((_a = message.guild) === null || _a === void 0 ? void 0 : _a.id))
45
- return null;
18
+ if (!message.reference) return null;
19
+
20
+ const isCrossGuild = message.reference.guildId !== message.guild?.id;
21
+
22
+ // Cross-guild reply: show a placeholder pill instead of silently dropping.
23
+ if (isCrossGuild) {
24
+ return (0, jsx_runtime_1.jsx)("discord-reply", {
25
+ slot: "reply",
26
+ server: true,
27
+ children: (0, jsx_runtime_1.jsx)("em", { "data-i18n": "crossServerReply", children: t(context, 'crossServerReply', 'Message from another server') })
28
+ });
29
+ }
30
+
46
31
  const referencedMessage = context.messages.find((m) => m.id === message.reference.messageId);
47
32
  if (!referencedMessage)
48
33
  return (0, jsx_runtime_1.jsx)("discord-reply", { slot: "reply", children: "Message could not be loaded." });
49
- const isCrossPost = referencedMessage.reference && referencedMessage.reference.guildId !== ((_b = message.guild) === null || _b === void 0 ? void 0 : _b.id);
50
- const isCommand = referencedMessage.interaction !== null;
51
- return ((0, jsx_runtime_1.jsx)("discord-reply", { slot: "reply", edited: !isCommand && referencedMessage.editedAt !== null, attachment: referencedMessage.attachments.size > 0, author: (_e = (_d = (_c = referencedMessage.member) === null || _c === void 0 ? void 0 : _c.nickname) !== null && _d !== void 0 ? _d : referencedMessage.author.displayName) !== null && _e !== void 0 ? _e : referencedMessage.author.username, avatar: (_f = referencedMessage.author.avatarURL({ size: 32 })) !== null && _f !== void 0 ? _f : undefined, roleColor: (_h = (_g = referencedMessage.member) === null || _g === void 0 ? void 0 : _g.displayHexColor) !== null && _h !== void 0 ? _h : undefined, bot: !isCrossPost && referencedMessage.author.bot, verified: (_j = referencedMessage.author.flags) === null || _j === void 0 ? void 0 : _j.has(discord_js_1.UserFlags.VerifiedBot), op: ((_l = (_k = message === null || message === void 0 ? void 0 : message.channel) === null || _k === void 0 ? void 0 : _k.isThread) === null || _l === void 0 ? void 0 : _l.call(_k)) && referencedMessage.author.id === ((_m = message === null || message === void 0 ? void 0 : message.channel) === null || _m === void 0 ? void 0 : _m.ownerId), server: isCrossPost !== null && isCrossPost !== void 0 ? isCrossPost : undefined, command: isCommand, children: referencedMessage.content ? ((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(Object.assign({}, context), { type: content_1.RenderType.REPLY }) }) })) : isCommand ? ((0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see command." })) : ((0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see attachment." })) }));
34
+
35
+ const isCrossPost = referencedMessage.reference && referencedMessage.reference.guildId !== message.guild?.id;
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;
42
+ const author = referencedMessage.member?.nickname ?? referencedMessage.author.displayName ?? referencedMessage.author.username;
43
+ const avatar = referencedMessage.author.avatarURL?.({ size: 32 }) ?? undefined;
44
+ const roleColor = referencedMessage.member?.displayHexColor ?? undefined;
45
+ const op = referencedMessage.author.id === message?.channel?.ownerId && message?.channel?.isThread?.();
46
+
47
+ return ((0, jsx_runtime_1.jsx)("discord-reply", {
48
+ slot: "reply",
49
+ edited: !isCommand && referencedMessage.editedAt !== null,
50
+ attachment: referencedMessage.attachments.size > 0,
51
+ author: author,
52
+ avatar: avatar,
53
+ roleColor: roleColor,
54
+ bot: !isCrossPost && referencedMessage.author.bot,
55
+ verified: referencedMessage.author.flags?.has?.(discord_js_1.UserFlags.VerifiedBot),
56
+ op: op,
57
+ server: isCrossPost ?? undefined,
58
+ command: isCommand,
59
+ children: hasContent
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 }) }) })
61
+ : isCommand
62
+ ? (0, jsx_runtime_1.jsx)("em", { "data-goto": referencedMessage.id, children: "Click to see command." })
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." })
70
+ }));
52
71
  }
53
- //# sourceMappingURL=reply.js.map
72
+ //# sourceMappingURL=reply.js.map