discord-html-transcripts-fix 1.1.0 โ†’ 1.2.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.
@@ -19,56 +19,52 @@ var RenderType;
19
19
  RenderType[RenderType["NORMAL"] = 2] = "NORMAL";
20
20
  RenderType[RenderType["WEBHOOK"] = 3] = "WEBHOOK";
21
21
  })(RenderType || (exports.RenderType = RenderType = {}));
22
- /**
23
- * Renders discord markdown content
24
- * @param content - The content to render
25
- * @param context - The context to render the content in
26
- * @returns
27
- */
22
+
28
23
  async function MessageContent({ content, context }) {
29
24
  if (context.type === RenderType.REPLY && content.length > 180)
30
25
  content = content.slice(0, 180) + '...';
31
- // parse the markdown
32
26
  const parsed = (0, discord_markdown_parser_1.default)(content, context.type === RenderType.EMBED || context.type === RenderType.WEBHOOK ? 'extended' : 'normal');
33
- // check if the parsed content is only emojis
34
27
  const isOnlyEmojis = parsed.every((node) => ['emoji', 'twemoji'].includes(node.type) || (node.type === 'text' && node.content.trim().length === 0));
35
28
  if (isOnlyEmojis) {
36
- // now check if there are less than or equal to 25 emojis
37
29
  const emojis = parsed.filter((node) => ['emoji', 'twemoji'].includes(node.type));
38
30
  if (emojis.length <= 25) {
39
- context._internal = {
40
- largeEmojis: true,
41
- };
31
+ context._internal = { largeEmojis: true };
42
32
  }
43
33
  }
44
34
  return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: parsed, context: context });
45
35
  }
46
- // This function can probably be combined into the MessageSingleASTNode function
47
- async function MessageASTNodes({ nodes, context, }) {
36
+
37
+ async function MessageASTNodes({ nodes, context }) {
48
38
  if (Array.isArray(nodes)) {
49
39
  return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: nodes.map((node, i) => ((0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: node, context: context }, i))) }));
50
40
  }
51
- else {
52
- return (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: nodes, context: context });
53
- }
41
+ return (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: nodes, context: context });
54
42
  }
43
+
55
44
  async function MessageSingleASTNode({ node, context }) {
56
- var _a, _b;
57
- if (!node)
58
- return null;
45
+ if (!node) return null;
59
46
  try {
60
47
  return await renderASTNode(node, context);
61
- }
62
- catch (err) {
63
- console.warn('[discord-html-transcripts-fix] Failed to render AST node, falling back to raw:', err && err.message ? err.message : err);
64
- // Fallback: render as plain text from node.content if possible
48
+ } catch (err) {
49
+ console.warn('[discord-html-transcripts-fix] AST node render failed:', err && err.message ? err.message : err);
65
50
  try {
66
51
  if (typeof node.content === 'string') return node.content;
67
52
  if (Array.isArray(node.content)) return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context });
68
- } catch (_e) { /* ignore */ }
53
+ } catch (_e) {}
69
54
  return null;
70
55
  }
71
56
  }
