discord-html-transcripts-fix 1.0.0 → 1.1.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/README.md CHANGED
@@ -6,13 +6,26 @@ This is a fork of [discord-html-transcripts](https://github.com/ItzDerock/discor
6
6
 
7
7
  - Fixed compatibility with the latest discord.js version
8
8
  - Added support for Discord Components v2
9
+ - **Bug fix**: defensive timestamp parsing — invalid `<t:...>` markers in messages no longer throw `RangeError: Invalid time value` and abort the whole transcript
10
+ - **Bug fix**: removed `process.exit(1)` on discord.js version mismatch — the library now only warns, so the host bot keeps running
11
+ - **Bug fix**: render error boundaries at AST-node and message level — a single broken message can no longer kill the entire transcript; it falls back to a "[message failed to render]" placeholder
12
+ - **Bug fix**: `parseDiscordEmoji` no longer crashes on deleted / unknown reactions where `emoji.name` is `null`
13
+ - **New**: clickable user / role / channel mentions — clicking opens a Discord-style popup with avatar, display name, username, roles (as colored pills), join date, account creation date, role color/position/member count, channel topic, IDs, etc.
14
+ - **New**: `buildExtendedContext` collects full user / role / channel details and ships them in `window.$discordMessage` for the popups (also includes mentioned-but-silent users)
15
+ - **New**: stickers in messages are rendered (image, name)
16
+ - **New**: polls are rendered with answer bars, vote counts, percentages, expiry time
17
+ - **New**: more reliable reaction rendering — handles missing emoji name / unknown custom emojis gracefully
18
+ - **New**: many system message types that were previously stubbed (`undefined`) are now rendered — channel name change, channel icon change, thread created, slash-command used, context-menu command, call started, channel-follow added, recipient left, AutoMod block, guild incident reports, poll result
19
+ - **New**: floating "back-to-top" button that fades in after scrolling
20
+ - **New**: `dht-mention` hover effect on all clickable mentions
21
+ - **DX**: `react`, `react-dom` and `debug` moved into regular `dependencies`, so users no longer have to install them manually (`debug` was actually a missing runtime dependency in the original — `images.js` requires it)
9
22
 
10
23
  ## Requirements
11
24
 
12
- This package requires `react` and `react-dom` as peer dependencies. Install them before using this package:
25
+ Only [`discord.js`](https://discord.js.org/) is required as a peer dependency (you already have it in your bot). `react`, `react-dom`, and `debug` are installed automatically. Just run:
13
26
 
14
27
  ```bash
15
- npm install react@^19.2.3 react-dom@^19.2.3
28
+ npm install discord-html-transcripts-fix
16
29
  ```
17
30
 
18
31
  ## Usage
@@ -41,19 +41,29 @@ catch (_b) { } // ignore errors
41
41
  async function render(_a) {
42
42
  var _b;
43
43
  var { messages, channel, callbacks } = _a, options = __rest(_a, ["messages", "channel", "callbacks"]);
44
- const profiles = (0, buildProfiles_1.buildProfiles)(messages);
45
- const { prelude } = await (0, static_1.prerenderToNodeStream)((0, jsx_runtime_1.jsxs)("html", { children: [(0, jsx_runtime_1.jsxs)("head", { children: [(0, jsx_runtime_1.jsx)("meta", { charSet: "utf-8" }), (0, jsx_runtime_1.jsx)("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), (0, jsx_runtime_1.jsx)("link", { rel: "preconnect", href: "https://cdn.jsdelivr.net/" }), (0, jsx_runtime_1.jsx)("link", { rel: "preconnect", href: "https://cdn.discordapp.com" }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.ggSansFont } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: styles_1.globalStyles } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: DiscordImage_1.DiscordAttachmentStyles } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: DiscordHighlightedCode_1.DiscordHighlightStyles } }), (0, jsx_runtime_1.jsx)("link", { rel: "icon", type: "image/png", href: options.favicon === 'guild'
44
+ const profiles = await (0, buildProfiles_1.buildProfiles)(messages);
45
+ const extended = await (0, buildProfiles_1.buildExtendedContext)(messages, channel).catch((e) => {
46
+ console.warn('[discord-html-transcripts-fix] buildExtendedContext failed:', e && e.message ? e.message : e);
47
+ return { users: {}, roles: {}, channels: {} };
48
+ });
49
+ const globalData = {
50
+ profiles,
51
+ users: extended.users,
52
+ roles: extended.roles,
53
+ channels: extended.channels,
54
+ };
55
+ const { prelude } = await (0, static_1.prerenderToNodeStream)((0, jsx_runtime_1.jsxs)("html", { children: [(0, jsx_runtime_1.jsxs)("head", { children: [(0, jsx_runtime_1.jsx)("meta", { charSet: "utf-8" }), (0, jsx_runtime_1.jsx)("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), (0, jsx_runtime_1.jsx)("link", { rel: "preconnect", href: "https://cdn.jsdelivr.net/" }), (0, jsx_runtime_1.jsx)("link", { rel: "preconnect", href: "https://cdn.discordapp.com" }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.ggSansFont } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: styles_1.globalStyles } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.mentionPopupStyles } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: client_1.transcriptUtils } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: DiscordImage_1.DiscordAttachmentStyles } }), (0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: DiscordHighlightedCode_1.DiscordHighlightStyles } }), (0, jsx_runtime_1.jsx)("link", { rel: "icon", type: "image/png", href: options.favicon === 'guild'
46
56
  ? channel.isDMBased()
47
57
  ? undefined
48
58
  : ((_b = channel.guild.iconURL({ size: 16, extension: 'png' })) !== null && _b !== void 0 ? _b : undefined)
49
59
  : options.favicon }), (0, jsx_runtime_1.jsx)("title", { children: channel.isDMBased() ? 'Direct Messages' : channel.name }), (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: {
50
60
  __html: client_1.scrollToMessage,
51
61
  } }), !options.hydrate && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: {
52
- __html: `globalThis.$discordMessage={profiles:${JSON.stringify(await profiles)}}`,
62
+ __html: `globalThis.$discordMessage=${JSON.stringify(globalData)}`,
53
63
  } }), (0, jsx_runtime_1.jsx)("script", { type: "module", src: `https://cdn.jsdelivr.net/npm/@skyra/discord-components-core@${discordComponentsVersion}/+esm` })] }))] }), (0, jsx_runtime_1.jsx)("body", { style: {
54
64
  margin: 0,
55
65
  minHeight: '100vh',
56
- }, children: (0, jsx_runtime_1.jsx)(transcript_1.default, Object.assign({ messages: messages, channel: channel, callbacks: callbacks }, options)) }), options.hydrate && (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.revealSpoiler } })] }));
66
+ }, children: (0, jsx_runtime_1.jsx)(transcript_1.default, Object.assign({ messages: messages, channel: channel, callbacks: callbacks }, options)) }), (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.mentionPopup } }), (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.backToTop } }), options.hydrate && (0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: { __html: client_1.revealSpoiler } })] }));
57
67
  const markup = await (0, utils_1.streamToString)(prelude);
