Cómo desarrollar un sistema de envío de mensajes a Telegram para los aplicativos, servicios y sistemas de nuestra organización. Nos será útil para enviar mensajes de aviso por Telegram cuando finaliza una copia de seguridad, cuando se produce una alerta en nuestro sistema de monitorización, como Pandora FMS y para cualquier otro tipo de notificación. Realizaremos este sistema independientemente del lenguaje de programación de los aplicativos, del sistema operativo de los servidores y del propio sistema de monitorización.
- Requisitos para montar un sistema de notificaciones por Telegram para todos los aplicativos, sistemas y servicios.
- Sistema de envío de mensajes a chat de Telegram con Visual Studio .Net C# C Sharp.
- Notificación de alertas por Telegram en Pandora FMS.
- Descarga del código fuente y la aplicación completa que envía mensajes a Telegram leyendo en una BD MySQL.
Requisitos para montar un sistema de notificaciones por Telegram para todos los aplicativos, sistemas y servicios
A continuación indicamos los requisitos previos para montar un sistema de notificaciones y avisos por Telegram en nuestra organización. El desarrollo de dicho sistema se implementa para que sea válido para cualquier servidor (con cualquier sistema operativo), para cualquier aplicativo (software de nuestra organización) y para cualquier sistema de monitorización (como Pandora FMS). Se basará en la lectura de una tabla de mensajes, cuando se inserte un registro en esta tabla, el sistema enviará el mensaje con los datos introducidos. Por ello, cualquier dispositivo/sistema/aplicativo/servicio que pueda insertar un registro en una tabla MySQL/MariaDB podrá enviar mensajes de Telegram.
Servidor de base de datos MySQL o MariaDB
Para el envío de mensajes de notificación/aviso a un chat de Telegram usaremos como base de datos MySQL Server (válido también para MariaDB y, con unas pequeñas modificaciones, para otros sistemas de bases de datos como PostgreSQL, Oracle, Firebird, SQLite, …). Por lo tanto, necesitaremos disponer de un servidor de bases de datos MySQL Server / MariaDB, sea sobre Windows o Linux, es indiferente.
Dispondremos de una tabla de MySQL que será donde cualquier aplicativo/sistema/servicio pueda insertar un registro. Una vez insertado, el sistema realizará el envío del mensaje por Telegram. El SQL para crear la tabla mensajes_telegram será:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE TABLE mensajes_telegram ( codigo int(11) NOT NULL AUTO_INCREMENT, codigousuario int(11) DEFAULT NULL, mensaje varchar(4096) DEFAULT NULL, enviado varchar(1) DEFAULT NULL, fechaenvio datetime DEFAULT NULL, error varchar(1) DEFAULT NULL, motivo_error varchar(500) DEFAULT NULL, codusuarioa int(10) DEFAULT NULL, fechaa datetime DEFAULT NULL, remitente varchar(200) DEFAULT NULL, PRIMARY KEY (codigo) ) DEFAULT CHARSET=latin1 |
Por otro lado, necesitaremos obligatoriamente una tabla de usuarios donde tendremos, al menos, el campo de ID de Chat de Telegram, para que el sistema de envío sepa a qué chat enviar el mensaje. Este ID de chat de Telegram irá asociado a cada usuario que pueda recibir mensajes de Telegram. De esta forma, en el insert que se realice en la tabla anterior mensajes_telegram, desde los aplicativos/servicios/sistema externos, indicaremos el código del usuario al que se le enviará el mensaje (codigousuario de la tabla mensajes_telegram). Y el campo de la tabla usuario que almacenará el ID del chat lo llamaremos id_chat_telegram.
Entender el punto anterior es muy importante porque con él enlazamos la tabla de usuarios de nuestra organización, que puede estar en cualquier servidor de base de datos, con la tabla de mensajes de envío a Telegram, que puede estar también en otro servidor de base de datos o en el mismo. El requisito es que el código de usuario, clave primaria de la tabla «usuario», sea el que introduzcamos en el insert del registro de la tabla mensajes_telegram, en el campo codigousuario.
El SQL de la creación de la tabla usuarios será el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
CREATE TABLE usuario ( codigo int(10) unsigned NOT NULL AUTO_INCREMENT, nick varchar(20) DEFAULT NULL, contrasena varchar(100) DEFAULT NULL, nombre varchar(100) DEFAULT NULL, codigodepartamento int(10) unsigned DEFAULT NULL, email varchar(150) DEFAULT NULL, fechaaltacontrato date DEFAULT NULL, fechaantiguedad date DEFAULT NULL, id_telegram int(11) DEFAULT NULL, clave_verificacion varchar(100) DEFAULT NULL, verificado varchar(1) DEFAULT NULL, id_telegram_nick varchar(50) DEFAULT NULL, enviado_mail_verificacion varchar(1) DEFAULT NULL, fecha_verificacion datetime DEFAULT NULL, fecha_envio_mail_verificacion datetime DEFAULT NULL, numero_veces_enviado_mail int(11) DEFAULT NULL, id_chat_telegram varchar(200) DEFAULT NULL, nombre_telegram varchar(150) DEFAULT NULL, apellidos_telegram varchar(150) DEFAULT NULL, enviar_telegram_tareas varchar(1) DEFAULT NULL, PRIMARY KEY (codigo) ) CHARSET=latin1 |
Los campos anteriores para la tabla «usuario» son un ejemplo, puede haber más o menos. Los que necesitaremos para el envío de mensajes por Telegram serán el «codigo» (código identificativo único del usuario) y el «id_chat_telegram» (ID del Chat de Telegram al que se enviará el mensaje). Por seguiridad, usaremos el campo «verificado» para comprobar que podemos enviar mensajes al usuario. Dicho campo debe tener el valor «S», verifiado = «S».
Bot de Telegram
Necesitaremos disponder de un Bot de Telegram. Cada usuario que queramos que pueda recibir mensajes de Telegram habrá agregado este Bot a su Telegram particular, en este Bot (en el chat con el Bot) será donde reciba los mensajes de notificaciones/avisos/alarmas.
En el siguiente enlace indicamos cómo crear un Bot de Telegram y obtener el token de seguridad:
Obtener el ID del Chat de Telegram de cada usuario con el Bot
Una vez que tengamos el Bot de Telegram creado, cada usuario que vaya a recibir mensajes debe buscar dicho bot y abrir un chat con él, por ejemplo en nuestro caso el bot se llama «ProyectoA», así que lo buscaremos en el buscador principal de Telegram y haremos clic sobre él:
Pulsaremos en «Iniciar» en el Bot de nuestra organización:
A partir de ahora tendremos un chat con el Bot, en el que recibiremos los mensajes de notificación. Cada usuario tendrá el suyo propio.
Para el funcionamiento del sistema de notificaciones, necesitaremos el ID del este chat. Para obtener el ID del chat podremos usar el comando «ID» que hemos programado en el siguiente punto. Nuestro propio sistema de envío de mensajes de Telegram también permite leer «comandos» que se introduzcan en el bot, por ello, si un usuario introduce el mensaje «ID» el sistema le devolcverá el ID de chat. Pero si no tenemos aún montado el sistema, para obtener el ID de chat, podremos hacerlo de diferentes formas, a continuación las comentamos en el siguiente artículo:
De esta forma, Telegram asignará un identificador a este chat, único, que será el que anotemos en la tabla de usuario, en el campo id_chat_telegram. Cada usuario tendrá su ID de chat asociado.
Sistema de envío de mensajes a chat de Telegram con Visual Studio .Net C# C Sharp
Para el desarrollo del programa de consola (línea de comandos, símbolo de sistema) que estará continuamente leyendo la tabla de mensajes de Telegram (mensajes_telegram) usaremos el lenguaje de programación C# (C Sharp) y como entorno de programación usaremos Microsoft Visual Studio Community 2019.
Realizaremos una aplicación de consola, para ello abriremos Visual Studio y en Crear un proyecto elegiremos «Aplicación de consola (.NET Framework):
Escogeremos un nombre para el proyecto, por ejemplo «SistemaEnvioMensajesTelegram» y una carpeta donde lo almacenaremos. También seleccionaremos la versión de .Net Framework con la que trabajaremos, por ejemplo «.NET Framework 4.7.2»:
A continuación mostramos el código fuente de cada clase que necesitaremos para esta aplicación. Lo incluimos completo en esta descarga. Casi todas las clases son las mismas que las mostradas en este artículo:
Por ello no las explicaremos, directamente mostramos el nombre de la clase para agregarla y el código C#.
- EscribirLog.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
using System; using System.IO; namespace SistemaEnvioMensajesTelegram { class EscribirLog { public string mensajeLog { get; set; } public Boolean mostrarConsola { get; set; } //Constructor si se pasa el mensaje por parámetro en la creación de la clase public EscribirLog(string mensajeEnviar, Boolean mostrarConsola) { mensajeLog = mensajeEnviar; if (mostrarConsola) monstrarMensajeConsola(); escribirLineaFichero(); } //Constructor si se pasa el mensaje por setter tras la creación de la clase public EscribirLog() { if (mostrarConsola) monstrarMensajeConsola(); escribirLineaFichero(); } public void monstrarMensajeConsola() { //Quitar posibles saltos de línea del mensaje if ((mensajeLog != null) && (mensajeLog != "")) { mensajeLog = mensajeLog.Replace(Environment.NewLine, " | "); mensajeLog = mensajeLog.Replace("\r\n", " | ").Replace("\n", " | ").Replace("\r", " | "); Console.WriteLine(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss") + " " + mensajeLog); } } //Escribe el mensaje de la propiedad mensajeLog en un fichero en la carpeta del ejecutable public void escribirLineaFichero() { try { if ((mensajeLog != null) && (mensajeLog != "")) { FileStream fs = new FileStream(@AppDomain.CurrentDomain.BaseDirectory + "estado.log", FileMode.OpenOrCreate, FileAccess.Write); StreamWriter m_streamWriter = new StreamWriter(fs); m_streamWriter.BaseStream.Seek(0, SeekOrigin.End); //Quitar posibles saltos de línea del mensaje mensajeLog = mensajeLog.Replace(Environment.NewLine, " | "); mensajeLog = mensajeLog.Replace("\r\n", " | ").Replace("\n", " | ").Replace("\r", " | "); m_streamWriter.WriteLine(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss") + " " + mensajeLog); m_streamWriter.Flush(); m_streamWriter.Close(); } } catch { //Silenciosa } } } } |
- CifrarDescifrarTexto.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
using System; using System.Security.Cryptography; using System.Text; using System.IO; namespace SistemaEnvioMensajesTelegram { class CifrarDescifrarTexto { public string cifrarTextoAES(string textoCifrar, string palabraPaso, string valorRGBSalt, string algoritmoEncriptacionHASH, int iteraciones, string vectorInicial, int tamanoClave) { try { byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(vectorInicial); byte[] saltValueBytes = Encoding.ASCII.GetBytes(valorRGBSalt); byte[] plainTextBytes = Encoding.UTF8.GetBytes(textoCifrar); PasswordDeriveBytes password = new PasswordDeriveBytes(palabraPaso, saltValueBytes, algoritmoEncriptacionHASH, iteraciones); byte[] keyBytes = password.GetBytes(tamanoClave / 8); RijndaelManaged symmetricKey = new RijndaelManaged(); symmetricKey.Mode = CipherMode.CBC; ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, InitialVectorBytes); MemoryStream memoryStream = new MemoryStream(); CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write); cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); cryptoStream.FlushFinalBlock(); byte[] cipherTextBytes = memoryStream.ToArray(); memoryStream.Close(); cryptoStream.Close(); string textoCifradoFinal = Convert.ToBase64String(cipherTextBytes); return textoCifradoFinal; } catch { return null; } } public string descifrarTextoAES(string textoCifrado, string palabraPaso, string valorRGBSalt, string algoritmoEncriptacionHASH, int iteraciones, string vectorInicial, int tamanoClave) { try { byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(vectorInicial); byte[] saltValueBytes = Encoding.ASCII.GetBytes(valorRGBSalt); byte[] cipherTextBytes = Convert.FromBase64String(textoCifrado); PasswordDeriveBytes password = new PasswordDeriveBytes(palabraPaso, saltValueBytes, algoritmoEncriptacionHASH, iteraciones); byte[] keyBytes = password.GetBytes(tamanoClave / 8); RijndaelManaged symmetricKey = new RijndaelManaged(); symmetricKey.Mode = CipherMode.CBC; ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, InitialVectorBytes); MemoryStream memoryStream = new MemoryStream(cipherTextBytes); CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); byte[] plainTextBytes = new byte[cipherTextBytes.Length]; int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); memoryStream.Close(); cryptoStream.Close(); string textoDescifradoFinal = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); return textoDescifradoFinal; } catch { return null; } } } } |
- LeerGuardarDatosConfiguracion.cs. Para el funcionamiento de esta clase necesitaremos agregar la referencia «System.Configuration», lo podemos hacer desde el menú «Proyecto» – «Agregar referencia»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
using System; using System.Configuration; namespace SistemaEnvioMensajesTelegram { class LeerGuardarDatosConfiguracion { public LeerGuardarDatosConfiguracion() { } public LeerGuardarDatosConfiguracion(string clave) { leerValorConfiguracion(clave); } //Constructor si se pasan los parámetros clave y valor //escribe en el fichero de configuración el valor de esa clave public LeerGuardarDatosConfiguracion(string clave, string valor) { //guardarValorConfiguracion(clave, valor); } //Lee un valor de una clave en el fichero de configuración XML de la aplicación public string leerValorConfiguracion(string clave) { try { string resultado = ConfigurationManager.AppSettings[clave].ToString(); return resultado; } catch (Exception error) { new EscribirLog("Error al leer valor de configuración: " + error.GetType().ToString() + " " + error.Message, false); return ""; } } //Guarda un valor en una clave en el fichero de configuración XML de la aplicación public void guardarValorConfiguracion(string clave, string valor) { try { Configuration ficheroConfXML = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); //Configuration ficheroConfXML = // ConfigurationManager.OpenExeConfiguration(AppDomain.CurrentDomain.BaseDirectory); //Eliminamos la clave actual (si existe), de lo contrario los valores //se irán acumulando separados por coma ficheroConfXML.AppSettings.Settings.Remove(clave); //Asignamos el valor en la clave indicada ficheroConfXML.AppSettings.Settings.Add(clave, valor); //Guardamos los cambios definitivamente en el fichero de configuración ficheroConfXML.Save(ConfigurationSaveMode.Modified); } catch (Exception error) { new EscribirLog("Error al guardar valor de configuración: " + error.GetType().ToString() + " " + error.Message, false); } } } } |
- BD.cs. Esta clase, que será la que lea y actualice la tabla de mensajes_telegram de un servidor MySQL Server / MariaDB, requerirá de una referencia a la librería Mysql.data.dll (que incluimos en la descarga del código fuente de la aplicación).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
using MySql.Data.MySqlClient; using System; using System.Collections.Generic; namespace SistemaEnvioMensajesTelegram { class BD { private MySqlConnection conexionBD; //Datos de conexión a BD MySQL MariaDB public string servidor { get; set; } public string puerto { get; set; } public string bd { get; set; } public string usuario { get; set; } public string contrasena { get; set; } //Datos de usuario de la BD de la tabla usuario public string mail { get; set; } public DateTime fechaRegistro { get; set; } public string nombre { get; set; } //Para guardar registros de BD en tupla: mensaje, id_chat, codigo, UsuarioAlta, Remitente public List<Tuple<string, long, int, string, string>> listaMensajes { get; set; } //Constructor, se le pasan los datos del servidor //MySQL MariaDB por parámetro en la creación de la clase public BD(string servidor, string puerto, string bd, string usuario, string contrasena) { this.servidor = servidor; this.puerto = puerto; this.bd = bd; this.usuario = usuario; this.contrasena = contrasena; } //Constructor si no se pasan los datos del servidor de BD, leer de fichero de configuración XML public BD() { //Leemos los datos que faltan (servidor, bd, usuario, contraseña) del fichero de configuración LeerGuardarDatosConfiguracion fConf = new LeerGuardarDatosConfiguracion(); this.servidor = fConf.leerValorConfiguracion("BD - Servidor"); this.puerto = fConf.leerValorConfiguracion("BD - Puerto"); this.bd = fConf.leerValorConfiguracion("BD - BD"); this.usuario = fConf.leerValorConfiguracion("BD - Usuario"); CifrarDescifrarTexto cTexto = new CifrarDescifrarTexto(); this.contrasena = cTexto.descifrarTextoAES(fConf.leerValorConfiguracion("BD - Contraseña"), "Encriptad0", "SalT", "MD5", 22, "1234567891234567", 128); //Para depuración // new EscribirLog(this.servidor + " || " + this.puerto + " || " + // this.bd + " || " + this.usuario + " || " + this.contrasena, true); conectarBD(); } //Obtiene los datos de conexión con el servidor de //MySQL MariaDB de los atributos (ya cargados en el constructor) //Y conecta con dicho servidor public void conectarBD() { if (conexionBD != null) conexionBD.Close(); string connStr = String.Format("server={0};port={1};user id={2}; password={3}; " + "database={4}; pooling=true;" + "Allow Zero Datetime=False;Convert Zero Datetime=True", this.servidor, this.puerto, this.usuario, this.contrasena, this.bd); try { conexionBD = new MySqlConnection(connStr); //conexionBD.Open(); new EscribirLog("Conectado a servidor BD MySQL/MariaDB " + this.servidor + " [" + this.bd + "]", true); } catch (MySqlException ex) { new EscribirLog("Error al conectar al servidor de base de datos MySQL " + this.servidor + " [" + this.bd + "] " + ex.Message, true); } finally { conexionBD.Close(); } } //Obtener todos los mensajes pendientes de notificar en tabla mensajes_telegram public int obtenerMensajesPendientesNotificarTelegram() { int numMensajes = 0; //Se filtran los mensajes pendientes de enviar (enviado = N or Null) //Que no tengan error error = N //Y que el técnico (codigousuario) tenga ID Telegram y que esté verificado string sqlEjecutar = "select ua.nombre UsuarioAlta, m.codigo, m.mensaje, m.remitente, u.id_chat_telegram " + "from mensajes_telegram m " + "left join usuario u on u.codigo = m.codigousuario " + "left join usuario ua on ua.codigo = m.codusuarioa " + "where (m.enviado = 'N' or m.enviado is null) and " + "(m.error = 'S' or m.error is null) and u.id_chat_telegram is not null " + "and u.verificado = 'S'"; try { conexionBD.Close(); MySqlCommand runSQL = new MySqlCommand(sqlEjecutar, conexionBD); conexionBD.Open(); MySqlDataReader datosSQL = runSQL.ExecuteReader(); string mensaje, UsuarioAlta, remitente; int codigo; long id_chat; this.listaMensajes = new List<Tuple<string, long, int, string, string>>(); while (datosSQL.Read()) { //Comprobamos los posibles nulos de cada campo de la consulta SQL if (!datosSQL.IsDBNull(datosSQL.GetOrdinal("UsuarioAlta"))) UsuarioAlta = datosSQL["UsuarioAlta"].ToString(); else UsuarioAlta = ""; if (!datosSQL.IsDBNull(datosSQL.GetOrdinal("mensaje"))) mensaje = datosSQL["mensaje"].ToString(); else mensaje = ""; if (!datosSQL.IsDBNull(datosSQL.GetOrdinal("id_chat_telegram"))) id_chat = Convert.ToInt64(datosSQL["id_chat_telegram"].ToString()); else id_chat = -1; if (!datosSQL.IsDBNull(datosSQL.GetOrdinal("codigo"))) codigo = Convert.ToInt32(datosSQL["codigo"].ToString()); else codigo = -1; if (!datosSQL.IsDBNull(datosSQL.GetOrdinal("remitente"))) remitente = datosSQL["remitente"].ToString(); else remitente = ""; //Guardamos cada registro en una tupla de la lista this.listaMensajes.Add(Tuple.Create(mensaje, id_chat, codigo, UsuarioAlta, remitente)); numMensajes++; } return numMensajes; } catch (MySqlException ex) { new EscribirLog("Error obtener mensajes pendientes de" + " enviar por Telegram: " + ex.Message, true); return numMensajes; } finally { conexionBD.Close(); } } //Actualiza un mensaje de la tabla mensajes_telegram a enviado por Telegram //enviado = "S" public bool actualizarMensajeEnviadoTelegram(int codigoMensaje, string enviado, string error, string motivoError) { string sqlEjecutar = "update mensajes_telegram set enviado = @enviado, " + "fechaenvio = @fechaenvio, " + "error = @error, " + "motivo_error = @motivoerror " + "where codigo = @codigo"; try { MySqlCommand comandoSQL = new MySqlCommand(); conexionBD.Close(); comandoSQL.Connection = conexionBD; comandoSQL.CommandText = sqlEjecutar; comandoSQL.Parameters.Add("@enviado", MySqlDbType.VarChar).Value = enviado; comandoSQL.Parameters.Add("@fechaenvio", MySqlDbType.Timestamp).Value = DateTime.Now; comandoSQL.Parameters.Add("@error", MySqlDbType.VarChar).Value = error; comandoSQL.Parameters.Add("@motivoerror", MySqlDbType.VarChar).Value = motivoError; comandoSQL.Parameters.Add("@codigo", MySqlDbType.Int32).Value = codigoMensaje; conexionBD.Open(); comandoSQL.ExecuteNonQuery(); return true; } catch (MySqlException ex) { new EscribirLog("Error al marcar como enviado a Telegram " + "un mensaje, con código [" + codigoMensaje.ToString() + "]: " + ex.Message, true); return false; } finally { conexionBD.Close(); } } } } |
- ComandosBot.cs. Esta clase no es necesaria para este proyecto, pero sirve como ejemplo para dotar al programa, además de la opción de enviar mensajes de Telegram, también de leer mensajes enviados al Chat del Bot y poder realizar tareas en función del mensaje recibido. Como ejemplo, cuando se escribe al bot el mensaje «ID», el programa enviará un mensaje con el ID del Chat de Telegram:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
using System; using System.Text.RegularExpressions; using System.Threading.Tasks; using Telegram.Bot.Types; namespace SistemaEnvioMensajesTelegram { class ComandosBot { //Comandos del BOT //Comando "ID" que devolverá un mensaje con el ID del chat de Telegram public string[] id = new string[] { "ID" }; public ComandosBot() { } //Escucha de mensajes desde el Bot de Telegram public async Task IniciarEscuchaMensajes() { int offset = 0; string mensaje; while (true) { try { System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; var me = Program.miBot.GetMeAsync().Result; Update[] updates = Program.miBot.GetUpdatesAsync(offset).Result; foreach (var update in updates) { offset = update.Id + 1; if (update.Message == null) continue; //Se obtiene el mensaje original mensaje = update.Message.Text.ToUpper(); new EscribirLog("Mensaje recibido en el chat " + update.Message.Chat.Id.ToString() + " de " + update.Message.Chat.Username, false); int idTelegram = update.Message.From.Id; long idChat = update.Message.Chat.Id; string nickTelegram = update.Message.From.Username; string nombreTelegram = update.Message.From.FirstName; //string apellidosTelegram = update.Message.From.LastName; string usuario = ""; if (nombreTelegram != "") usuario = nombreTelegram; else { if (usuario != "") usuario = nickTelegram; else usuario = "Anónimo"; } switch (mensaje) { //Enviar mensaje a chat de Telegram con el ID del chat actual case "id": await Program.EnviarMensaje(idChat, usuario + ", el ID del chat es: " + idChat.ToString(), Telegram.Bot.Types.Enums.ParseMode.Html, false); break; } } } catch (TaskCanceledException error) { new EscribirLog("Proceso cancelado: " + error.GetType().ToString() + " " + error.Message, false); } catch (AggregateException e) { foreach (var ex in e.InnerExceptions) { Console.WriteLine(ex.Message); } } catch (Exception error) { new EscribirLog("Error al enviar/recibir mensajes al bot: " + error.GetType().ToString() + " " + error.Message, false); } } } //Obtener comandos desde mensaje de Telegram public string ObtenerAccion(string mensaje, ref string parametro) { //Separamos el comando del posible texto adicional (parámetro) //El comando será siempre la primera palabra sin espacios //Quitamos alguna posible coma u otro carácter var comandoF = Regex.Match(mensaje, @"^([\w\-]+)"); string comando = comandoF.Value; //Quitamos del comando cualquier carácter especial string mensajeFormateado = FormatearTexto(comando); comando = ""; if (Array.Exists(id, E => E == mensajeFormateado)) { comando = "id"; } if (comando == "") comando = "desconocido"; return comando; } //Formatea el texto quitando tildes, espacios, / public string FormatearTexto(string texto) { string textoFormateado; //Quitamos las tildes textoFormateado = QuitarTildesTexto(texto); //Pasamos a mayúsculas textoFormateado = textoFormateado.ToUpper(); //Quitamos la / si la incluye el mensaje al principio //(si tenemos setprivacy del Bot de Telegram a ENABLED) if (textoFormateado.Substring(0, 1) == "/") { textoFormateado = textoFormateado.Substring(1, textoFormateado.Length - 1); } //Quitamos ¿?¡! textoFormateado = textoFormateado.Replace("?", ""); textoFormateado = textoFormateado.Replace("¿", ""); textoFormateado = textoFormateado.Replace("¡", ""); textoFormateado = textoFormateado.Replace("!", ""); //Quitamos los espacios textoFormateado = textoFormateado.Replace(" ", ""); return textoFormateado; } //Quita las tildes/acentos de un texto public string QuitarTildesTexto(string texto) { texto = texto.ToLower(); Regex a = new Regex("[á|à|ä|â]", RegexOptions.Compiled); Regex e = new Regex("[é|è|ë|ê]", RegexOptions.Compiled); Regex i = new Regex("[í|ì|ï|î]", RegexOptions.Compiled); Regex o = new Regex("[ó|ò|ö|ô]", RegexOptions.Compiled); Regex u = new Regex("[ú|ù|ü|û]", RegexOptions.Compiled); texto = a.Replace(texto, "a"); texto = e.Replace(texto, "e"); texto = i.Replace(texto, "i"); texto = o.Replace(texto, "o"); texto = u.Replace(texto, "u"); return texto; } } } |
El programa principal Program.cs requerirá también de la referencia a la DLL Telegram.Bot.dll, que incluimos también en la desacarga del código fuente de la aplicación.
Y tendrá el siguiente código fuente C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Timers; using Telegram.Bot; using Telegram.Bot.Types; namespace SistemaEnvioMensajesTelegram { class Program { public static TelegramBotClient miBot; //Conexión con Bot de Telegram para enviar/recibir mensajes static Thread syncThread = null; //Para crear hilos de ejecución (thread) static System.Timers.Timer temporizador; //Temporizador para que un proceso se ejecute repetidamente //Clase para la escucha de mensajes del Bot de Telegram public static ComandosBot miTelegram = new ComandosBot(); static void Main(string[] args) { //Guardamos valores de configuración en INI //De forma manual en tiempo de diseño GuardarValoresConfiguracion(); //Accederemos al Bot de Telegram para enviar los mensajes //También nos servirá por si queremos leer mensajes del Bot //que se hayan escrito en el Chat del Bot new EscribirLog("Obteniendo acceso al Bot ProyectoA...", true); //ID del Bot y token de seguridad //Cambiar ID y token por los del Bot al que queramos conectar en fichero XML de configuración LeerGuardarDatosConfiguracion fConf = new LeerGuardarDatosConfiguracion(); string tokenBotTelegram = fConf.leerValorConfiguracion("Telegram - Token"); miBot = new TelegramBotClient(tokenBotTelegram); //PROYECTOA new EscribirLog("Acceso al Bot concedido, ID del Bot ProyectoA: " + miBot.BotId.ToString(), true); //Por un lado inciciamos el temporizador que se ejecutará en un nuevo hilo //Se ejecutará cada 3 minutos new EscribirLog("Iniciando hilo (thread) de temporizador de 15 segundos...", true); temporizador = new System.Timers.Timer(); temporizador.Interval = 15000; //15 segundos temporizador.Enabled = true; //Método/Evento que se ejecutará cuando transcurran los 3 minutos temporizador.Elapsed += new ElapsedEventHandler(temporizadorElapsed); temporizador.Start(); //Como ejemplo, agregamos la opción de escucha de mensajes del Bot EscucharMensajes().Wait(); } //Cuando se cumple el tiempo establecido para el //temporizador (Interval), se ejecuta este evento protected static void temporizadorElapsed(object sender, ElapsedEventArgs e) { //Se inicia un hilo para comprobar las tareas del usuario pendientes de resolver syncThread = new Thread(new ThreadStart(inicioHiloTemporizadorEnviarMensajes)); syncThread.Start(); } //Hilo que se ejecuta cada intervalo establecido en el temporizador //El hilo ejecutará un método de la clase BD (que se instancia) y //lee de la tabla mensajes_telegram de MySQL, //si hay mensajes pendientes de enviar los envía al ID de chat del usuario protected static void inicioHiloTemporizadorEnviarMensajes() { new EscribirLog("Inicio hilo mensajes pendientes de notificar por Telegram...", false); BD conBD = new BD(); int numMensajesPendientes = conBD.obtenerMensajesPendientesNotificarTelegram(); if (numMensajesPendientes > 0) { //Realizamos la operación que queramos que se ejecute cada 15 segundos //Enviamos un mensaje al chat de Telegram de cada usuario con //los datos de la tabla mensajes_telegram new EscribirLog("Enviando mensajes a los usuarios, " + "total: " + numMensajesPendientes.ToString(), true); string textoMensaje, usuarioAlta, remitente; int codigoMensaje; long idChatMensaje; foreach (var mensaje in conBD.listaMensajes) { textoMensaje = mensaje.Item1; //Acortamos el mensaje si es muy largo (si supera el máximo de Telegram) if (textoMensaje.Length > 4096) textoMensaje = textoMensaje.Substring(0, 4095); idChatMensaje = mensaje.Item2; codigoMensaje = mensaje.Item3; usuarioAlta = mensaje.Item4; remitente = mensaje.Item5; if (idChatMensaje != -1) //Si el usuario tiene ID de chat de Telegram en la bd en la tabla usuario { //Si no se ha indicado remitente en el registro de la tabla //mensajes_telegram, en remitente establecemos el Usuario de Alta (codigousuario) if (remitente != "") textoMensaje = remitente + " \U00002192 " + textoMensaje; else textoMensaje = usuarioAlta + " \U00002192 " + textoMensaje; new EscribirLog("Enviando mensaje a ID de chat " + idChatMensaje.ToString() + "..." , true); EnviarMensaje(idChatMensaje, textoMensaje, Telegram.Bot.Types.Enums.ParseMode.Default, false); new EscribirLog("Mensaje enviado a ID de chat " + idChatMensaje.ToString() + "...", true); //Actualizamos el mensaje como enviado para que no se vuelva a enviar conBD.actualizarMensajeEnviadoTelegram(codigoMensaje, "S", "N", ""); } else { //Actualizamos el mensaje como error=S para que no se vuelva a enviar conBD.actualizarMensajeEnviadoTelegram(codigoMensaje, "N", "S", "El usuario no tiene ID de chat de Telegram"); new EscribirLog("Uno de los usuarios no tiene ID de chat de" + " Telegram asociado, en la tabla usuario, para enviar mensaje [" + codigoMensaje.ToString() + "]", false); } } } else { new EscribirLog("No hay mensajes pendientes de enviar", false); } } //Para que la aplicación quede a la espera de recibir mensajes desde el Bot de Telegram static async Task EscucharMensajes() { new EscribirLog("Iniciando escucha de mensajes...", true); //Instanciamos la clase ComandosBot para escuchar mensajes del Bot de Telegram //Podremos realizar acciones en función del mensaje recibido await miTelegram.IniciarEscuchaMensajes(); } //Enviar mensaje a chat de Telegram public static async Task EnviarMensaje(ChatId idChat, string mensaje, Telegram.Bot.Types.Enums.ParseMode tipo, bool vistaPreviaPagina) { /* //Si es de tipo HTML formatear el texto (Telegram solo admite <b></b>, <i></i>, <a href=...></a>,<code></code>,<pre></pre> //El resto de etiquetas hay que suprimirlas para que no dé error: //Telegram.Bot.Exceptions.ApiRequestException Bad Request: can't parse entities: Unsupported start tag "x" at byte offset x if (tipo == Telegram.Bot.Types.Enums.ParseMode.Html) { mensaje = formatearHTMLTelegram(mensaje); } */ //Si el texto del mensaje supera los 4096 caracteres enviar //en varios mensajes separados, es el límite de Telegram //De lo contrario daría error: Telegram.Bot.Exceptions.ApiRequestException //Bad Request: message is too long if (mensaje != null) { if (mensaje.Length <= 4096) { await miBot.SendTextMessageAsync(idChat, mensaje, tipo, !vistaPreviaPagina); } else { char[] arrayCaracter; arrayCaracter = mensaje.ToCharArray(0, mensaje.Length); int recorridos = 0; int recorridosTotal = 0; string mensajeCortado = ""; foreach (char caracter in arrayCaracter) { mensajeCortado = mensajeCortado + caracter; if ((recorridos == 4095) || recorridosTotal == mensaje.Length - 1) { await miBot.SendTextMessageAsync(idChat, mensajeCortado, tipo, !vistaPreviaPagina); recorridos = 0; mensajeCortado = ""; } else { recorridos++; } recorridosTotal++; } } } } //Se usa para quitar los tag HTML que Telegram no soporta en mensajes de tipo HTML public static string formatearHTMLTelegram(string texto) { //Para que no elimine el texto contenido entre p b i //Quitamos posibles <p></p> texto = texto.Replace("<p style=\"text-align: center; \">", ""); texto = texto.Replace("<p style=\"text-align: right; \">", ""); texto = texto.Replace("<p style=\"text-align: left; \">", ""); texto = texto.Replace("<p>", ""); texto = texto.Replace("</p>", ""); //Quitamos posibles <b></b> texto = texto.Replace("<b>", ""); texto = texto.Replace("</b>", ""); //Quitamos posibles <i></i> texto = texto.Replace("<i>", ""); texto = texto.Replace("</i>", ""); texto = texto.Replace("[caption]", ""); texto = texto.Replace("[/caption]", ""); texto = texto.Replace("[box]", ""); texto = texto.Replace("[/box]", ""); texto = texto.Replace(" ", ""); texto = texto.Replace("[box type=\"download\"]", ""); texto = Regex.Replace(texto, "<.*?>", string.Empty); return texto; } //Método para guardar los valores de configuración en el XML en tiempo de diseño //Sirve para guardar, por ejemplo, la contraseña de acceso a MySQL cifrada correctamente public static void GuardarValoresConfiguracion() { new LeerGuardarDatosConfiguracion("BD - Servidor", ""); new LeerGuardarDatosConfiguracion("BD - Puerto", ""); new LeerGuardarDatosConfiguracion("BD - BD", ""); new LeerGuardarDatosConfiguracion("BD - Usuario", ""); //Instanciamos la clase CifrarDescrifrarTexto para cifrar //la contraseña antes de guardarla en el XML CifrarDescifrarTexto cTexto = new CifrarDescifrarTexto(); new LeerGuardarDatosConfiguracion("BD - Contraseña", cTexto.cifrarTextoAES("contraseña", "Encriptad0", "SalT", "MD5", 22, "1234567891234567", 128)); } } } |
Para el correcto funcionamiento de la aplicación que envía los mensajes de Telegram necesiará el fichero XML de configuración SistemaEnvioMensajesTelegram.exe.config, que tendrá los datos de conexión con el servidor MySQL. Un ejemplo del contenido de este fichero:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="BD - Servidor" value="servidor_mysql" /> <add key="BD - Puerto" value="3306" /> <add key="BD - BD" value="base_datos" /> <add key="BD - Usuario" value="usuario_mysql" /> <add key="BD - Contraseña" value="contraseña_mysql" /> <add key="Telegram - Token" value="2030:AA0WXE" /> </appSettings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> |
Funcionamiento de la aplicación de envío de mensajes a chat de Telegram
El funcionamiento de la aplicación es sencillo, ejecutando la aplicación conectará con el Bot de Telegram indicado en el token y permanecerá a la espera de recibir mensajes. Esta acción no es importante en la aplicación. Por otro lado, la aplicación ejecutará un hilo cada 15 segundos que lee de la tabla mensajes_telegram, del servidor de MySQL Server indicado en el fichero de configuración, y si encuentra un registro que no haya sido enviado, lo envía al chat de Telegram asignado al usuario.
Como ejemplo del funcionamiento real, ejecutaremos la aplicación:
Una vez iniciada la aplicación, si insertamos un registro en la tabla mensajes_telegram del servidor de MySQL:
Transcurridos los 15 segundos, el temporizador de la aplicación leerá el registro insertado en la tabla mensajes_telegram y realizará el envío del mensaje a Telegram, al chat del Bot del usuario, en nuestro caso del Bot ProyectoA:
Con este sistema, cualquier dispositivo, servicio o sistema que pueda insertar un registro en una tabla MySQL, o bien que pueda llamar a otro programa o herramienta que lo haga, podrá notificar por Telegram incidencias, alarmas, alertas, avisos, información, indicadores, estado de sensores, etc..
El sistema también puede leer mensajes que se envíen al Bot, para hacerlo interactivo y bidireccional, de forma que el usuario pueda hacer mandatos de acciones al sistema. Por ejmplo, hemos programado la acción «ID», si se introduce el mensaje «id» [1] en el chat del bot de Telegram, el sistema le devolverá un mensaje con el ID del chat de Telegram [2]:
Como ejemplo de uso práctico de este sistema, a continuación mostramos cómo dotar a Pandora FMS de envío de alertas por Telegram.
Notificación de alertas por Telegram en Pandora FMS
Si queremos usar este sistema de envío de mensajes a Telegram para dotar al sistema de monitorización Pandora FMS de alertas por Telegram, seguiremos los pasos que indicamos a continuación.
En cuanto dispongamos de la tabla mensajes_telegram en el servidor de MySQL/MariaDB al que tenga acceso también el sistema de envío de mensajes, como hemos indicado anteriormente, procederemos a crear una acción en Pandora FMS que ejecute un comando Linux para insertar un registro en esta tabla.
Desde la consola de administración web de Pandora FMS, pulsaremos en el menú «Alertas» [1] – «Comandos» [2]:
Pulsaremos en «Crear» para agregar una nueva acción:
Introduciremos los datos de la nueva acción. El más importante es el comando [2], donde introduciremos un comando Linux que insertará un registro en el servidor de MySQL, en la tabla mensajes_telegram:
1 |
mysql -h 192.168.1.100 -u usu_my -pContraseña_my bd_my -sN --port 3306 -e "INSERT INTO mensajes_telegram (codigousuario, mensaje, codusuarioa, remitente) VALUES ('_field1_', '_field2_', '1','[PANDORA]');" |
Donde:
- 192.168.1.100: IP del servidor de base de datos MySQL que contiene la tabla mesajes_telegram.
- usu_my: nombre de usuario del servidor MySQL con permisos para insertar registros en la tabla mensajes_telegram.
- Contraseña_my: contraseña del usuario anterior.
- bd_my: nombre del catálogo/base de datos.
- 3306: puerto de conexión de MySQL.
- _field1_, _field2_: serán parámetros que Pandora FMS sustituirá por valores que indiquemos en la acción, que definiremos más adelante.
El comando anterior, podremos probarlo antes de agregarlo a Pandora FMS, en la línea de comandos de Linux:
El comando insertará un registro en la tabla mensajes_telegram:
Si funciona, lo agregaremos a Pandora FMS. El campo «codigousuarioa» y el campo «mensaje» lo pasaremos en el comando de Pandora FMS como parámetro, introduciendo «_field1_» en el valor de «codigousuarioa» y «_field2_» en el valor de «mensaje». Estos parámetros serán reemplazados por los datos que indiquemos en la acción (lo explicamos a continuación):
Una vez creado el comando de alerta, crearemos la acción o acciones en Pandora FMS, para ello pulsaremos en el menú «Alertas» – «Acciones»:
Pulsaremos en «Crear»:
Y crearemos una nueva acción, por ejemplo «Mensaje_Telegram_Adm_Sistemas» [1], eligiendo el comando creado anteriormente «Mensaje_Telegram» [1]. Y rellenando el campo 1, en nuestro caso el código de usuario de la aplicación con valor «3» [2] y el campo 2, en nuestro caso el mensaje a enviar [3]. Este mensaje será el que Pandora FMS envíe cuando se produce una alerta en un sensor. Un ejemplo de mensaje a enviar:
1 2 3 4 5 6 |
[ALERTA] _agent_ en _module_ Agente: _agent_ Modulo: _moduledescription_ Dato: _data_ Estado: _modulestatus_ Lanzada: _timestamp_ |
Y en el texto de recuperación [4] de la alerta. Este texto será el que Pandora FMS envíe cuando la alerta se recupere, cuando el sensor pasa a estado normal:
1 2 3 4 |
[RECUPERADA] Alerta _agent_ en _module_ Dato: _data_ Estado: _modulestatus_ Lanzada: _timestamp_ |
Crearemos la acción de la alerta personalizada a nuestro gusto y pulsaremos en «Crear». En nuestro caso la llamaremos «Mensaje_Telegram_Adm_Sistemas». Esta acción enviará un mensaje al técnico de nuestra organización Administrador de Sistemas, con código de usuario de la tabla usuario «3»:
Por último, agregaremos la acción de alerta al agente (servidor/dispositivo) y al módulo (sensor/sonda) que deseemos. Por ejemplo, si queremos que Pandora FMS nos avise mediante mensaje de Telegram cuando el servicio de una aplicación en un servidor se detiene. Accederemos al agente, en nuestro caso el servidor PCPROG y pulsaremos en «Gestionar»:
Pulsaremos en «Alertas»:
Agregaremos la alerta, si no existe, para el módulo que deseemos. En nuestro caso, para el módulo «Servicio_Oracle» elegiremos la acción «Mensaje_Telemgram_Adm_Sistemas» creada anteriormente. Elegiremos el tipo de plantilla, en nuestro caso cuando el módulo pasa a estado crítico (Critical condition) y pulsaremos en «Añadir alerta»:
Una vez agreada la alerta, si el módulo en cuestión pasa a estado crítico, porque en este caso se detenga el servicio monitorizado, Pandora FMS ejecutará la acción asociada e insertará un registro en la tabla mensajes_telegram con los datos de la alerta:
Y nuestro sistema de envío de mensajes a Telegram se encargará de enviarlo al técnico asignado y recibirá también otro mensaje cuando se haya solucionado la situación de alerta:
De esta forma, tendremos un valor añadido para las notificaciones de alertas/alarmas/avisos desde nuestro sistema de monitorización. La gran ventaja de que nos lleguen avisos a Telegram es que gracias a las notificaicones pop-pup estaremos siempre al tanto de cualquier problema en nuestros servicios/servidores/dispositivos.
Descarga del código fuente y la aplicación completa que envía mensajes a Telegram leyendo en una BD MySQL
A continuación os dejamos enlace a la descarga del código fuente completo en C Sharp, desarrollado con Visual Studio .Net Community 2019 (incluye ejecutable, XML y todas las DLL necesarias para el correcto funcionamiento):