57
+
58
+ // Detect Discord "guide" pseudo-channels: <id:guide>, <id:browse>, <id:customize>, <id:welcome>, <id:linked-roles>
59
+ const GUIDE_LABELS = {
60
+ 'guide': 'Server Guide',
61
+ 'browse': 'Channels & Roles',
62
+ 'customize': 'Customize',
63
+ 'welcome': 'Welcome',
64
+ 'linked-roles': 'Linked Roles',
65
+ 'home': 'Home',
66
+ };
67
+
72
68
  async function renderASTNode(node, context) {
73
69
  var _a, _b;
74
70
  const type = node.type;
@@ -76,10 +72,10 @@ async function renderASTNode(node, context) {
76
72
  case 'text':
77
73
  return node.content;
78
74
  case 'link':
79
- return ((0, jsx_runtime_1.jsx)("discord-link", { href: node.target, children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
75
+ return ((0, jsx_runtime_1.jsx)("discord-link", { href: (0, utils_1.safeHref)(node.target), children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
80
76
  case 'url':
81
77
  case 'autolink':
82
- return ((0, jsx_runtime_1.jsx)("discord-link", { href: node.target, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
78
+ return ((0, jsx_runtime_1.jsx)("discord-link", { href: (0, utils_1.safeHref)(node.target), target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
83
79
  case 'blockQuote':
84
80
  if (context.type === RenderType.REPLY) {
85
81
  return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context });
@@ -87,24 +83,35 @@ async function renderASTNode(node, context) {
87
83
  return ((0, jsx_runtime_1.jsx)("discord-quote", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
88
84
  case 'br':
89
85
  case 'newline':
90
- if (context.type === RenderType.REPLY)
91
- return ' ';
86
+ if (context.type === RenderType.REPLY) return ' ';
92
87
  return (0, jsx_runtime_1.jsx)("br", {});
93
88
  case 'channel': {
94
89
  const id = node.id;
90
+ // Discord pseudo channels (<id:guide>, <id:browse>, โ€ฆ) come through with letters as id
91
+ if (typeof id === 'string' && /^[a-z][a-z-]*$/.test(id)) {
92
+ const label = GUIDE_LABELS[id] || id;
93
+ return ((0, jsx_runtime_1.jsx)("discord-mention", { type: "channel", className: "dht-mention", children: label }));
94
+ }
95
95
  const channel = await context.callbacks.resolveChannel(id);
96
96
  return ((0, jsx_runtime_1.jsx)("discord-mention", { type: channel ? (channel.isDMBased() ? 'channel' : getChannelType(channel.type)) : 'channel', "data-mention-type": "channel", "data-mention-id": id, className: "dht-mention", children: channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `<#${id}>` }));
97
97
  }
98
98
  case 'role': {
99
99
  const id = node.id;
100
100
  const role = await context.callbacks.resolveRole(id);
101
- return ((0, jsx_runtime_1.jsx)("discord-mention", { type: "role", color: context.type === RenderType.REPLY ? undefined : role === null || role === void 0 ? void 0 : role.hexColor, "data-mention-type": "role", "data-mention-id": id, className: "dht-mention", children: role ? role.name : `<@&${id}>` }));
101
+ const color = role?.hexColor ? (0, utils_1.safeColor)(role.hexColor, undefined) : undefined;
102
+ return ((0, jsx_runtime_1.jsx)("discord-mention", { type: "role", color: context.type === RenderType.REPLY ? undefined : color, "data-mention-type": "role", "data-mention-id": id, className: "dht-mention", children: role ? role.name : `<@&${id}>` }));
102
103
  }
103
104
  case 'user': {
104
105
  const id = node.id;
105
106
  const user = await context.callbacks.resolveUser(id);
106
107
  return (0, jsx_runtime_1.jsx)("discord-mention", { type: "user", "data-mention-type": "user", "data-mention-id": id, className: "dht-mention", children: user ? ((_a = user.displayName) !== null && _a !== void 0 ? _a : user.username) : `<@${id}>` });
107
108
  }
109
+ case 'slashCommand':
110
+ case 'command': {
111
+ // </name:id> renders as blue pill like Discord
112
+ const name = node.name || node.content || 'command';
113
+ return ((0, jsx_runtime_1.jsx)("discord-mention", { type: "command", className: "dht-mention dht-mention--cmd", children: '/' + String(name) }));
114
+ }
108
115
  case 'here':
109
116
  case 'everyone':
110
117
  return ((0, jsx_runtime_1.jsx)("discord-mention", { type: 'role', highlight: true, children: `@${type}` }));
@@ -124,53 +131,46 @@ async function renderASTNode(node, context) {
124
131
  case 'strikethrough':
125
132
  return ((0, jsx_runtime_1.jsx)("s", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
126
133
  case 'emoticon':
127
- return typeof node.content === 'string' ? (node.content) : ((0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }));
134
+ return typeof node.content === 'string' ? node.content : ((0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }));
128
135
  case 'spoiler':
129
136
  return ((0, jsx_runtime_1.jsx)("discord-spoiler", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }) }));
130
137
  case 'emoji':
131
138
  case 'twemoji':
132
139
  return ((0, jsx_runtime_1.jsx)("discord-custom-emoji", { name: node.name, url: (0, utils_1.parseDiscordEmoji)(node), embedEmoji: context.type === RenderType.EMBED, jumbo: (_b = context._internal) === null || _b === void 0 ? void 0 : _b.largeEmojis }));
133
140
  case 'timestamp': {
134
- // TODO: Make this reactive
135
- // https://github.com/ItzDerock/discord-components/blob/main/packages/core/src/components/discord-time/discord-time.tsx
136
- // Defensive parsing: Discord timestamps are Unix seconds (string oder number),
137
- // ungรผltige Werte (z.B. <t:abc:F> oder absurd groรŸe Zahlen) sollen kein RangeError werfen.
138
- var raw = node.timestamp;
139
- var dateMs = NaN;
141
+ // Defensive: invalid <t:...> markers must not throw
142
+ const raw = node.timestamp;
143
+ let dateMs = NaN;
140
144
  if (typeof raw === 'number') {
141
145
  dateMs = raw < 1e12 ? raw * 1000 : raw;
142
146
  } else if (typeof raw === 'string') {
143
- var n = parseInt(raw, 10);
147
+ const n = parseInt(raw, 10);
144
148
  if (Number.isFinite(n)) dateMs = n < 1e12 ? n * 1000 : n;
145
149
  }
146
- var dt = new Date(dateMs);
147
- var iso = '';
150
+ const dt = new Date(dateMs);
151
+ let iso = '';
148
152
  try { iso = Number.isFinite(dt.getTime()) ? dt.toISOString() : ''; } catch (_e) { iso = ''; }
149
153
  return (0, jsx_runtime_1.jsx)("discord-time", { children: iso });
150
154
  }
151
- // return <DiscordTime timestamp={parseInt(node.timestamp) * 1000} format={node.format} />;
152
155
  default: {
153
- // console.log(`[discord-html-transcripts] Unknown node type: ${type}`, node);
154
- return typeof node.content === 'string' ? (node.content) : ((0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }));
156
+ return typeof node.content === 'string' ? node.content : ((0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context }));
155
157
  }
156
158
  }
157
159
  }
160
+
158
161
  function getChannelType(channelType) {
159
162
  switch (channelType) {
160
163
  case discord_js_1.ChannelType.GuildCategory:
161
164
  case discord_js_1.ChannelType.GuildAnnouncement:
162
165
  case discord_js_1.ChannelType.GuildText:
163
166
  case discord_js_1.ChannelType.DM:
164
- case discord_js_1.ChannelType.GroupDM:
165
- case discord_js_1.ChannelType.GuildDirectory:
166
- case discord_js_1.ChannelType.GuildMedia:
167
167
  return 'channel';
168
168
  case discord_js_1.ChannelType.GuildVoice:
169
169
  case discord_js_1.ChannelType.GuildStageVoice:
170
170
  return 'voice';
171
+ case discord_js_1.ChannelType.AnnouncementThread:
171
172
  case discord_js_1.ChannelType.PublicThread:
172
173
  case discord_js_1.ChannelType.PrivateThread:
173
- case discord_js_1.ChannelType.AnnouncementThread:
174
174
  return 'thread';
175
175
  case discord_js_1.ChannelType.GuildForum:
176
176
  return 'forum';
@@ -178,4 +178,4 @@ function getChannelType(channelType) {
178
178
  return 'channel';
179
179
  }
180
180
  }
181
- //# sourceMappingURL=content.js.map
181
+ //# sourceMappingURL=content.js.map
@@ -1,44 +1,65 @@
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.DiscordEmbed = DiscordEmbed;
37
8
  const jsx_runtime_1 = require("react/jsx-runtime");
38
9
  const embeds_1 = require("../../utils/embeds");
39
10
  const content_1 = __importStar(require("./content"));
11
+ const utils_1 = require("../../utils/utils");
12
+
40
13
  async function DiscordEmbed({ embed, context }) {
41
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
42
- return ((0, jsx_runtime_1.jsxs)("discord-embed", { "embed-title": (_a = embed.title) !== null && _a !== void 0 ? _a : undefined, slot: "embeds", "author-image": (_c = (_b = embed.author) === null || _b === void 0 ? void 0 : _b.proxyIconURL) !== null && _c !== void 0 ? _c : (_d = embed.author) === null || _d === void 0 ? void 0 : _d.iconURL, "author-name": (_e = embed.author) === null || _e === void 0 ? void 0 : _e.name, "author-url": (_f = embed.author) === null || _f === void 0 ? void 0 : _f.url, color: (_g = embed.hexColor) !== null && _g !== void 0 ? _g : undefined, image: (_j = (_h = embed.image) === null || _h === void 0 ? void 0 : _h.proxyURL) !== null && _j !== void 0 ? _j : (_k = embed.image) === null || _k === void 0 ? void 0 : _k.url, thumbnail: (_m = (_l = embed.thumbnail) === null || _l === void 0 ? void 0 : _l.proxyURL) !== null && _m !== void 0 ? _m : (_o = embed.thumbnail) === null || _o === void 0 ? void 0 : _o.url, url: (_p = embed.url) !== null && _p !== void 0 ? _p : undefined, children: [embed.description && ((0, jsx_runtime_1.jsx)("discord-embed-description", { slot: "description", children: (0, jsx_runtime_1.jsx)(content_1.default, { content: embed.description, context: Object.assign(Object.assign({}, context), { type: content_1.RenderType.EMBED }) }) })), embed.fields.length > 0 && ((0, jsx_runtime_1.jsx)("discord-embed-fields", { slot: "fields", children: embed.fields.map(async (field, id) => ((0, jsx_runtime_1.jsx)("discord-embed-field", { "field-title": field.name, inline: !!field.inline, "inline-index": field.inline ? (0, embeds_1.calculateInlineIndex)(embed.fields, id) : undefined, children: (0, jsx_runtime_1.jsx)(content_1.default, { content: field.value, context: Object.assign(Object.assign({}, context), { type: content_1.RenderType.EMBED }) }) }, `${context.message.id}-e-${context.index}-f-${id}`))) })), embed.footer && ((0, jsx_runtime_1.jsx)("discord-embed-footer", { slot: "footer", footerImage: (_q = embed.footer.proxyIconURL) !== null && _q !== void 0 ? _q : embed.footer.iconURL, timestamp: (_r = embed.timestamp) !== null && _r !== void 0 ? _r : undefined, children: embed.footer.text }))] }, `${context.message.id}-e-${context.index}`));
14
+ const title = embed.title ?? undefined;
15
+ const authorImage = embed.author?.proxyIconURL ?? embed.author?.iconURL;
16
+ const authorName = embed.author?.name;
17
+ const authorUrl = embed.author?.url ? (0, utils_1.safeHref)(embed.author.url) : undefined;
18
+ const color = embed.hexColor ? (0, utils_1.safeColor)(embed.hexColor, undefined) : undefined;
19
+ const image = embed.image?.proxyURL ?? embed.image?.url;
20
+ const thumb = embed.thumbnail?.proxyURL ?? embed.thumbnail?.url;
21
+ const url = embed.url ? (0, utils_1.safeHref)(embed.url) : undefined;
22
+ const provider = embed.provider?.name;
23
+ const video = embed.video?.url ? (0, utils_1.safeHref)(embed.video.url) : null;
24
+
25
+ return ((0, jsx_runtime_1.jsxs)("discord-embed", {
26
+ "embed-title": title,
27
+ slot: "embeds",
28
+ "author-image": authorImage,
29
+ "author-name": authorName,
30
+ "author-url": authorUrl,
31
+ color: color,
32
+ image: image,
33
+ thumbnail: thumb,
34
+ url: url,
35
+ children: [
36
+ provider && ((0, jsx_runtime_1.jsx)("div", { className: "dht-embed-provider", slot: "author", children: provider })),
37
+ embed.description && ((0, jsx_runtime_1.jsx)("discord-embed-description", {
38
+ slot: "description",
39
+ children: (0, jsx_runtime_1.jsx)(content_1.default, { content: embed.description, context: Object.assign({}, context, { type: content_1.RenderType.EMBED }) })
40
+ })),
41
+ embed.fields.length > 0 && ((0, jsx_runtime_1.jsx)("discord-embed-fields", {
42
+ slot: "fields",
43
+ children: embed.fields.map((field, id) => ((0, jsx_runtime_1.jsx)(EmbedField, { embed, field, id, context }, `${context.message.id}-e-${context.index}-f-${id}`)))
44
+ })),
45
+ video && ((0, jsx_runtime_1.jsx)("div", { className: "dht-embed-video", children: (0, jsx_runtime_1.jsx)("a", { href: video, target: "_blank", rel: "noreferrer", children: 'โ–ถ Open video' }) })),
46
+ embed.footer && ((0, jsx_runtime_1.jsx)("discord-embed-footer", {
47
+ slot: "footer",
48
+ footerImage: embed.footer.proxyIconURL ?? embed.footer.iconURL,
49
+ timestamp: embed.timestamp ?? undefined,
50
+ children: embed.footer.text
51
+ }))
52
+ ]
53
+ }, `${context.message.id}-e-${context.index}`));
43
54
  }
44
- //# sourceMappingURL=embed.js.map
55
+
56
+ // Extracted as proper async component (was inline async arrow inside .map() โ€” undefined React behavior)
57
+ async function EmbedField({ embed, field, id, context }) {
58
+ return (0, jsx_runtime_1.jsx)("discord-embed-field", {
59
+ "field-title": field.name,
60
+ inline: !!field.inline,
61
+ "inline-index": field.inline ? (0, embeds_1.calculateInlineIndex)(embed.fields, id) : undefined,
62
+ children: (0, jsx_runtime_1.jsx)(content_1.default, { content: field.value, context: Object.assign({}, context, { type: content_1.RenderType.EMBED }) })
63
+ });
64
+ }
65
+ //# sourceMappingURL=embed.js.map
@@ -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,21 @@ 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_SUPPRESS_EMBEDS = 1 << 2;
51
+ const FLAG_IS_VOICE_MESSAGE = 1 << 13;
52
+
53
+ function t(context, key, fallback) {
54
+ const dict = context?.i18n?.[context?.lang] || context?.i18n?.en || {};
55
+ return dict[key] || fallback || key;
56
+ }
57
+
58
+ async function DiscordMessage({ message, context }) {
50
59
  try {
51
60
  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);
61
+ return (0, jsx_runtime_1.jsx)(systemMessage_1.default, { message: message, context: context });
62
+
63
+ const isCrosspost = message.reference && message.reference.guildId !== message.guild?.id;
54
64
  const stickers = message.stickers && message.stickers.size > 0
55
65
  ? Array.from(message.stickers.values()).map((s) => ({
56
66
  id: s.id,
@@ -60,36 +70,93 @@ async function DiscordMessage({ message, context, }) {
60
70
  }))
61
71
  : [];
62
72
  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));
73
+
74
+ const flags = (typeof message.flags?.bitfield === 'number') ? message.flags.bitfield : 0;
75
+ const suppressEmbeds = !!(flags & FLAG_SUPPRESS_EMBEDS);
76
+ const isVoice = !!(flags & FLAG_IS_VOICE_MESSAGE);
77
+
78
+ // Forwarded message snapshots (messageSnapshots) โ€” render before the empty body
79
+ const snapshots = Array.isArray(message.messageSnapshots) ? message.messageSnapshots : [];
80
+
81
+ const isApp = !!(message.author?.bot && message.applicationId);
82
+
83
+ const editedAtIso = message.editedAt instanceof Date ? message.editedAt.toISOString() : null;
84
+ const pinned = !!message.pinned;
85
+
86
+ return ((0, jsx_runtime_1.jsxs)("discord-message", {
87
+ id: `m-${message.id}`,
88
+ timestamp: message.createdAt,
89
+ edited: message.editedAt !== null,
90
+ server: isCrosspost ?? undefined,
91
+ highlight: message.mentions.everyone || pinned,
92
+ profile: message.author.id,
93
+ children: [
94
+ pinned && (0, jsx_runtime_1.jsx)("div", { className: "dht-pinned-indicator", title: "Pinned", children: "๐Ÿ“Œ" }),
95
+ (0, jsx_runtime_1.jsx)(reply_1.default, { message: message, context: context }),
96
+ message.interaction && ((0, jsx_runtime_1.jsx)("discord-command", { slot: "reply", profile: message.interaction.user.id, command: '/' + message.interaction.commandName })),
97
+ isApp && (0, jsx_runtime_1.jsx)("span", { className: "dht-app-badge", "data-i18n": "appBadge", children: t(context, 'appBadge', 'APP') }),
98
+ editedAtIso && (0, jsx_runtime_1.jsx)("span", { className: "dht-edit-marker", title: editedAtIso, "data-edit-iso": editedAtIso, "data-i18n": "edited", "data-i18n-params": JSON.stringify({ time: editedAtIso }), children: '(' + t(context, 'edited', 'edited') + ')' }),
99
+ Array.isArray(message.editHistory) && message.editHistory.length > 0 && renderEditHistory(message.editHistory, context),
100
+ snapshots.length > 0 && renderSnapshots(snapshots, context),
101
+ 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 }) })),
102
+ (0, jsx_runtime_1.jsx)(attachment_1.Attachments, { message: message, context: context }),
103
+ isVoice && renderVoiceIndicator(message, context),
104
+ stickers.length > 0 && renderStickers(stickers),
105
+ poll && renderPoll(poll, context),
106
+ !suppressEmbeds && message.embeds.map((embed, id) => ((0, jsx_runtime_1.jsx)(embed_1.DiscordEmbed, { embed: embed, context: Object.assign({}, context, { index: id, message }) }, id))),
107
+ suppressEmbeds && message.embeds.length > 0 && (0, jsx_runtime_1.jsx)("span", { className: "dht-suppressed", "data-i18n": "suppressedEmbeds", children: t(context, 'suppressedEmbeds', '(embeds hidden)') }),
108
+ 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))) })),
109
+ message.reactions.cache.size > 0 && ((0, jsx_runtime_1.jsx)("discord-reactions", {
110
+ slot: "reactions",
111
+ children: Array.from(message.reactions.cache.values()).map((reaction, id) => {
112
+ const burst = reaction.burst_count > 0 || (reaction.count_details?.burst > 0);
113
+ return ((0, jsx_runtime_1.jsx)("discord-reaction", {
114
+ name: reaction.emoji?.name || ':unknown:',
115
+ emoji: (0, utils_1.parseDiscordEmoji)(reaction.emoji) || undefined,
116
+ count: reaction.count ?? 0,
117
+ "data-burst": burst ? 'true' : undefined,
118
+ className: burst ? 'dht-reaction--burst' : undefined
119
+ }, `${message.id}r${id}`));
120
+ })
121
+ })),
122
+ message.hasThread && message.thread && renderThread(message.thread, context),
123
+ ]
124
+ }, message.id));
125
+ } catch (err) {
126
+ console.warn('[discord-html-transcripts-fix] Render failed', message?.id, err?.message || err);
127
+ const safeAuthor = (message?.author?.displayName || message?.author?.username) || 'Unknown';
128
+ 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
129
  }
