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

In diesem Blogbeitrag wollen wir einen Telegram-Bot entwickeln, der PDF-Dateien in CSV Dateien konvertiert. Wir benutzen dafür Java und Maven.
Inhalt
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:

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:

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:

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.
