discord-html-transcripts-fix 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -78
- package/dist/downloader/images.js +71 -85
- package/dist/generator/index.js +231 -43
- package/dist/generator/renderers/attachment.js +27 -36
- package/dist/generator/renderers/components/Container.js +42 -14
- package/dist/generator/renderers/components/Media Gallery.js +24 -18
- package/dist/generator/renderers/components/Spacing.js +18 -6
- package/dist/generator/renderers/components/section/Section.js +13 -3
- package/dist/generator/renderers/components/section/SectionAccessory.js +8 -7
- package/dist/generator/renderers/components/section/SectionContent.js +9 -4
- package/dist/generator/renderers/components.js +7 -2
- package/dist/generator/renderers/content.js +254 -76
- package/dist/generator/renderers/embed.js +79 -34
- package/dist/generator/renderers/message.js +358 -52
- package/dist/generator/renderers/reply.js +59 -40
- package/dist/generator/renderers/systemMessage.js +182 -68
- package/dist/generator/transcript.js +42 -56
- package/dist/index.js +81 -84
- package/dist/static/client.js +675 -202
- package/dist/types.js +1 -0
- package/dist/utils/buildProfiles.js +70 -60
- package/dist/utils/utils.js +73 -26
- package/package.json +1 -1
|
@@ -22,8 +22,13 @@ function ComponentRow({ component, id, context, }) {
|
|
|
22
22
|
switch (component.type) {
|
|
23
23
|
case discord_js_1.ComponentType.ActionRow:
|
|
24
24
|
return ((0, jsx_runtime_1.jsx)("discord-action-row", { children: (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: component.components.map((nestedComponent, id) => ((0, jsx_runtime_1.jsx)(Component, { component: nestedComponent, id: id }, id))) }) }, id));
|
|
25
|
-
case discord_js_1.ComponentType.Container:
|
|
26
|
-
|
|
25
|
+
case discord_js_1.ComponentType.Container: {
|
|
26
|
+
const accentColor = component.accent_color !== undefined
|
|
27
|
+
? component.accent_color
|
|
28
|
+
: (component.accentColor !== undefined ? component.accentColor : null);
|
|
29
|
+
const spoiler = !!component.spoiler;
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)(Container_1.default, { accentColor: accentColor, spoiler: spoiler, children: (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: component.components.map((nestedComponent, id) => ((0, jsx_runtime_1.jsx)(ComponentRow, { component: nestedComponent, id: id, context: context }, id))) }) }, id));
|
|
31
|
+
}
|
|
27
32
|
case discord_js_1.ComponentType.File: {
|
|
28
33
|
const attachmentComponent = (0, jsx_runtime_1.jsx)("discord-file-attachment", { href: component.file.url });
|
|
29
34
|
if (component.spoiler) {
|
|
@@ -12,6 +12,7 @@ const discord_markdown_parser_1 = __importDefault(require("discord-markdown-pars
|
|
|
12
12
|
const discord_js_1 = require("discord.js");
|
|
13
13
|
const utils_1 = require("../../utils/utils");
|
|
14
14
|
const DiscordHighlightedCode_1 = require("./components/DiscordHighlightedCode");
|
|
15
|
+
|
|
15
16
|
var RenderType;
|
|
16
17
|
(function (RenderType) {
|
|
17
18
|
RenderType[RenderType["EMBED"] = 0] = "EMBED";
|
|
@@ -19,158 +20,335 @@ var RenderType;
|
|
|
19
20
|
RenderType[RenderType["NORMAL"] = 2] = "NORMAL";
|
|
20
21
|
RenderType[RenderType["WEBHOOK"] = 3] = "WEBHOOK";
|
|
21
22
|
})(RenderType || (exports.RenderType = RenderType = {}));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
|
|
24
|
+
function hexToRgba(hex, alpha) {
|
|
25
|
+
if (typeof hex !== 'string') return null;
|
|
26
|
+
let h = hex.replace(/^#/, '');
|
|
27
|
+
if (h.length === 3) h = h.split('').map(c => c + c).join('');
|
|
28
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
29
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
30
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
31
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
32
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Discord-specific block-level markdown that discord-markdown-parser misses in inline mode.
|
|
36
|
+
// Run BEFORE the parser, extract heading/subtext lines, parse the rest, splice back together.
|
|
37
|
+
const HEADING_RE = /^(#{1,3})\s+(.+?)\s*$/;
|
|
38
|
+
const SUBTEXT_RE = /^-#\s+(.+?)\s*$/;
|
|
39
|
+
const CODE_FENCE_RE = /^```([a-zA-Z0-9_+-]*)\s*\n([\s\S]*?)```$/m;
|
|
40
|
+
|
|
41
|
+
function preParseDiscord(content, mode) {
|
|
42
|
+
if (!content || typeof content !== 'string') return [];
|
|
43
|
+
const lines = content.split('\n');
|
|
44
|
+
const nodes = [];
|
|
45
|
+
let inlineBuffer = '';
|
|
46
|
+
let codeFenceActive = null; // {lang, lines}
|
|
47
|
+
|
|
48
|
+
const flushInline = () => {
|
|
49
|
+
if (!inlineBuffer) return;
|
|
50
|
+
const parsed = discord_markdown_parser_1.default(inlineBuffer, mode);
|
|
51
|
+
const arr = Array.isArray(parsed) ? parsed : [parsed];
|
|
52
|
+
nodes.push(...arr);
|
|
53
|
+
inlineBuffer = '';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
const line = lines[i];
|
|
58
|
+
|
|
59
|
+
// Code fence — close only on `^\s*\`\`\`\s*$` (Discord-style: triple-backticks alone on line)
|
|
60
|
+
if (codeFenceActive) {
|
|
61
|
+
if (/^\s*```\s*$/.test(line)) {
|
|
62
|
+
nodes.push({ type: 'codeBlock', lang: codeFenceActive.lang || '', content: codeFenceActive.lines.join('\n') });
|
|
63
|
+
codeFenceActive = null;
|
|
64
|
+
} else {
|
|
65
|
+
codeFenceActive.lines.push(line);
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const fenceMatch = line.match(/^```([a-zA-Z0-9_+-]*)\s*$/);
|
|
70
|
+
if (fenceMatch) {
|
|
71
|
+
flushInline();
|
|
72
|
+
codeFenceActive = { lang: fenceMatch[1], lines: [] };
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const h = line.match(HEADING_RE);
|
|
77
|
+
if (h) {
|
|
78
|
+
flushInline();
|
|
79
|
+
const level = h[1].length;
|
|
80
|
+
const headingInner = discord_markdown_parser_1.default(h[2], mode);
|
|
81
|
+
nodes.push({ type: 'heading', level, content: Array.isArray(headingInner) ? headingInner : [headingInner] });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const s = line.match(SUBTEXT_RE);
|
|
86
|
+
if (s) {
|
|
87
|
+
flushInline();
|
|
88
|
+
const innerNodes = discord_markdown_parser_1.default(s[1], mode);
|
|
89
|
+
nodes.push({ type: 'subtext', content: Array.isArray(innerNodes) ? innerNodes : [innerNodes] });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (inlineBuffer) inlineBuffer += '\n';
|
|
94
|
+
inlineBuffer += line;
|
|
95
|
+
}
|
|
96
|
+
if (codeFenceActive) {
|
|
97
|
+
// unclosed fence — flush as text-ish
|
|
98
|
+
inlineBuffer += '```' + (codeFenceActive.lang ? codeFenceActive.lang : '') + '\n' + codeFenceActive.lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
flushInline();
|
|
101
|
+
return nodes;
|
|
102
|
+
}
|
|
103
|
+
|
|
28
104
|
async function MessageContent({ content, context }) {
|
|
29
|
-
if (context.type === RenderType.REPLY && content.length > 180)
|
|
105
|
+
if (context.type === RenderType.REPLY && content.length > 180) {
|
|
30
106
|
content = content.slice(0, 180) + '...';
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
//
|
|
34
|
-
|
|
107
|
+
}
|
|
108
|
+
const mode = context.type === RenderType.EMBED || context.type === RenderType.WEBHOOK ? 'extended' : 'normal';
|
|
109
|
+
// REPLY mode is always inline-only (heading inside a reply preview makes no sense).
|
|
110
|
+
// For all other modes (NORMAL/EMBED/WEBHOOK) we pre-process headings/subtext/code-fences
|
|
111
|
+
// so they render correctly even though discord-markdown-parser is inline-only.
|
|
112
|
+
const parsed = context.type === RenderType.REPLY
|
|
113
|
+
? discord_markdown_parser_1.default(content, mode)
|
|
114
|
+
: preParseDiscord(content, mode);
|
|
115
|
+
|
|
116
|
+
const nodes = Array.isArray(parsed) ? parsed : [parsed];
|
|
117
|
+
const isOnlyEmojis = nodes.every((node) => ['emoji', 'twemoji'].includes(node.type) || (node.type === 'text' && node.content.trim().length === 0));
|
|
35
118
|
if (isOnlyEmojis) {
|
|
36
|
-
|
|
37
|
-
const emojis = parsed.filter((node) => ['emoji', 'twemoji'].includes(node.type));
|
|
119
|
+
const emojis = nodes.filter((node) => ['emoji', 'twemoji'].includes(node.type));
|
|
38
120
|
if (emojis.length <= 25) {
|
|
39
|
-
context._internal = {
|
|
40
|
-
largeEmojis: true,
|
|
41
|
-
};
|
|
121
|
+
context._internal = { largeEmojis: true };
|
|
42
122
|
}
|
|
43
123
|
}
|
|
44
|
-
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes
|
|
124
|
+
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes, context });
|
|
45
125
|
}
|
|
46
|
-
|
|
47
|
-
async function MessageASTNodes({ nodes, context
|
|
126
|
+
|
|
127
|
+
async function MessageASTNodes({ nodes, context }) {
|
|
48
128
|
if (Array.isArray(nodes)) {
|
|
49
|
-
return (
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
return (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: nodes, context: context });
|
|
129
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: nodes.map((node, i) => (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node, context }, i)) });
|
|
53
130
|
}
|
|
131
|
+
return (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: nodes, context });
|
|
54
132
|
}
|
|
133
|
+
|
|
55
134
|
async function MessageSingleASTNode({ node, context }) {
|
|
56
|
-
|
|
57
|
-
if (!node)
|
|
58
|
-
return null;
|
|
135
|
+
if (!node) return null;
|
|
59
136
|
try {
|
|
60
137
|
return await renderASTNode(node, context);
|
|
61
|
-
}
|
|
62
|
-
|
|
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
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.warn('[discord-html-transcripts-fix] AST node render failed:', err && err.message ? err.message : err);
|
|
65
140
|
try {
|
|
66
141
|
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
|
|
68
|
-
} catch (_e) {
|
|
142
|
+
if (Array.isArray(node.content)) return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
143
|
+
} catch (_e) {}
|
|
69
144
|
return null;
|
|
70
145
|
}
|
|
71
146
|
}
|
|
147
|
+
|
|
148
|
+
const GUIDE_LABELS = {
|
|
149
|
+
'guide': 'Server Guide',
|
|
150
|
+
'browse': 'Channels & Roles',
|
|
151
|
+
'customize': 'Customize',
|
|
152
|
+
'welcome': 'Welcome',
|
|
153
|
+
'linked-roles': 'Linked Roles',
|
|
154
|
+
'home': 'Home',
|
|
155
|
+
'members': 'Members',
|
|
156
|
+
'emoji': 'Emoji',
|
|
157
|
+
'soundboard': 'Soundboard',
|
|
158
|
+
'shop': 'Shop',
|
|
159
|
+
'events': 'Events',
|
|
160
|
+
'channel': 'Channels',
|
|
161
|
+
};
|
|
162
|
+
|
|
72
163
|
async function renderASTNode(node, context) {
|
|
73
|
-
var _a, _b;
|
|
74
164
|
const type = node.type;
|
|
75
165
|
switch (type) {
|
|
76
166
|
case 'text':
|
|
77
167
|
return node.content;
|
|
78
168
|
case 'link':
|
|
79
|
-
return (
|
|
169
|
+
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 }) });
|
|
80
170
|
case 'url':
|
|
81
171
|
case 'autolink':
|
|
82
|
-
return (
|
|
172
|
+
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 }) });
|
|
83
173
|
case 'blockQuote':
|
|
84
174
|
if (context.type === RenderType.REPLY) {
|
|
85
|
-
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context
|
|
175
|
+
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
86
176
|
}
|
|
87
|
-
return (
|
|
177
|
+
return (0, jsx_runtime_1.jsx)("discord-quote", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
88
178
|
case 'br':
|
|
89
179
|
case 'newline':
|
|
90
|
-
if (context.type === RenderType.REPLY)
|
|
91
|
-
return ' ';
|
|
180
|
+
if (context.type === RenderType.REPLY) return ' ';
|
|
92
181
|
return (0, jsx_runtime_1.jsx)("br", {});
|
|
182
|
+
|
|
183
|
+
// Headings: #/##/### in Discord. The discord-markdown-parser doesn't emit these
|
|
184
|
+
// in inline mode, so preParseDiscord extracts them before the parser sees them.
|
|
185
|
+
case 'heading': {
|
|
186
|
+
const level = Math.max(1, Math.min(3, node.level || 1));
|
|
187
|
+
const Tag = level === 1 ? 'h1' : level === 2 ? 'h2' : 'h3';
|
|
188
|
+
return (0, jsx_runtime_1.jsx)(Tag, { className: `dht-heading dht-heading-${level}`, children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
189
|
+
}
|
|
190
|
+
// Subtext: lines starting with `-# ` are small/dim text
|
|
191
|
+
case 'subtext':
|
|
192
|
+
return (0, jsx_runtime_1.jsx)("small", { className: "dht-subtext", children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
193
|
+
|
|
93
194
|
case 'channel': {
|
|
94
195
|
const id = node.id;
|
|
95
|
-
|
|
96
|
-
|
|
196
|
+
if (typeof id === 'string' && /^[a-z][a-z-]*$/.test(id)) {
|
|
197
|
+
const label = GUIDE_LABELS[id] || id;
|
|
198
|
+
return (0, jsx_runtime_1.jsxs)("span", { className: "dht-mention dht-mention--channel", children: ["#", label] });
|
|
199
|
+
}
|
|
200
|
+
const channel = await context.callbacks.resolveChannel(id).catch(() => null);
|
|
201
|
+
const name = channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `unknown-${id.slice(-6)}`;
|
|
202
|
+
return (0, jsx_runtime_1.jsxs)("span", {
|
|
203
|
+
className: "dht-mention dht-mention--channel",
|
|
204
|
+
"data-mention-type": "channel",
|
|
205
|
+
"data-mention-id": id,
|
|
206
|
+
children: ["#", name]
|
|
207
|
+
});
|
|
97
208
|
}
|
|
209
|
+
|
|
98
210
|
case 'role': {
|
|
99
211
|
const id = node.id;
|
|
100
|
-
const role = await context.callbacks.resolveRole(id);
|
|
101
|
-
|
|
212
|
+
const role = await context.callbacks.resolveRole(id).catch(() => null);
|
|
213
|
+
const name = role?.name ?? `role-${id.slice(-6)}`;
|
|
214
|
+
const color = role?.hexColor && role.hexColor !== '#000000' ? (0, utils_1.safeColor)(role.hexColor, undefined) : undefined;
|
|
215
|
+
const bg = color ? hexToRgba(color, 0.18) : undefined;
|
|
216
|
+
const style = color ? { color, backgroundColor: bg, borderColor: hexToRgba(color, 0.45) } : undefined;
|
|
217
|
+
return (0, jsx_runtime_1.jsxs)("span", {
|
|
218
|
+
className: "dht-mention dht-mention--role",
|
|
219
|
+
"data-mention-type": "role",
|
|
220
|
+
"data-mention-id": id,
|
|
221
|
+
style,
|
|
222
|
+
children: ["@", name]
|
|
223
|
+
});
|
|
102
224
|
}
|
|
225
|
+
|
|
103
226
|
case 'user': {
|
|
104
227
|
const id = node.id;
|
|
105
|
-
const user = await context.callbacks.resolveUser(id);
|
|
106
|
-
|
|
228
|
+
const user = await context.callbacks.resolveUser(id).catch(() => null);
|
|
229
|
+
const name = user ? (user.displayName ?? user.username ?? `user-${id.slice(-6)}`) : `user-${id.slice(-6)}`;
|
|
230
|
+
return (0, jsx_runtime_1.jsxs)("span", {
|
|
231
|
+
className: "dht-mention dht-mention--user",
|
|
232
|
+
"data-mention-type": "user",
|
|
233
|
+
"data-mention-id": id,
|
|
234
|
+
children: ["@", name]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case 'slashCommand':
|
|
239
|
+
case 'command': {
|
|
240
|
+
const name = node.name || node.content || 'command';
|
|
241
|
+
return (0, jsx_runtime_1.jsxs)("span", { className: "dht-mention dht-mention--command", children: ["/", String(name)] });
|
|
107
242
|
}
|
|
243
|
+
|
|
244
|
+
case 'guildNavigation': {
|
|
245
|
+
// discord-markdown-parser emits this for <id:guide> etc.
|
|
246
|
+
const id = node.id || node.content || 'guide';
|
|
247
|
+
const label = GUIDE_LABELS[id] || id;
|
|
248
|
+
return (0, jsx_runtime_1.jsxs)("span", { className: "dht-mention dht-mention--channel", children: ["#", label] });
|
|
249
|
+
}
|
|
250
|
+
|
|
108
251
|
case 'here':
|
|
109
252
|
case 'everyone':
|
|
110
|
-
return (
|
|
253
|
+
return (0, jsx_runtime_1.jsxs)("span", { className: "dht-mention dht-mention--role dht-mention--highlight", children: ["@", type] });
|
|
254
|
+
|
|
111
255
|
case 'codeBlock':
|
|
112
256
|
if (context.type !== RenderType.REPLY) {
|
|
113
257
|
return (0, jsx_runtime_1.jsx)(DiscordHighlightedCode_1.DiscordHighlightedCode, { language: node.lang, content: node.content });
|
|
114
258
|
}
|
|
115
|
-
return (0, jsx_runtime_1.jsx)("
|
|
259
|
+
return (0, jsx_runtime_1.jsx)("code", { className: "dht-inline-code", children: node.content });
|
|
116
260
|
case 'inlineCode':
|
|
117
|
-
return (0, jsx_runtime_1.jsx)("
|
|
261
|
+
return (0, jsx_runtime_1.jsx)("code", { className: "dht-inline-code", children: node.content });
|
|
262
|
+
|
|
118
263
|
case 'em':
|
|
119
|
-
return (
|
|
264
|
+
return (0, jsx_runtime_1.jsx)("em", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
120
265
|
case 'strong':
|
|
121
|
-
return (
|
|
266
|
+
return (0, jsx_runtime_1.jsx)("strong", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
122
267
|
case 'underline':
|
|
123
|
-
return (
|
|
268
|
+
return (0, jsx_runtime_1.jsx)("u", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
124
269
|
case 'strikethrough':
|
|
125
|
-
return (
|
|
270
|
+
return (0, jsx_runtime_1.jsx)("s", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
126
271
|
case 'emoticon':
|
|
127
|
-
return typeof node.content === 'string' ?
|
|
272
|
+
return typeof node.content === 'string' ? node.content : (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
128
273
|
case 'spoiler':
|
|
129
|
-
return (
|
|
274
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "discord-spoiler", children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
275
|
+
|
|
130
276
|
case 'emoji':
|
|
131
|
-
case 'twemoji':
|
|
132
|
-
|
|
277
|
+
case 'twemoji': {
|
|
278
|
+
const url = (0, utils_1.parseDiscordEmoji)(node);
|
|
279
|
+
const jumbo = context._internal?.largeEmojis;
|
|
280
|
+
return (0, jsx_runtime_1.jsx)("img", {
|
|
281
|
+
className: jumbo ? "dht-emoji dht-emoji--jumbo" : "dht-emoji",
|
|
282
|
+
src: url,
|
|
283
|
+
alt: node.name || '',
|
|
284
|
+
draggable: false
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
133
288
|
case 'timestamp': {
|
|
134
|
-
|
|
135
|
-
|
|
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;
|
|
289
|
+
const raw = node.timestamp;
|
|
290
|
+
let dateMs = NaN;
|
|
140
291
|
if (typeof raw === 'number') {
|
|
141
292
|
dateMs = raw < 1e12 ? raw * 1000 : raw;
|
|
142
293
|
} else if (typeof raw === 'string') {
|
|
143
|
-
|
|
294
|
+
const n = parseInt(raw, 10);
|
|
144
295
|
if (Number.isFinite(n)) dateMs = n < 1e12 ? n * 1000 : n;
|
|
145
296
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return (0, jsx_runtime_1.jsx)("
|
|
297
|
+
const dt = new Date(dateMs);
|
|
298
|
+
if (!Number.isFinite(dt.getTime())) return (0, jsx_runtime_1.jsx)("span", { className: "dht-timestamp", children: "—" });
|
|
299
|
+
const fmt = formatTimestamp(dt, node.format);
|
|
300
|
+
return (0, jsx_runtime_1.jsx)("time", { className: "dht-timestamp", dateTime: dt.toISOString(), title: dt.toISOString(), children: fmt });
|
|
150
301
|
}
|
|
151
|
-
|
|
152
|
-
default:
|
|
153
|
-
|
|
154
|
-
|
|
302
|
+
|
|
303
|
+
default:
|
|
304
|
+
return typeof node.content === 'string' ? node.content : (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function formatTimestamp(dt, format) {
|
|
309
|
+
// Discord formats: t (short time), T (long time), d (short date), D (long date), f (short datetime), F (long datetime), R (relative)
|
|
310
|
+
try {
|
|
311
|
+
if (format === 'R') {
|
|
312
|
+
const diff = (dt.getTime() - Date.now()) / 1000;
|
|
313
|
+
const abs = Math.abs(diff);
|
|
314
|
+
const past = diff < 0;
|
|
315
|
+
const pluralize = (n, unit) => Math.round(n) + ' ' + unit + (Math.round(n) !== 1 ? 's' : '');
|
|
316
|
+
let val;
|
|
317
|
+
if (abs < 60) val = pluralize(abs, 'second');
|
|
318
|
+
else if (abs < 3600) val = pluralize(abs / 60, 'minute');
|
|
319
|
+
else if (abs < 86400) val = pluralize(abs / 3600, 'hour');
|
|
320
|
+
else if (abs < 2592000) val = pluralize(abs / 86400, 'day');
|
|
321
|
+
else if (abs < 31536000) val = pluralize(abs / 2592000, 'month');
|
|
322
|
+
else val = pluralize(abs / 31536000, 'year');
|
|
323
|
+
return past ? `${val} ago` : `in ${val}`;
|
|
155
324
|
}
|
|
325
|
+
switch (format) {
|
|
326
|
+
case 't': return dt.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
327
|
+
case 'T': return dt.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
328
|
+
case 'd': return dt.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
329
|
+
case 'D': return dt.toLocaleDateString('en-US', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
330
|
+
case 'F': return dt.toLocaleString('en-US', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
331
|
+
case 'f':
|
|
332
|
+
default: return dt.toLocaleString('en-US', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
333
|
+
}
|
|
334
|
+
} catch (_e) {
|
|
335
|
+
return dt.toISOString();
|
|
156
336
|
}
|
|
157
337
|
}
|
|
338
|
+
|
|
158
339
|
function getChannelType(channelType) {
|
|
159
340
|
switch (channelType) {
|
|
160
341
|
case discord_js_1.ChannelType.GuildCategory:
|
|
161
342
|
case discord_js_1.ChannelType.GuildAnnouncement:
|
|
162
343
|
case discord_js_1.ChannelType.GuildText:
|
|
163
344
|
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
345
|
return 'channel';
|
|
168
346
|
case discord_js_1.ChannelType.GuildVoice:
|
|
169
347
|
case discord_js_1.ChannelType.GuildStageVoice:
|
|
170
348
|
return 'voice';
|
|
349
|
+
case discord_js_1.ChannelType.AnnouncementThread:
|
|
171
350
|
case discord_js_1.ChannelType.PublicThread:
|
|
172
351
|
case discord_js_1.ChannelType.PrivateThread:
|
|
173
|
-
case discord_js_1.ChannelType.AnnouncementThread:
|
|
174
352
|
return 'thread';
|
|
175
353
|
case discord_js_1.ChannelType.GuildForum:
|
|
176
354
|
return 'forum';
|
|
@@ -178,4 +356,4 @@ function getChannelType(channelType) {
|
|
|
178
356
|
return 'channel';
|
|
179
357
|
}
|
|
180
358
|
}
|
|
181
|
-
//# sourceMappingURL=content.js.map
|
|
359
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -1,44 +1,89 @@
|
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
14
|
+
const type = embed.type || 'rich';
|
|
15
|
+
const url = embed.url ? (0, utils_1.safeHref)(embed.url) : undefined;
|
|
16
|
+
const imageUrl = embed.image?.proxyURL ?? embed.image?.url;
|
|
17
|
+
const videoUrl = embed.video?.proxyURL ?? embed.video?.url;
|
|
18
|
+
const provider = embed.provider?.name;
|
|
19
|
+
const safeVid = videoUrl ? (0, utils_1.safeHref)(videoUrl) : null;
|
|
20
|
+
const safeImg = imageUrl ? (0, utils_1.safeHref)(imageUrl) : null;
|
|
21
|
+
|
|
22
|
+
// Image-only embeds (link previews): render the image alone with optional caption
|
|
23
|
+
if (type === 'image' && safeImg) {
|
|
24
|
+
return (0, jsx_runtime_1.jsx)("div", { className: "dht-embed dht-embed--image", children: (0, jsx_runtime_1.jsx)("a", { href: url || safeImg, target: "_blank", rel: "noreferrer", children: (0, jsx_runtime_1.jsx)("img", { src: safeImg, alt: embed.title || '', className: "dht-embed-image", loading: "lazy" }) }) });
|
|
25
|
+
}
|
|
26
|
+
// GIFV — auto-play loop video
|
|
27
|
+
if (type === 'gifv' && safeVid) {
|
|
28
|
+
return (0, jsx_runtime_1.jsx)("div", { className: "dht-embed dht-embed--gifv", children: (0, jsx_runtime_1.jsx)("video", { src: safeVid, autoPlay: true, loop: true, muted: true, playsInline: true, className: "dht-embed-gifv", "aria-label": embed.title || 'GIF' }) });
|
|
29
|
+
}
|
|
30
|
+
// Video embed (YouTube/Vimeo/etc.) — show thumbnail with a play overlay linking to original
|
|
31
|
+
if (type === 'video' && (safeVid || safeImg)) {
|
|
32
|
+
return (0, jsx_runtime_1.jsxs)("div", { className: "dht-embed dht-embed--video", children: [
|
|
33
|
+
(0, jsx_runtime_1.jsxs)("a", { href: url || safeVid || safeImg, target: "_blank", rel: "noreferrer", className: "dht-embed-video-wrap", children: [
|
|
34
|
+
safeImg && (0, jsx_runtime_1.jsx)("img", { src: safeImg, alt: embed.title || '', className: "dht-embed-video-thumb", loading: "lazy" }),
|
|
35
|
+
(0, jsx_runtime_1.jsx)("div", { className: "dht-embed-video-play", children: '▶' })
|
|
36
|
+
] }),
|
|
37
|
+
provider && (0, jsx_runtime_1.jsx)("div", { className: "dht-embed-provider", children: provider }),
|
|
38
|
+
embed.title && (0, jsx_runtime_1.jsx)("a", { href: url || safeVid, target: "_blank", rel: "noreferrer", className: "dht-embed-title", children: embed.title }),
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Default rich embed
|
|
43
|
+
const title = embed.title ?? undefined;
|
|
44
|
+
const authorImage = embed.author?.proxyIconURL ?? embed.author?.iconURL;
|
|
45
|
+
const authorName = embed.author?.name;
|
|
46
|
+
const authorUrl = embed.author?.url ? (0, utils_1.safeHref)(embed.author.url) : undefined;
|
|
47
|
+
const color = embed.hexColor ? (0, utils_1.safeColor)(embed.hexColor, undefined) : undefined;
|
|
48
|
+
const thumb = embed.thumbnail?.proxyURL ?? embed.thumbnail?.url;
|
|
49
|
+
|
|
50
|
+
return ((0, jsx_runtime_1.jsxs)("discord-embed", {
|
|
51
|
+
"embed-title": title,
|
|
52
|
+
slot: "embeds",
|
|
53
|
+
"author-image": authorImage,
|
|
54
|
+
"author-name": authorName,
|
|
55
|
+
"author-url": authorUrl,
|
|
56
|
+
color: color,
|
|
57
|
+
image: safeImg,
|
|
58
|
+
thumbnail: thumb,
|
|
59
|
+
url: url,
|
|
60
|
+
children: [
|
|
61
|
+
provider && ((0, jsx_runtime_1.jsx)("div", { className: "dht-embed-provider", slot: "author", children: provider })),
|
|
62
|
+
embed.description && ((0, jsx_runtime_1.jsx)("discord-embed-description", {
|
|
63
|
+
slot: "description",
|
|
64
|
+
children: (0, jsx_runtime_1.jsx)(content_1.default, { content: embed.description, context: Object.assign({}, context, { type: content_1.RenderType.EMBED }) })
|
|
65
|
+
})),
|
|
66
|
+
embed.fields.length > 0 && ((0, jsx_runtime_1.jsx)("discord-embed-fields", {
|
|
67
|
+
slot: "fields",
|
|
68
|
+
children: embed.fields.map((field, id) => ((0, jsx_runtime_1.jsx)(EmbedField, { embed, field, id, context }, `${context.message.id}-e-${context.index}-f-${id}`)))
|
|
69
|
+
})),
|
|
70
|
+
safeVid && ((0, jsx_runtime_1.jsx)("div", { className: "dht-embed-video", children: (0, jsx_runtime_1.jsxs)("a", { href: safeVid, target: "_blank", rel: "noreferrer", children: ['▶ Open video', provider ? ` (${provider})` : ''] }) })),
|
|
71
|
+
embed.footer && ((0, jsx_runtime_1.jsx)("discord-embed-footer", {
|
|
72
|
+
slot: "footer",
|
|
73
|
+
footerImage: embed.footer.proxyIconURL ?? embed.footer.iconURL,
|
|
74
|
+
timestamp: embed.timestamp ?? undefined,
|
|
75
|
+
children: embed.footer.text
|
|
76
|
+
}))
|
|
77
|
+
]
|
|
78
|
+
}, `${context.message.id}-e-${context.index}`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function EmbedField({ embed, field, id, context }) {
|
|
82
|
+
return (0, jsx_runtime_1.jsx)("discord-embed-field", {
|
|
83
|
+
"field-title": field.name,
|
|
84
|
+
inline: !!field.inline,
|
|
85
|
+
"inline-index": field.inline ? (0, embeds_1.calculateInlineIndex)(embed.fields, id) : undefined,
|
|
86
|
+
children: (0, jsx_runtime_1.jsx)(content_1.default, { content: field.value, context: Object.assign({}, context, { type: content_1.RenderType.EMBED }) })
|
|
87
|
+
});
|
|
43
88
|
}
|
|
44
|
-
//# sourceMappingURL=embed.js.map
|
|
89
|
+
//# sourceMappingURL=embed.js.map
|