77
130
  }
78
131
 
132
+ function renderThread(thread, context) {
133
+ const cta = thread.messageCount
134
+ ? `${thread.messageCount} Message${thread.messageCount > 1 ? 's' : ''}`
135
+ : 'View Thread';
136
+ const badges = [];
137
+ 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'));
138
+ 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'));
139
+ return (0, jsx_runtime_1.jsxs)("discord-thread", { slot: "thread", name: thread.name, cta, children: [
140
+ badges,
141
+ thread.lastMessage
142
+ ? (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 }) }) })
143
+ : 'Thread messages not saved.'
144
+ ] });
145
+ }
146
+
79
147
  function renderStickers(stickers) {
80
148
  return (0, jsx_runtime_1.jsx)("div", {
81
149
  className: "dht-stickers",
82
- style: { display: 'flex', gap: '8px', flexWrap: 'wrap', marginTop: '4px' },
83
150
  children: stickers.map((s) => (0, jsx_runtime_1.jsx)("img", {
84
151
  src: s.url,
85
152
  alt: s.name || 'sticker',
86
153
  title: s.name || '',
87
- style: { width: '160px', height: '160px', borderRadius: '8px', objectFit: 'contain', background: 'rgba(255,255,255,0.02)' }
154
+ className: "dht-sticker"
88
155
  }, s.id))
89
156
  });
90
157
  }
