discord-html-transcripts-fix 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/downloader/images.js +11 -0
- package/dist/generator/index.js +45 -8
- package/dist/generator/renderers/components/Container.js +10 -4
- package/dist/generator/renderers/components/Media Gallery.js +5 -5
- 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/content.js +219 -42
- package/dist/generator/renderers/embed.js +31 -7
- package/dist/generator/renderers/message.js +211 -30
- package/dist/generator/renderers/reply.js +14 -3
- package/dist/generator/renderers/systemMessage.js +62 -9
- package/dist/index.js +25 -3
- package/dist/static/client.js +496 -25
- package/dist/utils/buildProfiles.js +19 -1
- package/dist/utils/utils.js +1 -1
- package/package.json +1 -1
|
@@ -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";
|
|
@@ -20,25 +21,114 @@ var RenderType;
|
|
|
20
21
|
RenderType[RenderType["WEBHOOK"] = 3] = "WEBHOOK";
|
|
21
22
|
})(RenderType || (exports.RenderType = RenderType = {}));
|
|
22
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
|
+
|
|
23
104
|
async function MessageContent({ content, context }) {
|
|
24
|
-
if (context.type === RenderType.REPLY && content.length > 180)
|
|
105
|
+
if (context.type === RenderType.REPLY && content.length > 180) {
|
|
25
106
|
content = content.slice(0, 180) + '...';
|
|
26
|
-
|
|
27
|
-
const
|
|
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));
|
|
28
118
|
if (isOnlyEmojis) {
|
|
29
|
-
const emojis =
|
|
119
|
+
const emojis = nodes.filter((node) => ['emoji', 'twemoji'].includes(node.type));
|
|
30
120
|
if (emojis.length <= 25) {
|
|
31
121
|
context._internal = { largeEmojis: true };
|
|
32
122
|
}
|
|
33
123
|
}
|
|
34
|
-
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes
|
|
124
|
+
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes, context });
|
|
35
125
|
}
|
|
36
126
|
|
|
37
127
|
async function MessageASTNodes({ nodes, context }) {
|
|
38
128
|
if (Array.isArray(nodes)) {
|
|
39
|
-
return (
|
|
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)) });
|
|
40
130
|
}
|
|
41
|
-
return (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: nodes, context
|
|
131
|
+
return (0, jsx_runtime_1.jsx)(MessageSingleASTNode, { node: nodes, context });
|
|
42
132
|
}
|
|
43
133
|
|
|
44
134
|
async function MessageSingleASTNode({ node, context }) {
|
|
@@ -49,13 +139,12 @@ async function MessageSingleASTNode({ node, context }) {
|
|
|
49
139
|
console.warn('[discord-html-transcripts-fix] AST node render failed:', err && err.message ? err.message : err);
|
|
50
140
|
try {
|
|
51
141
|
if (typeof node.content === 'string') return node.content;
|
|
52
|
-
if (Array.isArray(node.content)) return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context
|
|
142
|
+
if (Array.isArray(node.content)) return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
53
143
|
} catch (_e) {}
|
|
54
144
|
return null;
|
|
55
145
|
}
|
|
56
146
|
}
|
|
57
147
|
|
|
58
|
-
// Detect Discord "guide" pseudo-channels: <id:guide>, <id:browse>, <id:customize>, <id:welcome>, <id:linked-roles>
|
|
59
148
|
const GUIDE_LABELS = {
|
|
60
149
|
'guide': 'Server Guide',
|
|
61
150
|
'browse': 'Channels & Roles',
|
|
@@ -63,82 +152,139 @@ const GUIDE_LABELS = {
|
|
|
63
152
|
'welcome': 'Welcome',
|
|
64
153
|
'linked-roles': 'Linked Roles',
|
|
65
154
|
'home': 'Home',
|
|
155
|
+
'members': 'Members',
|
|
156
|
+
'emoji': 'Emoji',
|
|
157
|
+
'soundboard': 'Soundboard',
|
|
158
|
+
'shop': 'Shop',
|
|
159
|
+
'events': 'Events',
|
|
160
|
+
'channel': 'Channels',
|
|
66
161
|
};
|
|
67
162
|
|
|
68
163
|
async function renderASTNode(node, context) {
|
|
69
|
-
var _a, _b;
|
|
70
164
|
const type = node.type;
|
|
71
165
|
switch (type) {
|
|
72
166
|
case 'text':
|
|
73
167
|
return node.content;
|
|
74
168
|
case 'link':
|
|
75
|
-
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 }) });
|
|
76
170
|
case 'url':
|
|
77
171
|
case 'autolink':
|
|
78
|
-
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 }) });
|
|
79
173
|
case 'blockQuote':
|
|
80
174
|
if (context.type === RenderType.REPLY) {
|
|
81
|
-
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context
|
|
175
|
+
return (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
82
176
|
}
|
|
83
|
-
return (
|
|
177
|
+
return (0, jsx_runtime_1.jsx)("discord-quote", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
84
178
|
case 'br':
|
|
85
179
|
case 'newline':
|
|
86
180
|
if (context.type === RenderType.REPLY) return ' ';
|
|
87
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
|
+
|
|
88
194
|
case 'channel': {
|
|
89
195
|
const id = node.id;
|
|
90
|
-
// Discord pseudo channels (<id:guide>, <id:browse>, …) come through with letters as id
|
|
91
196
|
if (typeof id === 'string' && /^[a-z][a-z-]*$/.test(id)) {
|
|
92
197
|
const label = GUIDE_LABELS[id] || id;
|
|
93
|
-
return (
|
|
198
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--channel", children: '#' + label });
|
|
94
199
|
}
|
|
95
|
-
const channel = await context.callbacks.resolveChannel(id);
|
|
96
|
-
|
|
200
|
+
const channel = await Promise.resolve(context.callbacks.resolveChannel(id)).catch(() => null);
|
|
201
|
+
const name = channel ? (channel.isDMBased() ? 'DM Channel' : channel.name) : `unknown-${(id || '').slice(-6)}`;
|
|
202
|
+
return (0, jsx_runtime_1.jsx)("span", {
|
|
203
|
+
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
|
-
const
|
|
102
|
-
|
|
212
|
+
const role = await Promise.resolve(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.jsx)("span", {
|
|
218
|
+
className: "dht-mention dht-mention--role",
|
|
219
|
+
"data-mention-type": "role",
|
|
220
|
+
"data-mention-id": id,
|
|
221
|
+
style,
|
|
222
|
+
children: '@' + name
|
|
223
|
+
});
|
|
103
224
|
}
|
|
225
|
+
|
|
104
226
|
case 'user': {
|
|
105
227
|
const id = node.id;
|
|
106
|
-
const user = await context.callbacks.resolveUser(id);
|
|
107
|
-
|
|
228
|
+
const user = await Promise.resolve(context.callbacks.resolveUser(id)).catch(() => null);
|
|
229
|
+
const name = user ? (user.displayName ?? user.username ?? `user-${(id || '').slice(-6)}`) : `user-${(id || '').slice(-6)}`;
|
|
230
|
+
return (0, jsx_runtime_1.jsx)("span", {
|
|
231
|
+
className: "dht-mention dht-mention--user",
|
|
232
|
+
"data-mention-type": "user",
|
|
233
|
+
"data-mention-id": id,
|
|
234
|
+
children: '@' + name
|
|
235
|
+
});
|
|
108
236
|
}
|
|
237
|
+
|
|
109
238
|
case 'slashCommand':
|
|
110
239
|
case 'command': {
|
|
111
|
-
// </name:id> renders as blue pill like Discord
|
|
112
240
|
const name = node.name || node.content || 'command';
|
|
113
|
-
return (
|
|
241
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--command", children: '/' + String(name) });
|
|
114
242
|
}
|
|
243
|
+
|
|
244
|
+
case 'guildNavigation': {
|
|
245
|
+
const id = node.id || node.content || 'guide';
|
|
246
|
+
const label = GUIDE_LABELS[id] || id;
|
|
247
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--channel", children: '#' + label });
|
|
248
|
+
}
|
|
249
|
+
|
|
115
250
|
case 'here':
|
|
116
251
|
case 'everyone':
|
|
117
|
-
return (
|
|
252
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "dht-mention dht-mention--role dht-mention--highlight", children: '@' + type });
|
|
253
|
+
|
|
118
254
|
case 'codeBlock':
|
|
119
255
|
if (context.type !== RenderType.REPLY) {
|
|
120
256
|
return (0, jsx_runtime_1.jsx)(DiscordHighlightedCode_1.DiscordHighlightedCode, { language: node.lang, content: node.content });
|
|
121
257
|
}
|
|
122
|
-
return (0, jsx_runtime_1.jsx)("
|
|
258
|
+
return (0, jsx_runtime_1.jsx)("code", { className: "dht-inline-code", children: node.content });
|
|
123
259
|
case 'inlineCode':
|
|
124
|
-
return (0, jsx_runtime_1.jsx)("
|
|
260
|
+
return (0, jsx_runtime_1.jsx)("code", { className: "dht-inline-code", children: node.content });
|
|
261
|
+
|
|
125
262
|
case 'em':
|
|
126
|
-
return (
|
|
263
|
+
return (0, jsx_runtime_1.jsx)("em", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
127
264
|
case 'strong':
|
|
128
|
-
return (
|
|
265
|
+
return (0, jsx_runtime_1.jsx)("strong", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
129
266
|
case 'underline':
|
|
130
|
-
return (
|
|
267
|
+
return (0, jsx_runtime_1.jsx)("u", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
131
268
|
case 'strikethrough':
|
|
132
|
-
return (
|
|
269
|
+
return (0, jsx_runtime_1.jsx)("s", { children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
133
270
|
case 'emoticon':
|
|
134
|
-
return typeof node.content === 'string' ? node.content : (
|
|
271
|
+
return typeof node.content === 'string' ? node.content : (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
135
272
|
case 'spoiler':
|
|
136
|
-
return (
|
|
273
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "discord-spoiler", children: (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context }) });
|
|
274
|
+
|
|
137
275
|
case 'emoji':
|
|
138
|
-
case 'twemoji':
|
|
139
|
-
|
|
276
|
+
case 'twemoji': {
|
|
277
|
+
const url = (0, utils_1.parseDiscordEmoji)(node);
|
|
278
|
+
const jumbo = context._internal?.largeEmojis;
|
|
279
|
+
return (0, jsx_runtime_1.jsx)("img", {
|
|
280
|
+
className: jumbo ? "dht-emoji dht-emoji--jumbo" : "dht-emoji",
|
|
281
|
+
src: url,
|
|
282
|
+
alt: node.name || '',
|
|
283
|
+
draggable: false
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
140
287
|
case 'timestamp': {
|
|
141
|
-
// Defensive: invalid <t:...> markers must not throw
|
|
142
288
|
const raw = node.timestamp;
|
|
143
289
|
let dateMs = NaN;
|
|
144
290
|
if (typeof raw === 'number') {
|
|
@@ -148,13 +294,44 @@ async function renderASTNode(node, context) {
|
|
|
148
294
|
if (Number.isFinite(n)) dateMs = n < 1e12 ? n * 1000 : n;
|
|
149
295
|
}
|
|
150
296
|
const dt = new Date(dateMs);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return (0, jsx_runtime_1.jsx)("
|
|
297
|
+
if (!Number.isFinite(dt.getTime())) return (0, jsx_runtime_1.jsx)("span", { className: "dht-timestamp", children: "—" });
|
|
298
|
+
const fmt = formatTimestamp(dt, node.format);
|
|
299
|
+
return (0, jsx_runtime_1.jsx)("time", { className: "dht-timestamp", dateTime: dt.toISOString(), title: dt.toISOString(), children: fmt });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
default:
|
|
303
|
+
return typeof node.content === 'string' ? node.content : (0, jsx_runtime_1.jsx)(MessageASTNodes, { nodes: node.content, context });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function formatTimestamp(dt, format) {
|
|
308
|
+
// Discord formats: t (short time), T (long time), d (short date), D (long date), f (short datetime), F (long datetime), R (relative)
|
|
309
|
+
try {
|
|
310
|
+
if (format === 'R') {
|
|
311
|
+
const diff = (dt.getTime() - Date.now()) / 1000;
|
|
312
|
+
const abs = Math.abs(diff);
|
|
313
|
+
const past = diff < 0;
|
|
314
|
+
const pluralize = (n, unit) => Math.round(n) + ' ' + unit + (Math.round(n) !== 1 ? 's' : '');
|
|
315
|
+
let val;
|
|
316
|
+
if (abs < 60) val = pluralize(abs, 'second');
|
|
317
|
+
else if (abs < 3600) val = pluralize(abs / 60, 'minute');
|
|
318
|
+
else if (abs < 86400) val = pluralize(abs / 3600, 'hour');
|
|
319
|
+
else if (abs < 2592000) val = pluralize(abs / 86400, 'day');
|
|
320
|
+
else if (abs < 31536000) val = pluralize(abs / 2592000, 'month');
|
|
321
|
+
else val = pluralize(abs / 31536000, 'year');
|
|
322
|
+
return past ? `${val} ago` : `in ${val}`;
|
|
154
323
|
}
|
|
155
|
-
|
|
156
|
-
|
|
324
|
+
switch (format) {
|
|
325
|
+
case 't': return dt.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
326
|
+
case 'T': return dt.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
327
|
+
case 'd': return dt.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
328
|
+
case 'D': return dt.toLocaleDateString('en-US', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
329
|
+
case 'F': return dt.toLocaleString('en-US', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
330
|
+
case 'f':
|
|
331
|
+
default: return dt.toLocaleString('en-US', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
157
332
|
}
|
|
333
|
+
} catch (_e) {
|
|
334
|
+
return dt.toISOString();
|
|
158
335
|
}
|
|
159
336
|
}
|
|
160
337
|
|
|
@@ -11,16 +11,41 @@ const content_1 = __importStar(require("./content"));
|
|
|
11
11
|
const utils_1 = require("../../utils/utils");
|
|
12
12
|
|
|
13
13
|
async function DiscordEmbed({ embed, context }) {
|
|
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
|
|
14
43
|
const title = embed.title ?? undefined;
|
|
15
44
|
const authorImage = embed.author?.proxyIconURL ?? embed.author?.iconURL;
|
|
16
45
|
const authorName = embed.author?.name;
|
|
17
46
|
const authorUrl = embed.author?.url ? (0, utils_1.safeHref)(embed.author.url) : undefined;
|
|
18
47
|
const color = embed.hexColor ? (0, utils_1.safeColor)(embed.hexColor, undefined) : undefined;
|
|
19
|
-
const image = embed.image?.proxyURL ?? embed.image?.url;
|
|
20
48
|
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
49
|
|
|
25
50
|
return ((0, jsx_runtime_1.jsxs)("discord-embed", {
|
|
26
51
|
"embed-title": title,
|
|
@@ -29,7 +54,7 @@ async function DiscordEmbed({ embed, context }) {
|
|
|
29
54
|
"author-name": authorName,
|
|
30
55
|
"author-url": authorUrl,
|
|
31
56
|
color: color,
|
|
32
|
-
image:
|
|
57
|
+
image: safeImg,
|
|
33
58
|
thumbnail: thumb,
|
|
34
59
|
url: url,
|
|
35
60
|
children: [
|
|
@@ -42,7 +67,7 @@ async function DiscordEmbed({ embed, context }) {
|
|
|
42
67
|
slot: "fields",
|
|
43
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}`)))
|
|
44
69
|
})),
|
|
45
|
-
|
|
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})` : ''] }) })),
|
|
46
71
|
embed.footer && ((0, jsx_runtime_1.jsx)("discord-embed-footer", {
|
|
47
72
|
slot: "footer",
|
|
48
73
|
footerImage: embed.footer.proxyIconURL ?? embed.footer.iconURL,
|
|
@@ -53,7 +78,6 @@ async function DiscordEmbed({ embed, context }) {
|
|
|
53
78
|
}, `${context.message.id}-e-${context.index}`));
|
|
54
79
|
}
|
|
55
80
|
|
|
56
|
-
// Extracted as proper async component (was inline async arrow inside .map() — undefined React behavior)
|
|
57
81
|
async function EmbedField({ embed, field, id, context }) {
|
|
58
82
|
return (0, jsx_runtime_1.jsx)("discord-embed-field", {
|
|
59
83
|
"field-title": field.name,
|