@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
que paso al controlador (propia clase 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(), "Я могу принимать только текстовые mensajes!");
}
}
UpdatesReceiver es un controlador central que, dependiendo del tipo de actualización, transfiere el control a otro controlador especializado: TextMessageHandler o CallbackQueryHandler, a cuyos constructores paso la actualización más adelante en la cadena. La actualización es lo más importante cuando se trabaja con un bot y no se puede perder, porque con la ayuda de la información almacenada en la actualización, averiguamos a qué usuario y a qué chat se debe enviar la respuesta. Para generar respuestas para el usuario, escribí una clase separada. Puede enviar mensajes de texto normales, mensajes con teclado en línea, mensajes con imagen y mensajes con teclado de respuesta. Un teclado en línea se ve así: define botones en los que, al hacer clic en ellos, el usuario envía una devolución de llamada al servidor, que se puede procesar casi de la misma manera que los mensajes normales. Para "mantenerlo" necesita su propio controlador. Establecemos una acción para cada botón, que luego se escribe en el objeto Actualizar. Aquellos. para el botón "Costo" configuramos la descripción "/precio" para la devolución de llamada, que luego podremos obtener de la actualización. Además, en una clase separada, ya puedo procesar esta devolución de llamada:
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;
...
El teclado de respuesta se ve así: Y, en esencia, reemplaza la escritura del usuario. Al hacer clic en el botón "Biblioteca" se enviará rápidamente un mensaje de "Biblioteca" al bot. Para cada tipo de teclado, escribí mi propia clase, implementando el patrón Builder: en línea y respuesta . Como resultado, básicamente puede "dibujar" el teclado deseado según sus requisitos. Esto es tremendamente conveniente, ya que los teclados pueden ser diferentes, pero el principio sigue siendo el mismo. A continuación se muestra un método intuitivo para enviar un mensaje con un teclado en línea:
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());
}
}
Para darle al bot una funcionalidad estricta, se inventaron comandos especiales que utilizan el carácter de barra diagonal: /library, /help, /game, etc. De lo contrario, tendríamos que procesar cualquier basura que pueda escribir el usuario. En realidad, para esto se escribió 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());
}
...
Por lo tanto, dependiendo del comando que envíe al bot, se incluirá un controlador especial en el trabajo. Vayamos más allá y observemos el trabajo del analizador y la biblioteca. Si envía al bot un enlace a un juego en la tienda Google Play, un controlador especial funcionará automáticamente . En respuesta, el usuario recibirá información sobre el juego de la siguiente forma: Al mismo tiempo, se llamará a un método que intentará agregar el juego a la biblioteca del bot (primero al mapa local, luego a -> archivo json ). Si el juego ya está en la biblioteca, se realizará una verificación (como en un mapa hash normal), y si los datos del campo (por ejemplo, el número de versión han cambiado), se sobrescribirá el juego en la biblioteca. Si no se detectan cambios, no se realizarán entradas. Si no había ningún juego en la biblioteca, primero se escribe en el mapa local (un objeto como tyk ) y luego se escribe en un archivo json, ya que si la aplicación en el servidor se cierra inesperadamente, los datos se perdido, pero siempre se puede leer usando el archivo. En realidad, cuando se inicia el programa, la biblioteca siempre se carga por primera vez desde un archivo de un bloque estático:
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("[Ошибка при чтении/записи archivo] {}", e.getMessage());
}
}
Aquí, además, debe leer los datos del archivo en un mapa temporal, que luego se "copia" en un mapa completo para no distinguir entre mayúsculas y minúsculas al buscar un juego en el archivo (al escribir tITan QuEST, el bot aún encontrará el juego Titan Quest en la biblioteca). No fue posible encontrar otra solución, estas son las características de la deserialización usando Jackson. Entonces, con cada solicitud de un enlace, el juego se agrega a la biblioteca, si es posible, y la biblioteca se expande. Se puede obtener más información sobre un juego específico usando el comando /libraryGame_Name. Puede conocer tanto un parámetro específico (por ejemplo, la versión actual) como todos los parámetros a la vez. Esto se implementa mediante el teclado en línea, que se analizó anteriormente. Durante el trabajo, también apliqué las habilidades adquiridas aquí mientras resolvía problemas. Por ejemplo, una lista de nombres de juegos aleatorios ubicados en la biblioteca (la opción está disponible usando el 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);
}
¿Cómo procesa el bot los enlaces? Los comprueba para ver si pertenecen a Google Play (host, protocolo, puerto):
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 todo está en orden, entonces el bot se conecta mediante un enlace utilizando la biblioteca Jsoup, que le permite obtener el código HTML de la página, que está sujeto a un mayor análisis y análisis. No podrás engañar al bot con un enlace incorrecto o dañino.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Aquí tuvimos que resolver un problema con la configuración regional. El bot se conecta a la tienda Google Play desde un servidor ubicado en Europa, por lo que la página de la tienda Google Play se abre en el idioma apropiado. Tuve que escribir una muleta que me obligara a "redireccionar" a la versión rusa de la página (después de todo, el proyecto estaba dirigido a nuestra audiencia). Para hacer esto, al final del enlace debe agregar cuidadosamente el parámetro hl: &hl=ru en la solicitud GET al servidor de 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;
}
Después de una conexión exitosa, recibimos un documento HTML listo para su análisis y análisis, pero esto está más allá del alcance de este artículo. El código del analizador está aquí . El propio analizador recupera la información necesaria y crea un objeto con el juego, que luego se agrega a la biblioteca si es necesario. <h2>Para resumir</h2>El bot admite varios comandos que contienen cierta funcionalidad. Recibe mensajes del usuario y los relaciona con sus comandos. Si es un enlace o el comando /juego + enlace, comprueba ese enlace para ver si pertenece a Google Play. Si el enlace es correcto, se conecta vía Jsoup y recibe el documento HTML. Este documento se analiza en función del analizador escrito. La información necesaria sobre el juego se extrae del documento y luego el objeto del juego se completa con estos datos. A continuación, el objeto con el juego se coloca en el almacenamiento local (si el juego aún no está allí) y se escribe inmediatamente en un archivo para evitar la pérdida de datos. Un juego grabado en la biblioteca (el nombre del juego es la clave del mapa, el objeto con el juego es el valor del mapa) se puede obtener usando el comando /library Game_name. Si el juego especificado se encuentra en la biblioteca del bot, el usuario recibirá un teclado en línea con el que podrá obtener información sobre el juego. Si no se encuentra el juego, debes asegurarte de que el nombre esté escrito correctamente (debe coincidir completamente con el nombre del juego en la tienda Google Play, excepto en este caso) o agregar el juego a la biblioteca enviando el bot. un enlace al juego. Implementé el bot en heroku y para aquellos que en el futuro planeen escribir su propio bot y alojarlo gratis en heroku, les daré un par de recomendaciones para resolver las dificultades que puedan encontrar (ya que yo mismo las encontré). Desafortunadamente, debido a la naturaleza de Heroku, la biblioteca de bots se "reinicia" constantemente una vez cada 24 horas. Mi plan no admite el almacenamiento de archivos en servidores Heroku, por lo que simplemente extrae el archivo de mi juego de Github. Había varias soluciones: usar una base de datos o buscar otro servidor que almacenara este archivo con el juego. Decidí no hacer nada por ahora, ya que esencialmente el bot no es tan útil. Lo necesitaba más bien para obtener una experiencia completa, que es básicamente lo que logré. Entonces, recomendaciones para Heroku:
-
Lo más probable es que tengas que registrarte en heroku usando una VPN si vives en Rusia.
-
En la raíz del proyecto, debe colocar un archivo sin extensión llamado Procfile. Su contenido debería ser así: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
En pom.xml, agregue las siguientes líneas según el ejemplo , donde en la etiqueta mainClass indique la ruta a la clase que contiene el método principal: bot.BotApplication (si la clase BotApplication está en la carpeta bot).
-
No cree ningún proyecto utilizando los comandos del paquete mvn, etc., Heroku ensamblará todo por usted.
-
Es recomendable agregar un gitignore al proyecto, por ejemplo este:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
En realidad, cargue el proyecto en github y luego conecte el repositorio a Heroku (o use otros métodos, hay 3, si no me equivoco).
-
Si la descarga fue exitosa ("Compilación exitosa"), asegúrese de ir a Configurar Dynos:
y cambie el control deslizante, y luego asegúrese de que esté en la posición ON (debido a que no hice esto, mi bot no funcionó y me devané los sesos durante un par de días e hice muchos movimientos innecesarios ).
-
Oculta el token del bot en Github. Para hacer esto, necesita obtener el token de la variable de entorno:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Y luego, después de implementar el bot, configure esta variable en el panel de Heroku en la pestaña Configuración (a la derecha de TOKEN habrá un campo VALOR, copie el token de su bot allí):
- recibió un proyecto completamente funcional escrito en Java;
- aprendí a trabajar con API de terceros (Telegram Bot API);
- en la práctica, profundicé en la serialización, trabajé mucho con JSON y la biblioteca Jackson (inicialmente usé GSON, pero hubo problemas con él);
- fortalecié mis habilidades al trabajar con archivos, me familiaricé con Java NIO;
- aprendí a trabajar con archivos de configuración .xml y me acostumbré a iniciar sesión;
- competencia mejorada en el entorno de desarrollo (IDEA);
- aprendí a trabajar con git y aprendí el valor de gitignore;
- adquirió habilidades en el análisis de páginas web (biblioteca Jsoup);
- aprendí y utilicé varios patrones de diseño;
- desarrolló un sentido y deseo de mejorar el código (refactorización);
- Aprendí a encontrar soluciones en línea y a no tener vergüenza de hacer preguntas para las que no encontraba respuesta.
GO TO FULL VERSION