@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
che passo al gestore (proprio classe UpdatesReceiver):
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(), "Я могу принимать только текстовые messages!");
}
}
UpdatesReceiver è un gestore centrale che, a seconda del tipo di aggiornamento, trasferisce il controllo a un altro gestore specializzato: TextMessageHandler o CallbackQueryHandler, ai cui costruttori passo l'aggiornamento più a valle della catena. L'aggiornamento è la cosa più importante quando si lavora con un bot e non può essere perso, perché con l'aiuto delle informazioni memorizzate nell'aggiornamento scopriamo a quale utente e a quale chat deve essere inviata la risposta. Per generare risposte all'utente, ho scritto una classe separata. Può inviare normali messaggi di testo, messaggi con tastiera in linea, messaggi con immagine e messaggi con tastiera di risposta. Una tastiera in linea si presenta così: definisce i pulsanti sui quali, cliccando su di essi, l'utente invia una richiamata al server, che può essere elaborata quasi allo stesso modo dei normali messaggi. Per “mantenerlo” è necessario il proprio gestore. Impostiamo un'azione per ciascun pulsante, che viene poi scritta nell'oggetto Update. Quelli. per il pulsante "Costo" impostiamo la descrizione "/prezzo" per la richiamata, che potremo successivamente ottenere dall'aggiornamento. Successivamente, in una classe separata, posso già elaborare questo callback:
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;
...
La tastiera di risposta si presenta così: E in sostanza sostituisce la digitazione dell'utente. Facendo clic sul pulsante "Libreria" verrà inviato rapidamente un messaggio "Libreria" al bot. Per ogni tipo di tastiera ho scritto la mia classe, implementando il pattern Builder: inline e repliche . Di conseguenza, puoi essenzialmente "disegnare" la tastiera desiderata a seconda delle tue esigenze. Questo è terribilmente conveniente, poiché le tastiere possono essere diverse, ma il principio rimane lo stesso. Ecco un metodo intuitivo per inviare un messaggio con una tastiera in linea:
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());
}
}
Per dare al bot una funzionalità rigorosa, sono stati inventati comandi speciali che utilizzano il carattere barra: /library, /help, /game, ecc. Altrimenti dovremmo elaborare tutta la spazzatura che l'utente potrebbe scrivere. In realtà, questo è lo scopo per cui è stato scritto MessageHandler:
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());
}
...
Pertanto, a seconda del comando inviato al bot, nel lavoro verrà incluso un gestore speciale. Andiamo oltre e guardiamo il lavoro del parser e della libreria. Se invii al bot un collegamento a un gioco nel Google Play Store, funzionerà automaticamente un gestore speciale . In risposta, l'utente riceverà informazioni sul gioco nel seguente formato: Allo stesso tempo, verrà chiamato un metodo che proverà ad aggiungere il gioco alla libreria del bot (prima sulla mappa locale, poi nel -> file json ). Se il gioco è già nella libreria, verrà effettuato un controllo (come in una normale hashmap) e se i dati del campo (ad esempio, il numero di versione è cambiato), il gioco nella libreria verrà sovrascritto. Se non vengono rilevate modifiche, non verrà effettuata alcuna voce. Se non c'era alcun gioco nella libreria, viene prima scritto sulla mappa locale (un oggetto come tyk ), e poi scritto in un file json, poiché se l'applicazione sul server viene chiusa inaspettatamente, i dati verranno perso, ma può sempre essere letto utilizzando il file. In realtà, all'avvio del programma, la libreria viene sempre caricata per la prima volta da un file proveniente da un blocco statico:
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("[Ошибка при чтении/записи file] {}", e.getMessage());
}
}
Qui devi inoltre leggere i dati dal file in una mappa temporanea, che viene poi “copiata” in una mappa completa per mantenere l'insensibilità alle maiuscole e minuscole durante la ricerca di un gioco nel file (scrivendo tITan QuEST, il bot troverà comunque il gioco Titan Quest nella libreria). Non è stato possibile trovare un'altra soluzione, queste sono le caratteristiche della deserializzazione tramite Jackson. Quindi, ad ogni richiesta di collegamento, il gioco viene aggiunto alla libreria, se possibile, e la libreria si espande così. Ulteriori informazioni su un gioco specifico possono essere ottenute utilizzando il comando /libraryGame_Name. Puoi scoprire sia un parametro specifico (ad esempio, la versione corrente) sia tutti i parametri contemporaneamente. Ciò viene implementato utilizzando la tastiera in linea, di cui abbiamo parlato in precedenza. Durante il lavoro ho anche applicato le competenze qui acquisite risolvendo i problemi. Ad esempio, un elenco di nomi di giochi casuali presenti nella libreria (l'opzione è disponibile utilizzando il comando /library):
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);
}
In che modo il bot elabora i collegamenti? Li controlla per vedere se appartengono a Google Play (host, protocollo, porta):
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;
}
}
Se tutto è in ordine, il bot si connette tramite un collegamento utilizzando la libreria Jsoup, che consente di ottenere il codice HTML della pagina, che è soggetto a ulteriori analisi e parsing. Non potrai ingannare il bot con un collegamento errato o dannoso.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Qui abbiamo dovuto risolvere un problema con le impostazioni regionali. Il bot si collega al Google Play Store da un server situato in Europa, quindi la pagina nel Google Play Store si apre nella lingua appropriata. Ho dovuto scrivere una stampella che “reindirizza” forzatamente alla versione russa della pagina (il progetto, dopotutto, era rivolto al nostro pubblico). Per fare ciò, alla fine del collegamento è necessario aggiungere con attenzione il parametro hl: &hl=ru nella richiesta GET al server di Google Play .
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;
}
Dopo una connessione riuscita, riceviamo un documento HTML pronto per l'analisi e il parsing, ma questo va oltre lo scopo di questo articolo. Il codice del parser è qui . Il parser stesso recupera le informazioni necessarie e crea un oggetto con il gioco, che viene successivamente aggiunto alla libreria, se necessario. <h2>Riassumendo</h2>Il bot supporta diversi comandi che contengono determinate funzionalità. Riceve messaggi dall'utente e li abbina ai suoi comandi. Se si tratta di un collegamento o del comando /game + link, controlla quel collegamento per vedere se appartiene a Google Play. Se il collegamento è corretto, si connette tramite Jsoup e riceve il documento HTML. Questo documento viene analizzato in base al parser scritto. Le informazioni necessarie sul gioco vengono estratte dal documento e quindi l'oggetto con il gioco viene riempito con questi dati. Successivamente, l'oggetto con il gioco viene inserito nella memoria locale (se il gioco non è ancora presente) e immediatamente scritto in un file per evitare la perdita di dati. Un gioco registrato nella libreria (il nome del gioco è la chiave per la mappa, l'oggetto con il gioco è il valore per la mappa) può essere ottenuto utilizzando il comando /library Game_name. Se il gioco specificato viene trovato nella libreria del bot, all'utente verrà restituita una tastiera in linea, con la quale potrà ottenere informazioni sul gioco. Se il gioco non viene trovato, devi assicurarti che il nome sia scritto correttamente (deve corrispondere completamente al nome del gioco nel Google Play Store, salvo caso), oppure aggiungere il gioco alla libreria inviando il bot un collegamento al gioco. Ho distribuito il bot su heroku e per coloro che in futuro intendono scrivere il proprio bot e ospitarlo gratuitamente su heroku, darò un paio di consigli per risolvere le difficoltà che potresti incontrare (poiché le ho incontrate io stesso). Sfortunatamente, a causa della natura di Heroku, la libreria dei bot viene costantemente “reimpostata” una volta ogni 24 ore. Il mio piano non supporta l'archiviazione di file sui server Heroku, quindi estrae semplicemente il mio file di gioco da Github. C'erano diverse soluzioni: utilizzare un database o cercare un altro server che archiviasse questo file con il gioco. Ho deciso di non fare nulla per ora, dato che sostanzialmente il bot non è poi così utile. Ne avevo bisogno piuttosto per fare un'esperienza completa, che in fondo è ciò che ho ottenuto. Quindi, consigli per Heroku:
-
Molto probabilmente dovrai registrarti su heroku utilizzando una VPN se vivi in Russia.
-
Alla radice del progetto bisogna mettere un file senza estensione chiamato Procfile. Il suo contenuto dovrebbe essere così: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
In pom.xml, aggiungi le seguenti righe secondo l'esempio , dove nel tag mainClass indica il percorso della classe che contiene il metodo principale: bot.BotApplication (se la classe BotApplication si trova nella cartella bot).
-
Non creare alcun progetto utilizzando i comandi del pacchetto mvn, ecc., Heroku assemblerà tutto per te.
-
È consigliabile aggiungere un gitignore al progetto, ad esempio questo:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Carica effettivamente il progetto su github e poi collega il repository a Heroku (o usa altri metodi, ce ne sono 3, se non sbaglio).
-
Se il download è andato a buon fine ("Build riuscita"), assicurati di andare su Configura Dynos:
e cambia il cursore, quindi assicurati che sia in posizione ON (a causa del fatto che non l'ho fatto, il mio bot non ha funzionato e mi sono scervellato per un paio di giorni e ho fatto molti movimenti non necessari ).
-
Nascondi il token del bot su Github. Per fare ciò, devi ottenere il token dalla variabile d'ambiente:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Quindi, dopo aver distribuito il bot, imposta questa variabile nella dashboard di Heroku nella scheda Impostazioni (a destra di TOKEN ci sarà un campo VALUE, copia lì il token del tuo bot):
- ricevuto un progetto completamente funzionante scritto in Java;
- imparato a lavorare con API di terze parti (API Bot di Telegram);
- in pratica ho approfondito la serializzazione, ho lavorato molto con JSON e la libreria Jackson (inizialmente usavo GSON, ma c'erano dei problemi);
- ho rafforzato le mie capacità quando lavoro con i file, ho conosciuto Java NIO;
- ho imparato a lavorare con i file di configurazione .xml e mi sono abituato al logging;
- miglioramento della competenza nell'ambiente di sviluppo (IDEA);
- ho imparato a lavorare con git e ho imparato il valore di gitignore;
- acquisita competenze nel parsing di pagine web (libreria Jsoup);
- appreso e utilizzato diversi modelli di progettazione;
- sviluppato il senso e il desiderio di migliorare il codice (refactoring);
- Ho imparato a trovare soluzioni online e a non essere timido nel fare domande alle quali non riuscivo a trovare una risposta.
GO TO FULL VERSION