Код: Выделить всё
com.itextpdf
kernel
7.0.4
com.itextpdf
io
7.0.4
com.itextpdf
layout
7.0.4
com.itextpdf
forms
7.0.4
com.itextpdf
pdfa
7.0.4
com.itextpdf
pdftest
7.0.4
Код: Выделить всё
public class ChatTranscriptServiceImpl implements ChatTranscriptService {
private final ChatParticipantClient chatParticipantClient;
private static final float MARGIN_X = 50;
private static final float MARGIN_Y = 50;
private static final float FONT_SIZE = 12;
private static final float LINE_HEIGHT = 1.5f * FONT_SIZE;
private List messages;
private String ticketNumber;
private String issuedTime;
private String closedTime;
private String clientWhatsAppNumber;
private String customerWhatsAppNumber;
private String agentName;
private String customerName;
private String lastOutgoingMessageTime;
private String fileName;
private PDFont emojiFont;
private PDFont fallbackFont;
private static final PDFont BOLD_FONT = PDType1Font.HELVETICA_BOLD;
private static final PDFont ITALIC_FONT = PDType1Font.TIMES_ITALIC;
private static final PDFont COURIER_FONT = PDType1Font.COURIER;
private static final String NORMAL_FONT = "normal";
public ChatTranscriptServiceImpl(ChatParticipantClient chatParticipantClient) {
this.chatParticipantClient = chatParticipantClient;
}
@Override
public ChatTranscriptResponse downloadChatTranscript(String chatParticipantId) {
this.messages = chatParticipantClient.getMessagesByChatParticipant(chatParticipantId);
byte[] pdfBytes = processMessages();
ChatTranscriptResponse response = new ChatTranscriptResponse();
response.setPdfBytes(pdfBytes);
response.setFilename(fileName);
return response;
}
private byte[] processMessages() {
Pattern ticketNumberPattern = Pattern.compile("Your (?:previous )?ticket number is (.*?) please retain the ticket number to reference this chat.", Pattern.DOTALL);
Pattern ratingMessagePattern = Pattern.compile("Please rate our service.");
for (Message message : this.messages) {
processTicketNumber(ticketNumberPattern, message);
processOutgoingMessage(message);
processRatingMessage(ratingMessagePattern, message);
extractWhatsAppNumbers(message);
}
return finalizeTranscript();
}
private void processTicketNumber(Pattern pattern, Message message) {
String text = message.getText();
if (text != null) {
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
ticketNumber = matcher.group(1).trim();
issuedTime = formatDateTime(message.getCreatedTime());
} else if (issuedTime == null) {
issuedTime = formatDateTime(message.getCreatedTime());
}
} else if (issuedTime == null) {
issuedTime = formatDateTime(message.getCreatedTime());
}
}
private void processOutgoingMessage(Message message) {
if (Pl4ChatMessageDirection.OUTGOING.equals(message.getDirection())) {
lastOutgoingMessageTime = formatDateTime(message.getCreatedTime());
if (message.getAgent() != null) {
agentName = getAgentName(message.getAgent());
} else {
agentName = "Bot";
}
if (message.getChatParticipant() != null) {
customerName = getCustomerName(message.getChatParticipant());
}
}
}
private void processRatingMessage(Pattern pattern, Message message) {
Matcher matcher = pattern.matcher(message.getText());
if (matcher.find()) {
closedTime = formatDateTime(message.getCreatedTime());
} else if (message.getChatParticipant() != null && message.getChatParticipant().isConversationClosed()) {
closedTime = lastOutgoingMessageTime != null ? lastOutgoingMessageTime : "Open";
} else {
closedTime = "Open";
}
}
private void extractWhatsAppNumbers(Message message) {
if (clientWhatsAppNumber == null && message.getChatParticipant() != null && message.getChatParticipant().getWhatsAppClientNumber() != null) {
clientWhatsAppNumber = message.getChatParticipant().getWhatsAppClientNumber();
}
if (customerWhatsAppNumber == null && message.getChatParticipant() != null && message.getChatParticipant().getPhoneNumber() != null) {
customerWhatsAppNumber = message.getChatParticipant().getPhoneNumber();
}
}
private String getAgentName(SystemUser agent) {
if (agent.getFirstName() != null && agent.getLastName() != null) {
return agent.getFirstName() + " " + agent.getLastName();
} else if (agent.getFirstName() != null) {
return agent.getFirstName();
} else if (agent.getLastName() != null) {
return agent.getLastName();
}
return "Bot";
}
private String getCustomerName(ChatParticipant participant) {
if (participant.getFullName() != null) {
return participant.getFullName();
} else if (participant.getFirstName() != null && participant.getLastName() != null) {
return participant.getFirstName() + " " + participant.getLastName();
} else if (participant.getFirstName() != null) {
return participant.getFirstName();
} else if (participant.getLastName() != null) {
return participant.getLastName();
}
return "Unknown";
}
private byte[] finalizeTranscript() {
try {
return this.generatePdfTranscript();
} catch (IOException e) {
log.error("Error generating file: {}", e.getMessage());
throw CustomParameterizedException.singleParameterProblem(
String.format("Error generating transcript... %n%s", e.getMessage()),
Status.BAD_REQUEST
);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
private byte[] generatePdfTranscript() throws IOException, DocumentException {
if (ticketNumber != null) {
fileName = sanitizeFilename(ticketNumber + "_transcript.pdf");
} else {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
fileName = sanitizeFilename(customerName + "_" + LocalDateTime.now().format(formatter) + "_transcript.pdf");
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4, MARGIN_X, MARGIN_X, MARGIN_Y, MARGIN_Y);
PdfWriter writer = PdfWriter.getInstance(document, byteArrayOutputStream);
document.open();
PDDocument pdDocument = new PDDocument();
initializeFonts(pdDocument);
setupPdfHeader(document);
addTranscriptTitle(document);
for (Message message : messages) {
addMessage(document, writer, message);
}
document.close();
pdDocument.close();
return byteArrayOutputStream.toByteArray();
}
private void setupPdfHeader(Document document) throws DocumentException {
Font headerFont = new Font(Font.FontFamily.HELVETICA, FONT_SIZE, Font.BOLD);
Paragraph header = new Paragraph();
header.setAlignment(Element.ALIGN_LEFT);
if (ticketNumber != null) {
header.add(new Chunk("Ticket Number: " + ticketNumber + "\n", headerFont));
header.add(new Chunk("Date & Time Ticket Issued: " + issuedTime + "\n", headerFont));
header.add(new Chunk("Date & Time Ticket Closed: " + closedTime + "\n", headerFont));
} else {
header.add(new Chunk("No ticket issued\n", headerFont));
}
if (clientWhatsAppNumber != null) {
header.add(new Chunk("Service's WhatsApp number: " + clientWhatsAppNumber + "\n", headerFont));
}
if (customerWhatsAppNumber != null) {
header.add(new Chunk("Customer's WhatsApp number: " + customerWhatsAppNumber + "\n", headerFont));
}
header.add(new Chunk("Agent's Name & Surname: " + agentName + "\n", headerFont));
header.add(new Chunk("Customer's Name & Surname: " + customerName + "\n", headerFont));
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell cell = new PdfPCell(header);
cell.setBorder(Rectangle.BOX);
cell.setPadding(10);
table.addCell(cell);
document.add(table);
document.add(Chunk.NEWLINE);
}
private void addTranscriptTitle(Document document) throws DocumentException {
Font titleFont = new Font(Font.FontFamily.HELVETICA, FONT_SIZE + 2, Font.BOLD);
Paragraph title = new Paragraph("Ticket Transcript:", titleFont);
title.setAlignment(Element.ALIGN_LEFT);
document.add(title);
document.add(Chunk.NEWLINE);
}
private void addMessage(Document document, PdfWriter writer, Message message) throws DocumentException, IOException {
String sender = message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ? agentName : customerName;
if (message.getContentType().equals(Pl4ContentType.MEDIA)) {
addMediaMessage(document, writer, message, sender);
} else {
String messageText = sender + ": " + message.getText();
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell cell = new PdfPCell();
cell.setPadding(10);
cell.setBorder(Rectangle.BOX);
cell.setBackgroundColor(message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ?
new BaseColor(220, 220, 220) : new BaseColor(220, 248, 198));
Paragraph paragraph = new Paragraph();
addMixedTextToParagraph(paragraph, messageText);
cell.addElement(paragraph);
table.addCell(cell);
document.add(table);
addTimestamp(document, message);
}
}
private void addMediaMessage(Document document, PdfWriter writer, Message message, String sender) throws DocumentException {
String mediaURL = message.getMediaURL();
String linkText = message.getText() != null ? message.getText() : "View Media";
String senderText = sender + ": ";
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
PdfPCell cell = new PdfPCell();
cell.setPadding(10);
cell.setBorder(Rectangle.BOX);
cell.setBackgroundColor(message.getDirection().equals(Pl4ChatMessageDirection.OUTGOING) ?
new BaseColor(220, 220, 220) : new BaseColor(220, 248, 198));
Paragraph paragraph = new Paragraph();
paragraph.add(new Chunk(senderText, new Font(Font.FontFamily.HELVETICA, FONT_SIZE)));
Anchor anchor = new Anchor(linkText, new Font(Font.FontFamily.HELVETICA, FONT_SIZE, Font.UNDERLINE, BaseColor.BLUE));
anchor.setReference(mediaURL);
paragraph.add(anchor);
cell.addElement(paragraph);
table.addCell(cell);
document.add(table);
addTimestamp(document, message);
}
private void addTimestamp(Document document, Message message) throws DocumentException {
String timeString = formatDateTime(message.getCreatedTime());
Paragraph timestamp = new Paragraph(timeString, new Font(Font.FontFamily.HELVETICA, 8));
timestamp.setAlignment(Element.ALIGN_RIGHT);
document.add(timestamp);
document.add(Chunk.NEWLINE);
}
private String formatDateTime(LocalDateTime dateTime) {
return dateTime != null ? dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "N/A";
}
private void initializeFonts(PDDocument document) throws IOException {
fallbackFont = PDType1Font.HELVETICA;
try (InputStream emojiFontStream = getClass().getClassLoader().getResourceAsStream("static/NotoColorEmoji-Regular.ttf")) {
if (emojiFontStream == null) {
throw new FileNotFoundException("Emoji font file not found in classpath.");
}
emojiFont = PDType0Font.load(document, emojiFontStream);
}
}
private void addMixedTextToParagraph(Paragraph paragraph, String text) {
for (int i = 0; i < text.length(); i++) {
int codePoint = text.codePointAt(i);
String charString = new String(Character.toChars(codePoint));
Font font = Character.isSupplementaryCodePoint(codePoint) ? new Font(Font.FontFamily.UNDEFINED) : new Font(Font.FontFamily.HELVETICA);
paragraph.add(new Chunk(charString, font));
if (Character.isSupplementaryCodePoint(codePoint)) {
i++;
}
}
}
private List
splitTextToLines(String text, float maxWidth) {
List processedParts = processText(text);
List lines = new ArrayList();
StringBuilder currentLine = new StringBuilder();
String currentStyle = "";
for (ProcessedPart part : processedParts) {
String[] words = part.text.split("\\s+");
for (String word : words) {
if (fitsInCurrentLine(currentLine, word, maxWidth)) {
appendWordToLine(currentLine, word);
} else {
addLineIfNotEmpty(lines, currentLine, currentStyle);
currentLine = new StringBuilder(word);
}
}
if (part.lineBreak > 0) {
addLineIfNotEmpty(lines, currentLine, currentStyle);
addLineBreak(lines, part.lineBreak);
}
currentStyle = part.style;
}
addLineIfNotEmpty(lines, currentLine, currentStyle);
return lines;
}
private boolean fitsInCurrentLine(StringBuilder currentLine, String word, float maxWidth) {
String testLine = currentLine.length() > 0 ? currentLine + " " + word : word;
return calculateWidth(testLine) 0) {
line.append(" ");
}
line.append(word);
}
private void addLineIfNotEmpty(List lines, StringBuilder line, String style) {
if (line.length() > 0) {
lines.add(new ProcessedPart(line.toString(), style, 0));
line.setLength(0);
}
}
private void addLineBreak(List lines, int lineBreak) {
lines.add(new ProcessedPart("", "", lineBreak));
}
private float calculateWidth(String text) {
float width = 0;
for (int i = 0; i < text.length(); i++) {
int codePoint = text.codePointAt(i);
PDFont currentFont = Character.isSupplementaryCodePoint(codePoint) ? emojiFont : fallbackFont;
try {
String charString = new String(Character.toChars(codePoint));
width += currentFont.getStringWidth(charString) / 1000 * FONT_SIZE;
} catch (IllegalArgumentException | IOException e) {
log.warn("Unable to calculate width for character: {} ({}). Using fallback width.",
String.format("U+%04X", codePoint), new String(Character.toChars(codePoint)));
width += fallbackFont.getAverageFontWidth() / 1000 * FONT_SIZE;
}
if (Character.isSupplementaryCodePoint(codePoint)) {
i++;
}
}
return width;
}
public static class ProcessedPart {
String text;
String style;
int lineBreak;
public ProcessedPart(String text, String style, int lineBreak) {
this.text = text;
this.style = style;
this.lineBreak = lineBreak;
}
@Override
public String toString() {
return "ProcessedPart{" +
"text='" + text + '\'' +
", style='" + style + '\'' +
", lineBreak=" + lineBreak +
'}';
}
}
private List processText(String messageText) {
List partsList = new ArrayList();
String regex = "(.*?[/b]|[i].*?[/i]|.*?|.*?Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(messageText);
int lastEnd = 0;
while (matcher.find()) {
if (matcher.start() > lastEnd) {
partsList.add(new ProcessedPart(messageText.substring(lastEnd, matcher.start()), NORMAL_FONT, 0));
}
String part = matcher.group();
if ("
".equals(part)) {
partsList.add(new ProcessedPart("", NORMAL_FONT, 1));
} else if ("\n".equals(part)) {
partsList.add(new ProcessedPart("", NORMAL_FONT, 1));
} else {
handleFormatters(partsList, part);
}
lastEnd = matcher.end();
}
if (lastEnd < messageText.length()) {
partsList.add(new ProcessedPart(messageText.substring(lastEnd), NORMAL_FONT, 0));
}
return partsList;
}
private void handleFormatters(List partsList, String part) {
String substring = part.substring(3, part.length() - 4);
if (part.startsWith("") && part.endsWith("")) {
partsList.add(new ProcessedPart(substring, "bold", 0));
} else if (part.startsWith("") && part.endsWith("")) {
partsList.add(new ProcessedPart(substring, "italic", 0));
} else if (part.startsWith("") && part.endsWith("")) {
partsList.add(new ProcessedPart(substring, "strikethrough", 0));
} else if (part.startsWith("
Код: Выделить всё
") && part.endsWith("partsList.add(new ProcessedPart(part.substring(6, part.length() - 7), "monospace", 0));
}
}
private String sanitizeFilename(String filename) {
// Remove or replace characters that are not allowed in filenames
return filename.replaceAll("[\\\\/:*?\"|]", "_");
}
}
Я пробовал отображать смайлы как они есть, но имя клиента перенастраивается без смайликов «Esphi N» вместо этого «
Подробнее здесь: https://stackoverflow.com/questions/790 ... -usernames