├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── pom.xml
└── src
└── main
├── java
└── me
│ └── ryzeon
│ └── transcripts
│ ├── DiscordHtmlTranscripts.java
│ ├── Formatter.java
│ └── utils
│ └── format
│ ├── IFormatHelper.java
│ └── impl
│ ├── AudioFormat.java
│ ├── ImageFormat.java
│ └── VideoFormat.java
└── resources
└── template.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
3 | target
4 |
5 | src/test/java/Example.java
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "automatic"
3 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Discord (JDA) HTML Transcripts
2 | [](https://jitpack.io/#Ryzeon/discord-html-transcripts)
3 |
4 | Discord HTML Transcripts is a node.js module (recode on JDA) to generate nice looking HTML transcripts. Processes discord markdown like **bold**, *italics*, ~~strikethroughs~~, and more. Nicely formats attachments and embeds. Built in XSS protection, preventing users from inserting html tags.
5 |
6 | **This module is designed to work with [JDA](https://github.com/DV8FromTheWorld/JDA).**
7 |
8 | HTML Template stolen from [DiscordChatExporter](https://github.com/Tyrrrz/DiscordChatExporter).
9 |
10 | ## Installation
11 |
12 | ```xml
13 |
14 |
15 | jitpack.io
16 | https://jitpack.io
17 |
18 |
19 | ```
20 |
21 | ```xml
22 |
23 | com.github.Ryzeon
24 | discord-html-transcripts
25 | Tag
26 |
27 | ```
28 |
29 | ## Example Output
30 | 
31 |
32 | ## Usage
33 | ### Example usage using the built-in message fetcher.
34 | ```java
35 | DiscordHtmlTranscripts transcript = DiscordHtmlTranscripts.getInstance();
36 | textChannel.sendFiles(transcript.createTranscript(textChannel)).queue()
37 | ```
38 |
39 | ### Or if you prefer, you can pass in your own messages.
40 | ```java
41 | DiscordHtmlTranscripts transcript = DiscordHtmlTranscripts.getInstance();
42 | transcript.generateFromMessages(messages); // return to InputStream
43 | ```
44 |
45 | ### You can also put the transcript into a variable
46 | ```java
47 | DiscordHtmlTranscripts transcripts = new DiscordHtmlTranscripts();
48 | try {
49 | testChannel.sendFiles(transcripts.createTranscript(testChannel, "test.html")).queue();
50 | } catch (IOException e) {
51 | throw new RuntimeException(e);
52 | }
53 | ```
54 |
55 |
56 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.ryzeon.html
8 | discord-html-transcripts
9 | 2.0-SNAPSHOT
10 |
11 |
12 |
13 | org.apache.maven.plugins
14 | maven-compiler-plugin
15 |
16 | 17
17 | 17
18 |
19 |
20 |
21 | maven-assembly-plugin
22 |
23 |
24 |
25 | me.ryzeon.html.DiscordHtmlTranscripts
26 |
27 |
28 |
29 | jar-with-dependencies
30 |
31 |
32 |
33 |
34 | make-assembly
35 | package
36 |
37 | single
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 17
47 | UTF-8
48 |
49 |
50 |
51 |
52 |
53 | org.projectlombok
54 | lombok
55 | 1.18.30
56 | provided
57 |
58 |
59 |
60 | net.dv8tion
61 | JDA
62 | 5.0.0
63 |
64 |
65 |
66 |
67 | org.jsoup
68 | jsoup
69 | 1.15.3
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/main/java/me/ryzeon/transcripts/DiscordHtmlTranscripts.java:
--------------------------------------------------------------------------------
1 | package me.ryzeon.transcripts;
2 |
3 | import kotlin.text.Charsets;
4 | import me.ryzeon.transcripts.utils.format.IFormatHelper;
5 | import me.ryzeon.transcripts.utils.format.impl.AudioFormat;
6 | import me.ryzeon.transcripts.utils.format.impl.ImageFormat;
7 | import me.ryzeon.transcripts.utils.format.impl.VideoFormat;
8 | import net.dv8tion.jda.api.entities.*;
9 | import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
10 | import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
11 | import net.dv8tion.jda.api.utils.FileUpload;
12 | import org.jsoup.Jsoup;
13 | import org.jsoup.nodes.Document;
14 | import org.jsoup.nodes.Element;
15 |
16 | import java.io.ByteArrayInputStream;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.time.format.DateTimeFormatter;
20 | import java.util.*;
21 | import java.util.stream.Collectors;
22 |
23 |
24 | /**
25 | * Created by Ryzeon
26 | * Contributors: Zemux1613, Inkception, IncbomDev
27 | * Project: discord-html-transcripts
28 | * Date: 1/3/2023 @ 10:50
29 | */
30 | public class DiscordHtmlTranscripts {
31 |
32 | private static DiscordHtmlTranscripts instance;
33 | private final IFormatHelper
34 | imageFormats = new ImageFormat(),
35 | videoFormats = new VideoFormat(),
36 | audioFormats = new AudioFormat();
37 |
38 | /**
39 | * Get the instance of the DiscordHtmlTranscripts
40 | *
41 | * @return a singleton instance of the DiscordHtmlTranscripts
42 | */
43 | public static DiscordHtmlTranscripts getInstance() {
44 | if (instance == null) {
45 | instance = new DiscordHtmlTranscripts();
46 | }
47 | return instance;
48 | }
49 |
50 | private void handleFooter(Document document, MessageEmbed embed, Element embedContentContainer) {
51 | Element embedFooter = document.createElement("div");
52 | embedFooter.addClass("chatlog__embed-footer");
53 |
54 |
55 | if (Objects.requireNonNull(embed.getFooter()).getIconUrl() != null) {
56 | Element embedFooterIcon = document.createElement("img");
57 | embedFooterIcon.addClass("chatlog__embed-footer-icon");
58 | embedFooterIcon.attr("src", embed.getFooter().getIconUrl());
59 | embedFooterIcon.attr("alt", "Footer icon");
60 | embedFooterIcon.attr("loading", "lazy");
61 |
62 | embedFooter.appendChild(embedFooterIcon);
63 | }
64 |
65 | Element embedFooterText = document.createElement("span");
66 | embedFooterText.addClass("chatlog__embed-footer-text");
67 | embedFooterText.text(embed.getTimestamp() != null
68 | ? embed.getFooter().getText() + " • " + embed.getTimestamp()
69 | .format(DateTimeFormatter.ofPattern("HH:mm:ss"))
70 | : embed.getFooter().getText());
71 |
72 | embedFooter.appendChild(embedFooterText);
73 |
74 | embedContentContainer.appendChild(embedFooter);
75 | }
76 |
77 | private void handleEmbedImage(Document document, MessageEmbed embed, Element embedContentContainer) {
78 | Element embedImage = document.createElement("div");
79 | embedImage.addClass("chatlog__embed-image-container");
80 |
81 | Element embedImageLink = document.createElement("a");
82 | embedImageLink.addClass("chatlog__embed-image-link");
83 | embedImageLink.attr("href", embed.getImage().getUrl());
84 |
85 | Element embedImageImage = document.createElement("img");
86 | embedImageImage.addClass("chatlog__embed-image");
87 | embedImageImage.attr("src", embed.getImage().getUrl());
88 | embedImageImage.attr("alt", "Image");
89 | embedImageImage.attr("loading", "lazy");
90 |
91 | embedImageLink.appendChild(embedImageImage);
92 | embedImage.appendChild(embedImageLink);
93 |
94 | embedContentContainer.appendChild(embedImage);
95 | }
96 |
97 | private void handleEmbedThumbnail(Document document, MessageEmbed embed, Element embedContent) {
98 | Element embedThumbnail = document.createElement("div");
99 | embedThumbnail.addClass("chatlog__embed-thumbnail-container");
100 |
101 | Element embedThumbnailLink = document.createElement("a");
102 | embedThumbnailLink.addClass("chatlog__embed-thumbnail-link");
103 | embedThumbnailLink.attr("href", embed.getThumbnail().getUrl());
104 |
105 | Element embedThumbnailImage = document.createElement("img");
106 | embedThumbnailImage.addClass("chatlog__embed-thumbnail");
107 | embedThumbnailImage.attr("src", embed.getThumbnail().getUrl());
108 | embedThumbnailImage.attr("alt", "Thumbnail");
109 | embedThumbnailImage.attr("loading", "lazy");
110 |
111 | embedThumbnailLink.appendChild(embedThumbnailImage);
112 | embedThumbnail.appendChild(embedThumbnailLink);
113 |
114 | embedContent.appendChild(embedThumbnail);
115 | }
116 |
117 | private void handleEmbedFields(Document document, MessageEmbed embed, Element embedText) {
118 | Element embedFields = document.createElement("div");
119 | embedFields.addClass("chatlog__embed-fields");
120 |
121 | for (MessageEmbed.Field field : embed.getFields()) {
122 | Element embedField = document.createElement("div");
123 | embedField.addClass("chatlog__embed-field");
124 |
125 | // Field nmae
126 | Element embedFieldName = document.createElement("div");
127 | embedFieldName.addClass("chatlog__embed-field-name");
128 |
129 | Element embedFieldNameMarkdown = document.createElement("div");
130 | embedFieldNameMarkdown.addClass("markdown preserve-whitespace");
131 | embedFieldNameMarkdown.html(field.getName());
132 |
133 | embedFieldName.appendChild(embedFieldNameMarkdown);
134 | embedField.appendChild(embedFieldName);
135 |
136 |
137 | // Field value
138 | Element embedFieldValue = document.createElement("div");
139 | embedFieldValue.addClass("chatlog__embed-field-value");
140 |
141 | Element embedFieldValueMarkdown = document.createElement("div");
142 | embedFieldValueMarkdown.addClass("markdown preserve-whitespace");
143 | embedFieldValueMarkdown
144 | .html(Formatter.format(field.getValue()));
145 |
146 | embedFieldValue.appendChild(embedFieldValueMarkdown);
147 | embedField.appendChild(embedFieldValue);
148 |
149 | embedFields.appendChild(embedField);
150 | }
151 |
152 | embedText.appendChild(embedFields);
153 | }
154 |
155 | private void handleEmbedDescription(Document document, MessageEmbed embed, Element embedText) {
156 | Element embedDescription = document.createElement("div");
157 | embedDescription.addClass("chatlog__embed-description");
158 |
159 | Element embedDescriptionMarkdown = document.createElement("div");
160 | embedDescriptionMarkdown.addClass("markdown preserve-whitespace");
161 | embedDescriptionMarkdown
162 | .html(Formatter.format(embed.getDescription()));
163 |
164 | embedDescription.appendChild(embedDescriptionMarkdown);
165 | embedText.appendChild(embedDescription);
166 | }
167 |
168 | private void handleEmbedTitle(Document document, MessageEmbed embed, Element embedText) {
169 | Element embedTitle = document.createElement("div");
170 | embedTitle.addClass("chatlog__embed-title");
171 |
172 | if (embed.getUrl() != null) {
173 | Element embedTitleLink = document.createElement("a");
174 | embedTitleLink.addClass("chatlog__embed-title-link");
175 | embedTitleLink.attr("href", embed.getUrl());
176 |
177 | Element embedTitleMarkdown = document.createElement("div");
178 | embedTitleMarkdown.addClass("markdown preserve-whitespace")
179 | .html(Formatter.format(embed.getTitle()));
180 |
181 | embedTitleLink.appendChild(embedTitleMarkdown);
182 | embedTitle.appendChild(embedTitleLink);
183 | } else {
184 | Element embedTitleMarkdown = document.createElement("div");
185 | embedTitleMarkdown.addClass("markdown preserve-whitespace")
186 | .html(Formatter.format(embed.getTitle()));
187 |
188 | embedTitle.appendChild(embedTitleMarkdown);
189 | }
190 | embedText.appendChild(embedTitle);
191 | }
192 |
193 | private void handleEmbedAuthor(Document document, MessageEmbed embed, Element embedText) {
194 | Element embedAuthor = document.createElement("div");
195 | embedAuthor.addClass("chatlog__embed-author");
196 |
197 | if (embed.getAuthor().getIconUrl() != null) {
198 | Element embedAuthorIcon = document.createElement("img");
199 | embedAuthorIcon.addClass("chatlog__embed-author-icon");
200 | embedAuthorIcon.attr("src", embed.getAuthor().getIconUrl());
201 | embedAuthorIcon.attr("alt", "Author icon");
202 | embedAuthorIcon.attr("loading", "lazy");
203 |
204 | embedAuthor.appendChild(embedAuthorIcon);
205 | }
206 |
207 | Element embedAuthorName = document.createElement("span");
208 | embedAuthorName.addClass("chatlog__embed-author-name");
209 |
210 | if (embed.getAuthor().getUrl() != null) {
211 | Element embedAuthorNameLink = document.createElement("a");
212 | embedAuthorNameLink.addClass("chatlog__embed-author-name-link");
213 | embedAuthorNameLink.attr("href", embed.getAuthor().getUrl());
214 | embedAuthorNameLink.text(embed.getAuthor().getName());
215 |
216 | embedAuthorName.appendChild(embedAuthorNameLink);
217 | } else {
218 | embedAuthorName.text(embed.getAuthor().getName());
219 | }
220 |
221 | embedAuthor.appendChild(embedAuthorName);
222 | embedText.appendChild(embedAuthor);
223 | }
224 |
225 | private void handleUnknownAttachmentTypes(Document document, Message.Attachment attach, Element attachmentsDiv) {
226 | Element attachmentGeneric = document.createElement("div");
227 | attachmentGeneric.addClass("chatlog__attachment-generic");
228 |
229 | Element attachmentGenericIcon = document.createElement("svg");
230 | attachmentGenericIcon.addClass("chatlog__attachment-generic-icon");
231 |
232 | Element attachmentGenericIconUse = document.createElement("use");
233 | attachmentGenericIconUse.attr("xlink:href", "#icon-attachment");
234 |
235 | attachmentGenericIcon.appendChild(attachmentGenericIconUse);
236 | attachmentGeneric.appendChild(attachmentGenericIcon);
237 |
238 | Element attachmentGenericName = document.createElement("div");
239 | attachmentGenericName.addClass("chatlog__attachment-generic-name");
240 |
241 | Element attachmentGenericNameLink = document.createElement("a");
242 | attachmentGenericNameLink.attr("href", attach.getUrl());
243 | attachmentGenericNameLink.text(attach.getFileName());
244 |
245 | attachmentGenericName.appendChild(attachmentGenericNameLink);
246 | attachmentGeneric.appendChild(attachmentGenericName);
247 |
248 | Element attachmentGenericSize = document.createElement("div");
249 | attachmentGenericSize.addClass("chatlog__attachment-generic-size");
250 |
251 | attachmentGenericSize.text(Formatter.formatBytes(attach.getSize()));
252 | attachmentGeneric.appendChild(attachmentGenericSize);
253 |
254 | attachmentsDiv.appendChild(attachmentGeneric);
255 | }
256 |
257 | private void handleAudios(Document document, Message.Attachment attach, Element attachmentsDiv) {
258 | Element attachmentAudio = document.createElement("audio");
259 | attachmentAudio.addClass("chatlog__attachment-media");
260 | attachmentAudio.attr("src", attach.getUrl());
261 | attachmentAudio.attr("alt", "Audio attachment");
262 | attachmentAudio.attr("controls", true);
263 | attachmentAudio.attr("title",
264 | "Audio: " + attach.getFileName() + Formatter.formatBytes(attach.getSize()));
265 |
266 | attachmentsDiv.appendChild(attachmentAudio);
267 | }
268 |
269 | private void handleVideos(Document document, Message.Attachment attach, Element attachmentsDiv) {
270 | Element attachmentVideo = document.createElement("video");
271 | attachmentVideo.addClass("chatlog__attachment-media");
272 | attachmentVideo.attr("src", attach.getUrl());
273 | attachmentVideo.attr("alt", "Video attachment");
274 | attachmentVideo.attr("controls", true);
275 | attachmentVideo.attr("title",
276 | "Video: " + attach.getFileName() + Formatter.formatBytes(attach.getSize()));
277 |
278 | attachmentsDiv.appendChild(attachmentVideo);
279 | }
280 |
281 | private void handleImages(Document document, Message.Attachment attach, Element attachmentsDiv) {
282 | Element attachmentLink = document.createElement("a");
283 |
284 | Element attachmentImage = document.createElement("img");
285 | attachmentImage.addClass("chatlog__attachment-media");
286 | attachmentImage.attr("src", attach.getUrl());
287 | attachmentImage.attr("alt", "Image attachment");
288 | attachmentImage.attr("loading", "lazy");
289 | attachmentImage.attr("title",
290 | "Image: " + attach.getFileName() + Formatter.formatBytes(attach.getSize()));
291 |
292 | attachmentLink.appendChild(attachmentImage);
293 | attachmentsDiv.appendChild(attachmentLink);
294 | }
295 |
296 | private void handleMessageReferences(GuildChannel channel, Document document, Message message, Element messageGroup) {
297 | // message.reference?.messageId
298 | // create symbol
299 | Element referenceSymbol = document.createElement("div");
300 | referenceSymbol.addClass("chatlog__reference-symbol");
301 |
302 | // create reference
303 | Element reference = document.createElement("div");
304 | reference.addClass("chatlog__reference");
305 |
306 | var referenceMessage = message.getReferencedMessage();
307 | User author = referenceMessage.getAuthor();
308 | Member member = channel.getGuild().getMember(author);
309 | var color = Formatter.toHex(Objects.requireNonNull(member.getColor()));
310 |
311 | // System.out.println("REFERENCE MSG " + referenceMessage.getContentDisplay());
312 | reference.html("
" +
314 | "" + author.getName() + "\"" +
316 | "
" +
317 | " " +
319 | "" +
320 | referenceMessage.getContentDisplay() != null
321 | ? referenceMessage.getContentDisplay().length() > 42
322 | ? referenceMessage.getContentDisplay().substring(0, 42)
323 | + "..."
324 | : referenceMessage.getContentDisplay()
325 | : "Click to see attachment" +
326 | "" +
327 | "" +
328 | "
");
329 |
330 | messageGroup.appendChild(referenceSymbol);
331 | messageGroup.appendChild(reference);
332 | }
333 |
334 | public FileUpload createTranscript(GuildMessageChannel channel) throws IOException {
335 | return createTranscript(channel, null);
336 | }
337 |
338 | public FileUpload createTranscript(GuildMessageChannel channel, String fileName) throws IOException {
339 | return FileUpload.fromData(generateFromMessages(channel.getIterableHistory().stream().collect(Collectors.toList())),
340 | fileName != null ? fileName : "transcript.html");
341 | }
342 |
343 | public InputStream generateFromMessages(Collection messages) throws IOException {
344 | InputStream htmlTemplate = findFile();
345 | if (messages.isEmpty()) {
346 | throw new IllegalArgumentException("No messages to generate a transcript from");
347 | }
348 |
349 | GuildChannel channel = messages.iterator().next().getChannel().asGuildMessageChannel();
350 |
351 | Document document = Jsoup.parse(htmlTemplate, "UTF-8", "template.html");
352 | document.outputSettings().indentAmount(0).prettyPrint(true);
353 | document.getElementsByClass("preamble__guild-icon").first().attr("src", channel.getGuild().getIconUrl()); // set guild icon
354 |
355 | document.getElementById("transcriptTitle").text("#" + channel.getName() + " | " + messages.size() + " messages"); // set title
356 | document.getElementById("guildname").text(channel.getGuild().getName()); // set guild name
357 | document.getElementById("ticketname").text("#" + channel.getName()); // set channel name
358 |
359 | Element chatLog = document.getElementById("chatlog"); // chat log
360 | for (Message message : messages.stream()
361 | .sorted(Comparator.comparing(ISnowflake::getTimeCreated))
362 | .toList()) {
363 |
364 | if (message.getAuthor().isBot()) {
365 | continue;
366 | }
367 | // create message group
368 | Element messageGroup = document.createElement("div");
369 | messageGroup.addClass("chatlog__message-group");
370 |
371 | // message reference
372 | if (message.getReferencedMessage() != null) { // preguntar si es eso
373 | handleMessageReferences(channel, document, message, messageGroup);
374 | }
375 |
376 | var author = message.getAuthor();
377 |
378 | Element authorElement = document.createElement("div");
379 | authorElement.addClass("chatlog__author-avatar-container");
380 |
381 | Element authorAvatar = document.createElement("img");
382 | authorAvatar.addClass("chatlog__author-avatar");
383 | authorAvatar.attr("alt", "Avatar");
384 | authorAvatar.attr("loading", "lazy");
385 |
386 | Element authorName = document.createElement("span");
387 | authorName.addClass("chatlog__author-name");
388 |
389 | if (author != null) {
390 | authorName.attr("title", Objects.requireNonNull(author.getGlobalName()));
391 | authorName.text(author.getName());
392 | authorName.attr("data-user-id", author.getId());
393 | authorAvatar.attr("src", Objects.requireNonNull(author.getEffectiveAvatarUrl()));
394 | } else {
395 | // Handle the case when author is null (e.g., when the message is from a bot)
396 | authorName.attr("title", "Bot");
397 | authorName.text("Bot");
398 | authorName.attr("data-user-id", "Bot");
399 | authorAvatar.attr("src", "default_bot_avatar_url"); // replace with your default bot avatar URL
400 | }
401 |
402 | authorElement.appendChild(authorAvatar);
403 | messageGroup.appendChild(authorElement);
404 |
405 | // message content
406 | Element content = document.createElement("div");
407 | content.addClass("chatlog__messages");
408 |
409 | content.appendChild(authorName);
410 |
411 | if (author != null && author.isBot()) {
412 | Element botTag = document.createElement("span");
413 | botTag.addClass("chatlog__bot-tag").text("BOT");
414 | content.appendChild(botTag);
415 | }
416 |
417 | // timestamp
418 | Element timestamp = document.createElement("span");
419 | timestamp.addClass("chatlog__timestamp");
420 | timestamp
421 | .text(message.getTimeCreated().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
422 |
423 | content.appendChild(timestamp);
424 |
425 | Element messageContent = document.createElement("div");
426 | messageContent.addClass("chatlog__message");
427 | messageContent.attr("data-message-id", message.getId());
428 | messageContent.attr("id", "message-" + message.getId());
429 | messageContent.attr("title", "Message sent: " + message.getTimeCreated().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
430 |
431 | if (!message.getContentDisplay().isEmpty()) {
432 | Element messageContentContent = document.createElement("div");
433 | messageContentContent.addClass("chatlog__content");
434 |
435 | Element messageContentContentMarkdown = document.createElement("div");
436 | messageContentContentMarkdown.addClass("markdown");
437 |
438 | Element messageContentContentMarkdownSpan = document.createElement("span");
439 | messageContentContentMarkdownSpan.addClass("preserve-whitespace");
440 | messageContentContentMarkdownSpan.html(Formatter.format(message.getContentDisplay()));
441 |
442 | messageContentContentMarkdown.appendChild(messageContentContentMarkdownSpan);
443 | messageContentContent.appendChild(messageContentContentMarkdown);
444 | messageContent.appendChild(messageContentContent);
445 | }
446 |
447 | // messsage attachments
448 | if (!message.getAttachments().isEmpty()) {
449 | for (Message.Attachment attach : message.getAttachments()) {
450 | Element attachmentsDiv = document.createElement("div");
451 | attachmentsDiv.addClass("chatlog__attachment");
452 |
453 | var attachmentType = attach.getFileExtension();
454 | if (imageFormats.isFormat(attachmentType)) {
455 | handleImages(document, attach, attachmentsDiv);
456 | } else if (videoFormats.isFormat(attachmentType)) {
457 | handleVideos(document, attach, attachmentsDiv);
458 | } else if (audioFormats.isFormat(attachmentType)) {
459 | handleAudios(document, attach, attachmentsDiv);
460 | } else {
461 | handleUnknownAttachmentTypes(document, attach, attachmentsDiv);
462 | }
463 |
464 | messageContent.appendChild(attachmentsDiv);
465 | }
466 | }
467 |
468 | content.appendChild(messageContent);
469 |
470 | if (!message.getEmbeds().isEmpty()) {
471 | for (MessageEmbed embed : message.getEmbeds()) {
472 | if (embed == null) {
473 | continue;
474 | }
475 | Element embedDiv = document.createElement("div");
476 | embedDiv.addClass("chatlog__embed");
477 |
478 | // embed color
479 | Element embedColorPill = document.createElement("div");
480 |
481 | if (embed.getColor() == null) {
482 | embedColorPill.addClass("chatlog__embed-color-pill chatlog__embed-color-pill--default");
483 | } else {
484 | embedColorPill.addClass("chatlog__embed-color-pill");
485 | embedColorPill.attr("style",
486 | "background-color: #" + Formatter.toHex(embed.getColor()));
487 | }
488 | embedDiv.appendChild(embedColorPill);
489 |
490 | Element embedContentContainer = document.createElement("div");
491 | embedContentContainer.addClass("chatlog__embed-content-container");
492 |
493 | Element embedContent = document.createElement("div");
494 | embedContent.addClass("chatlog__embed-content");
495 |
496 | Element embedText = document.createElement("div");
497 | embedText.addClass("chatlog__embed-text");
498 |
499 | // embed author
500 | if (embed.getAuthor() != null && embed.getAuthor().getName() != null) {
501 | handleEmbedAuthor(document, embed, embedText);
502 | }
503 |
504 | // embed title
505 | if (embed.getTitle() != null) {
506 | handleEmbedTitle(document, embed, embedText);
507 | }
508 |
509 | // embed description
510 | if (embed.getDescription() != null) {
511 | handleEmbedDescription(document, embed, embedText);
512 | }
513 |
514 | // embed fields
515 | if (!embed.getFields().isEmpty()) {
516 | handleEmbedFields(document, embed, embedText);
517 | }
518 |
519 | embedContent.appendChild(embedText);
520 |
521 | // embed thumbnail
522 | if (embed.getThumbnail() != null) {
523 | handleEmbedThumbnail(document, embed, embedContent);
524 | }
525 |
526 | embedContentContainer.appendChild(embedContent);
527 |
528 | // embed image
529 | if (embed.getImage() != null) {
530 | handleEmbedImage(document, embed, embedContentContainer);
531 | }
532 |
533 | // embed footer
534 | if (embed.getFooter() != null) {
535 | handleFooter(document, embed, embedContentContainer);
536 | }
537 |
538 | embedDiv.appendChild(embedContentContainer);
539 | content.appendChild(embedDiv);
540 | }
541 | }
542 |
543 | messageGroup.appendChild(content);
544 | assert chatLog != null;
545 | chatLog.appendChild(messageGroup);
546 | }
547 | return new ByteArrayInputStream(document.outerHtml().getBytes(Charsets.UTF_8));
548 | }
549 |
550 | private InputStream findFile() {
551 | InputStream inputStream = getClass().getClassLoader().getResourceAsStream("template.html");
552 | if (inputStream == null) {
553 | throw new IllegalArgumentException("file is not found: " + "template.html");
554 | }
555 | return inputStream;
556 | }
557 | }
--------------------------------------------------------------------------------
/src/main/java/me/ryzeon/transcripts/Formatter.java:
--------------------------------------------------------------------------------
1 | package me.ryzeon.transcripts;
2 |
3 | import lombok.experimental.UtilityClass;
4 |
5 | import java.awt.*;
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 |
9 | /**
10 | * Created by Ryzeon
11 | * Project: discord-html-transcripts
12 | * Date: 2/12/21 @ 00:32
13 | * Twitter: @Ryzeon_ 😎
14 | * Github: github.ryzeon.me
15 | */
16 | @UtilityClass
17 | public class Formatter {
18 |
19 | private final Pattern STRONG = Pattern.compile("\\*\\*(.+?)\\*\\*");
20 | private final Pattern EM = Pattern.compile("\\*(.+?)\\*");
21 | private final Pattern S = Pattern.compile("~~(.+?)~~");
22 | private final Pattern U = Pattern.compile("__(.+?)__");
23 | private final Pattern CODE = Pattern.compile("```(.+?)```");
24 | private final Pattern CODE_1 = Pattern.compile("`(.+?)`");
25 | private final Pattern QUOTE = Pattern.compile("^>{1,3} (.*)$");
26 | private final Pattern LINK = Pattern.compile("\\[([^\\[]+)\\](\\((www|http:|https:)+[^\\s]+[\\w]\\))");
27 | private final Pattern NEW_LINE = Pattern.compile("\\n");
28 |
29 | public String formatBytes(long bytes) {
30 | int unit = 1024;
31 | if (bytes < unit)
32 | return bytes + " B";
33 | int exp = (int) (Math.log(bytes) / Math.log(unit));
34 | String pre = String.valueOf("KMGTPE".charAt(exp - 1));
35 | return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
36 | }
37 |
38 | public String format(String originalText) {
39 | Matcher matcher = STRONG.matcher(originalText);
40 | String newText = originalText;
41 | while (matcher.find()) {
42 | String group = matcher.group();
43 | newText = newText.replace(group,
44 | "" + group.replace("**", "") + "");
45 | }
46 | matcher = EM.matcher(newText);
47 | while (matcher.find()) {
48 | String group = matcher.group();
49 | newText = newText.replace(group,
50 | "" + group.replace("*", "") + "");
51 | }
52 | matcher = S.matcher(newText);
53 | while (matcher.find()) {
54 | String group = matcher.group();
55 | newText = newText.replace(group,
56 | "" + group.replace("~~", "") + "");
57 | }
58 | matcher = U.matcher(newText);
59 | while (matcher.find()) {
60 | String group = matcher.group();
61 | newText = newText.replace(group,
62 | "" + group.replace("__", "") + "");
63 | }
64 | matcher = QUOTE.matcher(newText);
65 | while (matcher.find()) {
66 | String group = matcher.group();
67 | newText = newText.replace(group,
68 | "" + group.replaceFirst(">>>", "").replaceFirst(">", "") + "");
69 | }
70 | matcher = LINK.matcher(newText);
71 | while (matcher.find()) {
72 | String group = matcher.group(1);
73 | String link = matcher.group(2);
74 | String raw = "[" + group + "]" + link;
75 |
76 | newText = newText.replace(raw, "" + group + "");
77 | }
78 |
79 | matcher = CODE.matcher(newText);
80 | boolean findCode = false;
81 | while (matcher.find()) {
82 | String group = matcher.group();
83 | newText = newText.replace(group,
84 | ""
85 | + group.replace("```", "").substring(3, -3) + "
");
86 | findCode = true;
87 | }
88 | if (!findCode) {
89 | matcher = CODE_1.matcher(newText);
90 | while (matcher.find()) {
91 | String group = matcher.group();
92 | newText = newText.replace(group,
93 | "" + group.replace("`", "") + "");
94 | }
95 | }
96 | matcher = NEW_LINE.matcher(newText);
97 | while (matcher.find()) {
98 | newText = newText.replace(matcher.group(), "
");
99 | }
100 | return newText;
101 | }
102 |
103 | public String toHex(Color color) {
104 | String hex = Integer.toHexString(color.getRGB() & 0xffffff);
105 | while (hex.length() < 6) {
106 | hex = "0" + hex;
107 | }
108 | return hex;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/me/ryzeon/transcripts/utils/format/IFormatHelper.java:
--------------------------------------------------------------------------------
1 | package me.ryzeon.transcripts.utils.format;
2 |
3 | import java.util.List;
4 | import java.util.function.Predicate;
5 |
6 | public interface IFormatHelper {
7 |
8 | List formats();
9 |
10 | default boolean isFormat(String format) {
11 | return formats().stream().anyMatch(Predicate.isEqual(format));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/me/ryzeon/transcripts/utils/format/impl/AudioFormat.java:
--------------------------------------------------------------------------------
1 | package me.ryzeon.transcripts.utils.format.impl;
2 |
3 | import me.ryzeon.transcripts.utils.format.IFormatHelper;
4 |
5 | import java.util.Arrays;
6 | import java.util.List;
7 |
8 | public class AudioFormat implements IFormatHelper {
9 |
10 | final List formats = Arrays.asList("mp3", "wav", "ogg", "flac");
11 |
12 | @Override
13 | public List formats() {
14 | return formats;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/ryzeon/transcripts/utils/format/impl/ImageFormat.java:
--------------------------------------------------------------------------------
1 | package me.ryzeon.transcripts.utils.format.impl;
2 |
3 | import me.ryzeon.transcripts.utils.format.IFormatHelper;
4 |
5 | import java.util.Arrays;
6 | import java.util.List;
7 | import java.util.function.Predicate;
8 |
9 | public class ImageFormat implements IFormatHelper {
10 |
11 | final List formats = Arrays.asList("png", "jpg", "jpeg", "gif");
12 | @Override
13 | public List formats() {
14 | return formats;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/ryzeon/transcripts/utils/format/impl/VideoFormat.java:
--------------------------------------------------------------------------------
1 | package me.ryzeon.transcripts.utils.format.impl;
2 |
3 | import me.ryzeon.transcripts.utils.format.IFormatHelper;
4 |
5 | import java.util.Arrays;
6 | import java.util.List;
7 |
8 | public class VideoFormat implements IFormatHelper {
9 | final List formats = Arrays.asList("mp4", "webm", "mkv", "avi", "mov", "flv", "wmv", "mpg", "mpeg");
10 | @Override
11 | public List formats() {
12 | return formats;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Here title
6 |
7 |
8 |
9 |
618 |
619 |
621 |
622 |
623 |
628 |
629 |
630 |
631 |
636 |
637 |
668 |
669 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |

689 |
690 |
691 |
Guild Name Here
692 |
Channel Name Here
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
--------------------------------------------------------------------------------