91
158
 
92
- function renderPoll(poll) {
159
+ function renderPoll(poll, context) {
93
160
  let totalVotes = 0;
94
161
  const answers = poll.answers ? Array.from(poll.answers.values()) : [];
95
162
  for (const a of answers) totalVotes += a.voteCount || 0;
@@ -97,39 +164,85 @@ function renderPoll(poll) {
97
164
 
98
165
  return (0, jsx_runtime_1.jsxs)("div", {
99
166
  className: "dht-poll",
100
- style: {
101
- border: '1px solid #2b2d31', background: '#1e1f22', borderRadius: '8px',
102
- padding: '12px 14px', marginTop: '6px', maxWidth: '480px'
103
- },
104
167
  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'] }),
168
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-q", children: ['๐Ÿ“Š ', poll.question?.text || 'Poll'] }),
106
169
  (0, jsx_runtime_1.jsx)("div", {
107
- style: { display: 'flex', flexDirection: 'column', gap: '6px' },
170
+ className: "dht-poll-answers",
108
171
  children: answers.map((a, i) => {
109
- const text = (a.text || a.poll_media?.text || a.media?.text || a.answer_id || `Option ${i + 1}`);
172
+ const text = a.text ?? a.media?.text ?? `Option ${i + 1}`;
110
173
  const votes = a.voteCount || 0;
111
174
  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);
175
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-a", children: [
176
+ (0, jsx_runtime_1.jsx)("div", { className: "dht-poll-bar", style: { width: pct + '%' } }),
177
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-line", children: [
178
+ (0, jsx_runtime_1.jsx)("span", { children: String(text) }),
179
+ (0, jsx_runtime_1.jsxs)("span", { className: "dht-poll-pct", children: [votes, ' (', pct, '%)'] })
180
+ ] })
181
+ ] }, i);
127
182
  })
128
183
  }),
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
- })
184
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-poll-foot", children: [totalVotes, ' votes', expires ? ' ยท ends ' + expires : ''] })
132
185
  ]
133
186
  });
134
187
  }
135
- //# sourceMappingURL=message.js.map
188
+
189
+ function renderVoiceIndicator(message, context) {
190
+ const audio = message.attachments?.find?.((a) => typeof a.contentType === 'string' && a.contentType.startsWith('audio/'));
191
+ const duration = audio?.duration_secs || audio?.durationSecs;
192
+ const waveformB64 = audio?.waveform;
193
+ let waveformSvg = null;
194
+ if (typeof waveformB64 === 'string' && waveformB64.length > 0) {
195
+ try {
196
+ const buf = Buffer.from(waveformB64, 'base64');
197
+ const bars = Array.from(buf).slice(0, 100);
198
+ const max = bars.length ? Math.max(1, ...bars) : 1;
199
+ const width = bars.length * 3;
200
+ const rects = bars.map((b, i) => {
201
+ const h = Math.max(2, (b / max) * 20);
202
+ return `<rect x="${i * 3}" y="${(22 - h) / 2}" width="2" height="${h}" rx="1" fill="#5865F2"/>`;
203
+ }).join('');
204
+ waveformSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} 22" width="${width}" height="22">${rects}</svg>`;
205
+ } catch (_e) {}
206
+ }
207
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-voice", children: [
208
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-voice-icon", children: '๐ŸŽค' }),
209
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-voice-label", "data-i18n": "voiceMessage", children: t(context, 'voiceMessage', 'Voice message') }),
210
+ waveformSvg && (0, jsx_runtime_1.jsx)("span", { className: "dht-voice-wave", dangerouslySetInnerHTML: { __html: waveformSvg } }),
211
+ duration ? (0, jsx_runtime_1.jsxs)("span", { className: "dht-voice-dur", children: [Math.round(duration), 's'] }) : null,
212
+ ] });
213
+ }
214
+
215
+ function renderSnapshots(snapshots, context) {
216
+ return (0, jsx_runtime_1.jsx)("div", {
217
+ className: "dht-forwarded-wrap",
218
+ children: snapshots.map((snap, i) => {
219
+ const m = snap.message || snap;
220
+ const author = m?.author?.displayName || m?.author?.username || 'Unknown';
221
+ const text = m?.content || '';
222
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded", children: [
223
+ (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-head", children: [
224
+ (0, jsx_runtime_1.jsx)("span", { className: "dht-forwarded-icon", children: 'โ†ช๏ธ' }),
225
+ (0, jsx_runtime_1.jsx)("span", { "data-i18n": "forwardedFrom", children: t(context, 'forwardedFrom', 'Forwarded from') }),
226
+ (0, jsx_runtime_1.jsx)("strong", { children: ' ' + author }),
227
+ ] }),
228
+ text && (0, jsx_runtime_1.jsx)("div", { className: "dht-forwarded-body", children: (0, jsx_runtime_1.jsx)(content_1.default, { content: text, context: Object.assign({}, context, { type: content_1.RenderType.REPLY }) }) }),
229
+ Array.isArray(m?.attachments) && m.attachments.length > 0 && (0, jsx_runtime_1.jsxs)("div", { className: "dht-forwarded-attachments", children: [m.attachments.length, ' attachment(s)'] }),
230
+ ] }, i);
231
+ })
232
+ });
233
+ }
234
+
235
+ function renderEditHistory(history, context) {
236
+ // Optional feature โ€” host bot may attach `message.editHistory: [{content, editedAt}, โ€ฆ]`
237
+ return (0, jsx_runtime_1.jsxs)("details", { className: "dht-edit-history", children: [
238
+ (0, jsx_runtime_1.jsx)("summary", { "data-i18n": "editHistoryTitle", children: t(context, 'editHistoryTitle', 'Edit history') }),
239
+ (0, jsx_runtime_1.jsx)("ol", { children: history.map((entry, i) => {
240
+ const when = entry.editedAt instanceof Date ? entry.editedAt.toISOString() : (typeof entry.editedAt === 'string' ? entry.editedAt : '');
241
+ return (0, jsx_runtime_1.jsxs)("li", { children: [
242
+ when && (0, jsx_runtime_1.jsxs)("time", { dateTime: when, children: [t(context, 'editHistoryAt', 'at'), ' ', when] }),
243
+ (0, jsx_runtime_1.jsx)("div", { children: String(entry.content || '') })
244
+ ] }, i);
245
+ }) })
246
+ ] });
247
+ }
248
+ //# sourceMappingURL=message.js.map