JavaRush /Blog Java /Random-ES /El bot de Telegram como primer proyecto y su importancia ...
Pavel Mironov (Miroha)
Nivel 16
Москва

El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal

Publicado en el grupo Random-ES
¡Saludos a todos! Cuéntanos acerca de tí. Tengo 24 años, me gradué de una universidad técnica el año pasado y aún no tengo experiencia laboral. De cara al futuro, quiero decir que inicialmente, en el plan establecido (elaborado en el otoño de 2019), planeaba ir a trabajar en marzo-abril de 2020, pero, desafortunadamente, intervino la cuarentena, por lo que pospuse todo hasta mediados. -verano y en el futuro espero escribir mi propia historia de éxito. El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 1Nunca me atrajo la programación. En la universidad enseñaban bastante programación, pero este oficio no podía interesarme entonces. También hubo lenguajes procedimentales (C), un curso de un año de programación orientada a objetos (Java), bases de datos, incluso ensamblador y C++. Pero, para ser honesto, en general me era indiferente al estudio, ya que la mayoría de las disciplinas impartidas me parecían inútiles, aptas únicamente para fines informativos (en principio, así es). Después de graduarme de la universidad, tuve que tomar una decisión: no adquirí algunas habilidades, pero necesitaba trabajar. Tuve que pensar en la autoeducación (oh, ya me he perdido al menos 2 años completos por quedarme de brazos cruzados) y la elección recayó naturalmente en Java, ya que en un curso de programación orientada a objetos en la universidad uno de los chicos recomendó el curso de javarush. , y él, como sabéis, se dedica específicamente al lenguaje Java. Me interesó la presentación del curso. Sí, no me gustaba la programación en ese entonces, porque inmediatamente me di por vencido cuando encontré alguna dificultad, y hay dificultades más que suficientes en la programación. Pero al mismo tiempo sentí que quería escribir código, así que al final decidí dedicarme a la programación. Os contaré brevemente mi experiencia con javarush. Comencé en agosto de 2019, inmediatamente compré una suscripción por un mes, pero en el nivel 7 me di cuenta de que las tareas eran difíciles. Dejé el curso a un lado y recogí a Shildt. Entonces, en paralelo, completé el curso durante 3 meses. Llegué al nivel 20 (esta es mi segunda cuenta), leí Shildt casi por completo, luego me cansé de las tareas aquí, en las que dejé de ver beneficios prácticos para mí. Fui a codewars, leetcode y comencé a ver cursos en video. Por cierto, en 3 meses pasé de "Oh no, ¿qué es una matriz? ¿Cómo trabajar con ella y por qué da tanto miedo"? a un estudio detallado del código fuente de las clases de colección (ArrayList, HashMap, etc.). Basándome en mi experiencia personal, les diré a los principiantes: lo principal aquí es superar el sentimiento que surge si no entiendes nada y no puedes resolver nada. Cuando surge, simplemente quieres dejarlo todo y parece que eres demasiado estúpido para este asunto. Si superas esos momentos dentro de ti y descansas mentalmente, el éxito llegará. Creo que muchas personas no pueden hacer frente a esto, por lo que rápidamente abandonan tales esfuerzos. Como resultado, en diciembre de 2019 comencé a pensar en mi proyecto. Decidí elegir un bot de Telegram, pero no tenía idea. Al mismo tiempo, un amigo necesitaba en un telegrama una funcionalidad para su grupo que le gustaría automatizar. Él simplemente se dio cuenta de que estaba estudiando programación en profundidad y me ofreció un proyecto. Para mí, por experiencia y futuro currículum, para él, por el desarrollo del grupo. Incluso me permitiré citar su idea: "Недавно софтину хотел у программиста заказать, которая загружала бы в выбранное Облако файлы по прямым enlaceм. Это интересно, так Cómo аналогов нет. И просто очень удобно. Суть: копируешь ссылку, вставляешь в окно и выбираешь нужное Облако (GDrive, Mail, Яндекс Диск и т.п), в своё время софт всё делает на стороне servidorа и юзеру ничего не нужно загружать на свою машину (особенно круто, когда у тебя сборка на SSD-накопителях). Думали сделать в web-интерфейсе, чтобы можно было запускать Cómo с телефонов, так и с десктопа... Можно в принципе через приложение реализовать, а не через web-интерфейс. Тебе такое по силам?"Empecé a trabajar, pero al final, después de un par de días, me di cuenta de que nada nos saldría bien, en gran parte debido a la falta de conocimiento. Un amigo necesitaba estos mismos enlaces a Cloud.Mail, pero todavía no los necesita. No tiene una API. Hubo un intento de armar algo a través de GDrive, pero la implementación fue deficiente, además este servicio en la nube no se adaptaba al "cliente". Aunque inicialmente ofreció varias nubes para elegir, finalmente rechazó todo excepto el correo. .ru, para el cual no se encontró solución. De alguna manera todo resultó costoso, fue necesario conectar la base de datos, usar un servidor para almacenamiento, etc. Por cierto, todavía necesita esta aplicación web. Ya que las cosas no Si no nos funcionó, decidí crear un bot de información, que debía recibir enlaces al juego desde la tienda Google Play, analizar el enlace y guardar la información recibida en la biblioteca, y luego escribirla en un archivo json. Así, con cada solicitud, la biblioteca puede ampliarse gracias al esfuerzo de los usuarios. En el futuro, no podrás obtener información sobre el juego de forma cómoda yendo a Google Play. Simplemente escribe el comando /libraryHere_game_name y obtienes todo lo que necesitas. Pero hay varias dificultades de las que os hablaré más adelante. Al principio progresé lentamente, ya que comencé a tomar dos cursos de SQL al mismo tiempo. Simplemente no podía entender cómo funcionaba el bot y cómo procesar las solicitudes. Conocí a un amigo que también estaba interesado en trabajar en el proyecto. La primera versión del bot estuvo lista en aproximadamente un mes, pero surgieron desacuerdos con un amigo (de mi parte). Tomé la parte del bot responsable del análisis y él trabajó directamente en las solicitudes al bot y su procesamiento. Por alguna razón, comenzó a complicar el bot, a introducir algún tipo de autorización, a inventar administradores, a agregar funciones innecesarias y, además, no me gustó mucho su estilo de codificación. En mi opinión, esto no era necesario en un robot de información. Así que decidí que yo mismo escribiría un bot desde cero con la funcionalidad que necesitaba. Ahora te diré qué hace realmente el bot (usando un ejemplo del código del proyecto). Adjuntaré el código completo del proyecto al final del artículo y, lamentablemente, no podré comentarlo por completo. Cualquier mensaje de usuario enviado al bot es un objeto de la clase Actualización. Contiene mucha información (identificación de mensaje, identificación de chat, identificación de usuario única, etc.). Hay varios tipos de actualización: puede ser un mensaje de texto, puede ser una respuesta del teclado de Telegram (devolución de llamada), una foto, un audio, etc. Para evitar que el usuario pierda demasiado tiempo, proceso solo solicitudes de texto y devoluciones de llamada desde el teclado. Si el usuario envía una foto, el bot le notificará que no tiene intención de hacer nada con ella. En la clase de bot principal, en el método onUpdateReceived, el bot recibe una actualización.
@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í: El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 1define 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í: El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 2Y, 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: El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 3Al 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:
  1. Lo más probable es que tengas que registrarte en heroku usando una VPN si vives en Rusia.

  2. 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

  3. 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).

  4. No cree ningún proyecto utilizando los comandos del paquete mvn, etc., Heroku ensamblará todo por usted.

  5. Es recomendable agregar un gitignore al proyecto, por ejemplo este:

    # Log file
    *.log
    
    # Compiled resources
    target
    
    # Tests
    test
    
    # IDEA files
    .idea
    *.iml
  6. 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).

  7. Si la descarga fue exitosa ("Compilación exitosa"), asegúrese de ir a Configurar Dynos:

    El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 4

    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 ).

  8. 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í):

    El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 5
