@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
was ich an den Handler (eigene UpdatesReceiver-Klasse) übergebe:
public static void handleUpdates(Update update) {
...
if (update.hasMessage() && update.getMessage().hasText()){
log.info("[Update (id {}) типа \"Текстовое сообщение\"]", update.getUpdateId());
new TextMessageHandler(update, replyGenerator).handleTextMessage();
}
else if (update.hasCallbackQuery()) {
//логгирование
new CallbackQueryHandler(update, replyGenerator).handleCallBackQuery();
}
else {
//логгирование
replyGenerator.sendTextMessage(update.getMessage().getChatId(), "Я могу принимать только текстовые Mitteilungen!");
}
}
UpdatesReceiver ist ein zentraler Handler, der je nach Art des Updates die Kontrolle an einen anderen spezialisierten Handler übergibt: TextMessageHandler oder CallbackQueryHandler, an dessen Konstruktoren ich update weiter unten in der Kette übergebe. Das Update ist das Wichtigste bei der Arbeit mit einem Bot und kann nicht verloren gehen, denn mithilfe der im Update gespeicherten Informationen finden wir heraus, an welchen Benutzer und an welchen Chat die Antwort gesendet werden soll. Um Antworten an den Benutzer zu generieren, habe ich eine separate Klasse geschrieben. Es kann normale Textnachrichten, Nachrichten mit integrierter Tastatur, Nachrichten mit Bildern und Nachrichten mit Antworttastatur senden. Eine Inline-Tastatur sieht so aus: Sie definiert Schaltflächen, bei deren Anklicken der Benutzer einen Rückruf an den Server sendet, der fast genauso wie normale Nachrichten verarbeitet werden kann. Um es zu „pflegen“, benötigen Sie einen eigenen Handler. Wir legen für jede Schaltfläche eine Aktion fest, die dann in das Update-Objekt geschrieben wird. Diese. Für den Button „Kosten“ setzen wir die Beschreibung „/Preis“ für den Rückruf, die wir später aus dem Update erhalten können. Als nächstes kann ich in einer separaten Klasse diesen Callback bereits verarbeiten:
public void handleCallBackQuery() {
String call_data = update.getCallbackQuery().getData();
long message_id = update.getCallbackQuery().getMessage().getMessageId();
long chat_id = update.getCallbackQuery().getMessage().getChatId();
switch (call_date){
case "/price" :
//тут что-то сделать
break;
...
Die Reply-Tastatur sieht so aus: Und im Wesentlichen ersetzt sie das Tippen des Benutzers. Durch Klicken auf die Schaltfläche „Bibliothek“ wird schnell eine „Bibliothek“-Nachricht an den Bot gesendet. Für jeden Tastaturtyp habe ich meine eigene Klasse geschrieben und das Builder-Muster implementiert: inline und Reply . Dadurch können Sie im Wesentlichen die gewünschte Tastatur entsprechend Ihren Anforderungen „zeichnen“. Das ist unheimlich praktisch, da die Tastaturen zwar unterschiedlich sind, das Prinzip aber das gleiche bleibt. Hier ist eine intuitive Methode zum Senden einer Nachricht mit einer Inline-Tastatur:
public synchronized void sendInlineKeyboardMessage(long chat_id, String gameTitle) {
SendMessage keyboard = InlineKeyboardMarkupBuilder.create(chat_id)
.setText("Вы может узнать следующую информацию об игре " + gameTitle)
.row()
.button("Стоимость " + "\uD83D\uDCB0", "/price " + gameTitle)
.button("Обновлено " + "\uD83D\uDDD3", "/updated " + gameTitle)
.button("Версия " + "\uD83D\uDEE0", "/version " + gameTitle)
.endRow()
.row()
.button("Требования " + "\uD83D\uDCF5", "/requirements " + gameTitle)
.button("Покупки " + "\uD83D\uDED2", "/iap " + gameTitle)
.button("Размер " + "\uD83D\uDD0E", "/size " + gameTitle)
.endRow()
.row()
.button("Получить всю информацию об игре" + "\uD83D\uDD79", "/all " + gameTitle)
.endRow()
.row()
.button("Скрыть клавиатуру", "close")
.endRow()
.build();
try {
execute(keyboard);
} catch (TelegramApiException e) {
log.error("[Не удалось отправить сообщение с -inline- клавиатурой]: {}", e.getMessage());
}
}
Um dem Bot eine strenge Funktionalität zu verleihen, wurden spezielle Befehle erfunden, die den Schrägstrich verwenden: /library, /help, /game usw. Andernfalls müssten wir jeglichen Müll verarbeiten, den der Benutzer möglicherweise schreibt. Eigentlich wurde MessageHandler dafür geschrieben:
if (message.equals(ChatCommands.START.getDescription())) {
replyGenerator.sendTextMessage(chat_id, new StartMessageHandler().reply());
replyGenerator.sendReplyKeyboardMessage(chat_id);
}
else if (message.equals(ChatCommands.HELP.getDescription())
|| message.equalsIgnoreCase("Помощь")) {
replyGenerator.sendTextMessage(chat_id, new HelpMessageHandler().reply());
}
...
Je nachdem, welchen Befehl Sie an den Bot senden, wird also ein spezieller Handler in die Arbeit einbezogen. Gehen wir weiter und schauen wir uns die Arbeit des Parsers und der Bibliothek an. Wenn Sie dem Bot einen Link zu einem Spiel im Google Play Store senden, wird automatisch ein spezieller Handler aktiviert . Als Antwort erhält der Benutzer Informationen über das Spiel in folgender Form: Gleichzeitig wird eine Methode aufgerufen, die versucht, das Spiel zur Bibliothek des Bots hinzuzufügen (zuerst zur lokalen Karte, dann zur -> JSON-Datei). ). Befindet sich das Spiel bereits in der Bibliothek, wird eine Prüfung durchgeführt (wie bei einer regulären Hashmap) und wenn sich die Felddaten (z. B. die Versionsnummer) geändert haben, wird das Spiel in der Bibliothek überschrieben. Wenn keine Änderungen festgestellt werden, werden keine Einträge vorgenommen. Wenn in der Bibliothek überhaupt kein Spiel vorhanden war, wird es zuerst auf die lokale Karte (ein Objekt wie tyk ) und dann in eine JSON-Datei geschrieben, da die Daten gelöscht werden, wenn die Anwendung auf dem Server unerwartet geschlossen wird verloren, kann aber jederzeit über die Datei gelesen werden. Tatsächlich wird die Bibliothek beim Programmstart immer zum ersten Mal aus einer Datei aus einem statischen Block geladen:
static {
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(ConcurrentSkipListMap.class, String.class, GooglePlayGame.class);
try {
Path path = Paths.get(LIBRARY_PATH);
if (!Files.exists(path)) {
Files.createDirectories(path.getParent());
Files.createFile(path);
log.info("[Файл библиотеки создан]");
}
else {
ConcurrentMap<string, googleplaygame=""> temporary = mapper.readValue(new File(LIBRARY_PATH), mapType);
games.putAll(temporary);
log.info("[Количество игр в загруженной библиотеке] = " + games.size());
}
}
catch (IOException e) {
log.error("[Ошибка при чтении/записи Datei] {}", e.getMessage());
}
}
Hier müssen Sie zusätzlich Daten aus der Datei in eine temporäre Karte einlesen, die dann in eine vollständige Karte „kopiert“ wird, um bei der Suche nach einem Spiel in der Datei die Groß-/Kleinschreibung zu wahren (durch Schreiben von tITan QuEST wird der Bot trotzdem finden). das Spiel Titan Quest in der Bibliothek). Eine andere Lösung konnte nicht gefunden werden, das sind die Merkmale der Deserialisierung mit Jackson. Bei jeder Anfrage nach einem Link wird das Spiel also nach Möglichkeit zur Bibliothek hinzugefügt und die Bibliothek dadurch erweitert. Weitere Informationen zu einem bestimmten Spiel erhalten Sie mit dem Befehl /libraryGame_Name. Sie können sowohl einen bestimmten Parameter (zum Beispiel die aktuelle Version) als auch alle Parameter auf einmal herausfinden. Dies wird über die zuvor besprochene Inline-Tastatur implementiert. Während der Arbeit habe ich die hier erworbenen Fähigkeiten auch bei der Lösung von Problemen angewendet. Zum Beispiel eine Liste mit Namen von Zufallsspielen, die sich in der Bibliothek befinden (die Option ist mit dem Befehl /library verfügbar):
private String getRandomTitles(){
if (LibraryService.getLibrary().size() < 10){
return String.join("\n", LibraryService.getLibrary().keySet());
}
List<string> keys = new ArrayList<>(LibraryService.getLibrary().keySet());
Collections.shuffle(keys);
List<string> randomKeys = keys.subList(0, 10);
return String.join("\n", randomKeys);
}
Wie verarbeitet der Bot Links? Es prüft, ob sie zu Google Play gehören (Host, Protokoll, Port):
private static class GooglePlayCorrectURL {
private static final String VALID_HOST = "play.google.com";
private static final String VALID_PROTOCOL = "https";
private static final int VALID_PORT = -1;
private static boolean isLinkValid(URI link) {
return (isHostExist(link) && isProtocolExist(link) && link.getPort() == VALID_PORT);
}
private static boolean isProtocolExist(URI link) {
if (link.getScheme() != null) {
return link.getScheme().equals(VALID_PROTOCOL);
}
else {
return false;
}
}
private static boolean isHostExist(URI link) {
if (link.getHost() != null) {
return link.getHost().equals(VALID_HOST);
}
else {
return false;
}
}
Wenn alles in Ordnung ist, verbindet sich der Bot über einen Link mit der Jsoup-Bibliothek, wodurch Sie den HTML-Code der Seite abrufen können, der einer weiteren Analyse und Analyse unterzogen wird. Sie können den Bot nicht mit einem falschen oder schädlichen Link täuschen.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Hier mussten wir ein Problem mit regionalen Einstellungen lösen. Der Bot stellt von einem Server in Europa eine Verbindung zum Google Play Store her, sodass die Seite im Google Play Store in der entsprechenden Sprache geöffnet wird. Ich musste eine Krücke schreiben, die zwangsweise zur russischen Version der Seite „umleitet“ (das Projekt war schließlich an unser Publikum gerichtet). Dazu müssen Sie am Ende des Links sorgfältig den Parameter hl: &hl=ru in die GET-Anfrage an den Google Play-Server einfügen .
private String forceToRusLocalization(String URL) {
if (URL.endsWith("&hl=ru")){
return URL;
}
else {
if (URL.contains("&hl=")){
URL = URL.replace(
URL.substring(URL.length()-"&hl=ru".length()), "&hl=ru");
}
else {
URL += "&hl=ru";
}
}
return URL;
}
Nach einer erfolgreichen Verbindung erhalten wir ein HTML-Dokument, das zur Analyse und Analyse bereit ist. Dies würde jedoch den Rahmen dieses Artikels sprengen. Der Parser-Code ist hier . Der Parser selbst ruft die notwendigen Informationen ab und erstellt ein Objekt mit dem Spiel, das später bei Bedarf der Bibliothek hinzugefügt wird. <h2>Zusammenfassend</h2>Der Bot unterstützt mehrere Befehle, die bestimmte Funktionen enthalten. Es empfängt Nachrichten vom Benutzer und ordnet sie seinen Befehlen zu. Wenn es sich um einen Link oder den Befehl /game + link handelt, prüft es, ob dieser Link zu Google Play gehört. Wenn der Link korrekt ist, verbindet er sich über Jsoup und empfängt das HTML-Dokument. Dieses Dokument wird basierend auf dem geschriebenen Parser analysiert. Aus dem Dokument werden die notwendigen Informationen zum Spiel extrahiert und anschließend das Objekt mit dem Spiel mit diesen Daten gefüllt. Als nächstes wird das Objekt mit dem Spiel im lokalen Speicher abgelegt (sofern das Spiel noch nicht vorhanden ist) und sofort in eine Datei geschrieben, um Datenverlust zu vermeiden. Ein in der Bibliothek aufgezeichnetes Spiel (der Name des Spiels ist der Schlüssel für die Karte, das Objekt mit dem Spiel ist der Wert für die Karte) kann mit dem Befehl /library Game_name abgerufen werden. Wenn das angegebene Spiel in der Bibliothek des Bots gefunden wird, erhält der Benutzer eine Inline-Tastatur zurück, mit der er Informationen über das Spiel erhalten kann. Wenn das Spiel nicht gefunden wird, müssen Sie entweder sicherstellen, dass der Name richtig geschrieben ist (er muss bis auf die Groß-/Kleinschreibung vollständig mit dem Namen des Spiels im Google Play Store übereinstimmen) oder das Spiel durch Senden des Bots zur Bibliothek hinzufügen ein Link zum Spiel. Ich habe den Bot auf Heroku bereitgestellt und für diejenigen, die in Zukunft planen, ihren eigenen Bot zu schreiben und ihn kostenlos auf Heroku zu hosten, werde ich ein paar Empfehlungen zur Lösung der Schwierigkeiten geben, auf die Sie möglicherweise stoßen (da ich selbst auf sie gestoßen bin). Leider wird die Bot-Bibliothek aufgrund der Natur von Heroku ständig einmal alle 24 Stunden „zurückgesetzt“. Mein Plan unterstützt das Speichern von Dateien auf Heroku-Servern nicht, daher ruft er meine Spieldatei einfach von Github ab. Es gab mehrere Lösungen: Verwenden Sie eine Datenbank oder suchen Sie nach einem anderen Server, der diese Datei mit dem Spiel speichert. Ich habe beschlossen, vorerst nichts zu unternehmen, da der Bot im Grunde genommen nicht so nützlich ist. Ich brauchte es eher, um eine umfassende Erfahrung zu sammeln, und das ist im Grunde das, was ich erreicht habe. Also, Empfehlungen für Heroku:
-
Wenn Sie in Russland leben, müssen Sie sich höchstwahrscheinlich über ein VPN bei Heroku registrieren.
-
Im Stammverzeichnis des Projekts müssen Sie eine Datei ohne Erweiterung namens Procfile ablegen. Der Inhalt sollte so aussehen: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
Fügen Sie in pom.xml die folgenden Zeilen entsprechend dem Beispiel hinzu, wobei im mainClass-Tag der Pfad zur Klasse angegeben wird, die die Hauptmethode enthält: bot.BotApplication (wenn sich die BotApplication-Klasse im Bot-Ordner befindet).
-
Erstellen Sie kein Projekt mit MVN-Paketbefehlen usw., Heroku stellt alles für Sie zusammen.
-
Es empfiehlt sich, dem Projekt einen Gitignore hinzuzufügen, zum Beispiel diesen:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Laden Sie das Projekt tatsächlich auf Github hoch und verbinden Sie dann das Repository mit Heroku (oder verwenden Sie andere Methoden, es gibt drei davon, wenn ich mich nicht irre).
-
Wenn der Download erfolgreich war („Build erfolgreich“), gehen Sie unbedingt zu „Dynos konfigurieren“:
und schalten Sie den Schieberegler um und stellen Sie dann sicher, dass er auf ON steht (da ich dies nicht getan habe, hat mein Bot nicht funktioniert und ich habe mir ein paar Tage lang den Kopf zerbrochen und viele unnötige Bewegungen gemacht ).
-
Verstecken Sie den Bot-Token auf Github. Dazu müssen Sie das Token aus der Umgebungsvariablen abrufen:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Und dann, nachdem Sie den Bot bereitgestellt haben, legen Sie diese Variable im Heroku-Dashboard auf der Registerkarte „Einstellungen“ fest (rechts neben TOKEN befindet sich ein WERT-Feld, kopieren Sie den Token Ihres Bots dorthin):
- ein voll funktionsfähiges, in Java geschriebenes Projekt erhalten;
- gelernt, mit der API eines Drittanbieters (Telegram Bot API) zu arbeiten;
- In der Praxis habe ich mich tiefer mit der Serialisierung beschäftigt, viel mit JSON und der Jackson-Bibliothek gearbeitet (anfangs habe ich GSON verwendet, aber es gab Probleme damit);
- Ich habe meine Fähigkeiten im Umgang mit Dateien gestärkt und mich mit Java NIO vertraut gemacht.
- habe den Umgang mit Konfigurations-XML-Dateien gelernt und mich an die Protokollierung gewöhnt;
- verbesserte Kenntnisse in der Entwicklungsumgebung (IDEA);
- lernte, mit Git zu arbeiten und lernte den Wert von Gitignore kennen;
- erlangte Kenntnisse im Parsen von Webseiten (Jsoup-Bibliothek);
- erlernte und nutzte mehrere Entwurfsmuster;
- entwickelte ein Gespür und den Wunsch, Code zu verbessern (Refactoring);
- Ich habe gelernt, Lösungen online zu finden und mich nicht zu scheuen, Fragen zu stellen, auf die ich keine Antwort finden konnte.
GO TO FULL VERSION