58
68
  if (options.hydrate) {
59
69
  const { render: renderLit } = await import('@lit-labs/ssr');
@@ -56,6 +56,21 @@ async function MessageSingleASTNode({ node, context }) {
56
56
  var _a, _b;
57
57
  if (!node)
58
58
  return null;
59
+ try {
60
+ 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
65
+ try {
66
+ if (typeof node.content === 'string') return node.content;
67
+ if (Array.isArray(node.content)) return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context: context });
68
+ } catch (_e) { /* ignore */ }
69
+ return null;
70
+ }
71
+ }
72
+ async function renderASTNode(node, context) {
73
+ var _a, _b;
59
74
  const type = node.type;
60
75
  switch (type) {
61
76
  case 'text':
@@ -78,17 +93,17 @@ async function MessageSingleASTNode({ node, context }) {
78
93
  case 'channel': {
79
94
  const id = node.id;
80
95
  const channel = await context.callbacks.resolveChannel(id);
81
- return ((0, jsx_runtime_1.jsx)("discord-mention", { type: channel ? (channel.isDMBased() ? 'channel' : getChannelType(channel.type)) : 'channel', children: channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `<#${id}>` }));
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}>` }));
82
97
  }
83
98
  case 'role': {
84
99
  const id = node.id;
85
100
  const role = await context.callbacks.resolveRole(id);
86
- 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, children: role ? role.name : `<@&${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}>` }));
87
102
  }
88
103
  case 'user': {
89
104
  const id = node.id;
90
105
  const user = await context.callbacks.resolveUser(id);
91
- return (0, jsx_runtime_1.jsx)("discord-mention", { type: "user", children: user ? ((_a = user.displayName) !== null && _a !== void 0 ? _a : user.username) : `<@${id}>` });
106
+ 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}>` });
92
107
  }
93
108
  case 'here':
94
109
  case 'everyone':
@@ -115,10 +130,24 @@ async function MessageSingleASTNode({ node, context }) {
115
130
  case 'emoji':
116
131
  case 'twemoji':
117
132
  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 }));
118
- case 'timestamp':
133
+ case 'timestamp': {
119
134
  // TODO: Make this reactive
120
135
  // https://github.com/ItzDerock/discord-components/blob/main/packages/core/src/components/discord-time/discord-time.tsx
121
- return (0, jsx_runtime_1.jsx)("discord-time", { children: new Date(node.timestamp).toISOString() });
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;
140
+ if (typeof raw === 'number') {
141
+ dateMs = raw < 1e12 ? raw * 1000 : raw;
142
+ } else if (typeof raw === 'string') {
143
+ var n = parseInt(raw, 10);
144
+ if (Number.isFinite(n)) dateMs = n < 1e12 ? n * 1000 : n;
145
+ }
146
+ var dt = new Date(dateMs);
147
+ var iso = '';
148
+ try { iso = Number.isFinite(dt.getTime()) ? dt.toISOString() : ''; } catch (_e) { iso = ''; }
149
+ return (0, jsx_runtime_1.jsx)("discord-time", { children: iso });
150
+ }
122
151
  // return <DiscordTime timestamp={parseInt(node.timestamp) * 1000} format={node.format} />;
123
152
  default: {
124
153
  // console.log(`[discord-html-transcripts] Unknown node type: ${type}`, node);
@@ -47,13 +47,89 @@ const reply_1 = __importDefault(require("./reply"));
47
47
  const systemMessage_1 = __importDefault(require("./systemMessage"));
48
48
  async function DiscordMessage({ message, context, }) {
49
49
  var _a;
50
- if (message.system)
51
- return (0, jsx_runtime_1.jsx)(systemMessage_1.default, { message: message });
52
- const isCrosspost = message.reference && message.reference.guildId !== ((_a = message.guild) === null || _a === void 0 ? void 0 : _a.id);
53
- 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 }), 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: message.reactions.cache.map((reaction, id) => ((0, jsx_runtime_1.jsx)("discord-reaction", { name: reaction.emoji.name, emoji: (0, utils_1.parseDiscordEmoji)(reaction.emoji), count: reaction.count }, `${message.id}r${id}`))) })), message.hasThread && message.thread && ((0, jsx_runtime_1.jsx)("discord-thread", { slot: "thread", name: message.thread.name, cta: message.thread.messageCount
54
- ? `${message.thread.messageCount} Message${message.thread.messageCount > 1 ? 's' : ''}`
55
- : '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
56
- ? message.thread.lastMessage.content.substring(0, 125) + '...'
57
- : message.thread.lastMessage.content, context: Object.assign(Object.assign({}, context), { type: content_1.RenderType.REPLY }) }) })) : (`Thread messages not saved.`) }))] }, message.id));
50
+ try {
51
+ 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);
54
+ const stickers = message.stickers && message.stickers.size > 0
55
+ ? Array.from(message.stickers.values()).map((s) => ({
56
+ id: s.id,
57
+ name: s.name,
58
+ url: s.url || `https://media.discordapp.net/stickers/${s.id}.png`,
59
+ format: s.format,
60
+ }))
61
+ : [];
62
+ 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));
76
+ }
77
+ }
78
+
79
+ function renderStickers(stickers) {
80
+ return (0, jsx_runtime_1.jsx)("div", {
81
+ 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))
89
+ });
90
+ }
91
+
92
+ function renderPoll(poll) {
93
+ let totalVotes = 0;
94
+ const answers = poll.answers ? Array.from(poll.answers.values()) : [];
95
+ for (const a of answers) totalVotes += a.voteCount || 0;
96
+ const expires = poll.expiresAt instanceof Date ? poll.expiresAt.toLocaleString('de-DE') : '';
97
+
98
+ return (0, jsx_runtime_1.jsxs)("div", {
99
+ className: "dht-poll",
100
+ style: {
101
+ border: '1px solid #2b2d31', background: '#1e1f22', borderRadius: '8px',
102
+ padding: '12px 14px', marginTop: '6px', maxWidth: '480px'
103
+ },
104
+ 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'] }),
106
+ (0, jsx_runtime_1.jsx)("div", {
107
+ style: { display: 'flex', flexDirection: 'column', gap: '6px' },
108
+ children: answers.map((a, i) => {
109
+ const text = (a.text || a.poll_media?.text || a.media?.text || a.answer_id || `Option ${i + 1}`);
110
+ const votes = a.voteCount || 0;
111
+ 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);
127
+ })
128
+ }),
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
+ })
132
+ ]
133
+ });
58
134
  }
59
135
  //# sourceMappingURL=message.js.map
@@ -21,23 +21,62 @@ async function SystemMessage({ message }) {
21
21
  return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [(0, jsx_runtime_1.jsx)(Highlight, { color: (_f = (_e = message.member) === null || _e === void 0 ? void 0 : _e.roles.color) === null || _f === void 0 ? void 0 : _f.hexColor, children: (_g = message.author.displayName) !== null && _g !== void 0 ? _g : message.author.username }), ' ', "boosted the server!"] }, message.id));
22
22
  case discord_js_1.MessageType.ThreadStarterMessage:
23
23
  return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `ms-${message.id}`, type: "thread", children: [(0, jsx_runtime_1.jsx)(Highlight, { color: (_j = (_h = message.member) === null || _h === void 0 ? void 0 : _h.roles.color) === null || _j === void 0 ? void 0 : _j.hexColor, children: (_k = message.author.displayName) !== null && _k !== void 0 ? _k : message.author.username }), ' ', "started a thread: ", (0, jsx_runtime_1.jsx)("i", { "data-goto": (_l = message.reference) === null || _l === void 0 ? void 0 : _l.messageId, children: message.content })] }, message.id));
24
- // TODO: implement support for these:
24
+ case discord_js_1.MessageType.ChannelNameChange: {
25
+ const authorName = message.author.displayName || message.author.username;
26
+ const color = message.member?.roles?.color?.hexColor;
27
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'changed the channel name: ', (0, jsx_runtime_1.jsx)("strong", { children: message.content })] }, message.id));
28
+ }
29
+ case discord_js_1.MessageType.ChannelIconChange: {
30
+ const authorName = message.author.displayName || message.author.username;
31
+ const color = message.member?.roles?.color?.hexColor;
32
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'changed the channel icon.'] }, message.id));
33
+ }
34
+ case discord_js_1.MessageType.ThreadCreated: {
35
+ const authorName = message.author.displayName || message.author.username;
36
+ const color = message.member?.roles?.color?.hexColor;
37
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "thread", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'started a thread: ', (0, jsx_runtime_1.jsx)("i", { "data-goto": message.reference?.messageId, children: message.content || 'New thread' })] }, message.id));
38
+ }
39
+ case discord_js_1.MessageType.ChatInputCommand:
40
+ case discord_js_1.MessageType.ContextMenuCommand: {
41
+ const authorName = message.author.displayName || message.author.username;
42
+ const color = message.member?.roles?.color?.hexColor;
43
+ const cmd = message.interaction?.commandName ? '/' + message.interaction.commandName : 'a command';
44
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'used ', (0, jsx_runtime_1.jsx)("strong", { children: cmd })] }, message.id));
45
+ }
46
+ case discord_js_1.MessageType.Call: {
47
+ const authorName = message.author.displayName || message.author.username;
48
+ const color = message.member?.roles?.color?.hexColor;
49
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'started a call.'] }, message.id));
50
+ }
51
+ case discord_js_1.MessageType.ChannelFollowAdd: {
52
+ const authorName = message.author.displayName || message.author.username;
53
+ const color = message.member?.roles?.color?.hexColor;
54
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'added ', (0, jsx_runtime_1.jsx)("strong", { children: message.content }), ' to follow this channel.'] }, message.id));
55
+ }
56
+ case discord_js_1.MessageType.RecipientRemove: {
57
+ const authorName = message.author.displayName || message.author.username;
58
+ const color = message.member?.roles?.color?.hexColor;
59
+ return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "leave", children: [(0, jsx_runtime_1.jsx)(Highlight, { color, children: authorName }), ' ', 'left.'] }, message.id));
60
+ }
61
+ case discord_js_1.MessageType.AutoModerationAction:
62
+ return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: 'AutoMod blocked a message.' }, message.id));
63
+ case discord_js_1.MessageType.GuildIncidentReportRaid:
64
+ case discord_js_1.MessageType.GuildIncidentReportFalseAlarm:
65
+ case discord_js_1.MessageType.GuildIncidentAlertModeEnabled:
66
+ case discord_js_1.MessageType.GuildIncidentAlertModeDisabled:
67
+ return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: 'Server safety alert.' }, message.id));
68
+ case discord_js_1.MessageType.PollResult:
69
+ return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: 'A poll ended.' }, message.id));
70
+ // These types are rendered through the normal message pipeline (DiscordMessage)
25
71
  case discord_js_1.MessageType.Default:
26
- case discord_js_1.MessageType.RecipientRemove:
27
- case discord_js_1.MessageType.Call:
28
- case discord_js_1.MessageType.ChannelNameChange:
29
- case discord_js_1.MessageType.ChannelIconChange:
30
- case discord_js_1.MessageType.ChannelFollowAdd:
72
+ case discord_js_1.MessageType.Reply:
73
+ return undefined;
74
+ // Less common / niche types: rendered nothing (Discord client also de-emphasizes these)
31
75
  case discord_js_1.MessageType.GuildDiscoveryDisqualified:
32
76
  case discord_js_1.MessageType.GuildDiscoveryRequalified:
33
77
  case discord_js_1.MessageType.GuildDiscoveryGracePeriodInitialWarning:
34
78
  case discord_js_1.MessageType.GuildDiscoveryGracePeriodFinalWarning:
35
- case discord_js_1.MessageType.ThreadCreated:
36
- case discord_js_1.MessageType.Reply:
37
- case discord_js_1.MessageType.ChatInputCommand:
38
79
  case discord_js_1.MessageType.GuildInviteReminder:
39
- case discord_js_1.MessageType.ContextMenuCommand:
40
- case discord_js_1.MessageType.AutoModerationAction:
41
80
  case discord_js_1.MessageType.RoleSubscriptionPurchase:
42
81
  case discord_js_1.MessageType.InteractionPremiumUpsell:
43
82
  case discord_js_1.MessageType.StageStart:
@@ -46,12 +85,7 @@ async function SystemMessage({ message }) {
46
85
  case discord_js_1.MessageType.StageRaiseHand:
47
86
  case discord_js_1.MessageType.StageTopic:
48
87
  case discord_js_1.MessageType.GuildApplicationPremiumSubscription:
49
- case discord_js_1.MessageType.GuildIncidentAlertModeEnabled:
50
- case discord_js_1.MessageType.GuildIncidentAlertModeDisabled:
51
- case discord_js_1.MessageType.GuildIncidentReportRaid:
52
- case discord_js_1.MessageType.GuildIncidentReportFalseAlarm:
53
88
  case discord_js_1.MessageType.PurchaseNotification:
54
- case discord_js_1.MessageType.PollResult:
55
89
  return undefined;
56
90
  default:
57
91
  return undefined;
package/dist/index.js CHANGED
@@ -30,12 +30,10 @@ var transcript_1 = require("./generator/transcript");
30
30
  Object.defineProperty(exports, "DiscordMessages", { enumerable: true, get: function () { return __importDefault(transcript_1).default; } });
31
31
  var images_2 = require("./downloader/images");
32
32
  Object.defineProperty(exports, "TranscriptImageDownloader", { enumerable: true, get: function () { return images_2.TranscriptImageDownloader; } });
33
- // version check
33
+ // version check (warn only — never kill the host process from a library)
34
34
  const versionPrefix = discord_js_1.version.split('.')[0];
35
35
  if (versionPrefix !== '14' && versionPrefix !== '15') {
36
- console.error(`[discord-html-transcripts] Versions v3.x.x of discord-html-transcripts are only compatible with discord.js v14.x.x and v15.x.x, and you are using v${discord_js_1.version}.` +
37
- ` For v13.x.x support, please install discord-html-transcripts v2.x.x using "npm install discord-html-transcripts@^2".`);
38
- process.exit(1);
36
+ console.warn(`[discord-html-transcripts-fix] Compatible with discord.js v14.x and v15.x, you are using v${discord_js_1.version}. Continuing — transcripts may render incorrectly.`);
39
37
  }
40
38
  /**
41
39
  *
@@ -1,31 +1,224 @@
1
1
  "use strict";
2
- // TODO: create some sort of build system to compile this file
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.ggSansFont = exports.revealSpoiler = exports.scrollToMessage = void 0;
3
+ exports.ggSansFont = exports.revealSpoiler = exports.scrollToMessage = exports.mentionPopup = exports.mentionPopupStyles = exports.backToTop = exports.transcriptUtils = void 0;
4
+
5
5
  /*
6
- // whenever user clicks on element with data-goto attribute, scroll to that message
6
+ // === scrollToMessage source ===
7
+ // Whenever the user clicks on element with data-goto attribute, scroll to that message
7
8
  document.addEventListener('click', (e) => {
8
9
  const target = e.target;
9
10
  if(!target) return;
10
-
11
11
  const goto = target?.getAttribute('data-goto');
12
-
13
12
  if (goto) {
14
- const message = document.getElementById(`m-\${goto}`);
13
+ const message = document.getElementById(`m-${goto}`);
15
14
  if (message) {
16
15
  message.scrollIntoView({ behavior: 'smooth', block: 'center' });
17
16
  message.style.backgroundColor = 'rgba(148, 156, 247, 0.1)';
18
17
  message.style.transition = 'background-color 0.5s ease';
19
- setTimeout(() => {
20
- message.style.backgroundColor = 'transparent';
21
- }, 1000);
18
+ setTimeout(() => { message.style.backgroundColor = 'transparent'; }, 1000);
22
19
  } else {
23
- console.warn(`Message \${goto} not found.`);
20
+ console.warn(`Message ${goto} not found.`);
24
21
  }
25
22
  }
26
23
  });
27
24
  */
28
25
  exports.scrollToMessage = 'document.addEventListener("click",t=>{let e=t.target;if(!e)return;let o=e?.getAttribute("data-goto");if(o){let r=document.getElementById(`m-${o}`);r?(r.scrollIntoView({behavior:"smooth",block:"center"}),r.style.backgroundColor="rgba(148, 156, 247, 0.1)",r.style.transition="background-color 0.5s ease",setTimeout(()=>{r.style.backgroundColor="transparent"},1e3)):console.warn("Message ${goto} not found.")}});';
26
+
29
27
  exports.revealSpoiler = 'const s=document.querySelectorAll(".discord-spoiler");s.forEach(s=>s.addEventListener("click",()=>{if(s.classList.contains("discord-spoiler")){s.classList.remove("discord-spoiler");s.classList.add("discord-spoiler--revealed");}}));';
28
+
29
+ // ============================================================================
30
+ // Mention popup: click on a user/role/channel mention to see detailed info
31
+ // Reads from globalThis.$discordMessage.{users,roles,channels,profiles}.
32
+ // ============================================================================
33
+ exports.mentionPopup = `(function(){
34
+ var DATA = function(){ return (typeof globalThis !== 'undefined' && globalThis.$discordMessage) || {}; };
35
+ function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g, function(c){ return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]; }); }
36
+ function fmt(iso){ if(!iso) return ''; try { var d = new Date(iso); return isNaN(d.getTime()) ? '' : d.toLocaleString('de-DE',{dateStyle:'medium', timeStyle:'short'}); } catch(_){ return ''; } }
37
+
38
+ var popup = null;
39
+ function ensure(){
40
+ if (popup) return popup;
41
+ popup = document.createElement('div');
42
+ popup.className = 'dht-popup';
43
+ popup.innerHTML = '<div class="dht-popup-body"></div><button type="button" class="dht-popup-close" aria-label="Schliessen">&times;</button>';
44
+ document.body.appendChild(popup);
45
+ popup.querySelector('.dht-popup-close').addEventListener('click', hide);
46
+ document.addEventListener('click', function(e){
47
+ if (!popup || popup.style.display !== 'block') return;
48
+ if (popup.contains(e.target)) return;
49
+ if (e.target.closest && e.target.closest('[data-mention-id]')) return;
50
+ hide();
51
+ });
52
+ document.addEventListener('keydown', function(e){ if (e.key === 'Escape') hide(); });
53
+ window.addEventListener('resize', hide);
54
+ return popup;
55
+ }
56
+ function hide(){ if (popup) popup.style.display = 'none'; }
57
+ function showAt(el, html){
58
+ var p = ensure();
59
+ p.querySelector('.dht-popup-body').innerHTML = html;
60
+ p.style.display = 'block';
61
+ var r = el.getBoundingClientRect();
62
+ var pr = p.getBoundingClientRect();
63
+ var pad = 8;
64
+ var top = r.bottom + 6;
65
+ var left = r.left;
66
+ if (top + pr.height > window.innerHeight - pad) top = Math.max(pad, r.top - pr.height - 6);
67
+ if (left + pr.width > window.innerWidth - pad) left = Math.max(pad, window.innerWidth - pr.width - pad);
68
+ if (left < pad) left = pad;
69
+ p.style.top = top + 'px';
70
+ p.style.left = left + 'px';
71
+ }
72
+ function rolePill(role){
73
+ var c = role && role.color ? role.color : '#5865F2';
74
+ return '<span class="dht-role-pill" style="--dht-role:' + esc(c) + ';">' + esc(role && role.name ? role.name : '@role') + '</span>';
75
+ }
76
+ function userHtml(id){
77
+ var d = DATA();
78
+ var u = (d.users && d.users[id]) || null;
79
+ var p = (d.profiles && d.profiles[id]) || null;
80
+ if (!u && !p) return '<i>Unbekannter User</i><div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
81
+ var name = (u && (u.displayName || u.username)) || (p && p.author) || 'Unknown';
82
+ var username = u && u.username ? '@' + u.username : '';
83
+ var avatar = (u && u.avatar) || (p && p.avatar) || '';
84
+ var color = (u && u.bannerColor) || (p && p.roleColor) || '#dbdee1';
85
+ var bot = (u && u.bot) || (p && p.bot);
86
+ var roles = u && Array.isArray(u.roles) && u.roles.length ? u.roles.map(rolePill).join('') : '';
87
+ var joinedAt = u && u.joinedAt ? fmt(u.joinedAt) : '';
88
+ var createdAt = u && u.createdAt ? fmt(u.createdAt) : '';
89
+ return ''
90
+ + '<div class="dht-popup-head">'
91
+ + (avatar ? '<img class="dht-popup-avatar" src="' + esc(avatar) + '" alt="">' : '<div class="dht-popup-avatar dht-popup-avatar--ph"></div>')
92
+ + '<div class="dht-popup-headtext">'
93
+ + '<div class="dht-popup-name" style="color:' + esc(color) + ';">' + esc(name) + (bot ? '<span class="dht-popup-badge">BOT</span>' : '') + '</div>'
94
+ + (username ? '<div class="dht-popup-sub">' + esc(username) + '</div>' : '')
95
+ + '</div>'
96
+ + '</div>'
97
+ + (roles ? '<div class="dht-popup-section"><div class="dht-popup-label">Rollen</div><div class="dht-popup-roles">' + roles + '</div></div>' : '')
98
+ + (joinedAt ? '<div class="dht-popup-row"><span class="dht-popup-label">Server seit</span><span>' + esc(joinedAt) + '</span></div>' : '')
99
+ + (createdAt ? '<div class="dht-popup-row"><span class="dht-popup-label">Account seit</span><span>' + esc(createdAt) + '</span></div>' : '')
100
+ + '<div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
101
+ }
102
+ function roleHtml(id){
103
+ var r = (DATA().roles || {})[id];
104
+ if (!r) return '<i>Unbekannte Rolle</i><div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
105
+ var color = r.color || '#dbdee1';
106
+ return ''
107
+ + '<div class="dht-popup-head">'
108
+ + '<div class="dht-popup-rolemark" style="background:' + esc(color) + ';"></div>'
109
+ + '<div class="dht-popup-headtext">'
110
+ + '<div class="dht-popup-name" style="color:' + esc(color) + ';">@' + esc(r.name) + '</div>'
111
+ + (r.color ? '<div class="dht-popup-sub"><code>' + esc(r.color) + '</code></div>' : '')
112
+ + '</div>'
113
+ + '</div>'
114
+ + (r.memberCount != null ? '<div class="dht-popup-row"><span class="dht-popup-label">Mitglieder</span><span>' + esc(r.memberCount) + '</span></div>' : '')
115
+ + (r.position != null ? '<div class="dht-popup-row"><span class="dht-popup-label">Position</span><span>' + esc(r.position) + '</span></div>' : '')
116
+ + (r.hoist ? '<div class="dht-popup-row"><span class="dht-popup-label">Hoisted</span><span>Ja</span></div>' : '')
117
+ + (r.mentionable ? '<div class="dht-popup-row"><span class="dht-popup-label">Erwähnbar</span><span>Ja</span></div>' : '')
118
+ + (r.managed ? '<div class="dht-popup-row"><span class="dht-popup-label">Verwaltet</span><span>Bot/Integration</span></div>' : '')
119
+ + '<div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
120
+ }
121
+ function channelHtml(id){
122
+ var c = (DATA().channels || {})[id];
123
+ if (!c) return '<i>Channel</i><div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
124
+ return ''
125
+ + '<div class="dht-popup-head">'
126
+ + '<div class="dht-popup-rolemark" style="background:#5865F2;">#</div>'
127
+ + '<div class="dht-popup-headtext">'
128
+ + '<div class="dht-popup-name">#' + esc(c.name) + '</div>'
129
+ + (c.parent ? '<div class="dht-popup-sub">in ' + esc(c.parent) + '</div>' : '')
130
+ + '</div>'
131
+ + '</div>'
132
+ + (c.topic ? '<div class="dht-popup-section"><div class="dht-popup-label">Topic</div><div>' + esc(c.topic) + '</div></div>' : '')
133
+ + (c.nsfw ? '<div class="dht-popup-row"><span class="dht-popup-label">NSFW</span><span>Ja</span></div>' : '')
134
+ + '<div class="dht-popup-id">ID: <code>' + esc(id) + '</code></div>';
135
+ }
136
+ document.addEventListener('click', function(e){
137
+ var el = e.target && e.target.closest ? e.target.closest('[data-mention-id]') : null;
138
+ if (!el) return;
139
+ var t = el.getAttribute('data-mention-type');
140
+ var id = el.getAttribute('data-mention-id');
141
+ if (!t || !id) return;
142
+ e.preventDefault();
143
+ e.stopPropagation();
144
+ var html = '';
145
+ if (t === 'user') html = userHtml(id);
146
+ else if (t === 'role') html = roleHtml(id);
147
+ else if (t === 'channel') html = channelHtml(id);
148
+ else return;
149
+ showAt(el, html);
150
+ });
151
+ })();`;
152
+
153
+ // ============================================================================
154
+ // Mention popup styles
155
+ // ============================================================================
156
+ exports.mentionPopupStyles = `
157
+ .dht-mention { cursor: pointer; }
158
+ .dht-mention:hover { filter: brightness(1.15); }
159
+ .dht-popup {
160
+ position: fixed; display: none; max-width: 340px; min-width: 240px;
161
+ background: #1e1f22; color: #dbdee1; border: 1px solid #2b2d31;
162
+ border-radius: 10px; padding: 14px 16px 12px;
163
+ box-shadow: 0 12px 32px rgba(0,0,0,0.55), 0 2px 6px rgba(0,0,0,0.35);
164
+ font-family: "gg sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
165
+ font-size: 14px; line-height: 1.4; z-index: 99999;
166
+ }
167
+ .dht-popup-close {
168
+ position: absolute; top: 6px; right: 8px; background: none; border: none;
169
+ color: #949ba4; cursor: pointer; font-size: 20px; line-height: 1; padding: 4px 8px;
170
+ border-radius: 4px;
171
+ }
172
+ .dht-popup-close:hover { background: rgba(255,255,255,0.06); color: #dbdee1; }
173
+ .dht-popup-head { display: flex; align-items: center; gap: 10px; margin-right: 18px; }
174
+ .dht-popup-avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; flex: none; }
175
+ .dht-popup-avatar--ph { background: #313338; }
176
+ .dht-popup-rolemark { width: 32px; height: 32px; border-radius: 8px; flex: none; display: flex; align-items: center; justify-content: center; font-weight: 700; color: #fff; }
177
+ .dht-popup-headtext { min-width: 0; }
178
+ .dht-popup-name { font-weight: 600; font-size: 16px; word-break: break-word; }
179
+ .dht-popup-sub { font-size: 12px; color: #949ba4; word-break: break-all; }
180
+ .dht-popup-badge { background: #5865F2; color: #fff; font-size: 10px; padding: 1px 6px; border-radius: 4px; margin-left: 6px; vertical-align: middle; }
181
+ .dht-popup-section { margin-top: 10px; }
182
+ .dht-popup-row { display: flex; justify-content: space-between; gap: 12px; margin-top: 6px; font-size: 13px; }
183
+ .dht-popup-label { color: #949ba4; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700; }
184
+ .dht-popup-roles { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
185
+ .dht-role-pill {
186
+ display: inline-flex; align-items: center;
187
+ background: color-mix(in srgb, var(--dht-role) 18%, transparent);
188
+ color: var(--dht-role); border: 1px solid color-mix(in srgb, var(--dht-role) 40%, transparent);
189
+ padding: 1px 8px; border-radius: 999px; font-size: 11px; line-height: 1.5;
190
+ }
191
+ .dht-popup-id { margin-top: 10px; padding-top: 8px; border-top: 1px solid #2b2d31; font-size: 11px; color: #6e727a; }
192
+ .dht-popup-id code { background: rgba(255,255,255,0.05); padding: 1px 4px; border-radius: 3px; }
193
+ `;
194
+
195
+ // ============================================================================
196
+ // Back-to-top floating button
197
+ // ============================================================================
198
+ exports.backToTop = `(function(){
199
+ var btn = document.createElement('button');
200
+ btn.className = 'dht-back-top';
201
+ btn.type = 'button';
202
+ btn.title = 'Nach oben scrollen';
203
+ btn.setAttribute('aria-label','Nach oben scrollen');
204
+ btn.innerHTML = '&#x25B2;';
205
+ document.body.appendChild(btn);
206
+ btn.addEventListener('click', function(){ window.scrollTo({ top: 0, behavior: 'smooth' }); });
207
+ function toggle(){ btn.style.opacity = window.scrollY > 400 ? '1' : '0'; btn.style.pointerEvents = window.scrollY > 400 ? 'auto' : 'none'; }
208
+ window.addEventListener('scroll', toggle, { passive: true });
209
+ toggle();
210
+ })();`;
211
+
212
+ exports.transcriptUtils = `
213
+ .dht-back-top {
214
+ position: fixed; bottom: 24px; right: 24px; width: 44px; height: 44px;
215
+ border-radius: 50%; border: none; background: #5865F2; color: #fff;
216
+ font-size: 16px; cursor: pointer; box-shadow: 0 6px 18px rgba(0,0,0,0.45);
217
+ opacity: 0; pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease;
218
+ z-index: 9999;
219
+ }
220
+ .dht-back-top:hover { transform: translateY(-2px); }
221
+ `;
222
+
30
223
  exports.ggSansFont = '@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-400.woff2);font-family:"gg sans";font-weight:400;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-500.woff2);font-family:"gg sans";font-weight:500;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-600.woff2);font-family:"gg sans";font-weight:600;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-700.woff2);font-family:"gg sans";font-weight:700;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-normal-800.woff2);font-family:"gg sans";font-weight:800;font-style:normal}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-400.woff2);font-family:"gg sans";font-weight:400;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-500.woff2);font-family:"gg sans";font-weight:500;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-600.woff2);font-family:"gg sans";font-weight:600;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-700.woff2);font-family:"gg sans";font-weight:700;font-style:italic}@font-face{src:url(https://cdn.jsdelivr.net/gh/Tyrrrz/DiscordFonts@master/ggsans-italic-800.woff2);font-family:"gg sans";font-weight:800;font-style:italic}';
31
- //# sourceMappingURL=client.js.map
224
+ //# sourceMappingURL=client.js.map
@@ -1,32 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildProfiles = buildProfiles;
4
+ exports.buildExtendedContext = buildExtendedContext;
4
5
  const discord_js_1 = require("discord.js");
6
+
7
+ /**
8
+ * Original profile builder — kept for backwards compatibility with
9
+ * <discord-mention> web component lookups via window.$discordMessage.profiles.
10
+ */
5
11
  async function buildProfiles(messages) {
6
12
  const profiles = {};
7
- // loop through messages
8
13
  for (const message of messages) {
9
- // add all users
10
14
  const author = message.author;
11
- if (!profiles[author.id]) {
12
- // add profile
15
+ if (author && !profiles[author.id]) {
13
16
  profiles[author.id] = buildProfile(message.member, author);
14
17
  }
15
- // add interaction users
16
18
  if (message.interaction) {
17
19
  const user = message.interaction.user;
18
- if (!profiles[user.id]) {
20
+ if (user && !profiles[user.id]) {
19
21
  profiles[user.id] = buildProfile(null, user);
20
22
  }
21
23
  }
22
- // threads
23
- if (message.thread && message.thread.lastMessage) {
24
+ if (message.thread && message.thread.lastMessage && message.thread.lastMessage.author) {
24
25
  profiles[message.thread.lastMessage.author.id] = buildProfile(message.thread.lastMessage.member, message.thread.lastMessage.author);
25
26
  }
27
+ // Also include users mentioned in messages — so popups work even if they never spoke
28
+ if (message.mentions && message.mentions.users) {
29
+ for (const [uid, u] of message.mentions.users) {
30
+ if (!profiles[uid]) {
31
+ const member = message.mentions.members && message.mentions.members.get
32
+ ? message.mentions.members.get(uid)
33
+ : null;
34
+ profiles[uid] = buildProfile(member, u);
35
+ }
36
+ }
37
+ }
26
38
  }
27
- // return as a JSON
28
39
  return profiles;
29
40
  }
41
+
30
42
  function buildProfile(member, author) {
31
43
  var _a, _b, _c, _d, _e, _f, _g, _h;
32
44
  return {
@@ -39,4 +51,112 @@ function buildProfile(member, author) {
39
51
  verified: (_h = author.flags) === null || _h === void 0 ? void 0 : _h.has(discord_js_1.UserFlags.VerifiedBot),
40
52
  };
41
53
  }
42
- //# sourceMappingURL=buildProfiles.js.map
54
+
55
+ /**
56
+ * Extended dictionaries used by the mention-popup client script.
57
+ * Contains:
58
+ * users: full user/member details (avatar, banner color, all roles, joinedAt, createdAt, flags)
59
+ * roles: all roles referenced by any rendered user OR mentioned in messages
60
+ * channels: channel info (name, parent category, topic, type)
61
+ */
62
+ async function buildExtendedContext(messages, channel) {
63
+ const users = {};
64
+ const roles = {};
65
+ const channels = {};
66
+ const guild = channel && !channel.isDMBased && !channel.isDMBased() ? channel.guild : null;
67
+
68
+ const seenUserIds = new Set();
69
+ const seenRoleIds = new Set();
70
+ const seenChannelIds = new Set();
71
+
72
+ const includeRoleFromGuild = (roleId) => {
73
+ if (!guild || seenRoleIds.has(roleId)) return;
74
+ const role = guild.roles.cache.get(roleId);
75
+ if (!role) return;
76
+ seenRoleIds.add(roleId);
77
+ roles[roleId] = {
78
+ id: role.id,
79
+ name: role.name,
80
+ color: role.hexColor && role.hexColor !== '#000000' ? role.hexColor : null,
81
+ position: role.position,
82
+ hoist: role.hoist,
83
+ mentionable: role.mentionable,
84
+ memberCount: typeof role.members?.size === 'number' ? role.members.size : null,
85
+ managed: role.managed,
86
+ };
87
+ };
88
+
89
+ const includeChannelFromGuild = (channelId) => {
90
+ if (!guild || seenChannelIds.has(channelId)) return;
91
+ const ch = guild.channels.cache.get(channelId);
92
+ if (!ch) return;
93
+ seenChannelIds.add(channelId);
94
+ channels[channelId] = {
95
+ id: ch.id,
96
+ name: ch.name,
97
+ type: ch.type,
98
+ parent: ch.parent ? ch.parent.name : null,
99
+ topic: 'topic' in ch && typeof ch.topic === 'string' ? ch.topic : null,
100
+ nsfw: 'nsfw' in ch ? !!ch.nsfw : false,
101
+ };
102
+ };
103
+
104
+ const includeUser = (member, author) => {
105
+ if (!author || seenUserIds.has(author.id)) return;
106
+ seenUserIds.add(author.id);
107
+ const memberRoles = member && member.roles && member.roles.cache
108
+ ? Array.from(member.roles.cache.values()).filter((r) => r.id !== guild?.id)
109
+ .sort((a, b) => b.position - a.position)
110
+ : [];
111
+ // include role records as well
112
+ for (const r of memberRoles) includeRoleFromGuild(r.id);
113
+ users[author.id] = {
114
+ id: author.id,
115
+ username: author.username,
116
+ displayName: member?.nickname || author.displayName || author.username,
117
+ globalName: author.globalName || null,
118
+ avatar: member?.displayAvatarURL?.({ size: 128 }) || author.displayAvatarURL?.({ size: 128 }) || null,
119
+ bannerColor: member?.displayHexColor && member.displayHexColor !== '#000000' ? member.displayHexColor : null,
120
+ bot: !!author.bot,
121
+ system: !!author.system,
122
+ joinedAt: member?.joinedAt ? member.joinedAt.toISOString() : null,
123
+ createdAt: author.createdAt ? author.createdAt.toISOString() : null,
124
+ roles: memberRoles.map((r) => ({
125
+ id: r.id,
126
+ name: r.name,
127
+ color: r.hexColor && r.hexColor !== '#000000' ? r.hexColor : null,
128
+ position: r.position,
129
+ })),
130
+ highestRole: memberRoles[0]
131
+ ? { name: memberRoles[0].name, color: memberRoles[0].hexColor }
132
+ : null,
133
+ };
134
+ };
135
+
136
+ for (const message of messages) {
137
+ if (message.author) includeUser(message.member, message.author);
138
+ if (message.interaction?.user) includeUser(null, message.interaction.user);
139
+ if (message.thread?.lastMessage?.author) {
140
+ includeUser(message.thread.lastMessage.member, message.thread.lastMessage.author);
141
+ }
142
+ if (message.mentions) {
143
+ if (message.mentions.users) {
144
+ for (const [uid, u] of message.mentions.users) {
145
+ const member = message.mentions.members && message.mentions.members.get
146
+ ? message.mentions.members.get(uid)
147
+ : null;
148
+ includeUser(member, u);
149
+ }
150
+ }
151
+ if (message.mentions.roles) {
152
+ for (const [rid] of message.mentions.roles) includeRoleFromGuild(rid);
153
+ }
154
+ if (message.mentions.channels) {
155
+ for (const [cid] of message.mentions.channels) includeChannelFromGuild(cid);
156
+ }
157
+ }
158
+ }
159
+
160
+ return { users, roles, channels };
161
+ }
162
+ //# sourceMappingURL=buildProfiles.js.map
@@ -27,13 +27,21 @@ function formatBytes(bytes, decimals = 2) {
27
27
  return [parseFloat((bytes / Math.pow(k, i)).toFixed(dm)), sizes[i]];
28
28
  }
29
29
  function parseDiscordEmoji(emoji) {
30
+ if (!emoji) return '';
30
31
  if (emoji.id) {
31
32
  return `https://cdn.discordapp.com/emojis/${emoji.id}.${emoji.animated ? 'gif' : 'png'}`;
32
33
  }
33
- const codepoints = twemoji_1.default.convert
34
- .toCodePoint(emoji.name.indexOf(String.fromCharCode(0x200d)) < 0 ? emoji.name.replace(/\uFE0F/g, '') : emoji.name)
35
- .toLowerCase();
36
- return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/${codepoints}.svg`;
34
+ // Defensive: emoji.name may be null for deleted/unknown reactions
35
+ if (typeof emoji.name !== 'string' || !emoji.name) return '';
36
+ try {
37
+ const codepoints = twemoji_1.default.convert
38
+ .toCodePoint(emoji.name.indexOf(String.fromCharCode(0x200d)) < 0 ? emoji.name.replace(/\uFE0F/g, '') : emoji.name)
39
+ .toLowerCase();
40
+ return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/${codepoints}.svg`;
41
+ }
42
+ catch (_e) {
43
+ return '';
44
+ }
37
45
  }
38
46
  /**
39
47
  * Converts a stream to a string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discord-html-transcripts-fix",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A nicely formatted html transcript generator for discord.js. Bugfix fork with support for the latest discord.js and Components v2.",
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -19,7 +19,6 @@
19
19
  "@types/react-dom": "^19.2.3",
20
20
  "@typescript-eslint/eslint-plugin": "^8.50.0",
21
21
  "@typescript-eslint/parser": "^8.50.0",
22
- "debug": "^4.4.3",
23
22
  "discord.js": "^14.25.1",
24
23
  "dotenv": "^17.2.3",
25
24
  "eslint": "^9.39.2",
@@ -34,16 +33,17 @@
34
33
  "dependencies": {
35
34
  "@lit-labs/ssr": "^3.3.1",
36
35
  "@skyra/discord-components-core": "^4.0.2",
36
+ "debug": "^4.4.3",
37
37
  "discord-markdown-parser": "~1.3.0",
38
38
  "highlight.js": "^11.11.1",
39
+ "react": "^19.2.3",
40
+ "react-dom": "^19.2.3",
39
41
  "simple-markdown": "^0.7.3",
40
42
  "twemoji": "^14.0.2",
41
43
  "undici": "^7.16.0"
42
44
  },
43
45
  "peerDependencies": {
44
- "discord.js": "^14.0.0 || ^15.0.0",
45
- "react": "^19.2.3",
46
- "react-dom": "^19.2.3"
46
+ "discord.js": "^14.0.0 || ^15.0.0"
47
47
  },
48
48
  "scripts": {
49
49
  "test": "echo \"Error: no test specified\" && exit 1",