discord2html 1.0.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/index.mjs ADDED
@@ -0,0 +1,1365 @@
1
+ import "./chunk-3GDQP6AS.mjs";
2
+
3
+ // src/index.ts
4
+ import { AttachmentBuilder, version, Collection } from "discord.js";
5
+
6
+ // src/generator/index.tsx
7
+ import { prerenderToNodeStream } from "react-dom/static";
8
+ import React18 from "react";
9
+
10
+ // src/utils/buildProfiles.ts
11
+ import { UserFlags } from "discord.js";
12
+ async function buildProfiles(messages) {
13
+ const profiles = {};
14
+ for (const message of messages) {
15
+ const author = message.author;
16
+ if (!profiles[author.id]) {
17
+ profiles[author.id] = buildProfile(message.member, author);
18
+ }
19
+ if (message.interaction) {
20
+ const user = message.interaction.user;
21
+ if (!profiles[user.id]) {
22
+ profiles[user.id] = buildProfile(null, user);
23
+ }
24
+ }
25
+ if (message.thread && message.thread.lastMessage) {
26
+ profiles[message.thread.lastMessage.author.id] = buildProfile(
27
+ message.thread.lastMessage.member,
28
+ message.thread.lastMessage.author
29
+ );
30
+ }
31
+ }
32
+ return profiles;
33
+ }
34
+ function buildProfile(member, author) {
35
+ return {
36
+ author: member?.nickname ?? author.displayName ?? author.username,
37
+ avatar: member?.displayAvatarURL({ size: 64 }) ?? author.displayAvatarURL({ size: 64 }),
38
+ roleColor: member?.displayHexColor,
39
+ roleIcon: member?.roles.icon?.iconURL() ?? void 0,
40
+ roleName: member?.roles.hoist?.name ?? void 0,
41
+ bot: author.bot,
42
+ verified: author.flags?.has(UserFlags.VerifiedBot)
43
+ };
44
+ }
45
+
46
+ // src/static/client.ts
47
+ var 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.")}});';
48
+ var 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");}}));';
49
+
50
+ // src/generator/index.tsx
51
+ import { readFileSync } from "fs";
52
+ import path from "path";
53
+ import { renderToString } from "@derockdev/discord-components-core/hydrate";
54
+
55
+ // src/generator/transcript.tsx
56
+ import { DiscordHeader, DiscordMessages as DiscordMessagesComponent } from "@derockdev/discord-components-react";
57
+ import { ChannelType as ChannelType2 } from "discord.js";
58
+ import React17 from "react";
59
+
60
+ // src/generator/renderers/content.tsx
61
+ import {
62
+ DiscordBold,
63
+ DiscordCodeBlock,
64
+ DiscordCustomEmoji,
65
+ DiscordInlineCode,
66
+ DiscordItalic,
67
+ DiscordMention,
68
+ DiscordQuote,
69
+ DiscordSpoiler,
70
+ DiscordTime,
71
+ DiscordUnderlined
72
+ } from "@derockdev/discord-components-react";
73
+ import parse from "discord-markdown-parser";
74
+ import { ChannelType } from "discord.js";
75
+ import React from "react";
76
+
77
+ // src/utils/utils.ts
78
+ import twemoji from "twemoji";
79
+ function formatBytes(bytes, decimals = 2) {
80
+ if (bytes === 0) return "0 Bytes";
81
+ const k = 1024;
82
+ const dm = decimals < 0 ? 0 : decimals;
83
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
84
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
85
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
86
+ }
87
+ function parseDiscordEmoji(emoji) {
88
+ if (emoji.id) {
89
+ return `https://cdn.discordapp.com/emojis/${emoji.id}.${emoji.animated ? "gif" : "png"}`;
90
+ }
91
+ const codepoints = twemoji.convert.toCodePoint(
92
+ emoji.name.indexOf(String.fromCharCode(8205)) < 0 ? emoji.name.replace(/\uFE0F/g, "") : emoji.name
93
+ ).toLowerCase();
94
+ return `https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/${codepoints}.svg`;
95
+ }
96
+ function streamToString(stream) {
97
+ const chunks = [];
98
+ return new Promise((resolve, reject) => {
99
+ stream.on("data", (chunk) => chunks.push(chunk));
100
+ stream.on("error", reject);
101
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
102
+ });
103
+ }
104
+
105
+ // src/generator/renderers/content.tsx
106
+ async function MessageContent({ content, context }) {
107
+ if (context.type === 1 /* REPLY */ && content.length > 180) content = content.slice(0, 180) + "...";
108
+ const parsed = parse(
109
+ content,
110
+ context.type === 0 /* EMBED */ || context.type === 3 /* WEBHOOK */ ? "extended" : "normal"
111
+ );
112
+ const isOnlyEmojis = parsed.every(
113
+ (node) => ["emoji", "twemoji"].includes(node.type) || node.type === "text" && node.content.trim().length === 0
114
+ );
115
+ if (isOnlyEmojis) {
116
+ const emojis = parsed.filter((node) => ["emoji", "twemoji"].includes(node.type));
117
+ if (emojis.length <= 25) {
118
+ context._internal = {
119
+ largeEmojis: true
120
+ };
121
+ }
122
+ }
123
+ return /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: parsed, context });
124
+ }
125
+ async function MessageASTNodes({
126
+ nodes,
127
+ context
128
+ }) {
129
+ if (Array.isArray(nodes)) {
130
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, nodes.map((node, i) => /* @__PURE__ */ React.createElement(MessageSingleASTNode, { node, context, key: i })));
131
+ } else {
132
+ return /* @__PURE__ */ React.createElement(MessageSingleASTNode, { node: nodes, context });
133
+ }
134
+ }
135
+ async function MessageSingleASTNode({ node, context }) {
136
+ if (!node) return null;
137
+ const type = node.type;
138
+ switch (type) {
139
+ case "text":
140
+ return node.content;
141
+ case "link":
142
+ return /* @__PURE__ */ React.createElement("a", { href: node.target }, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
143
+ case "url":
144
+ case "autolink":
145
+ return /* @__PURE__ */ React.createElement("a", { href: node.target, target: "_blank", rel: "noreferrer" }, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
146
+ case "blockQuote":
147
+ if (context.type === 1 /* REPLY */) {
148
+ return /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context });
149
+ }
150
+ return /* @__PURE__ */ React.createElement(DiscordQuote, null, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
151
+ case "br":
152
+ case "newline":
153
+ if (context.type === 1 /* REPLY */) return " ";
154
+ return /* @__PURE__ */ React.createElement("br", null);
155
+ case "channel": {
156
+ const id = node.id;
157
+ const channel = await context.callbacks.resolveChannel(id);
158
+ return /* @__PURE__ */ React.createElement(DiscordMention, { type: channel ? channel.isDMBased() ? "channel" : getChannelType(channel.type) : "channel" }, channel ? channel.isDMBased() ? "DM Channel" : channel.name : `<#${id}>`);
159
+ }
160
+ case "role": {
161
+ const id = node.id;
162
+ const role = await context.callbacks.resolveRole(id);
163
+ return /* @__PURE__ */ React.createElement(DiscordMention, { type: "role", color: context.type === 1 /* REPLY */ ? void 0 : role?.hexColor }, role ? role.name : `<@&${id}>`);
164
+ }
165
+ case "user": {
166
+ const id = node.id;
167
+ const user = await context.callbacks.resolveUser(id);
168
+ return /* @__PURE__ */ React.createElement(DiscordMention, { type: "user" }, user ? user.displayName ?? user.username : `<@${id}>`);
169
+ }
170
+ case "here":
171
+ case "everyone":
172
+ return /* @__PURE__ */ React.createElement(DiscordMention, { type: "role", highlight: true }, `@${type}`);
173
+ case "codeBlock":
174
+ if (context.type !== 1 /* REPLY */) {
175
+ return /* @__PURE__ */ React.createElement(DiscordCodeBlock, { language: node.lang, code: node.content });
176
+ }
177
+ return /* @__PURE__ */ React.createElement(DiscordInlineCode, null, node.content);
178
+ case "inlineCode":
179
+ return /* @__PURE__ */ React.createElement(DiscordInlineCode, null, node.content);
180
+ case "em":
181
+ return /* @__PURE__ */ React.createElement(DiscordItalic, null, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
182
+ case "strong":
183
+ return /* @__PURE__ */ React.createElement(DiscordBold, null, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
184
+ case "underline":
185
+ return /* @__PURE__ */ React.createElement(DiscordUnderlined, null, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
186
+ case "strikethrough":
187
+ return /* @__PURE__ */ React.createElement("s", null, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
188
+ case "emoticon":
189
+ return typeof node.content === "string" ? node.content : /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context });
190
+ case "spoiler":
191
+ return /* @__PURE__ */ React.createElement(DiscordSpoiler, null, /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context }));
192
+ case "emoji":
193
+ case "twemoji":
194
+ return /* @__PURE__ */ React.createElement(
195
+ DiscordCustomEmoji,
196
+ {
197
+ name: node.name,
198
+ url: parseDiscordEmoji(node),
199
+ embedEmoji: context.type === 0 /* EMBED */,
200
+ largeEmoji: context._internal?.largeEmojis
201
+ }
202
+ );
203
+ case "timestamp":
204
+ return /* @__PURE__ */ React.createElement(DiscordTime, { timestamp: parseInt(node.timestamp) * 1e3, format: node.format });
205
+ default: {
206
+ console.log(`Unknown node type: ${type}`, node);
207
+ return typeof node.content === "string" ? node.content : /* @__PURE__ */ React.createElement(MessageASTNodes, { nodes: node.content, context });
208
+ }
209
+ }
210
+ }
211
+ function getChannelType(channelType) {
212
+ switch (channelType) {
213
+ case ChannelType.GuildCategory:
214
+ case ChannelType.GuildAnnouncement:
215
+ case ChannelType.GuildText:
216
+ case ChannelType.DM:
217
+ case ChannelType.GroupDM:
218
+ case ChannelType.GuildDirectory:
219
+ case ChannelType.GuildMedia:
220
+ return "channel";
221
+ case ChannelType.GuildVoice:
222
+ case ChannelType.GuildStageVoice:
223
+ return "voice";
224
+ case ChannelType.PublicThread:
225
+ case ChannelType.PrivateThread:
226
+ case ChannelType.AnnouncementThread:
227
+ return "thread";
228
+ case ChannelType.GuildForum:
229
+ return "forum";
230
+ default:
231
+ return "channel";
232
+ }
233
+ }
234
+
235
+ // src/generator/renderers/message.tsx
236
+ import {
237
+ DiscordAttachments as DiscordAttachments2,
238
+ DiscordCommand,
239
+ DiscordMessage as DiscordMessageComponent,
240
+ DiscordReaction as DiscordReaction2,
241
+ DiscordReactions as DiscordReactions2,
242
+ DiscordThread,
243
+ DiscordThreadMessage
244
+ } from "@derockdev/discord-components-react";
245
+ import React16 from "react";
246
+
247
+ // src/generator/renderers/attachment.tsx
248
+ import { DiscordAttachment, DiscordAttachments } from "@derockdev/discord-components-react";
249
+ import React2 from "react";
250
+ async function Attachments(props) {
251
+ if (props.message.attachments.size === 0) return /* @__PURE__ */ React2.createElement(React2.Fragment, null);
252
+ return /* @__PURE__ */ React2.createElement(DiscordAttachments, { slot: "attachments" }, props.message.attachments.map((attachment, id) => /* @__PURE__ */ React2.createElement(Attachment, { attachment, message: props.message, context: props.context, key: id })));
253
+ }
254
+ function getAttachmentType(attachment) {
255
+ const type = attachment.contentType?.split("/")?.[0] ?? "unknown";
256
+ if (["audio", "video", "image"].includes(type)) return type;
257
+ return "file";
258
+ }
259
+ async function Attachment({
260
+ attachment,
261
+ context,
262
+ message
263
+ }) {
264
+ let url = attachment.url;
265
+ const name = attachment.name;
266
+ const width = attachment.width;
267
+ const height = attachment.height;
268
+ const type = getAttachmentType(attachment);
269
+ if (type === "image") {
270
+ const downloaded = await context.callbacks.resolveImageSrc(
271
+ attachment.toJSON(),
272
+ message.toJSON()
273
+ );
274
+ if (downloaded !== null) {
275
+ url = downloaded ?? url;
276
+ }
277
+ }
278
+ return /* @__PURE__ */ React2.createElement(
279
+ DiscordAttachment,
280
+ {
281
+ type,
282
+ size: formatBytes(attachment.size),
283
+ key: attachment.id,
284
+ slot: "attachment",
285
+ url,
286
+ alt: name ?? void 0,
287
+ width: width ?? void 0,
288
+ height: height ?? void 0
289
+ }
290
+ );
291
+ }
292
+
293
+ // src/generator/renderers/components.tsx
294
+ import { DiscordActionRow, DiscordAttachment as DiscordAttachment2, DiscordSpoiler as DiscordSpoiler2 } from "@derockdev/discord-components-react";
295
+ import {
296
+ ComponentType as ComponentType3
297
+ } from "discord.js";
298
+ import React12 from "react";
299
+
300
+ // src/generator/renderers/components/Select Menu.tsx
301
+ import React3 from "react";
302
+ import { ComponentType as ComponentType2 } from "discord.js";
303
+
304
+ // src/generator/renderers/components/utils.ts
305
+ import { ComponentType } from "discord.js";
306
+
307
+ // src/generator/renderers/components/styles.ts
308
+ import { ButtonStyle } from "discord.js";
309
+ var containerStyle = {
310
+ display: "grid",
311
+ gap: "4px",
312
+ width: "100%",
313
+ maxWidth: "500px",
314
+ borderRadius: "8px",
315
+ overflow: "hidden"
316
+ };
317
+ var baseImageStyle = {
318
+ overflow: "hidden",
319
+ position: "relative",
320
+ background: "#2b2d31"
321
+ };
322
+ var ButtonStyleMapping = {
323
+ [ButtonStyle.Primary]: "primary",
324
+ [ButtonStyle.Secondary]: "secondary",
325
+ [ButtonStyle.Success]: "success",
326
+ [ButtonStyle.Danger]: "destructive",
327
+ [ButtonStyle.Link]: "secondary",
328
+ [ButtonStyle.Premium]: "primary"
329
+ };
330
+ var globalStyles = `
331
+ .discord-container {
332
+ display: grid;
333
+ gap: 4px;
334
+ width: 100%;
335
+ max-width: 500px;
336
+ border-radius: 8px;
337
+ overflow: hidden;
338
+ }
339
+
340
+ .discord-base-image {
341
+ overflow: hidden;
342
+ position: relative;
343
+ background: #2b2d31;
344
+ }
345
+
346
+ .discord-button {
347
+ color: #ffffff !important;
348
+ padding: 2px 16px;
349
+ border-radius: 8px;
350
+ text-decoration: none !important;
351
+ display: inline-flex;
352
+ align-items: center;
353
+ justify-content: center;
354
+ font-size: 14px;
355
+ font-weight: 500;
356
+ height: 32px;
357
+ min-height: 32px;
358
+ min-width: 60px;
359
+ cursor: pointer;
360
+ font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
361
+ text-align: center;
362
+ box-sizing: border-box;
363
+ border: none;
364
+ outline: none;
365
+ transition: background-color 0.2s ease;
366
+ }
367
+
368
+ .discord-button-primary {
369
+ background-color: hsl(234.935 calc(1*85.556%) 64.706% /1);
370
+ }
371
+
372
+ .discord-button-secondary {
373
+ background-color: hsl(240 calc(1*4%) 60.784% /0.12156862745098039);
374
+ }
375
+
376
+ .discord-button-success {
377
+ background-color: hsl(145.97 calc(1*100%) 26.275% /1);
378
+ }
379
+
380
+ .discord-button-destructive {
381
+ background-color: hsl(355.636 calc(1*64.706%) 50% /1);
382
+ }
383
+
384
+ .discord-select-menu {
385
+ margin-top: 2px;
386
+ margin-bottom: 2px;
387
+ position: relative;
388
+ width: 100%;
389
+ max-width: 500px;
390
+ height: 40px;
391
+ background-color: #2b2d31;
392
+ border-radius: 4px;
393
+ color: #b5bac1;
394
+ cursor: pointer;
395
+ font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
396
+ font-size: 14px;
397
+ display: flex;
398
+ align-items: center;
399
+ padding: 0 8px;
400
+ justify-content: space-between;
401
+ box-sizing: border-box;
402
+ border: 1px solid #1e1f22;
403
+ }
404
+ `;
405
+
406
+ // src/generator/renderers/components/utils.ts
407
+ var SELECT_LABEL_MAP = {
408
+ [ComponentType.UserSelect]: "Select User",
409
+ [ComponentType.RoleSelect]: "Select Role",
410
+ [ComponentType.MentionableSelect]: "Select Mentionable",
411
+ [ComponentType.ChannelSelect]: "Select Channel",
412
+ [ComponentType.StringSelect]: "Make a Selection"
413
+ };
414
+ function getSelectTypeLabel(type) {
415
+ return SELECT_LABEL_MAP[type] ?? "Select Option";
416
+ }
417
+ function getGalleryLayout(count) {
418
+ switch (count) {
419
+ case 1:
420
+ return {
421
+ ...containerStyle,
422
+ gridTemplateColumns: "1fr",
423
+ gridTemplateRows: "auto"
424
+ };
425
+ case 2:
426
+ return {
427
+ ...containerStyle,
428
+ gridTemplateColumns: "1fr 1fr",
429
+ gridTemplateRows: "auto"
430
+ };
431
+ case 3:
432
+ case 4:
433
+ return {
434
+ ...containerStyle,
435
+ gridTemplateColumns: "1fr 1fr",
436
+ gridTemplateRows: "1fr 1fr"
437
+ };
438
+ case 5:
439
+ return {
440
+ ...containerStyle,
441
+ gridTemplateColumns: "1fr 1fr 1fr",
442
+ gridTemplateRows: "auto auto"
443
+ };
444
+ default:
445
+ if (count >= 7) {
446
+ return {
447
+ ...containerStyle,
448
+ gridTemplateColumns: "1fr 1fr 1fr",
449
+ gridTemplateRows: "auto auto auto auto"
450
+ };
451
+ } else {
452
+ return {
453
+ ...containerStyle,
454
+ gridTemplateColumns: "1fr 1fr 1fr",
455
+ gridTemplateRows: "auto"
456
+ };
457
+ }
458
+ }
459
+ }
460
+ function getImageStyle(idx, count) {
461
+ switch (count) {
462
+ case 3:
463
+ if (idx === 0) {
464
+ return {
465
+ ...baseImageStyle,
466
+ gridRow: "1 / span 2",
467
+ gridColumn: "1",
468
+ aspectRatio: "1/2"
469
+ };
470
+ }
471
+ break;
472
+ case 5:
473
+ if (idx < 2) {
474
+ return {
475
+ ...baseImageStyle,
476
+ gridRow: "1",
477
+ gridColumn: idx === 0 ? "1 / span 2" : "3"
478
+ };
479
+ } else {
480
+ return {
481
+ ...baseImageStyle,
482
+ gridRow: "2",
483
+ gridColumn: `${idx - 2 + 1}`
484
+ };
485
+ }
486
+ case 7:
487
+ if (idx === 0) {
488
+ return {
489
+ ...baseImageStyle,
490
+ gridRow: "1",
491
+ gridColumn: "1 / span 3"
492
+ };
493
+ } else if (idx <= 3) {
494
+ return {
495
+ ...baseImageStyle,
496
+ gridRow: "2",
497
+ gridColumn: `${idx - 0}`
498
+ };
499
+ } else {
500
+ return {
501
+ ...baseImageStyle,
502
+ gridRow: "3",
503
+ gridColumn: `${idx - 3}`
504
+ };
505
+ }
506
+ case 8:
507
+ if (idx < 2) {
508
+ return {
509
+ ...baseImageStyle,
510
+ gridRow: "1",
511
+ gridColumn: idx === 0 ? "1 / span 2" : "3"
512
+ };
513
+ } else if (idx < 5) {
514
+ return {
515
+ ...baseImageStyle,
516
+ gridRow: "2",
517
+ gridColumn: `${idx - 2 + 1}`
518
+ };
519
+ } else {
520
+ return {
521
+ ...baseImageStyle,
522
+ gridRow: "3",
523
+ gridColumn: `${idx - 5 + 1}`
524
+ };
525
+ }
526
+ case 10:
527
+ if (idx === 0) {
528
+ return {
529
+ ...baseImageStyle,
530
+ gridRow: "1",
531
+ gridColumn: "1 / span 3"
532
+ };
533
+ } else if (idx <= 3) {
534
+ return {
535
+ ...baseImageStyle,
536
+ gridRow: "2",
537
+ gridColumn: `${idx - 0}`
538
+ };
539
+ } else if (idx <= 6) {
540
+ return {
541
+ ...baseImageStyle,
542
+ gridRow: "3",
543
+ gridColumn: `${idx - 3}`
544
+ };
545
+ } else {
546
+ return {
547
+ ...baseImageStyle,
548
+ gridRow: "4",
549
+ gridColumn: `${idx - 6}`
550
+ };
551
+ }
552
+ }
553
+ return baseImageStyle;
554
+ }
555
+
556
+ // src/generator/renderers/components/Select Menu.tsx
557
+ function DiscordSelectMenu({
558
+ component
559
+ }) {
560
+ const isStringSelect = component.type === ComponentType2.StringSelect;
561
+ const placeholder = component.placeholder || getSelectTypeLabel(component.type);
562
+ return /* @__PURE__ */ React3.createElement("div", { className: "discord-select-menu" }, /* @__PURE__ */ React3.createElement("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, placeholder), /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", alignItems: "center", marginLeft: "8px" } }, /* @__PURE__ */ React3.createElement("svg", { width: "24", height: "24", viewBox: "0 0 24 24" }, /* @__PURE__ */ React3.createElement("path", { fill: "currentColor", d: "M7 10L12 15L17 10H7Z" }))), isStringSelect && component.options && component.options.length > 0 && /* @__PURE__ */ React3.createElement(
563
+ "div",
564
+ {
565
+ style: {
566
+ display: "none",
567
+ position: "absolute",
568
+ top: "44px",
569
+ left: "0",
570
+ width: "100%",
571
+ backgroundColor: "#2b2d31",
572
+ borderRadius: "4px",
573
+ zIndex: 10,
574
+ border: "1px solid #1e1f22",
575
+ maxHeight: "320px",
576
+ overflowY: "auto"
577
+ }
578
+ },
579
+ component.options.map((option, idx) => /* @__PURE__ */ React3.createElement(
580
+ "div",
581
+ {
582
+ key: idx,
583
+ style: {
584
+ padding: "8px 12px",
585
+ cursor: "pointer",
586
+ display: "flex",
587
+ alignItems: "center",
588
+ borderBottom: idx < component.options.length - 1 ? "1px solid #1e1f22" : "none"
589
+ }
590
+ },
591
+ option.emoji && /* @__PURE__ */ React3.createElement(
592
+ "img",
593
+ {
594
+ src: parseDiscordEmoji(option.emoji),
595
+ alt: option.emoji.name ?? "emoji",
596
+ style: { width: "16px", height: "16px", marginRight: "8px", verticalAlign: "middle" }
597
+ }
598
+ ),
599
+ /* @__PURE__ */ React3.createElement("span", null, option.label)
600
+ ))
601
+ ));
602
+ }
603
+ var Select_Menu_default = DiscordSelectMenu;
604
+
605
+ // src/generator/renderers/components/Container.tsx
606
+ import React4 from "react";
607
+ function DiscordContainer({ children }) {
608
+ return /* @__PURE__ */ React4.createElement(
609
+ "div",
610
+ {
611
+ style: {
612
+ display: "flex",
613
+ width: "500px",
614
+ flexDirection: "column",
615
+ backgroundColor: "#3f4248",
616
+ padding: "16px",
617
+ border: "1px solid #4f5359",
618
+ marginTop: "2px",
619
+ marginBottom: "2px",
620
+ borderRadius: "10px",
621
+ gap: "8px"
622
+ }
623
+ },
624
+ children
625
+ );
626
+ }
627
+ var Container_default = DiscordContainer;
628
+
629
+ // src/generator/renderers/components/section/Section.tsx
630
+ import React7 from "react";
631
+
632
+ // src/generator/renderers/components/section/SectionContent.tsx
633
+ import React5 from "react";
634
+ function SectionContent({ children }) {
635
+ return /* @__PURE__ */ React5.createElement(
636
+ "div",
637
+ {
638
+ style: {
639
+ display: "flex",
640
+ flexDirection: "column",
641
+ width: "100%"
642
+ }
643
+ },
644
+ children
645
+ );
646
+ }
647
+ var SectionContent_default = SectionContent;
648
+
649
+ // src/generator/renderers/components/section/SectionAccessory.tsx
650
+ import React6 from "react";
651
+ function SectionAccessory({ children }) {
652
+ if (!children) return null;
653
+ return /* @__PURE__ */ React6.createElement(
654
+ "div",
655
+ {
656
+ style: {
657
+ display: "flex",
658
+ width: "100%",
659
+ maxWidth: "500px",
660
+ justifyContent: "flex-end",
661
+ alignItems: "center"
662
+ }
663
+ },
664
+ children
665
+ );
666
+ }
667
+ var SectionAccessory_default = SectionAccessory;
668
+
669
+ // src/generator/renderers/components/section/Section.tsx
670
+ function DiscordSection({ children, accessory, id }) {
671
+ return /* @__PURE__ */ React7.createElement(
672
+ "div",
673
+ {
674
+ style: {
675
+ display: "flex",
676
+ flexDirection: "row",
677
+ width: "100%",
678
+ maxWidth: "500px"
679
+ }
680
+ },
681
+ /* @__PURE__ */ React7.createElement(SectionContent_default, null, children),
682
+ /* @__PURE__ */ React7.createElement(SectionAccessory_default, null, accessory && /* @__PURE__ */ React7.createElement(Component, { component: accessory, id }))
683
+ );
684
+ }
685
+ var Section_default = DiscordSection;
686
+
687
+ // src/generator/renderers/components/Media Gallery.tsx
688
+ import React8 from "react";
689
+ function DiscordMediaGallery({ component }) {
690
+ if (!component.items || component.items.length === 0) {
691
+ return null;
692
+ }
693
+ const count = component.items.length;
694
+ const imagesToShow = component.items.slice(0, 10);
695
+ const hasMore = component.items.length > 10;
696
+ return /* @__PURE__ */ React8.createElement("div", { style: getGalleryLayout(count) }, imagesToShow.map((media, idx) => /* @__PURE__ */ React8.createElement("div", { key: idx, style: getImageStyle(idx, count) }, /* @__PURE__ */ React8.createElement(
697
+ "img",
698
+ {
699
+ src: media.media.url,
700
+ alt: media.description || "Media content",
701
+ style: {
702
+ width: "100%",
703
+ height: "100%",
704
+ objectFit: "cover"
705
+ }
706
+ }
707
+ ), hasMore && idx === imagesToShow.length - 1 && /* @__PURE__ */ React8.createElement(
708
+ "div",
709
+ {
710
+ style: {
711
+ position: "absolute",
712
+ top: 0,
713
+ left: 0,
714
+ width: "100%",
715
+ height: "100%",
716
+ display: "flex",
717
+ alignItems: "center",
718
+ justifyContent: "center",
719
+ backgroundColor: "rgba(0, 0, 0, 0.7)",
720
+ color: "white",
721
+ fontSize: "20px",
722
+ fontWeight: "bold"
723
+ }
724
+ },
725
+ "+",
726
+ component.items.length - 10
727
+ ))));
728
+ }
729
+ var Media_Gallery_default = DiscordMediaGallery;
730
+
731
+ // src/generator/renderers/components/Spacing.tsx
732
+ import React9 from "react";
733
+ import { SeparatorSpacingSize } from "discord.js";
734
+ function DiscordSeparator({ divider, spacing }) {
735
+ return /* @__PURE__ */ React9.createElement(
736
+ "div",
737
+ {
738
+ style: {
739
+ width: "100%",
740
+ height: divider ? "1px" : "0px",
741
+ backgroundColor: "#4f5359",
742
+ margin: spacing === SeparatorSpacingSize.Large ? "8px 0" : "0"
743
+ }
744
+ }
745
+ );
746
+ }
747
+ var Spacing_default = DiscordSeparator;
748
+
749
+ // src/generator/renderers/components/Button.tsx
750
+ import React10 from "react";
751
+ function DiscordButton({ type, url, emoji, children }) {
752
+ return /* @__PURE__ */ React10.createElement("a", { href: url, target: "_blank", className: `discord-button discord-button-${type}` }, emoji && /* @__PURE__ */ React10.createElement("span", { style: { display: "flex", alignItems: "center" } }, /* @__PURE__ */ React10.createElement("img", { src: emoji, alt: "emoji", style: { width: "16px", height: "16px", marginRight: "8px" } })), /* @__PURE__ */ React10.createElement("span", { style: { display: "flex", alignItems: "center" } }, children), url && /* @__PURE__ */ React10.createElement("span", { style: { marginLeft: "8px", display: "flex", alignItems: "center" } }, /* @__PURE__ */ React10.createElement("svg", { role: "img", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "none", viewBox: "0 0 24 24" }, /* @__PURE__ */ React10.createElement(
753
+ "path",
754
+ {
755
+ fill: "currentColor",
756
+ d: "M15 2a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V4.41l-4.3 4.3a1 1 0 1 1-1.4-1.42L19.58 3H16a1 1 0 0 1-1-1Z"
757
+ }
758
+ ), /* @__PURE__ */ React10.createElement(
759
+ "path",
760
+ {
761
+ fill: "currentColor",
762
+ d: "M5 2a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-6a1 1 0 1 0-2 0v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h6a1 1 0 1 0 0-2H5Z"
763
+ }
764
+ ))));
765
+ }
766
+ var Button_default = DiscordButton;
767
+
768
+ // src/generator/renderers/components/Thumbnail.tsx
769
+ import React11 from "react";
770
+ function DiscordThumbnail({ url }) {
771
+ return /* @__PURE__ */ React11.createElement(
772
+ "img",
773
+ {
774
+ src: url,
775
+ alt: "Thumbnail",
776
+ style: {
777
+ width: "85px",
778
+ height: "85px",
779
+ objectFit: "cover",
780
+ borderRadius: "8px"
781
+ }
782
+ }
783
+ );
784
+ }
785
+ var Thumbnail_default = DiscordThumbnail;
786
+
787
+ // src/generator/renderers/components.tsx
788
+ function ComponentRow({
789
+ component,
790
+ id,
791
+ context
792
+ }) {
793
+ switch (component.type) {
794
+ case ComponentType3.ActionRow:
795
+ return /* @__PURE__ */ React12.createElement(DiscordActionRow, { key: id }, /* @__PURE__ */ React12.createElement(React12.Fragment, null, component.components.map((nestedComponent, id2) => /* @__PURE__ */ React12.createElement(Component, { component: nestedComponent, id: id2, key: id2 }))));
796
+ case ComponentType3.Container:
797
+ return /* @__PURE__ */ React12.createElement(Container_default, { key: id }, /* @__PURE__ */ React12.createElement(React12.Fragment, null, component.components.map((nestedComponent, id2) => /* @__PURE__ */ React12.createElement(ComponentRow, { component: nestedComponent, id: id2, key: id2, context }))));
798
+ case ComponentType3.File:
799
+ return /* @__PURE__ */ React12.createElement(React12.Fragment, null, component.spoiler ? /* @__PURE__ */ React12.createElement(DiscordSpoiler2, { key: component.id, slot: "attachment" }, /* @__PURE__ */ React12.createElement(
800
+ DiscordAttachment2,
801
+ {
802
+ type: "file",
803
+ key: component.id,
804
+ slot: "attachment",
805
+ url: component.file.url,
806
+ alt: "Discord Attachment"
807
+ }
808
+ )) : /* @__PURE__ */ React12.createElement(
809
+ DiscordAttachment2,
810
+ {
811
+ type: "file",
812
+ key: component.id,
813
+ slot: "attachment",
814
+ url: component.file.url,
815
+ alt: "Discord Attachment"
816
+ }
817
+ ));
818
+ case ComponentType3.MediaGallery:
819
+ return /* @__PURE__ */ React12.createElement(Media_Gallery_default, { component, key: id });
820
+ case ComponentType3.Section:
821
+ return /* @__PURE__ */ React12.createElement(Section_default, { key: id, accessory: component.accessory, id }, component.components.map((nestedComponent, id2) => /* @__PURE__ */ React12.createElement(ComponentRow, { component: nestedComponent, id: id2, key: id2, context })));
822
+ case ComponentType3.Separator:
823
+ return /* @__PURE__ */ React12.createElement(Spacing_default, { key: id, spacing: component.spacing, divider: component.divider });
824
+ case ComponentType3.TextDisplay:
825
+ return /* @__PURE__ */ React12.createElement(MessageContent, { key: id, content: component.content, context: { ...context, type: 2 /* NORMAL */ } });
826
+ default:
827
+ return null;
828
+ }
829
+ }
830
+ function Component({
831
+ component,
832
+ id
833
+ }) {
834
+ switch (component.type) {
835
+ case ComponentType3.Button:
836
+ return /* @__PURE__ */ React12.createElement(
837
+ Button_default,
838
+ {
839
+ key: id,
840
+ type: ButtonStyleMapping[component.style] ?? "secondary",
841
+ url: component.url ?? void 0,
842
+ emoji: component.emoji ? parseDiscordEmoji(component.emoji) : void 0
843
+ },
844
+ component.label
845
+ );
846
+ case ComponentType3.StringSelect:
847
+ case ComponentType3.UserSelect:
848
+ case ComponentType3.RoleSelect:
849
+ case ComponentType3.MentionableSelect:
850
+ case ComponentType3.ChannelSelect:
851
+ return /* @__PURE__ */ React12.createElement(Select_Menu_default, { key: id, component });
852
+ case ComponentType3.Thumbnail:
853
+ return /* @__PURE__ */ React12.createElement(Thumbnail_default, { key: id, url: component.media.url });
854
+ default:
855
+ return null;
856
+ }
857
+ }
858
+
859
+ // src/generator/renderers/embed.tsx
860
+ import {
861
+ DiscordEmbed as DiscordEmbedComponent,
862
+ DiscordEmbedDescription,
863
+ DiscordEmbedField,
864
+ DiscordEmbedFields,
865
+ DiscordEmbedFooter
866
+ } from "@derockdev/discord-components-react";
867
+ import React13 from "react";
868
+
869
+ // src/utils/embeds.ts
870
+ function calculateInlineIndex(fields, currentFieldIndex) {
871
+ const startIndex = currentFieldIndex - 1;
872
+ for (let i = startIndex; i >= 0; i--) {
873
+ const field = fields[i];
874
+ if (!field) continue;
875
+ if (field.inline === false) {
876
+ const amount = startIndex - i;
877
+ return amount % 3 + 1;
878
+ }
879
+ }
880
+ return currentFieldIndex % 3 + 1;
881
+ }
882
+
883
+ // src/generator/renderers/embed.tsx
884
+ async function DiscordEmbed({ embed, context }) {
885
+ return /* @__PURE__ */ React13.createElement(
886
+ DiscordEmbedComponent,
887
+ {
888
+ embedTitle: embed.title ?? void 0,
889
+ slot: "embeds",
890
+ key: `${context.message.id}-e-${context.index}`,
891
+ authorImage: embed.author?.proxyIconURL ?? embed.author?.iconURL,
892
+ authorName: embed.author?.name,
893
+ authorUrl: embed.author?.url,
894
+ color: embed.hexColor ?? void 0,
895
+ image: embed.image?.proxyURL ?? embed.image?.url,
896
+ thumbnail: embed.thumbnail?.proxyURL ?? embed.thumbnail?.url,
897
+ url: embed.url ?? void 0
898
+ },
899
+ embed.description && /* @__PURE__ */ React13.createElement(DiscordEmbedDescription, { slot: "description" }, /* @__PURE__ */ React13.createElement(MessageContent, { content: embed.description, context: { ...context, type: 0 /* EMBED */ } })),
900
+ embed.fields.length > 0 && /* @__PURE__ */ React13.createElement(DiscordEmbedFields, { slot: "fields" }, embed.fields.map(async (field, id) => /* @__PURE__ */ React13.createElement(
901
+ DiscordEmbedField,
902
+ {
903
+ key: `${context.message.id}-e-${context.index}-f-${id}`,
904
+ fieldTitle: field.name,
905
+ inline: field.inline,
906
+ inlineIndex: calculateInlineIndex(embed.fields, id)
907
+ },
908
+ /* @__PURE__ */ React13.createElement(MessageContent, { content: field.value, context: { ...context, type: 0 /* EMBED */ } })
909
+ ))),
910
+ embed.footer && /* @__PURE__ */ React13.createElement(
911
+ DiscordEmbedFooter,
912
+ {
913
+ slot: "footer",
914
+ footerImage: embed.footer.proxyIconURL ?? embed.footer.iconURL,
915
+ timestamp: embed.timestamp ?? void 0
916
+ },
917
+ embed.footer.text
918
+ )
919
+ );
920
+ }
921
+
922
+ // src/generator/renderers/reply.tsx
923
+ import { DiscordReply } from "@derockdev/discord-components-react";
924
+ import { UserFlags as UserFlags2 } from "discord.js";
925
+ import React14 from "react";
926
+ async function MessageReply({ message, context }) {
927
+ if (!message.reference) return null;
928
+ if (message.reference.guildId !== message.guild?.id) return null;
929
+ const referencedMessage = context.messages.find((m) => m.id === message.reference.messageId);
930
+ if (!referencedMessage) return /* @__PURE__ */ React14.createElement(DiscordReply, { slot: "reply" }, "Message could not be loaded.");
931
+ const isCrossPost = referencedMessage.reference && referencedMessage.reference.guildId !== message.guild?.id;
932
+ const isCommand = referencedMessage.interaction !== null;
933
+ return /* @__PURE__ */ React14.createElement(
934
+ DiscordReply,
935
+ {
936
+ slot: "reply",
937
+ edited: !isCommand && referencedMessage.editedAt !== null,
938
+ attachment: referencedMessage.attachments.size > 0,
939
+ author: referencedMessage.member?.nickname ?? referencedMessage.author.displayName ?? referencedMessage.author.username,
940
+ avatar: referencedMessage.author.avatarURL({ size: 32 }) ?? void 0,
941
+ roleColor: referencedMessage.member?.displayHexColor ?? void 0,
942
+ bot: !isCrossPost && referencedMessage.author.bot,
943
+ verified: referencedMessage.author.flags?.has(UserFlags2.VerifiedBot),
944
+ op: message?.channel?.isThread?.() && referencedMessage.author.id === message?.channel?.ownerId,
945
+ server: isCrossPost ?? void 0,
946
+ command: isCommand
947
+ },
948
+ referencedMessage.content ? /* @__PURE__ */ React14.createElement("span", { "data-goto": referencedMessage.id }, /* @__PURE__ */ React14.createElement(MessageContent, { content: referencedMessage.content, context: { ...context, type: 1 /* REPLY */ } })) : isCommand ? /* @__PURE__ */ React14.createElement("em", { "data-goto": referencedMessage.id }, "Click to see command.") : /* @__PURE__ */ React14.createElement("em", { "data-goto": referencedMessage.id }, "Click to see attachment.")
949
+ );
950
+ }
951
+
952
+ // src/generator/renderers/systemMessage.tsx
953
+ import { DiscordReaction, DiscordReactions, DiscordSystemMessage } from "@derockdev/discord-components-react";
954
+ import { MessageType } from "discord.js";
955
+ import React15 from "react";
956
+ async function SystemMessage({ message }) {
957
+ switch (message.type) {
958
+ case MessageType.RecipientAdd:
959
+ case MessageType.UserJoin:
960
+ return /* @__PURE__ */ React15.createElement(DiscordSystemMessage, { id: `m-${message.id}`, key: message.id, type: "join" }, /* @__PURE__ */ React15.createElement(JoinMessage, { member: message.member, fallbackUser: message.author }));
961
+ case MessageType.ChannelPinnedMessage:
962
+ return /* @__PURE__ */ React15.createElement(DiscordSystemMessage, { id: `m-${message.id}`, key: message.id, type: "pin" }, /* @__PURE__ */ React15.createElement(Highlight, { color: message.member?.roles.color?.hexColor }, message.author.displayName ?? message.author.username), " ", "pinned ", /* @__PURE__ */ React15.createElement("i", { "data-goto": message.reference?.messageId }, "a message"), " to this channel.", message.reactions.cache.size > 0 && /* @__PURE__ */ React15.createElement(DiscordReactions, { slot: "reactions" }, message.reactions.cache.map((reaction, id) => /* @__PURE__ */ React15.createElement(
963
+ DiscordReaction,
964
+ {
965
+ key: `${message.id}r${id}`,
966
+ name: reaction.emoji.name,
967
+ emoji: parseDiscordEmoji(reaction.emoji),
968
+ count: reaction.count
969
+ }
970
+ ))));
971
+ case MessageType.GuildBoost:
972
+ case MessageType.GuildBoostTier1:
973
+ case MessageType.GuildBoostTier2:
974
+ case MessageType.GuildBoostTier3:
975
+ return /* @__PURE__ */ React15.createElement(DiscordSystemMessage, { id: `m-${message.id}`, key: message.id, type: "boost" }, /* @__PURE__ */ React15.createElement(Highlight, { color: message.member?.roles.color?.hexColor }, message.author.displayName ?? message.author.username), " ", "boosted the server!");
976
+ case MessageType.ThreadStarterMessage:
977
+ return /* @__PURE__ */ React15.createElement(DiscordSystemMessage, { id: `ms-${message.id}`, key: message.id, type: "thread" }, /* @__PURE__ */ React15.createElement(Highlight, { color: message.member?.roles.color?.hexColor }, message.author.displayName ?? message.author.username), " ", "started a thread: ", /* @__PURE__ */ React15.createElement("i", { "data-goto": message.reference?.messageId }, message.content));
978
+ // TODO: implement support for these:
979
+ case MessageType.Default:
980
+ case MessageType.RecipientRemove:
981
+ case MessageType.Call:
982
+ case MessageType.ChannelNameChange:
983
+ case MessageType.ChannelIconChange:
984
+ case MessageType.ChannelFollowAdd:
985
+ case MessageType.GuildDiscoveryDisqualified:
986
+ case MessageType.GuildDiscoveryRequalified:
987
+ case MessageType.GuildDiscoveryGracePeriodInitialWarning:
988
+ case MessageType.GuildDiscoveryGracePeriodFinalWarning:
989
+ case MessageType.ThreadCreated:
990
+ case MessageType.Reply:
991
+ case MessageType.ChatInputCommand:
992
+ case MessageType.GuildInviteReminder:
993
+ case MessageType.ContextMenuCommand:
994
+ case MessageType.AutoModerationAction:
995
+ case MessageType.RoleSubscriptionPurchase:
996
+ case MessageType.InteractionPremiumUpsell:
997
+ case MessageType.StageStart:
998
+ case MessageType.StageEnd:
999
+ case MessageType.StageSpeaker:
1000
+ case MessageType.StageRaiseHand:
1001
+ case MessageType.StageTopic:
1002
+ case MessageType.GuildApplicationPremiumSubscription:
1003
+ case MessageType.GuildIncidentAlertModeEnabled:
1004
+ case MessageType.GuildIncidentAlertModeDisabled:
1005
+ case MessageType.GuildIncidentReportRaid:
1006
+ case MessageType.GuildIncidentReportFalseAlarm:
1007
+ case MessageType.PurchaseNotification:
1008
+ case MessageType.PollResult:
1009
+ return void 0;
1010
+ default:
1011
+ return void 0;
1012
+ }
1013
+ }
1014
+ function Highlight({ children, color }) {
1015
+ return /* @__PURE__ */ React15.createElement("i", { style: { color: color ?? "white" } }, children);
1016
+ }
1017
+ var allJoinMessages = [
1018
+ "{user} just joined the server - glhf!",
1019
+ "{user} just joined. Everyone, look busy!",
1020
+ "{user} just joined. Can I get a heal?",
1021
+ "{user} joined your party.",
1022
+ "{user} joined. You must construct additional pylons.",
1023
+ "Ermagherd. {user} is here.",
1024
+ "Welcome, {user}. Stay awhile and listen.",
1025
+ "Welcome, {user}. We were expecting you ( \u0361\xB0 \u035C\u0296 \u0361\xB0)",
1026
+ "Welcome, {user}. We hope you brought pizza.",
1027
+ "Welcome {user}. Leave your weapons by the door.",
1028
+ "A wild {user} appeared.",
1029
+ "Swoooosh. {user} just landed.",
1030
+ "Brace yourselves {user} just joined the server.",
1031
+ "{user} just joined. Hide your bananas.",
1032
+ "{user} just arrived. Seems OP - please nerf.",
1033
+ "{user} just slid into the server.",
1034
+ "A {user} has spawned in the server.",
1035
+ "Big {user} showed up!",
1036
+ "Where's {user}? In the server!",
1037
+ "{user} hopped into the server. Kangaroo!!",
1038
+ "{user} just showed up. Hold my beer.",
1039
+ "Challenger approaching - {user} has appeared!",
1040
+ "It's a bird! It's a plane! Nevermind, it's just {user}.",
1041
+ "It's {user}! Praise the sun! \\\\[T]/",
1042
+ "Never gonna give {user} up. Never gonna let {user} down.",
1043
+ "Ha! {user} has joined! You activated my trap card!",
1044
+ "Cheers, love! {user} is here!",
1045
+ "Hey! Listen! {user} has joined!",
1046
+ "We've been expecting you {user}",
1047
+ "It's dangerous to go alone, take {user}!",
1048
+ "{user} has joined the server! It's super effective!",
1049
+ "Cheers, love! {user} is here!",
1050
+ "{user} is here, as the prophecy foretold.",
1051
+ "{user} has arrived. Party's over.",
1052
+ "Ready player {user}",
1053
+ "{user} is here to kick butt and chew bubblegum. And {user} is all out of gum.",
1054
+ "Hello. Is it {user} you're looking for?"
1055
+ ];
1056
+ function JoinMessage({ member, fallbackUser }) {
1057
+ const randomMessage = allJoinMessages[Math.floor(Math.random() * allJoinMessages.length)];
1058
+ return randomMessage.split("{user}").flatMap((item, i) => [
1059
+ item,
1060
+ /* @__PURE__ */ React15.createElement(Highlight, { color: member?.roles.color?.hexColor, key: i }, member?.nickname ?? fallbackUser.displayName ?? fallbackUser.username)
1061
+ ]).slice(0, -1);
1062
+ }
1063
+
1064
+ // src/generator/renderers/message.tsx
1065
+ async function DiscordMessage({
1066
+ message,
1067
+ context
1068
+ }) {
1069
+ if (message.system) return /* @__PURE__ */ React16.createElement(SystemMessage, { message });
1070
+ const isCrosspost = message.reference && message.reference.guildId !== message.guild?.id;
1071
+ return /* @__PURE__ */ React16.createElement(
1072
+ DiscordMessageComponent,
1073
+ {
1074
+ id: `m-${message.id}`,
1075
+ timestamp: message.createdAt.toISOString(),
1076
+ key: message.id,
1077
+ edited: message.editedAt !== null,
1078
+ server: isCrosspost ?? void 0,
1079
+ highlight: message.mentions.everyone,
1080
+ profile: message.author.id
1081
+ },
1082
+ /* @__PURE__ */ React16.createElement(MessageReply, { message, context }),
1083
+ message.interaction && /* @__PURE__ */ React16.createElement(
1084
+ DiscordCommand,
1085
+ {
1086
+ slot: "reply",
1087
+ profile: message.interaction.user.id,
1088
+ command: "/" + message.interaction.commandName
1089
+ }
1090
+ ),
1091
+ message.content && /* @__PURE__ */ React16.createElement(
1092
+ MessageContent,
1093
+ {
1094
+ content: message.content,
1095
+ context: { ...context, type: message.webhookId ? 3 /* WEBHOOK */ : 2 /* NORMAL */ }
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ React16.createElement(Attachments, { message, context }),
1099
+ message.embeds.map((embed, id) => /* @__PURE__ */ React16.createElement(DiscordEmbed, { embed, context: { ...context, index: id, message }, key: id })),
1100
+ message.components.length > 0 && /* @__PURE__ */ React16.createElement(DiscordAttachments2, { slot: "components" }, message.components.map((component, id) => /* @__PURE__ */ React16.createElement(ComponentRow, { key: id, id, component, context }))),
1101
+ message.reactions.cache.size > 0 && /* @__PURE__ */ React16.createElement(DiscordReactions2, { slot: "reactions" }, message.reactions.cache.map((reaction, id) => /* @__PURE__ */ React16.createElement(
1102
+ DiscordReaction2,
1103
+ {
1104
+ key: `${message.id}r${id}`,
1105
+ name: reaction.emoji.name,
1106
+ emoji: parseDiscordEmoji(reaction.emoji),
1107
+ count: reaction.count
1108
+ }
1109
+ ))),
1110
+ message.hasThread && message.thread && /* @__PURE__ */ React16.createElement(
1111
+ DiscordThread,
1112
+ {
1113
+ slot: "thread",
1114
+ name: message.thread.name,
1115
+ cta: message.thread.messageCount ? `${message.thread.messageCount} Message${message.thread.messageCount > 1 ? "s" : ""}` : "View Thread"
1116
+ },
1117
+ message.thread.lastMessage ? /* @__PURE__ */ React16.createElement(DiscordThreadMessage, { profile: message.thread.lastMessage.author.id }, /* @__PURE__ */ React16.createElement(
1118
+ MessageContent,
1119
+ {
1120
+ content: message.thread.lastMessage.content.length > 128 ? message.thread.lastMessage.content.substring(0, 125) + "..." : message.thread.lastMessage.content,
1121
+ context: { ...context, type: 1 /* REPLY */ }
1122
+ }
1123
+ )) : `Thread messages not saved.`
1124
+ )
1125
+ );
1126
+ }
1127
+
1128
+ // src/generator/transcript.tsx
1129
+ async function DiscordMessages({ messages, channel, callbacks, ...options }) {
1130
+ return /* @__PURE__ */ React17.createElement(DiscordMessagesComponent, { style: { minHeight: "100vh" } }, /* @__PURE__ */ React17.createElement("style", { dangerouslySetInnerHTML: { __html: globalStyles } }), /* @__PURE__ */ React17.createElement(
1131
+ DiscordHeader,
1132
+ {
1133
+ guild: channel.isDMBased() ? "Direct Messages" : channel.guild.name,
1134
+ channel: channel.isDMBased() ? channel.type === ChannelType2.DM ? channel.recipient?.tag ?? "Unknown Recipient" : "Unknown Recipient" : channel.name,
1135
+ icon: channel.isDMBased() ? void 0 : channel.guild.iconURL({ size: 128 }) ?? void 0
1136
+ },
1137
+ channel.isThread() ? `Thread channel in ${channel.parent?.name ?? "Unknown Channel"}` : channel.isDMBased() ? `Direct Messages` : channel.isVoiceBased() ? `Voice Text Channel for ${channel.name}` : channel.type === ChannelType2.GuildCategory ? `Category Channel` : "topic" in channel && channel.topic ? /* @__PURE__ */ React17.createElement(
1138
+ MessageContent,
1139
+ {
1140
+ content: channel.topic,
1141
+ context: { messages, channel, callbacks, type: 1 /* REPLY */, ...options }
1142
+ }
1143
+ ) : `This is the start of #${channel.name} channel.`
1144
+ ), messages.map((message) => /* @__PURE__ */ React17.createElement(DiscordMessage, { message, context: { messages, channel, callbacks, ...options }, key: message.id })), /* @__PURE__ */ React17.createElement("div", { style: { textAlign: "center", width: "100%" } }, options.footerText ? options.footerText.replaceAll("{number}", messages.length.toString()).replaceAll("{s}", messages.length > 1 ? "s" : "") : `Exported ${messages.length} message${messages.length > 1 ? "s" : ""}.`, " ", options.poweredBy ? /* @__PURE__ */ React17.createElement("span", { style: { textAlign: "center" } }, "Powered by", " ", /* @__PURE__ */ React17.createElement("a", { href: "https://github.com/devjoseh/discord2html", style: { color: "lightblue" } }, "discord2html"), ".") : null));
1145
+ }
1146
+
1147
+ // src/generator/index.tsx
1148
+ var discordComponentsVersion = "^3.6.1";
1149
+ try {
1150
+ const packagePath = path.join(__dirname, "..", "..", "package.json");
1151
+ const packageJSON = JSON.parse(readFileSync(packagePath, "utf8"));
1152
+ discordComponentsVersion = packageJSON.dependencies["@derockdev/discord-components-core"] ?? discordComponentsVersion;
1153
+ } catch {
1154
+ }
1155
+ async function render({ messages, channel, callbacks, ...options }) {
1156
+ const profiles = buildProfiles(messages);
1157
+ const { prelude } = await prerenderToNodeStream(
1158
+ /* @__PURE__ */ React18.createElement("html", null, /* @__PURE__ */ React18.createElement("head", null, /* @__PURE__ */ React18.createElement("meta", { charSet: "utf-8" }), /* @__PURE__ */ React18.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), /* @__PURE__ */ React18.createElement(
1159
+ "link",
1160
+ {
1161
+ rel: "icon",
1162
+ type: "image/png",
1163
+ href: options.favicon === "guild" ? channel.isDMBased() ? void 0 : channel.guild.iconURL({ size: 16, extension: "png" }) ?? void 0 : options.favicon
1164
+ }
1165
+ ), /* @__PURE__ */ React18.createElement("title", null, channel.isDMBased() ? "Direct Messages" : channel.name), /* @__PURE__ */ React18.createElement(
1166
+ "script",
1167
+ {
1168
+ dangerouslySetInnerHTML: {
1169
+ __html: scrollToMessage
1170
+ }
1171
+ }
1172
+ ), !options.hydrate && /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(
1173
+ "script",
1174
+ {
1175
+ dangerouslySetInnerHTML: {
1176
+ __html: `window.$discordMessage={profiles:${JSON.stringify(await profiles)}}`
1177
+ }
1178
+ }
1179
+ ), /* @__PURE__ */ React18.createElement(
1180
+ "script",
1181
+ {
1182
+ type: "module",
1183
+ src: `https://cdn.jsdelivr.net/npm/@derockdev/discord-components-core@${discordComponentsVersion}/dist/derockdev-discord-components-core/derockdev-discord-components-core.esm.js`
1184
+ }
1185
+ ))), /* @__PURE__ */ React18.createElement(
1186
+ "body",
1187
+ {
1188
+ style: {
1189
+ margin: 0,
1190
+ minHeight: "100vh"
1191
+ }
1192
+ },
1193
+ /* @__PURE__ */ React18.createElement(DiscordMessages, { messages, channel, callbacks, ...options })
1194
+ ), options.hydrate && /* @__PURE__ */ React18.createElement("script", { dangerouslySetInnerHTML: { __html: revealSpoiler } }))
1195
+ );
1196
+ const markup = await streamToString(prelude);
1197
+ if (options.hydrate) {
1198
+ const result = await renderToString(markup, {
1199
+ beforeHydrate: async (document) => {
1200
+ document.defaultView.$discordMessage = {
1201
+ profiles: await profiles
1202
+ };
1203
+ }
1204
+ });
1205
+ return result.html;
1206
+ }
1207
+ return markup;
1208
+ }
1209
+
1210
+ // src/types.ts
1211
+ var ExportReturnType = /* @__PURE__ */ ((ExportReturnType2) => {
1212
+ ExportReturnType2["Buffer"] = "buffer";
1213
+ ExportReturnType2["String"] = "string";
1214
+ ExportReturnType2["Attachment"] = "attachment";
1215
+ return ExportReturnType2;
1216
+ })(ExportReturnType || {});
1217
+
1218
+ // src/downloader/images.ts
1219
+ import { request } from "undici";
1220
+ import debug from "debug";
1221
+ var _TranscriptImageDownloader = class _TranscriptImageDownloader {
1222
+ constructor() {
1223
+ this.log = _TranscriptImageDownloader.log;
1224
+ }
1225
+ /**
1226
+ * Sets the maximum file size for *each* individual image.
1227
+ * @param size The maximum file size in kilobytes
1228
+ */
1229
+ withMaxSize(size) {
1230
+ this.maxFileSize = size;
1231
+ return this;
1232
+ }
1233
+ /**
1234
+ * Sets the compression quality for each image. This requires `sharp` to be installed.
1235
+ * Optionally, images can be converted to WebP format which is smaller in size.
1236
+ * @param quality The quality of the image (1 lowest - 100 highest). Lower quality means smaller file size.
1237
+ * @param convertToWebP Whether to convert the image to WebP format
1238
+ */
1239
+ withCompression(quality = 80, convertToWebP = false, options = {}) {
1240
+ if (quality < 1 || quality > 100) throw new Error("Quality must be between 1 and 100");
1241
+ import("./lib-V24P5ASW.mjs").catch((err) => {
1242
+ console.error(err);
1243
+ console.error(
1244
+ `[discord2html] Failed to import 'sharp'. Image compression requires the 'sharp' package to be installed. Either install sharp or remove the compression options.`
1245
+ );
1246
+ });
1247
+ this.compression = { quality, convertToWebP, options };
1248
+ return this;
1249
+ }
1250
+ /**
1251
+ * Builds the image saving callback.
1252
+ */
1253
+ build() {
1254
+ return async (attachment) => {
1255
+ if (!attachment.width || !attachment.height) return void 0;
1256
+ if (this.maxFileSize && attachment.size > this.maxFileSize * 1024) return void 0;
1257
+ this.log(`Fetching attachment ${attachment.id}: ${attachment.url}`);
1258
+ const response = await request(attachment.url).catch((err) => {
1259
+ console.error(`[discord2html] Failed to download image for transcript: `, err);
1260
+ return null;
1261
+ });
1262
+ if (!response) return void 0;
1263
+ const mimetype = response.headers["content-type"];
1264
+ const buffer = await response.body.arrayBuffer().then((res) => Buffer.from(res));
1265
+ this.log(`Finished fetching ${attachment.id} (${buffer.length} bytes)`);
1266
+ if (this.compression) {
1267
+ const sharp = await import("./lib-V24P5ASW.mjs");
1268
+ this.log(`Compressing ${attachment.id} with 'sharp'`);
1269
+ const sharpbuf = await sharp.default(buffer).webp({
1270
+ quality: this.compression.quality,
1271
+ force: this.compression.convertToWebP,
1272
+ effort: 2,
1273
+ ...this.compression.options
1274
+ }).toBuffer({ resolveWithObject: true });
1275
+ this.log(`Finished compressing ${attachment.id} (${sharpbuf.info.size} bytes)`);
1276
+ return `data:image/${sharpbuf.info.format};base64,${sharpbuf.data.toString("base64")}`;
1277
+ }
1278
+ return `data:${mimetype};base64,${buffer.toString("base64")}`;
1279
+ };
1280
+ }
1281
+ };
1282
+ _TranscriptImageDownloader.log = debug("discord2html:TranscriptImageDownloader");
1283
+ var TranscriptImageDownloader = _TranscriptImageDownloader;
1284
+
1285
+ // src/index.ts
1286
+ var versionPrefix = version.split(".")[0];
1287
+ if (versionPrefix !== "14" && versionPrefix !== "15") {
1288
+ console.error(
1289
+ `[discord2html] discord2html requires discord.js v14.x.x or v15.x.x, but you are using v${version}. For v13.x.x support, use the original package: "npm install discord-html-transcripts@^2".`
1290
+ );
1291
+ process.exit(1);
1292
+ }
1293
+ async function generateFromMessages(messages, channel, options = {}) {
1294
+ const transformedMessages = messages instanceof Collection ? Array.from(messages.values()) : messages;
1295
+ let resolveImageSrc = options.callbacks?.resolveImageSrc ?? ((attachment) => attachment.url);
1296
+ if (options.saveImages) {
1297
+ if (options.callbacks?.resolveImageSrc) {
1298
+ console.warn(
1299
+ `[discord2html] You have specified both saveImages and resolveImageSrc, please only specify one. resolveImageSrc will be used.`
1300
+ );
1301
+ } else {
1302
+ resolveImageSrc = new TranscriptImageDownloader().build();
1303
+ console.log("Using default downloader");
1304
+ }
1305
+ }
1306
+ const html = await render({
1307
+ messages: transformedMessages,
1308
+ channel,
1309
+ saveImages: options.saveImages ?? false,
1310
+ callbacks: {
1311
+ resolveImageSrc,
1312
+ resolveChannel: async (id) => channel.client.channels.fetch(id).catch(() => null),
1313
+ resolveUser: async (id) => channel.client.users.fetch(id).catch(() => null),
1314
+ resolveRole: channel.isDMBased() ? () => null : async (id) => channel.guild?.roles.fetch(id).catch(() => null),
1315
+ ...options.callbacks ?? {}
1316
+ },
1317
+ poweredBy: options.poweredBy ?? true,
1318
+ footerText: options.footerText ?? "Exported {number} message{s}.",
1319
+ favicon: options.favicon ?? "guild",
1320
+ hydrate: options.hydrate ?? false
1321
+ });
1322
+ if (options.returnType === "buffer" /* Buffer */) {
1323
+ return Buffer.from(html);
1324
+ }
1325
+ if (options.returnType === "string" /* String */) {
1326
+ return html;
1327
+ }
1328
+ return new AttachmentBuilder(Buffer.from(html), {
1329
+ name: options.filename ?? `transcript-${channel.id}.html`
1330
+ });
1331
+ }
1332
+ async function createTranscript(channel, options = {}) {
1333
+ if (!channel.isTextBased()) {
1334
+ throw new TypeError(`Provided channel must be text-based, received ${channel.type}`);
1335
+ }
1336
+ let allMessages = [];
1337
+ let lastMessageId;
1338
+ const { limit, filter } = options;
1339
+ const resolvedLimit = typeof limit === "undefined" || limit === -1 ? Infinity : limit;
1340
+ while (true) {
1341
+ const fetchLimitOptions = { limit: 100, before: lastMessageId };
1342
+ if (!lastMessageId) delete fetchLimitOptions.before;
1343
+ const messages = await channel.messages.fetch(fetchLimitOptions);
1344
+ const filteredMessages = typeof filter === "function" ? messages.filter(filter) : messages;
1345
+ allMessages.push(...filteredMessages.values());
1346
+ lastMessageId = messages.lastKey();
1347
+ if (messages.size < 100) break;
1348
+ if (allMessages.length >= resolvedLimit) break;
1349
+ }
1350
+ if (resolvedLimit < allMessages.length) allMessages = allMessages.slice(0, limit);
1351
+ return generateFromMessages(allMessages.reverse(), channel, options);
1352
+ }
1353
+ var index_default = {
1354
+ createTranscript,
1355
+ generateFromMessages
1356
+ };
1357
+ export {
1358
+ DiscordMessages,
1359
+ ExportReturnType,
1360
+ TranscriptImageDownloader,
1361
+ createTranscript,
1362
+ index_default as default,
1363
+ generateFromMessages
1364
+ };
1365
+ //# sourceMappingURL=index.mjs.map