discord-message-transcript 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/LICENSE +201 -0
- package/dist/core/clientManager.d.ts +3 -0
- package/dist/core/clientManager.js +9 -0
- package/dist/core/componentHelpers.d.ts +3 -0
- package/dist/core/componentHelpers.js +175 -0
- package/dist/core/componentToJson.d.ts +3 -0
- package/dist/core/componentToJson.js +174 -0
- package/dist/core/error.d.ts +3 -0
- package/dist/core/error.js +7 -0
- package/dist/core/fetchMessages.d.ts +7 -0
- package/dist/core/fetchMessages.js +169 -0
- package/dist/core/getMentions.d.ts +3 -0
- package/dist/core/getMentions.js +99 -0
- package/dist/core/imageToBase64.d.ts +1 -0
- package/dist/core/imageToBase64.js +30 -0
- package/dist/core/mappers.d.ts +8 -0
- package/dist/core/mappers.js +101 -0
- package/dist/core/markdown.d.ts +2 -0
- package/dist/core/markdown.js +175 -0
- package/dist/core/output.d.ts +4 -0
- package/dist/core/output.js +28 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +120 -0
- package/dist/renderers/html/clientRenderer.d.ts +0 -0
- package/dist/renderers/html/clientRenderer.js +73 -0
- package/dist/renderers/html/css.d.ts +11 -0
- package/dist/renderers/html/css.js +663 -0
- package/dist/renderers/html/html copy.d.ts +19 -0
- package/dist/renderers/html/html copy.js +371 -0
- package/dist/renderers/html/html-backup.d.ts +19 -0
- package/dist/renderers/html/html-backup.js +371 -0
- package/dist/renderers/html/html.d.ts +19 -0
- package/dist/renderers/html/html.js +415 -0
- package/dist/renderers/html/html2.d.ts +8 -0
- package/dist/renderers/html/html2.js +233 -0
- package/dist/renderers/html/js.d.ts +4 -0
- package/dist/renderers/html/js.js +174 -0
- package/dist/renderers/json/json.d.ts +16 -0
- package/dist/renderers/json/json.js +55 -0
- package/dist/types/types copy.d.ts +284 -0
- package/dist/types/types copy.js +35 -0
- package/dist/types/types.d.ts +137 -0
- package/dist/types/types.js +7 -0
- package/package.json +45 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { CustomError } from "../../core/error";
|
|
2
|
+
import { markdownToHTML } from "../../core/markdown";
|
|
3
|
+
import { JsonButtonStyle, JsonComponentType } from "../../types/types";
|
|
4
|
+
import { ACTIONROW_CSS, ATTACHMENT_CSS, BUTTON_CSS, COMPONENTS_CSS, COMPONENTSV2_CSS, DEFAULT_CSS, EMBED_CSS, MESSAGE_CSS, POLL_CSS, POLL_RESULT_EMBED_CSS, REACTIONS_CSS } from "./css";
|
|
5
|
+
import { script } from "./js";
|
|
6
|
+
const COUNT_UNIT = ["KB", "MB", "GB", "TB"];
|
|
7
|
+
const BUTTON_COLOR = ["black", "#5865f2", "gray", "lime", "red", "black", "#5865f2"];
|
|
8
|
+
export class Html {
|
|
9
|
+
data;
|
|
10
|
+
dateFormat;
|
|
11
|
+
constructor(data) {
|
|
12
|
+
this.data = data;
|
|
13
|
+
try {
|
|
14
|
+
this.dateFormat = new Intl.DateTimeFormat(data.options.localDate, {
|
|
15
|
+
timeZone: data.options.timeZone,
|
|
16
|
+
day: '2-digit',
|
|
17
|
+
month: '2-digit',
|
|
18
|
+
year: 'numeric',
|
|
19
|
+
hour: '2-digit',
|
|
20
|
+
minute: '2-digit',
|
|
21
|
+
second: '2-digit'
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new CustomError("[discord-message-transcript] Invalid LocalDate and/or TimeZone.");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
headerBuilder() {
|
|
29
|
+
const { channel, guild } = this.data;
|
|
30
|
+
return `
|
|
31
|
+
<div style="display: flex; gap: 1.5rem; align-items: center; width 100vw">
|
|
32
|
+
${channel.img ? `<img src="${channel.img}" style="width: 7rem; height: 7rem; border-radius: 50%;">` : ""}
|
|
33
|
+
<div style="display: flex; flex-direction: column; justify-content: center; gap: 1.25rem;">
|
|
34
|
+
${guild ? `<div id="guild" class="line">
|
|
35
|
+
<h4>Guild: </h4>
|
|
36
|
+
<h4 style="font-weight: normal;">${guild.name}</h4>
|
|
37
|
+
</div>` : ""}
|
|
38
|
+
${channel.parent ? `<div id="category" class="line">
|
|
39
|
+
<h4>Category: </h4>
|
|
40
|
+
<h4 style="font-weight: normal;">${channel.parent.name}</h4>
|
|
41
|
+
</div>` : ""}
|
|
42
|
+
<div id="channel" class="line">
|
|
43
|
+
<h4>Channel: </h4>
|
|
44
|
+
<h4 style="font-weight: normal;">${channel.name}</h4>
|
|
45
|
+
</div>
|
|
46
|
+
${channel.topic ? `<div id="topic" class="line">
|
|
47
|
+
<h4>Topic: </h4>
|
|
48
|
+
<h4 style="font-weight: normal;">${channel.topic}</h4>
|
|
49
|
+
</div>` : ""}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
messagesBuilder() {
|
|
55
|
+
return this.data.messages.map(message => {
|
|
56
|
+
const date = new Date(message.createdTimestamp);
|
|
57
|
+
return `
|
|
58
|
+
<div class="messageDiv" id="${message.id}" data-author-id="${message.authorId}">
|
|
59
|
+
${message.references && message.references.messageId ?
|
|
60
|
+
`<div class="messageReply" data-id="${message.references.messageId}">
|
|
61
|
+
<svg class="messageReplySvg"><use href="#reply-icon"></use></svg>
|
|
62
|
+
<img class="messageReplyImg" src="">
|
|
63
|
+
<div class="replyBadges"></div>
|
|
64
|
+
<div class="messageReplyText"></div>
|
|
65
|
+
</div>` : ""}
|
|
66
|
+
<div class="messageBotton">
|
|
67
|
+
<img src="" class="messageImg">
|
|
68
|
+
<div class="messageDivRight">
|
|
69
|
+
<div class="messageUser">
|
|
70
|
+
<h3 class="messageUsername"></h3>
|
|
71
|
+
<div class="badges"></div>
|
|
72
|
+
<p class="messageTimeStamp">${this.dateFormat.format(date)}</p>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="messageContent">${markdownToHTML(message.content, message.mentions, this.dateFormat)}</div>
|
|
75
|
+
${message.poll ? this.pollBuilder(message.poll) : ""}
|
|
76
|
+
${message.embeds.length > 0 ? this.embedBuilder(message, message.embeds) : ""}
|
|
77
|
+
${message.attachments.length > 0 ? this.attachmentBuilder(message.attachments) : ""}
|
|
78
|
+
${message.components.length > 0 ? this.componentBuilder(message, message.components) : ""}
|
|
79
|
+
${message.reactions.length > 0 ? this.reactionsBuilder(message.reactions) : ""}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
}).join("");
|
|
85
|
+
}
|
|
86
|
+
toHTML() {
|
|
87
|
+
const { options } = this.data;
|
|
88
|
+
return `
|
|
89
|
+
<!DOCTYPE html>
|
|
90
|
+
<html lang="pt-BR">
|
|
91
|
+
<head>
|
|
92
|
+
<meta charset="UTF-8" />
|
|
93
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
94
|
+
<title>${options.fileName}</title>
|
|
95
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/atom-one-dark.min.css">
|
|
96
|
+
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script>
|
|
97
|
+
<style>
|
|
98
|
+
${DEFAULT_CSS}
|
|
99
|
+
${MESSAGE_CSS}
|
|
100
|
+
${options.includePolls ? POLL_CSS + POLL_RESULT_EMBED_CSS : ""}
|
|
101
|
+
${options.includeEmbeds ? EMBED_CSS : ""}
|
|
102
|
+
${options.includeButtons || options.includeComponents ? ACTIONROW_CSS : ""}
|
|
103
|
+
${options.includeAttachments || options.includeV2Components ? ATTACHMENT_CSS : ""}
|
|
104
|
+
${options.includeButtons || options.includeV2Components ? BUTTON_CSS : ""}
|
|
105
|
+
${options.includeComponents ? COMPONENTS_CSS : ""}
|
|
106
|
+
${options.includeV2Components ? COMPONENTSV2_CSS : ""}
|
|
107
|
+
${options.includeReactions ? REACTIONS_CSS : ""}
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
${this.svgBuilder()}
|
|
112
|
+
<header>
|
|
113
|
+
${this.headerBuilder()}
|
|
114
|
+
</header>
|
|
115
|
+
<main style="display: flex; flex-direction: column; padding: 2.25%;">
|
|
116
|
+
${this.messagesBuilder()}
|
|
117
|
+
</main>
|
|
118
|
+
<footer>
|
|
119
|
+
<br>
|
|
120
|
+
<div style="padding: 1rem 0; font-weight: 700; text-align: center; font-size: 1.5rem; background-color: #2b2d31;">Transcript generated by <a href="https://github.com/HenriqueMairesse/discord-channel-transcript">discord-channel-transcript</a></div>
|
|
121
|
+
</footer>
|
|
122
|
+
<script id="authorData" type="application/json">
|
|
123
|
+
${JSON.stringify({ authors: this.data.authors })}
|
|
124
|
+
</script>
|
|
125
|
+
<script>
|
|
126
|
+
${script(options)}
|
|
127
|
+
</script>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
pollBuilder(poll) {
|
|
133
|
+
const totalVotes = poll.answers.reduce((acc, answer) => acc + answer.count, 0);
|
|
134
|
+
let footerText = `${totalVotes} votes`;
|
|
135
|
+
if (poll.isFinalized) {
|
|
136
|
+
footerText = `Final results • ${totalVotes} votes`;
|
|
137
|
+
}
|
|
138
|
+
else if (poll.expiry) {
|
|
139
|
+
footerText += ` • Ends on ${this.dateFormat.format(new Date(poll.expiry))}`;
|
|
140
|
+
}
|
|
141
|
+
return `
|
|
142
|
+
<div class="pollDiv">
|
|
143
|
+
<div class="pollQuestion">${poll.question}</div>
|
|
144
|
+
<div class="pollAnswers">
|
|
145
|
+
${poll.answers.map(answer => {
|
|
146
|
+
const voteCount = answer.count;
|
|
147
|
+
const percentage = totalVotes > 0 ? (voteCount / totalVotes) * 100 : 0;
|
|
148
|
+
return `
|
|
149
|
+
<div class="pollAnswer">
|
|
150
|
+
<div class="pollAnswerBar" style="width: ${percentage}%;"></div>
|
|
151
|
+
<div class="pollAnswerContent">
|
|
152
|
+
<div class="pollAnswerDetails">
|
|
153
|
+
${answer.emoji ? `<div class="pollAnswerEmoji">${answer.emoji.name}</div>` : ''}
|
|
154
|
+
<div class="pollAnswerText">${answer.text}</div>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="pollAnswerVotes">${voteCount}</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
`;
|
|
160
|
+
}).join('')}
|
|
161
|
+
</div>
|
|
162
|
+
<div class="pollFooter">${footerText}</div>
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
pollResultEmbedBuilder(embed, message) {
|
|
167
|
+
const getField = (name) => embed.fields?.find(f => f.name === name)?.value;
|
|
168
|
+
const winnerText = getField("victor_answer_text");
|
|
169
|
+
const winnerVotes = parseInt(getField("victor_answer_votes") ?? "0");
|
|
170
|
+
const totalVotes = parseInt(getField("total_votes") ?? "0");
|
|
171
|
+
const winnerPercentage = totalVotes > 0 ? (winnerVotes / totalVotes) * 100 : 0;
|
|
172
|
+
if (!winnerText)
|
|
173
|
+
return '';
|
|
174
|
+
return `
|
|
175
|
+
<div class="pollResultEmbed">
|
|
176
|
+
<div>
|
|
177
|
+
<div class="pollResultEmbedWinner">
|
|
178
|
+
<span>${winnerText}</span>
|
|
179
|
+
<span class="pollResultEmbedCheckmark">✔</span>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="pollResultEmbedSubtitle">${totalVotes} votes (${winnerPercentage.toFixed(1)}%)</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="pollResultEmbedButtonDiv">
|
|
184
|
+
<div data-id="${message.references?.messageId ?? ""}" class="pollResultEmbedButton">View Poll</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
embedBuilder(message, embeds) {
|
|
190
|
+
return embeds.map(embed => {
|
|
191
|
+
if (embed.type === 'poll_result')
|
|
192
|
+
return this.data.options.includePolls ? this.pollResultEmbedBuilder(embed, message) : "";
|
|
193
|
+
const embedAuthor = embed.author ? (embed.author.url ? `<a class="embedHeaderRightAuthorName" href="${embed.author.url}" target="_blank">${embed.author.name}</a>` : `<p class="embedHeaderRightAuthorName">${embed.author.name}</p>`) : "";
|
|
194
|
+
const embedTitle = embed.title ? (embed.url ? `<a class="embedHeaderRightTitle" href="${embed.url}" target="_blank">${embed.title}</a>` : `<p class="embedHeaderRightTitle">${embed.title}</p>`) : "";
|
|
195
|
+
return `
|
|
196
|
+
<div class="embed" style="${embed.hexColor ? `border-left-color: ${embed.hexColor}` : ''}">
|
|
197
|
+
${embed.author || embed.title || embed.thumbnail ? `
|
|
198
|
+
<div class="embedHeader">
|
|
199
|
+
<div class="embedHeaderRight">
|
|
200
|
+
${embed.author ? `
|
|
201
|
+
<div class="embedHeaderRightAuthor">
|
|
202
|
+
${embed.author.iconURL ? `<img class="embedHeaderRightAuthorImg" src="${embed.author.iconURL}">` : ""}
|
|
203
|
+
${embedAuthor}
|
|
204
|
+
</div>` : ""}
|
|
205
|
+
${embedTitle}
|
|
206
|
+
</div>
|
|
207
|
+
${embed.thumbnail ? `<img class="embedHeaderThumbnail" src="${embed.thumbnail.url}">` : ""}
|
|
208
|
+
</div>` : ""}
|
|
209
|
+
${embed.description ? `<div class="embedDescription">${markdownToHTML(embed.description, message.mentions, this.dateFormat)}</div>` : ""}
|
|
210
|
+
${embed.fields && embed.fields.length > 0 ? `
|
|
211
|
+
<div class="embedFields">
|
|
212
|
+
${embed.fields.map(field => `
|
|
213
|
+
<div class="embedFieldsField" style="${field.inline ? 'display: inline-block;' : ''}">
|
|
214
|
+
<p class="embedFieldsFieldTitle">${field.name}</p>
|
|
215
|
+
<p class="embedFieldsFieldValue">${markdownToHTML(field.value, message.mentions, this.dateFormat)}</p>
|
|
216
|
+
</div>`).join("")}
|
|
217
|
+
</div>` : ""}
|
|
218
|
+
${embed.image ? `
|
|
219
|
+
<div class="embedImage">
|
|
220
|
+
<img src="${embed.image.url}">
|
|
221
|
+
</div>` : ""}
|
|
222
|
+
${embed.footer || embed.timestamp ? `
|
|
223
|
+
<div class="embedFooter">
|
|
224
|
+
${embed.footer?.iconURL ? `<img class="embedFooterImg" src="${embed.footer.iconURL}">` : ""}
|
|
225
|
+
${embed.footer?.text || embed.timestamp ? `<p class="embedFooterText">${embed.footer?.text ?? ''}${embed.footer?.text && embed.timestamp ? ' | ' : ''}${embed.timestamp ? this.dateFormat.format(new Date(embed.timestamp)) : ''}</p>` : ""}
|
|
226
|
+
</div>` : ""}
|
|
227
|
+
</div>
|
|
228
|
+
`;
|
|
229
|
+
}).join("");
|
|
230
|
+
}
|
|
231
|
+
attachmentBuilder(attachments) {
|
|
232
|
+
return attachments.map(attachment => {
|
|
233
|
+
let html = "";
|
|
234
|
+
if (attachment.contentType?.startsWith('image/')) {
|
|
235
|
+
html = `<img class="attachmentImage" src="${attachment.url}">`;
|
|
236
|
+
}
|
|
237
|
+
else if (attachment.contentType?.startsWith('video/')) {
|
|
238
|
+
html = `<video class="attachmentVideo" controls src="${attachment.url}"></video>`;
|
|
239
|
+
}
|
|
240
|
+
else if (attachment.contentType?.startsWith('audio/')) {
|
|
241
|
+
html = `<audio class="attachmentAudio" controls src="${attachment.url}"></audio>`;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
let fileSize = attachment.size / 1024;
|
|
245
|
+
let count = 0;
|
|
246
|
+
while (fileSize > 512 && count < COUNT_UNIT.length - 1) {
|
|
247
|
+
fileSize = fileSize / 1024;
|
|
248
|
+
count++;
|
|
249
|
+
}
|
|
250
|
+
html = `
|
|
251
|
+
<div class="attachmentFile">
|
|
252
|
+
<div class="attachmentFileInfo">
|
|
253
|
+
<p class="attachmentFileName">${attachment.name ?? 'attachment'}</p>
|
|
254
|
+
<div class="attachmentFileSize">${fileSize.toFixed(2)} ${COUNT_UNIT[count]}</div>
|
|
255
|
+
</div>
|
|
256
|
+
<a class="attachmentDownload" href="${attachment.url}" target="_blank">
|
|
257
|
+
<svg class="attachmentDownloadIcon"><use href="#download-icon"></use></svg>
|
|
258
|
+
</a>
|
|
259
|
+
</div>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
return this.spoilerAttachmentBuilder(attachment.spoiler, html);
|
|
263
|
+
}).join("");
|
|
264
|
+
}
|
|
265
|
+
componentBuilder(message, components) {
|
|
266
|
+
return components.map(component => {
|
|
267
|
+
switch (component.type) {
|
|
268
|
+
case JsonComponentType.ActionRow: {
|
|
269
|
+
if (!component.components[0])
|
|
270
|
+
return "";
|
|
271
|
+
return `
|
|
272
|
+
<div class="actionRow">
|
|
273
|
+
${component.components[0].type == JsonComponentType.Button ? component.components.map(button => {
|
|
274
|
+
return button.type == JsonComponentType.Button ? this.buttonBuilder(button) : "";
|
|
275
|
+
}).join("") : this.selectorBuilder(component.components[0])}
|
|
276
|
+
</div>
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
case JsonComponentType.Container: {
|
|
280
|
+
const html = `
|
|
281
|
+
<div class="container" style="${component.hexAccentColor ? `border-left-color: ${component.hexAccentColor}` : ''}">
|
|
282
|
+
${this.componentBuilder(message, component.components)}
|
|
283
|
+
</div>
|
|
284
|
+
`;
|
|
285
|
+
return this.spoilerAttachmentBuilder(component.spoiler, html);
|
|
286
|
+
}
|
|
287
|
+
case JsonComponentType.File: {
|
|
288
|
+
let fileSize = (component.size ?? 0) / 1024;
|
|
289
|
+
let count = 0;
|
|
290
|
+
while (fileSize > 512 && count < COUNT_UNIT.length - 1) {
|
|
291
|
+
fileSize = fileSize / 1024;
|
|
292
|
+
count++;
|
|
293
|
+
}
|
|
294
|
+
const html = `
|
|
295
|
+
<div class="attachmentFile">
|
|
296
|
+
<div class="attachmentFileInfo">
|
|
297
|
+
<p class="attachmentFileName">${component.fileName ?? 'file'}</p>
|
|
298
|
+
<div class="attachmentFileSize">${fileSize.toFixed(2)} ${COUNT_UNIT[count]}</div>
|
|
299
|
+
</div>
|
|
300
|
+
<a class="attachmentDownload" href="${component.url ?? ''}" target="_blank">
|
|
301
|
+
<svg class="attachmentDownloadIcon"><use href="#download-icon"></use></svg>
|
|
302
|
+
</a>
|
|
303
|
+
</div>
|
|
304
|
+
`;
|
|
305
|
+
return this.spoilerAttachmentBuilder(component.spoiler, html);
|
|
306
|
+
}
|
|
307
|
+
case JsonComponentType.MediaGallery: {
|
|
308
|
+
return `
|
|
309
|
+
<div class="mediaGallery">
|
|
310
|
+
${component.items.map(image => {
|
|
311
|
+
return `
|
|
312
|
+
<div class="mediaGalleryItem">
|
|
313
|
+
${this.spoilerAttachmentBuilder(image.spoiler, `<img class="mediaGalleryImg" src="${image.media.url}">`)}
|
|
314
|
+
</div>
|
|
315
|
+
`;
|
|
316
|
+
}).join("")}
|
|
317
|
+
</div>
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
case JsonComponentType.Section: {
|
|
321
|
+
return `
|
|
322
|
+
<div class="section">
|
|
323
|
+
<div class="sectionLeft">
|
|
324
|
+
${this.componentBuilder(message, component.components)}
|
|
325
|
+
</div>
|
|
326
|
+
<div class="sectionRight">
|
|
327
|
+
${component.accessory.type == JsonComponentType.Button ? this.buttonBuilder(component.accessory)
|
|
328
|
+
: component.accessory.type == JsonComponentType.Thumbnail ? this.spoilerAttachmentBuilder(component.accessory.spoiler, `
|
|
329
|
+
<img class="sectionThumbnail" src="${component.accessory.media.url}">
|
|
330
|
+
`) : ""}
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
`;
|
|
334
|
+
}
|
|
335
|
+
case JsonComponentType.Separator: {
|
|
336
|
+
return `<hr>`;
|
|
337
|
+
}
|
|
338
|
+
case JsonComponentType.TextDisplay: {
|
|
339
|
+
return markdownToHTML(component.content, message.mentions, this.dateFormat);
|
|
340
|
+
}
|
|
341
|
+
default:
|
|
342
|
+
return ``;
|
|
343
|
+
}
|
|
344
|
+
}).join("");
|
|
345
|
+
}
|
|
346
|
+
buttonBuilder(button) {
|
|
347
|
+
return `
|
|
348
|
+
<div class="button" style="background-color: ${BUTTON_COLOR[button.style]}">
|
|
349
|
+
${button.style == JsonButtonStyle.Link ? `
|
|
350
|
+
<a class="buttonLink" href="${button.url}" target="_blank">
|
|
351
|
+
${button.emoji ? `<p class="buttonEmoji">${button.emoji}</p>` : ""}
|
|
352
|
+
${button.label ? `<p class="buttonLabel">${button.label}</p>` : ""}
|
|
353
|
+
<svg class="buttonLinkIcon"><use href="#link-icon"></use></svg>
|
|
354
|
+
</a>` : `
|
|
355
|
+
${button.emoji ? `<p class="buttonEmoji">${button.emoji}</p>` : ""}
|
|
356
|
+
${button.label ? `<p class="buttonLabel">${button.label}</p>` : ""}
|
|
357
|
+
`}
|
|
358
|
+
</div>
|
|
359
|
+
`;
|
|
360
|
+
}
|
|
361
|
+
selectorBuilder(selector) {
|
|
362
|
+
return `
|
|
363
|
+
<div class="selector">
|
|
364
|
+
<div class="selectorInput">
|
|
365
|
+
<p class="selectorInputText">${selector.placeholder}</p>
|
|
366
|
+
</div>
|
|
367
|
+
<div class="selectorOptionMenu">
|
|
368
|
+
${selector.type == JsonComponentType.StringSelect ? selector.options.map(option => {
|
|
369
|
+
return `
|
|
370
|
+
<div class="selectorOption">
|
|
371
|
+
${option.emoji ? `<p class="selectorOptionEmoji">${option.emoji}</p>` : ""}
|
|
372
|
+
<div class="selectorOptionRight">
|
|
373
|
+
<p class="selectorOptionTitle">${option.label}</p>
|
|
374
|
+
${option.description ? `<p class="selectorOptionDesc">${option.description}</p>` : ""}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
`;
|
|
378
|
+
}).join("") : ""}
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
reactionsBuilder(reactions) {
|
|
384
|
+
return `
|
|
385
|
+
<div class="reactionsDiv">
|
|
386
|
+
${reactions.map(reaction => `
|
|
387
|
+
<div class="reaction"><p>${reaction.count} ${reaction.emoji}</p></div>
|
|
388
|
+
`).join('')}
|
|
389
|
+
</div>
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
spoilerAttachmentBuilder(spoiler, html) {
|
|
393
|
+
return spoiler ? `<div class="spoilerAttachment"><div class="spoilerAttachmentOverlay">SPOILER</div><div class="spoilerAttachmentContent">${html}</div></div>` : html;
|
|
394
|
+
}
|
|
395
|
+
svgBuilder() {
|
|
396
|
+
const { options } = this.data;
|
|
397
|
+
return `
|
|
398
|
+
<svg style="display: none;">
|
|
399
|
+
<defs>
|
|
400
|
+
<symbol id="reply-icon" viewBox="0 0 16 16" fill="none">
|
|
401
|
+
<g transform="rotate(90 8 8)">
|
|
402
|
+
<path d="M6 2V9C6 11.5 8.5 14 11 14H14" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
403
|
+
</g>
|
|
404
|
+
</symbol>
|
|
405
|
+
${options.includeAttachments ? `<symbol id="download-icon" viewBox="0 -960 960 960">
|
|
406
|
+
<path d="m720-120 160-160-56-56-64 64v-167h-80v167l-64-64-56 56 160 160ZM560 0v-80h320V0H560ZM240-160q-33 0-56.5-23.5T160-240v-560q0-33 23.5-56.5T240-880h280l240 240v121h-80v-81H480v-200H240v560h240v80H240Zm0-80v-560 560Z"/>
|
|
407
|
+
</symbol> ` : ""}
|
|
408
|
+
${options.includeButtons ? `<symbol id="link-icon" viewBox="0 -960 960 960" fill="#e3e3e3">
|
|
409
|
+
<path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"/>
|
|
410
|
+
</symbol>` : ""}
|
|
411
|
+
</defs>
|
|
412
|
+
</svg>
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { ACTIONROW_CSS, ATTACHMENT_CSS, BUTTON_CSS, COMPONENTS_CSS, COMPONENTSV2_CSS, DEFAULT_CSS, EMBED_CSS, MESSAGE_CSS } from "./css";
|
|
2
|
+
const clientRendererJs = `
|
|
3
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
4
|
+
const messagesContainer = document.getElementById('messages-container');
|
|
5
|
+
const messageTemplate = document.getElementById('message-template');
|
|
6
|
+
const transcriptDataElement = document.getElementById('transcript-data');
|
|
7
|
+
|
|
8
|
+
if (!messagesContainer || !messageTemplate || !transcriptDataElement) {
|
|
9
|
+
console.error('Missing required elements for rendering transcript.');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const data = JSON.parse(transcriptDataElement.textContent);
|
|
14
|
+
const authorMap = new Map(data.authors.map(author => [author.id, author]));
|
|
15
|
+
|
|
16
|
+
const fragment = new DocumentFragment();
|
|
17
|
+
|
|
18
|
+
data.messages.forEach(message => {
|
|
19
|
+
const author = authorMap.get(message.authorId);
|
|
20
|
+
if (!author) {
|
|
21
|
+
console.warn(`, Author, not, found;
|
|
22
|
+
for (message; $; { message, : .id } `);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const clone = messageTemplate.content.cloneNode(true);
|
|
27
|
+
|
|
28
|
+
// Populate common message elements
|
|
29
|
+
const messageDiv = clone.querySelector('.messageDiv');
|
|
30
|
+
if (messageDiv) messageDiv.id = message.id;
|
|
31
|
+
|
|
32
|
+
const avatarImg = clone.querySelector('.messageImg');
|
|
33
|
+
if (avatarImg) avatarImg.src = author.avatarURL;
|
|
34
|
+
|
|
35
|
+
const usernameH3 = clone.querySelector('.messageUsername');
|
|
36
|
+
if (usernameH3) {
|
|
37
|
+
usernameH3.textContent = author.member?.displayName ?? author.displayName;
|
|
38
|
+
usernameH3.style.color = author.member?.displayHexColor ?? '#dbdee1';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const badgesDiv = clone.querySelector('.badges'); // Assuming a container for badges
|
|
42
|
+
if (badgesDiv) {
|
|
43
|
+
if (author.bot) {
|
|
44
|
+
const badge = document.createElement('p');
|
|
45
|
+
badge.className = 'badge';
|
|
46
|
+
badge.textContent = 'APP';
|
|
47
|
+
badgesDiv.appendChild(badge);
|
|
48
|
+
}
|
|
49
|
+
if (author.system) {
|
|
50
|
+
const badge = document.createElement('p');
|
|
51
|
+
badge.className = 'badge';
|
|
52
|
+
badge.textContent = 'SYSTEM';
|
|
53
|
+
badgesDiv.appendChild(badge);
|
|
54
|
+
}
|
|
55
|
+
if (author.guildTag) {
|
|
56
|
+
const badge = document.createElement('p');
|
|
57
|
+
badge.className = 'badgeTag';
|
|
58
|
+
badge.textContent = author.guildTag;
|
|
59
|
+
badgesDiv.appendChild(badge);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const messageTimestamp = clone.querySelector('.messageTimeStamp');
|
|
64
|
+
if (messageTimestamp) {
|
|
65
|
+
// Use the client's locale for date formatting
|
|
66
|
+
messageTimestamp.textContent = new Date(message.createdTimestamp).toLocaleString(data.options.localDate, {
|
|
67
|
+
day: '2-digit',
|
|
68
|
+
month: '2-digit',
|
|
69
|
+
year: 'numeric',
|
|
70
|
+
hour: '2-digit',
|
|
71
|
+
minute: '2-digit',
|
|
72
|
+
second: '2-digit',
|
|
73
|
+
timeZone: data.options.timeZone // Use the provided timezone
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const messageContentDiv = clone.querySelector('.messageContent');
|
|
78
|
+
if (messageContentDiv) messageContentDiv.innerHTML = message.content;
|
|
79
|
+
|
|
80
|
+
// Handle references/replies
|
|
81
|
+
const messageReplyDiv = clone.querySelector('.messageReply');
|
|
82
|
+
if (messageReplyDiv && message.references && message.references.messageId) {
|
|
83
|
+
messageReplyDiv.dataset.id = message.references.messageId;
|
|
84
|
+
// Additional logic for reply avatar/name can be added here if needed,
|
|
85
|
+
// requires looking up the replied message's author. For simplicity,
|
|
86
|
+
// we'll leave it as just the ID for now.
|
|
87
|
+
} else if (messageReplyDiv) {
|
|
88
|
+
messageReplyDiv.remove(); // Remove if no reply
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// TODO: Implement logic for embeds, attachments, and components based on data.options and message properties
|
|
92
|
+
// This will be more complex and might require sub-templates or helper functions
|
|
93
|
+
// For now, these sections might remain empty or be removed if not present in message.
|
|
94
|
+
|
|
95
|
+
fragment.appendChild(clone);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
messagesContainer.appendChild(fragment);
|
|
99
|
+
|
|
100
|
+
// Initial highlighting
|
|
101
|
+
if (window.hljs) {
|
|
102
|
+
hljs.highlightAll();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Helper functions for embeds, attachments, components would go here if needed.
|
|
107
|
+
// For instance:
|
|
108
|
+
/*
|
|
109
|
+
function renderEmbeds(embeds, messageMentions, dateFormat) {
|
|
110
|
+
// ... logic to build embed HTML
|
|
111
|
+
}
|
|
112
|
+
*/
|
|
113
|
+
`)
|
|
114
|
+
;
|
|
115
|
+
export class Html {
|
|
116
|
+
data;
|
|
117
|
+
constructor(data) {
|
|
118
|
+
this.data = data;
|
|
119
|
+
}
|
|
120
|
+
headerBuilder() {
|
|
121
|
+
const { channel, guild } = this.data;
|
|
122
|
+
return `
|
|
123
|
+
<div style="display: flex; gap: 1.5rem; align-items: center;">
|
|
124
|
+
${channel.img ? `<img src="${channel.img}" style="width: 7rem; height: 7rem; border-radius: 50%;">` : ""}
|
|
125
|
+
<div style="display: flex; flex-direction: column; justify-content: center; gap: 1.25rem;">
|
|
126
|
+
${guild ? `<div id="guild" class="line">
|
|
127
|
+
<h4>Guild: </h4>
|
|
128
|
+
<h4 style="font-weight: normal;">${guild.name}</h4>
|
|
129
|
+
</div>` : ""}
|
|
130
|
+
${channel.parent ? `<div id="category" class="line">
|
|
131
|
+
<h4>Category: </h4>
|
|
132
|
+
<h4 style="font-weight: normal;">${channel.parent.name}</h4>
|
|
133
|
+
</div>` : ""}
|
|
134
|
+
<div id="channel" class="line">
|
|
135
|
+
<h4>Channel: </h4>
|
|
136
|
+
<h4 style="font-weight: normal;">${channel.name}</h4>
|
|
137
|
+
</div>
|
|
138
|
+
${channel.topic ? `<div id="topic" class="line">
|
|
139
|
+
<h4>Topic: </h4>
|
|
140
|
+
<h4 style="font-weight: normal;">${channel.topic}</h4>
|
|
141
|
+
</div>` : ""}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
toHTML() {
|
|
147
|
+
const { options } = this.data;
|
|
148
|
+
return `
|
|
149
|
+
<!DOCTYPE html>
|
|
150
|
+
<html lang="pt-BR">
|
|
151
|
+
<head>
|
|
152
|
+
<meta charset="UTF-8" />
|
|
153
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
154
|
+
<title>${options.fileName}</title>
|
|
155
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/atom-one-dark.min.css">
|
|
156
|
+
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script>
|
|
157
|
+
<style>
|
|
158
|
+
${DEFAULT_CSS}
|
|
159
|
+
${MESSAGE_CSS}
|
|
160
|
+
${options.includeEmbeds ? EMBED_CSS : ""}
|
|
161
|
+
${options.includeButtons || options.includeComponents ? ACTIONROW_CSS : ""}
|
|
162
|
+
${options.includeAttachments || options.includeV2Components ? ATTACHMENT_CSS : ""}
|
|
163
|
+
${options.includeButtons || options.includeV2Components ? BUTTON_CSS : ""}
|
|
164
|
+
${options.includeComponents ? COMPONENTS_CSS : ""}
|
|
165
|
+
${options.includeV2Components ? COMPONENTSV2_CSS : ""}
|
|
166
|
+
</style>
|
|
167
|
+
</head>
|
|
168
|
+
<body>
|
|
169
|
+
${this.svgBuilder()}
|
|
170
|
+
<header>
|
|
171
|
+
${this.headerBuilder()}
|
|
172
|
+
</header>
|
|
173
|
+
<main id="messages-container" style="display: flex; flex-direction: column; gap: 1.75rem; padding: 2.25rem;"></main>
|
|
174
|
+
<footer>
|
|
175
|
+
<br>
|
|
176
|
+
<h2>Transcript generated by <a href="https://github.com/HenriqueMairesse/discord-channel-transcript">discord-channel-transcript</a></h2>
|
|
177
|
+
</footer>
|
|
178
|
+
|
|
179
|
+
<!-- MESSAGE TEMPLATE -->
|
|
180
|
+
<template id="message-template">
|
|
181
|
+
<div class="messageDiv">
|
|
182
|
+
<div class="messageBotton">
|
|
183
|
+
<img src="" class="messageImg">
|
|
184
|
+
<div class="messageDivRight">
|
|
185
|
+
<div class="messageUser">
|
|
186
|
+
<h3 class="messageUsername"></h3>
|
|
187
|
+
<div class="badges"></div>
|
|
188
|
+
<p class="messageTimeStamp"></p>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="messageContent"></div>
|
|
191
|
+
<!-- Placeholders for embeds, attachments, components will be handled by client-side logic -->
|
|
192
|
+
<!-- Example: <div class="embeds-container"></div> -->
|
|
193
|
+
<!-- Example: <div class="attachments-container"></div> -->
|
|
194
|
+
<!-- Example: <div class="components-container"></div> -->
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</template>
|
|
199
|
+
|
|
200
|
+
<!-- TRANSCRIPT DATA -->
|
|
201
|
+
<script id="transcript-data" type="application/json">
|
|
202
|
+
${JSON.stringify(this.data)}
|
|
203
|
+
</script>
|
|
204
|
+
|
|
205
|
+
<!-- CLIENT-SIDE RENDERING SCRIPT -->
|
|
206
|
+
<script>
|
|
207
|
+
${clientRendererJs}
|
|
208
|
+
</script>
|
|
209
|
+
</body>
|
|
210
|
+
</html>
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
svgBuilder() {
|
|
214
|
+
const { options } = this.data;
|
|
215
|
+
return `
|
|
216
|
+
<svg style="display: none;">
|
|
217
|
+
<defs>
|
|
218
|
+
<symbol id="reply-icon" viewBox="0 0 16 16" fill="none">
|
|
219
|
+
<g transform="rotate(90 8 8)">
|
|
220
|
+
<path d="M6 2V9C6 11.5 8.5 14 11 14H14" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
221
|
+
</g>
|
|
222
|
+
</symbol>
|
|
223
|
+
${options.includeAttachments ? `<symbol id="download-icon" viewBox="0 -960 960 960">
|
|
224
|
+
<path d="m720-120 160-160-56-56-64 64v-167h-80v167l-64-64-56 56 160 160ZM560 0v-80h320V0H560ZM240-160q-33 0-56.5-23.5T160-240v-560q0-33 23.5-56.5T240-880h280l240 240v121h-80v-81H480v-200H240v560h240v80H240Zm0-80v-560 560Z"/>
|
|
225
|
+
</symbol> ` : ""}
|
|
226
|
+
${options.includeButtons ? `<symbol id="link-icon" viewBox="0 -960 960 960" fill="#e3e3e3">
|
|
227
|
+
<path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"/>
|
|
228
|
+
</symbol>` : ""}
|
|
229
|
+
</defs>
|
|
230
|
+
</svg>
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { TranscriptOptions } from "../../types/types";
|
|
2
|
+
export declare function script(options: TranscriptOptions): string;
|
|
3
|
+
export declare const POLL_JS = "\nconst pollDiv = event.target.closest('.pollResultEmbedButton');\nif (pollDiv) {\n event.preventDefault();\n const messageId = pollDiv.dataset.id;\n if (!messageId || messageId == \"\") return;\n const message = document.getElementById(messageId);\n\n if (message) {\n message.scrollIntoView({ behavior: 'smooth', block: 'center' });\n message.classList.add('highlight');\n setTimeout(() => {\n message.classList.remove('highlight');\n }, 1500);\n }\n}\n";
|
|
4
|
+
export declare const SELECTOR_JS = "\nconst selectorInput = event.target.closest('.selectorInput');\ndocument.querySelectorAll('.selector').forEach(selector => {\n if (!selector.contains(event.target)) {\n selector.classList.remove('active');\n }\n});\n\nif (selectorInput) {\n const selector = selectorInput.closest('.selector');\n if (selector) {\n selector.classList.toggle('active');\n }\n}\n";
|