@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
que je transmets au gestionnaire (propre 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 est un gestionnaire central qui, selon le type de mise à jour, transfère le contrôle à un autre gestionnaire spécialisé : TextMessageHandler ou CallbackQueryHandler, aux constructeurs desquels je transmets la mise à jour plus loin dans la chaîne. La mise à jour est la chose la plus importante lorsque l'on travaille avec un bot et ne peut pas être perdue, car grâce aux informations stockées dans la mise à jour, nous découvrons quel utilisateur et à quel chat la réponse doit être envoyée. Pour générer des réponses à l'utilisateur, j'ai écrit une classe distincte. Il peut envoyer un message texte normal, un message avec un clavier en ligne, un message avec une image et un message avec un clavier de réponse. Un clavier en ligne ressemble à ceci : il définit des boutons qui, en cliquant dessus, l'utilisateur envoie un rappel au serveur, qui peut être traité presque de la même manière que des messages normaux. Pour le « maintenir », vous avez besoin de votre propre gestionnaire. Nous définissons une action pour chaque bouton, qui est ensuite écrite dans l'objet Update. Ceux. pour le bouton "Coût", nous définissons la description "/prix" pour le rappel, que nous pourrons obtenir plus tard grâce à la mise à jour. De plus, dans une classe distincte, je peux déjà traiter ce rappel :
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;
...
Le clavier de réponse ressemble à ceci : Et essentiellement, il remplace la saisie de l'utilisateur. Cliquer sur le bouton « Bibliothèque » enverra rapidement un message « Bibliothèque » au bot. Pour chaque type de clavier, j'ai écrit ma propre classe, en implémentant le modèle Builder : inline etanswer . En conséquence, vous pouvez essentiellement « dessiner » le clavier souhaité en fonction de vos besoins. C'est terriblement pratique, puisque les claviers peuvent être différents, mais le principe reste le même. Voici une méthode intuitive pour envoyer un message avec un clavier en ligne :
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());
}
}
Pour donner au bot une fonctionnalité stricte, des commandes spéciales utilisant le caractère slash ont été inventées : /library, /help, /game, etc. Sinon, nous devrions traiter tous les déchets que l'utilisateur pourrait écrire. En fait, c'est pour cela que MessageHandler a été écrit :
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());
}
...
Ainsi, en fonction de la commande que vous envoyez au bot, un gestionnaire spécial sera inclus dans le travail. Allons plus loin et regardons le travail de l'analyseur et de la bibliothèque. Si vous envoyez au bot un lien vers un jeu dans le Google Play Store, un gestionnaire spécial fonctionnera automatiquement . En réponse, l'utilisateur recevra des informations sur le jeu sous la forme suivante : En même temps, une méthode sera appelée qui tentera d'ajouter le jeu à la bibliothèque du bot (d'abord sur la carte locale, puis vers -> fichier json ). Si le jeu est déjà dans la bibliothèque, alors une vérification sera effectuée (comme dans un hashmap classique), et si les données du champ (par exemple, le numéro de version ont changé), alors le jeu dans la bibliothèque sera écrasé. Si aucun changement n’est détecté, aucune entrée ne sera effectuée. S'il n'y avait aucun jeu dans la bibliothèque, il est d'abord écrit sur la carte locale (un objet comme tyk ), puis écrit dans un fichier json, car si l'application sur le serveur est fermée de manière inattendue, les données seront perdu, mais il peut toujours être lu à l'aide du fichier. En effet, au démarrage du programme, la bibliothèque est toujours chargée pour la première fois à partir d'un fichier issu d'un bloc statique :
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());
}
}
Ici, vous devez en outre lire les données du fichier dans une carte temporaire, qui est ensuite « copiée » dans une carte complète afin de maintenir l'insensibilité à la casse lors de la recherche d'un jeu dans le fichier (en écrivant tITan QuEST, le bot trouvera toujours le jeu Titan Quest dans la bibliothèque). Il n'a pas été possible de trouver une autre solution, ce sont les particularités de la désérialisation utilisant Jackson. Ainsi, à chaque demande de lien, le jeu est ajouté à la bibliothèque, si possible, et la bibliothèque s'agrandit ainsi. De plus amples informations sur un jeu spécifique peuvent être obtenues à l'aide de la commande /libraryGame_Name. Vous pouvez connaître à la fois un paramètre spécifique (par exemple, la version actuelle) et tous les paramètres à la fois. Ceci est implémenté à l’aide du clavier en ligne, dont il a été question précédemment. Pendant le travail, j'ai également appliqué les compétences acquises ici tout en résolvant des problèmes. Par exemple, une liste de noms de jeux aléatoires situés dans la bibliothèque (l'option est disponible via la commande /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);
}
Comment le bot traite-t-il les liens ? Il les vérifie pour voir s'ils appartiennent à Google Play (hôte, protocole, 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;
}
}
Si tout est en ordre, alors le bot se connecte via un lien utilisant la bibliothèque Jsoup, qui permet d'obtenir le code HTML de la page, qui fait l'objet d'une analyse et d'une analyse plus approfondies. Vous ne pourrez pas tromper le bot avec un lien incorrect ou nuisible.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Ici, nous avons dû résoudre un problème avec les paramètres régionaux. Le bot se connecte au Google Play Store à partir d'un serveur situé en Europe, de sorte que la page du Google Play Store s'ouvre dans la langue appropriée. J'ai dû écrire une béquille qui « redirige » de force vers la version russe de la page (le projet était, après tout, destiné à notre public). Pour ce faire, à la fin du lien, vous devez ajouter soigneusement le paramètre hl: &hl=ru dans la requête GET au serveur 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;
}
Après une connexion réussie, nous recevons un document HTML prêt à être analysé et analysé, mais cela dépasse le cadre de cet article. Le code de l'analyseur est ici . L'analyseur lui-même récupère les informations nécessaires et crée un objet avec le jeu, qui est ensuite ajouté à la bibliothèque si nécessaire. <h2>Pour résumer</h2>Le bot prend en charge plusieurs commandes qui contiennent certaines fonctionnalités. Il reçoit les messages de l'utilisateur et les associe à ses commandes. S'il s'agit d'un lien ou de la commande /game + link, il vérifie ce lien pour voir s'il appartient à Google Play. Si le lien est correct, il se connecte via Jsoup et reçoit le document HTML. Ce document est analysé sur la base de l'analyseur écrit. Les informations nécessaires sur le jeu sont extraites du document, puis l'objet contenant le jeu est rempli de ces données. Ensuite, l'objet contenant le jeu est placé dans le stockage local (si le jeu n'y est pas encore) et immédiatement écrit dans un fichier pour éviter la perte de données. Un jeu enregistré dans la bibliothèque (le nom du jeu est la clé de la carte, l'objet avec le jeu est la valeur de la carte) peut être obtenu à l'aide de la commande /library Game_name. Si le jeu spécifié est trouvé dans la bibliothèque du bot, l'utilisateur recevra un clavier en ligne, avec lequel il pourra obtenir des informations sur le jeu. Si le jeu n'est pas trouvé, vous devez soit vous assurer que le nom est correctement orthographié (il doit correspondre parfaitement au nom du jeu dans le Google Play store, sauf cas), soit ajouter le jeu à la bibliothèque en envoyant le bot un lien vers le jeu. J'ai déployé le bot sur heroku et pour ceux qui envisagent à l'avenir d'écrire leur propre bot et de l'héberger gratuitement sur heroku, je vais donner quelques recommandations pour résoudre les difficultés que vous pourriez rencontrer (puisque je les ai moi-même rencontrées). Malheureusement, en raison de la nature de Heroku, la bibliothèque de robots est constamment « réinitialisée » toutes les 24 heures. Mon plan ne prend pas en charge le stockage de fichiers sur les serveurs Heroku, il extrait donc simplement mon fichier de jeu de Github. Il y avait plusieurs solutions : utiliser une base de données, ou chercher un autre serveur qui stockerait ce fichier avec le jeu. J'ai décidé de ne rien faire pour l'instant, car le bot n'est essentiellement pas très utile. J’en avais plutôt besoin pour acquérir une expérience complète, ce qui est essentiellement ce que j’ai réalisé. Alors, recommandations pour Heroku :
-
Vous devrez probablement vous inscrire sur Heroku en utilisant un VPN si vous vivez en Russie.
-
A la racine du projet, vous devez mettre un fichier sans extension appelé Procfile. Son contenu devrait ressembler à ceci : https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
Dans pom.xml, ajoutez les lignes suivantes selon l'exemple , où dans la balise mainClass indiquez le chemin d'accès à la classe qui contient la méthode principale : bot.BotApplication (si la classe BotApplication est dans le dossier bot).
-
Ne construisez aucun projet à l'aide des commandes du package mvn, etc., Heroku assemblera tout pour vous.
-
Il est conseillé d'ajouter un gitignore au projet, par exemple ceci :
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Téléchargez en fait le projet sur github, puis connectez le référentiel à Heroku (ou utilisez d'autres méthodes, il y en a 3, si je ne me trompe pas).
-
Si le téléchargement a réussi (« Build réussi »), assurez-vous d'aller dans Configure Dynos :
et changez le curseur, puis assurez-vous qu'il est en position ON (en raison du fait que je ne l'ai pas fait, mon bot n'a pas fonctionné et je me suis creusé la tête pendant quelques jours et j'ai fait beaucoup de mouvements inutiles ).
-
Cachez le jeton du bot sur Github. Pour ce faire, vous devez obtenir le jeton de la variable d'environnement :
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Et puis après avoir déployé le bot, définissez cette variable dans le tableau de bord Heroku dans l'onglet Paramètres (à droite de TOKEN il y aura un champ VALEUR, copiez-y le token de votre bot) :
- reçu un projet entièrement fonctionnel écrit en Java ;
- appris à travailler avec des API tierces (API Telegram Bot);
- en pratique, j'ai approfondi la sérialisation, j'ai beaucoup travaillé avec JSON et la bibliothèque Jackson (au départ j'utilisais GSON, mais cela posait des problèmes) ;
- renforcé mes compétences dans le travail avec des fichiers, fait connaissance avec Java NIO ;
- j'ai appris à travailler avec des fichiers de configuration .xml et je me suis habitué à la journalisation ;
- meilleure maîtrise de l'environnement de développement (IDEA);
- appris à travailler avec git et appris la valeur de gitignore ;
- acquis des compétences en analyse de pages Web (bibliothèque Jsoup);
- appris et utilisé plusieurs modèles de conception ;
- développé un sens et un désir d'améliorer le code (refactoring);
- J’ai appris à trouver des solutions en ligne et à ne pas hésiter à poser des questions auxquelles je ne trouvais pas de réponse.
GO TO FULL VERSION