
En Habré, miles de artículos sobre cómo hacer un bot de Telegram para diferentes lenguajes y plataformas de programación. El tema está lejos de ser nuevo.
Pero Telegraff es el mejor marco para implementar bots de Telegram, y lo probaré por debajo.
Preámbulo
En 2015, el rublo ruso tenía fiebre. Tenía ahorros en dólares y revisé la tasa literalmente cada cinco minutos para vender la moneda a la tasa que necesitaba. La fiebre se prolongó, me cansé y escribí un bot de Telegram ( @TinkoffRatesBot ), que le notifica si el tipo de cambio alcanza el valor umbral (esperado).
Me conmovió mucho esta tarea. Botha escribió bastante rápido, pero no recibió satisfacción.
La integración con Telegram no es y no hubo problemas. Este problema se resuelve en un par de horas. Incluso me sorprende que haya bibliotecas enteras en Java (subjetivamente, con un código de calidad repugnante) para la integración con Telegrams, que han ganado más de mil estrellas en Github.
El principal desafío para mí fue el sistema de secuencias de comandos: el usuario llama a un comando, por ejemplo, "/ taxi", el bot le hace una serie de preguntas, cada respuesta se valida y puede afectar el orden de las preguntas posteriores, se forma el "formulario" habitual, dado el método de procesamiento final para la formación respuesta
Hice esto, pero la estructura de las clases, los niveles de abstracción, todo era tan heterogéneo que era amargo verlo. Me atormentó la pregunta: ¿cómo se puede transferir de manera sucinta y orgánica a un modelo orientado a objetos?
Quería tener algo simple, conveniente y lo más importante: poder describir el script completo en un archivo aislado para no tener que ver la mitad del proyecto para comprender la cadena de interacción del usuario.
No quiere decir que el problema fuera muy agudo, porque la tarea ya está resuelta. Más bien, a veces pensaba en él. La idea era Groovy DSL, pero cuando llegó Kotlin, la elección se hizo evidente. Entonces apareció Telegraff.
Sí, por supuesto, no había competencia que ganaría Telegraff. Y la afirmación de que Telegraff es el mejor no debe tomarse literalmente. Pero Telegraff es un enfoque nuevo y único para este desafío. Es fácil ser el mejor, ser el único.
¿Cómo usarlo?
Dependencias
El primer paso es especificar un repositorio adicional para las dependencias. Quizás en algún momento publique Telegraff en Maven Central o en JCenter, pero por ahora.
Gradlerepositories { maven { url "https://dl.bintray.com/ruslanys/maven" } }
Maven <repositories> <repository> <snapshots> <enabled>false</enabled> </snapshots> <id>bintray-ruslanys-maven</id> <name>bintray</name> <url>https://dl.bintray.com/ruslanys/maven</url> </repository> </repositories>
Sigue siendo el caso para los pequeños. Para usar Telegraff, debe especificar solo una dependencia de arranque por arranque de resorte:
Gradle compile("me.ruslanys.telegraff:telegraff-starter:1.0.0")
Maven <dependency> <groupId>me.ruslanys.telegraff</groupId> <artifactId>telegraff-starter</artifactId> <version>1.0.0</version> </dependency>
Configuracion
La configuración del proyecto es simple y puede limitarse a los primeros dos o tres parámetros:
application.properties telegram.access-key=123 # ① telegram.mode=webhook # ② telegram.webhook-base-url=https://ruslanys.me # ③ telegram.webhook-endpoint-url=/telegram # ④ telegram.handlers-path=handlers # ⑤ telegram.unresolved-filter.enabled=false # ⑥
- Tu clave para la API de Telegram.
- El modo de recibir mensajes (actualizaciones) de Telegram. Puede ser sondeo o webhook.
- Si el método para recibir actualizaciones se indica mediante "webhook", debe especificar la ruta a su aplicación.
- Si lo desea, puede especificar su propia ruta al punto final. Si este parámetro no se redefine, se generará una ruta de la siguiente forma:
/telegram/${UUID}
. Antes de iniciar la aplicación, la dirección especificada se establece como la dirección de enlace web. Al final del trabajo, la dirección de enlace web se sobrescribe para poder cambiar a sondeo la próxima vez que comience. - Si lo desea, puede cambiar la carpeta en la que se ubicarán los scripts de los controladores. Por defecto, esta es la carpeta de
handlers
. UnresolvedFilter
está incluido en la "entrega" y está habilitado de forma predeterminada. En el caso de que no se encuentre ningún controlador en el mensaje del usuario, UnresolvedFilter
responde con algo como "Lo siento, no te entiendo :(".
¡Es hora de escribir guiones!
Manipuladores
Los controladores (scripts) son una parte clave de Telegraff. Aquí es donde se establece la cadena de interacción del usuario. La conclusión es que cada comando, como "/ start", "/ taxi", "/ help", es un script / script / handler / handler separado.
Un script puede contener un conjunto de pasos (preguntas) que un usuario debe seguir para ejecutar un comando. En otras palabras, el usuario debe completar el formulario. Y dado que el messenger es de la interfaz, debe hablar y preguntarle al usuario.
¿Debo explicar que las respuestas de los usuarios deben validarse? Lo primero que hará el usuario es que responderá de manera diferente a lo que usted espera.
Bueno, al final, el script puede ramificarse, es decir Cada respuesta a una pregunta puede afectar el orden de las siguientes.
Por ejemplo!
Para comenzar, coloque el archivo con la extensión .kts
en la carpeta con handlers
recursos: src/main/resources/handlers/ExampleHandler.kts
.
Escenario de llamada de taxi enum class PaymentMethod { CARD, CASH } handler("/taxi", "") {
Las llaves de las estepas no fueron deliberadamente tomadas en constantes. En producción, por supuesto, es mejor evitarlo.
Vamos a resolverlo:
- Declaramos el guión. Se requiere al menos un nombre de equipo. En este caso, hay dos equipos: "/ taxi", "taxi". Si el mensaje del usuario comienza con estas palabras, se llamará al controlador correspondiente.
- Determinamos los pasos (preguntas). Se requiere un nombre de paso único porque posteriormente, se puede acceder a la respuesta del usuario precisamente por esta clave ("locationFrom").
- Cada paso contiene tres secciones, la primera de las cuales es la pregunta en sí. La pregunta es una sección obligatoria que debe estar presente en cada paso. No tiene sentido en un paso sin una pregunta.
- Puede completar la pregunta como desee. En este caso, se le pedirá al usuario a través del teclado que seleccione una de las opciones: "Tarjeta" o "Efectivo". Como resultado de llamar a este bloque, debe haber un objeto de tipo
TelegramSendRequest
. Lo sentimos, no se me ocurrió nada mejor que el sufijo SendRequest
, que describe la estructura como una solicitud saliente en Telegram.

- El segundo paso más importante es verificar la respuesta del usuario. El tipo de cada paso está parametrizado (genérico) y, por lo tanto, el bloque de validación debe devolver exactamente el tipo por el cual se parametriza su paso.
- Si la respuesta del usuario no es satisfactoria, puede lanzar una
ValidationException
con texto aclaratorio, pero el mismo teclado, si se indicó en la pregunta. - La sección del paso final es un bloque que indica el siguiente paso. Por defecto, los pasos se ejecutarán en el orden de su declaración, de arriba a abajo. Pero este proceso puede verse influenciado al anular el bloque correspondiente. La clave del siguiente paso (
String
) o "nulo" se puede devolver como resultado de la ejecución de este bloque, lo que indica que no hay más pasos y que es hora de proceder a la ejecución del comando. - Cuando se genera una solicitud de usuario, se requiere su procesamiento. Los argumentos en el lambda son State (esto es algo así como una sesión) y las respuestas del usuario.
- Tenga en cuenta que la respuesta fallida ya no es la cadena de respuesta del usuario, sino un objeto ya procesado del tipo deseado.
- La respuesta al comando puede ser cualquiera, similar al párrafo 4. Si no se requiere la respuesta al comando, puede devolver "nulo".
Un controlador puede no tener pasos en absoluto. En este caso, solo necesita determinar el comportamiento del controlador para invocar el comando.
Guión de bienvenida handler("/start") { process { _, _ -> MarkdownMessage("!") } }
Prueba
Para intentarlo, bifurca el repositorio , clónalo en la máquina local y ve a la carpeta de telegraff-sample
. Configurar, iniciar, tocar!
En general, telegraff-sample
es un proyecto deliberadamente independiente que no está relacionado con el padre e incluso tiene su propio Gradle Wrapper. Solo puede dejar esta carpeta. Este es un tipo de arquetipo.
Como funciona
Telegrama
La integración con Telegram es muy simple e implementada en TelegramApi
.
Cada método se implementó deliberadamente individualmente debido a una serie de circunstancias: comenzando por el uso de Spring's RestTemplate (y las pruebas para ello), hasta la especificidad de la API de Telegram.
Como puede ver en la configuración, hay dos tipos de clientes de esta API en Telegraff: PollingClient , WebhookClient . Dependiendo de la configuración, se declarará un bin particular.
Y aunque los métodos para recibir actualizaciones (mensajes nuevos) difieren de Telegram, la esencia no cambia y se reduce a una sola cosa: publicar un evento ( TelegramUpdateEvent
) sobre mensajes nuevos a través de EventPublisher
de Spring (patrón "Observador"). Si lo desea, puede implementar su propio oyente suscribiéndose a este tipo de evento. Una lógica, como me parece, una capa de abstracción, porque no importa en absoluto cómo se recibió el mensaje.
Filtros
Tan pronto como se reciba un nuevo mensaje, es necesario procesarlo y responder al usuario. Para hacer esto, el mensaje debe pasar por la cadena de filtros.
Esto es similar a los filtros Java EE familiares para los programadores Java. La única diferencia es que los llamados controladores (si dibujamos un paralelo con Java EE, estos son Servlets) no son independientes de los filtros, sino que son parte de ellos.

Entonces, los filtros están optimizados y pueden permitir que los mensajes vayan más abajo en la cadena, tal vez no.
LoggingFilter
es obviamente el filtro de prioridad más alta (primero) que se llamará como parte del procesamiento de un nuevo mensaje. Registra información en un mensaje entrante y lo envía más abajo en la cadena. A propósito agregué LoggingFilter
como ejemplo. De hecho, puede no tener sentido, porque Los mensajes entrantes se registran a nivel del cliente.
El siguiente filtro es CancelFilter
. Básicamente funciona en conjunto con HandlersFilter
y es un complemento para él. Su tarea es simple: si el usuario desea abandonar la secuencia de comandos actual, puede escribir "/ cancelar" o "cancelar" y su estado (sesión) debe borrarse. Puede comenzar cualquier nuevo escenario sin completar el anterior. Por esta razón, CancelFilter
"mayor" (prioridad) HandlersFilter
.
HandlersFilter
es el filtro principal en el proceso actual. Es este filtro el que almacena el estado de los chats del usuario, encuentra y llama al controlador (script) deseado, aplica bloques de validación, determina el orden de los pasos y responde al usuario.
Si HandlersFilter
no encontró ningún controlador adecuado para el mensaje del usuario, ya sea en la sesión o en el contenido, el mensaje se envía más abajo en la cadena. El filtro extremo es UnresolvedFilter
. Este es un filtro que sabe que es el último, por lo tanto, su funcionalidad es simple: si me contactaron, la forma de responder a un mensaje no está clara, diré que no entendí nada. Me parece que es mejor recibir al menos algunos mensajes del bot si no sabe cómo responder, que no recibir nada en absoluto.
Para agregar su filtro, debe declarar un Bean de la clase TelegramFilter
y especificar la anotación @TelegramFilterOrder(ORDER_NUMBER)
.
Ejemplo de filtro @Component @TelegramFilterOrder(Integer.MIN_VALUE) class LoggingFilter : TelegramFilter { override fun handleMessage(message: TelegramMessage, chain: TelegramFilterChain) { log.info("New message from #{}: {}", message.chat.id, message.text) chain.doFilter(message) } companion object { private val log = LoggerFactory.getLogger(LoggingFilter::class.java) } }
Así es como @TinkoffRatesBot implementa una "calculadora". Sin llamar a ningún script y comando, puede enviar un número, por ejemplo, "1000", o incluso una expresión completa, por ejemplo, "4500 * 3 - 12000". El bot calculará el resultado de la expresión, aplicará los tipos de cambio actuales al resultado y mostrará información al respecto. De hecho, el resultado de tales acciones es la ejecución de CalculationFilter
, que se encuentra en la cadena debajo de HandlersFilter
, pero por encima de UnresolvedFilter
.
Manipuladores
El sistema de secuencias de comandos de Telegraff (controladores) se basa en el DSL de Kotlin. En resumen, se trata de lambdas y de constructores.
No veo el punto de ver por separado el DSL Kotlin, porque Esta es una conversación completamente diferente. Hay una gran documentación de JetBrains y un informe completo de i_osipov .
Matices
Esta sección está dedicada a las características actuales. Todos ellos, en mi opinión, no son críticos, algunos pueden ser reparados, otros no. Pero necesitas saber sobre estos aspectos.
Si desea participar o sabe cómo corregir uno u otro punto de esta sección, se lo agradeceré.
Telegrama
La capa de integración con Telegram probablemente no se describe completamente. Solo se implementaron los métodos que necesitaba. Si hay algo que personalmente le falta, corrija TelegramApi
y envíe PR.
Una de las partes importantes en este momento es la falta de soporte de teclado en línea (esto es cuando el teclado está directamente debajo del mensaje en la cinta de opciones). La tarea se ve agravada por el hecho de que los teclados en línea deben ser "ingresados" correctamente en la estructura existente para que siga siendo simple, conveniente, aislada. Ya existe una buena idea para implementar esta funcionalidad, pero aún no se ha implementado y probado de ninguna forma.
Tarro de grasa
Desafortunadamente, algunas bibliotecas, como JRuby
y probablemente el Kotlin Embedded Compiler
(necesario para compilar scripts) pueden tener problemas como parte del Fat JAR
. Fat JAR
es cuando su código y todas sus dependencias se empaquetan en un archivo ( *.jar
).
Para resolver este problema, puede desempaquetar dependencias en tiempo de ejecución. Es decir, cuando se inicia la aplicación, el JAR de dependencia del paquete principal se implementa en algún lugar del disco y la ruta de clase se indica antes. Esto es bastante fácil de hacer a través de la configuración de bootJar
:
Configuración del complemento bootJar { requiresUnpack "**/**kotlin**.jar" requiresUnpack "**/**telegraff**.jar" }
Sin embargo, para referirse de los controladores (scripts) a sus beans (servicios, por ejemplo), también deben estar desempaquetados. Lo cual, en principio, elimina el beneficio de este enfoque.
Tal como lo veo, el uso del complemento de application
Gradle sigue siendo el método más confiable, simple y conveniente. Además, si está conteniendo su aplicación en contenedores, no hay diferencia por el resultado.
Sobre todo esto escribí en detalle aquí .
Orden de inicialización
Aquí me gustaría señalar dos circunstancias.
En primer lugar, si observa el escenario de la llamada de taxi, puede ver que la clase de enum
se define sobre la llamada al handler(...)
. Esta necesidad se impone por el hecho de que, de hecho, el handler
es una llamada a la función. Una llamada a función, cuyo resultado debería ser alguna estructura, que Telegraff usará más adelante. Si, de acuerdo con el resultado de la ejecución de su script, la fábrica no puede llevar el resultado al tipo deseado, se producirá un error en la etapa de inicialización.
En segundo lugar, debe recordar que sus scripts se pueden inicializar antes que toda su aplicación y beans. Si, por ejemplo, coloca un enlace a un contexto en una variable estática e intenta obtener algún servicio en la primera línea del archivo de script, puede resultar que el contexto no lo tenga, porque aún no se ha inicializado. Para evitar tales problemas, use este método de Telegraff. Asegura que el contexto se inicialice y que todos los beans necesarios estén disponibles. Un ejemplo se puede ver aquí .
Conclusión
Quería probar - tenedor,
Quería arreglarlo: enviar relaciones públicas,
Quería agradecer: ¡pon un asterisco en Github, como la publicación y cuéntaselo a tus amigos!
Repositorio de proyectos