En total, en 2 meses trabajando en mi propio proyecto yo:
  • 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.
El bot de Telegram como primer proyecto y su importancia para el crecimiento profesional a partir de la experiencia personal - 7No sé qué tan útil o inútil resultó ser el bot, o qué tan bonito/feo era el código, pero la experiencia que obtuve definitivamente valió la pena. Sentí un sentido de responsabilidad por mi proyecto. De vez en cuando quiero mejorarlo, agregar algo nuevo. Cuando pude ejecutarlo y ver que todo funcionaba como quería, fue una verdadera emoción. ¿No es eso lo principal? Disfruta lo que haces y disfruta cada línea de código funcional como la última barra de chocolate. Por lo tanto, si dominas la programación, te doy un consejo: no te quedes aquí hasta el nivel 40, sino que comienza tu propio proyecto lo antes posible. Si alguien está interesado, el código fuente del proyecto está aquí (reescrito para Spring): https://github.com/miroha/GooglePlayGames-TelegramBot Durante los últimos dos meses apenas he estado estudiando material nuevo, ya que me parece que he llegado a un callejón sin salida. Sin trabajo, ya no veo dónde desarrollarme, salvo quizás enseñar el Spring Framework, que es lo que planeo hacer el próximo mes. Y luego intentaré "reescribir" el bot usando este marco. Listo para responder cualquier pregunta. :) ¡Buena suerte a todos! ACTUALIZACIÓN del 07/07/2020 El repositorio con el bot en Java puro se perdió (lo eliminé, quedó una copia en otra máquina local), pero descargué el bot reescrito para Spring Boot: https://github.com/miroha /GooglePlayGames-TelegramBot
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION