2. Januar 2022

Ein Telegram-Bot zur Konvertierung von PDF-Dateien nach CSV

Von steffenboe
rss

In diesem Blogbeitrag wollen wir einen Telegram-Bot entwickeln, der PDF-Dateien in CSV Dateien konvertiert. Wir benutzen dafür Java und Maven.

Einrichten des Bots

Zunächst muss ein neuer Telegram-Bot eingerichtet werden. Dafür muss der Telegram-Bot @BotFather angeschrieben werden, wie in dieser Anleitung beschrieben. Der Bot legt einen neuen Bot an und gibt ein Bot-Token aus, der zur Kommunikation mit der Telegram-API verwendet wird. Unser Bot ist unter dem Namen „PDF>CSV“ erreichbar:

PDF>CSV Bot ist bereit!

Der Bot lernt laufen

Nun kann konkrete Funktionalität in den Bot implementiert werden. Dafür muss zunächst ein neues Maven-Projekt angelegt und folgende Abhängigkeit in die pom.xml eingefügt werden:

<dependency> <groupId>org.telegram</groupId> <artifactId>telegrambots</artifactId> <version>4.0.1</version> </dependency>
Code-Sprache: HTML, XML (xml)

Eine neue Klasse TelegramBot erweitert den TelegramLongPollingBot und implementiert dessen onUpdateReceived(Update update)-, getName()- und getBotToken()-Methode:

package com.example; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.api.objects.Update; public class TelegramBot extends TelegramLongPollingBot { /** * Method for receiving messages. * * @param update Contains a message from the user. */ @Override public void onUpdateReceived(Update update) { // TODO } /** * This method returns the bot's name, which was specified during registration. * * @return bot name */ @Override public String getBotUsername() { return "myCSV_bot"; } /** * This method returns the bot's token for communicating with the Telegram * server * * @return the bot's token */ @Override public String getBotToken() { return "<YOUR_BOT_TOKEN>"; } }
Code-Sprache: PHP (php)

In der main(String[] args)-Methode wird der Bot registriert:

public static void main( String[] args ) { ApiContextInitializer.init(); TelegramBotsApi telegramBotApi = new TelegramBotsApi(); try { telegramBotApi.registerBot(new TelegramBot()); } catch (TelegramApiRequestException e) { e.printStacktrace(); } }
Code-Sprache: JavaScript (javascript)

Herunterladen von Dokumenten

Nun muss eine vom Benutzer gesendete PDF-Datei empfangen werden. Angehängte Dokumente sind im Message-Objekt verfügbar:

@Override public void onUpdateReceived(Update update) { Document document = update.getMessage().getDocument(); }
Code-Sprache: JavaScript (javascript)

Das enthaltene Document-Objekt enthält nicht den Dateiinhalt an sich, aber eine fileId, die benutzt werden kann, um den Pfad zu der auf den Telegram-Servern gespeicherten Datei zu erhalten:

@Override public void onUpdateReceived(Update update) { Document document = update.getMessage().getDocument(); // mit der FileId erhalten wir weiter unten die File GetFile getFile = new GetFile(); getFile.setFileId(document.getFileId()); File documentFile = null; try { org.telegram.telegrambots.meta.api.objects.File file = execute(getFile); documentFile = downloadFile(file.getFilePath()); } catch (TelegramApiException e) { e.printStackTrace(); } }
Code-Sprache: JavaScript (javascript)

Der Aufruf der downloadFile(file.getFilePath())-Methode lädt das vom Benutzer hochgeladene Dokument als Datei herunter.

PDF -> CSV Konvertierung mit pdfbox und opencsv

Aus der heruntergeladenen Datei wird mithilfe von pdfbox ein PDDocument erzeugt. Dafür muss zunächst folgende Abhängigkeit in die pom.xml eingefügt werden:

<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>3.0.0-RC1</version> </dependency> <dependency> <groupId>com.opencsv</groupId> <artifactId>opencsv</artifactId> <version>4.4</version> </dependency>
Code-Sprache: HTML, XML (xml)

Nun kann das PDDocument und der CSVWriter erzeugt werden:

if(documentFile != null) { File csvFile = new File("./file.csv"); try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile)); PDDocument pdDocument = Loader.loadPDF(documentFile)) { // TODO } catch (IOException e) { e.printStacktrace(); } }
Code-Sprache: JavaScript (javascript)

Konvertierungslogik

Die eigentliche Konvertierungslogik ist abhängig von dem PDF-Dokument, das konvertiert werden soll. Für unser Beispiel überführen wir einen Transaktionsverlauf von einem Kontoauszug in das CSV-Format.

Quell-PDF

So eine PDF-Datei soll konvertiert werden:

Transaktionsverlauf

Ziel-CSV

Dieser Transaktionsverlauf soll in folgendes CSV-Format überführt werden:

"Buchungstag","Betrag","Verwendungszweck","Name" "15.12.2021","+30.73","N26 SPACE UMBUCHUNG","" "15.12.2021","-30.73","N26 SPACE UMBUCHUNG","" "31.12.2021","+10.00","N26 SPACE UMBUCHUNG",""
Code-Sprache: JavaScript (javascript)

Implementierung

Die Implementierung besteht aus 3 Schritten:

  • den gesamten Text aus der PDF-Datei lesen
  • Transaktionen per Pattern Matching erkennen
  • Transaktionen in eine CSV-Datei schreiben

#1 Text aus PDF lesen

Das erzeugte PDDocument ermöglicht es, sämtlichen Text aus der PDF-Datei zu lesen:

if(documentFile != null) { File csvFile = new File("./file.csv"); try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile)); PDDocument pdDocument = Loader.loadPDF(documentFile)) { // #1: den Inhalt der PDF auslesen String pdfContent = new PDFTextStripper().getText(pdDocument); } catch (IOException e) { e.printStacktrace(); } }
Code-Sprache: JavaScript (javascript)

#2 Transaktionen per Pattern-Matching erkennen

Die jeweiligen Transaktionen können über ein Pattern-Matching erkannt werden:

if(documentFile != null) { File csvFile = new File("./file.csv"); try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile)); PDDocument pdDocument = Loader.loadPDF(documentFile)) { String pdfContent = new PDFTextStripper().getText(pdDocument); // #2: Alle Transaktionen per Pattern-Matching filtern Pattern transactionPattern = Pattern.compile("\\d{1,2}\\.\\d{1,2}\\.\\d{4} [+-].*"); Matcher matcher = transactionPattern.matcher(pdfContent); } catch (IOException e) { e.printStacktrace(); } }
Code-Sprache: JavaScript (javascript)

Der Regex „\\d{1,2}\\.\\d{1,2}\\.\\d{4} [+-].*“ matcht auf das Datum, gefolgt von einem + oder Symbol. Damit erhält man eine Liste aller Transaktionen, also z.B.:

15.12.2021 +30,72 15.12.2021 -30,72 31.12.2021 +10,00
Code-Sprache: CSS (css)

#3 Transaktionen in CSV-Datei schreiben

Jeden dieser Strings wird nun noch einmal in Datum und Transaktion zerlegt und mit dem CSVWriter in eine CSV-Datei geschrieben:

if(documentFile != null) { File csvFile = new File("./file.csv"); try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile)); PDDocument pdDocument = Loader.loadPDF(documentFile)) { String pdfContent = new PDFTextStripper().getText(pdDocument); Pattern transactionPattern = Pattern.compile("\\d{1,2}\\.\\d{1,2}\\.\\d{4} [+-].*"); Matcher matcher = transactionPattern.matcher(pdfContent); // #3 Transaktionen in CSV-Datei schreiben List<String> transactions = new ArrayList<>(); String line1[] = { "Buchungstag", "Betrag", "Verwendungszweck", "Name" }; writer.writeNext(line1); while (matcher.find()) { transactions.add(matcher.group()); String date = matcher.group().split(" ")[0]; String transacted = matcher.group().split(" ")[1]; String newLine[] = { date, transacted.replace(EURO_IDENTIFIER, "").replace(",", "."), "N26 SPACE UMBUCHUNG", "" }; writer.writeNext(newLine); writer.flush(); } } catch (IOException e) { e.printStacktrace(); } }
Code-Sprache: JavaScript (javascript)

Die onUpdateReceived-Methode

Die gesamte onUpdateReceived(Update update)-Methode sieht nun so aus:

@Override public void onUpdateReceived(Update update) { // get the document from the message Document document = update.getMessage().getDocument(); GetFile getFile = new GetFile(); getFile.setFileId(document.getFileId()); File documentFile = null; try { org.telegram.telegrambots.meta.api.objects.File file = execute(getFile); documentFile = downloadFile(file.getFilePath()); } catch (TelegramApiException e) { e.printStackTrace(); } if(documentFile != null) { File csvFile = new File("./file.csv"); try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile)); PDDocument pdDocument = Loader.loadPDF(documentFile)) { String pdfContent = new PDFTextStripper().getText(pdDocument); Pattern transactionPattern = Pattern.compile("\\d{1,2}\\.\\d{1,2}\\.\\d{4} [+-].*"); Matcher matcher = transactionPattern.matcher(pdfContent); List<String> allTransactions = new ArrayList<>(); String[] header = { "Buchungstag", "Betrag", "Verwendungszweck", "Name" }; writer.writeNext(header); while (matcher.find()) { String match = matcher.group(); allTransactions.add(match); String date = match.split(" ")[0]; String transacted = match.split(" ")[1]; String[] newLine = { date, transacted.replace(EURO_IDENTIFIER, "").replace(",", "."), "N26 SPACE UMBUCHUNG", "" }; writer.writeNext(newLine); } writer.flush(); } catch (IOException e) { e.printStackTrace(); } } }
Code-Sprache: JavaScript (javascript)

Die CSV-Datei ist nun bereit, vom Telegram-Bot als Antwort auf die hochgeladene PDF-Datei versendet zu werden.

Versenden von Dokumenten

Um die generierte CSV-Datei zu Versenden, kann die SendDocument-Klasse benutzt werden:

SendDocument sendDocumentRequest = new SendDocument(); sendDocumentRequest.setChatId(update.getMessage().getChatId().toString()); sendDocumentRequest.setDocument(new InputFile(csvFile, csvFile.getName())); sendDocumentRequest.setCaption("Hurray"); try { execute(sendDocumentRequest); } catch (TelegramApiException e) { e.printStackTrace(); }
Code-Sprache: JavaScript (javascript)

Die Klasse TelegramBot sieht nun so aus:

package com.example; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.opencsv.CSVWriter; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.api.methods.GetFile; import org.telegram.telegrambots.meta.api.methods.send.SendDocument; import org.telegram.telegrambots.meta.api.objects.Document; import org.telegram.telegrambots.meta.api.objects.InputFile; import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; public class TelegramBot extends TelegramLongPollingBot { /** * */ private static final String EURO_IDENTIFIER = "\u20ac"; /** * Method for receiving messages. * * @param update Contains a message from the user. */ @Override public void onUpdateReceived(Update update) { // get the document from the message Document document = update.getMessage().getDocument(); GetFile getFile = new GetFile(); getFile.setFileId(document.getFileId()); File documentFile = null; try { org.telegram.telegrambots.meta.api.objects.File file = execute(getFile); documentFile = downloadFile(file.getFilePath()); } catch (TelegramApiException e) { e.printStackTrace(); } if(documentFile != null) { File csvFile = new File("./file.csv"); try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile)); PDDocument pdDocument = Loader.loadPDF(documentFile)) { String pdfContent = new PDFTextStripper().getText(pdDocument); Pattern transactionPattern = Pattern.compile("\\d{1,2}\\.\\d{1,2}\\.\\d{4} [+-].*"); Matcher matcher = transactionPattern.matcher(pdfContent); List<String> transactions = new ArrayList<>(); String line1[] = { "Buchungstag", "Betrag", "Verwendungszweck", "Name" }; writer.writeNext(line1); while (matcher.find()) { transactions.add(matcher.group()); String date = matcher.group().split(" ")[0]; String transacted = matcher.group().split(" ")[1]; String newLine[] = { date, transacted.replace(EURO_IDENTIFIER, "").replace(",", "."), "N26 SPACE UMBUCHUNG", "" }; writer.writeNext(newLine); writer.flush(); } } catch (IOException e) { e.printStackTrace(); } SendDocument sendDocumentRequest = new SendDocument(); sendDocumentRequest.setChatId(update.getMessage().getChatId().toString()); sendDocumentRequest.setDocument(new InputFile(csvFile, csvFile.getName())); sendDocumentRequest.setCaption("Hurray"); try { execute(sendDocumentRequest); } catch (TelegramApiException e) { e.printStackTrace(); } } } /** * This method returns the bot's name, which was specified during registration. * * @return bot name */ @Override public String getBotUsername() { return "myCSV_bot"; } /** * This method returns the bot's token for communicating with the Telegram * server * * @return the bot's token */ @Override public String getBotToken() { return "50522352369:AAE9IQpU5ewcYYRa9UxU5Fgdh8274AhqwertnotmyrealtokenpQPXKg"; } }
Code-Sprache: JavaScript (javascript)

Wird dem Bot nun die entsprechende PDF-Datei gesendet, antwortet er mit der konvertierten CSV-Datei:

Konvertierung der PDF durch den Telegram Bot

Das war´s! So können PDF-Dateien von einem Telegram-Bot in CSV-Dateien umgewandelt werden. Abschließend muss eine ausführbare .jar-Datei gebaut werden, um den Bot auszuliefern.

Bauen eines .jar-Archives für die Auslieferung

Wo der Bot ausgeführt wird, ist letztendlich egal; nur eine Internetverbindung muss vorhanden sein, damit die Anwendung mit den Telegram-Servern kommunizieren kann. Damit Maven eine ausführbare .jar-Datei erstellen kann, muss der folgende <build>-Abschnitt in die pom.xml eingefügt werden:

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass> com.example.App <!-- eure Main Klasse --> </mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> </plugins> </build>
Code-Sprache: HTML, XML (xml)

Durch Ausführen von

mvn clean verify

wird ein .jar-Archiv erzeugt, das alle benötigten Abhängigkeiten enthält (beachtet den jar-with-dependencies Suffix!) und direkt mit java -jar ausgeführt werden kann.

rss