La creación de WhatsApp bot en C#. Guía completa

Repositorio con fuente bot

Introducción

En esta guía, explicaremos cómo crear un bot de WhatsApp en C# usando nuestra puerta de enlace API de WhatsApp

Este bot responde a los comandos recibidos en forma de mensajes regulares en WhatsApp y responderá a ellos. La funcionalidad de prueba del bot estará limitada por las siguientes funciones:

  • La reacción con un texto de bienvenida a un mensaje cuyo bot no tiene un comando. Visualización del menú bot
  • Visualización de ID de chat actual (en chat privado o conversación)
  • Envío de archivos de varios formatos (pdf, jpg, doc, mp3, etc.)
  • Envío de mensajes de voz (archivos *.ogg)
  • Envío de geolocalización
  • Creación de un grupo privado con interlocutor y el bot.

Escribiremos nuestro bot usando la tecnología ASP.Net para crear un servidor que procesará y responderá a las solicitudes de los usuarios

Obtener acceso gratuito a la WhatsApp API

Capítulo 1. Creación de un proyecto ASP.Net

Abrimos el Visual Studio y creamos el proyecto "Aplicación web ASP.NET Core".

A continuación, seleccionamos una plantilla con un proyecto vacío (también puede seleccionar una plantilla API que incluirá los controladores necesarios, que solo tendrán que editarse. Sin embargo, por simplicidad, crearemos todo desde cero)

Abrimos el archivo Startup.cs y escribimos este código en el método Configure:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
         if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

Esto nos permitirá configurar la navegación usando el controlador. Ahora vamos a empezar a escribir el propio controlador.

Para eso, creamos en el proyecto una carpeta llamada Controllers, en la que creamos nuestra clase de controlador WebHookController

Nuestro controlador debe heredar la clase ControllerBase y estar marcado con atributos

using Microsoft.AspNetCore.Mvc;
namespace WaBot.Controllers
    {
        [ApiController]
        [Route("/")]
        public class WebHookController : ControllerBase
            {

            }
    }

El atributo Route será responsable de la dirección en la que operará este controlador. Especificamos el camino base del dominio.

En esta etapa, nuestro controlador está casi listo. Ahora necesitamos agregar métodos para trabajar con la API WA y otras clases auxiliares que nos serán útiles en nuestro trabajo.


La autorización del teléfono

Asociamos whatsapp con nuestro script, de modo que, al escribir el código, verifiquemos su funcionamiento. Para hacer esto, vaya a su cuenta personal y obtenga un código QR allí. Luego abra WhatsApp en su teléfono, vaya a Configuración -> WhatsApp Web -> Escanee el código QR.


Capítulo 2. Clase de API

En este capítulo, consideramos escribir una clase que será responsable de interactuar con nuestra puerta de enlace API. La documentación se puede leer aquí. Creamos una clase WaApi:

public class WaApi
    {
        private string APIUrl = "";
        private string token = "";

        public WaApi(string aPIUrl, string token)
            {
                APIUrl = aPIUrl;
                this.token = token;
            }
    }

Esta clase almacenará los campos APIUrl y token, que son necesarios para trabajar con la API. Puedes obtenerlos de su cuenta personal.

Lo mismo ocurre con un constructor que da valores a los campos. Gracias a eso, podemos tener varios objetos que pueden representar diferentes bots, en caso de que sea necesario configurar el trabajo de varios bots al mismo tiempo.

El método para el envío de solicitudes

Añadimos un método asíncrono a esa clase que enviará solicitudes POST:

public async Task<string> SendRequest(string method, string data)
    {
        string url = $"{APIUrl}{method}?token={token}";

        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri(url);
            var content = new StringContent(data, Encoding.UTF8, "application/json");
            var result = await client.PostAsync("", content);
            return await result.Content.ReadAsStringAsync();
        }
    }

Este método acepta dos argumentos.

  •   method - el nombre del método deseado de acuerdo con documentación
  •   data - json cadena (string) para el envío

En el método, formamos la cadena de url a la que se enviará la solicitud. Y luego hacemos una solicitud POST a esa dirección usando la clase System.Net.Http.HttpClient. Devolvemos la respuesta del servidor.

Basándonos en ese método, podemos crear la funcionalidad necesaria para que el bot funcione.

El envío de mensajes

public async Task<string> SendMessage(string chatID, string text)
    {
        var data = new Dictionary<string, string>()
        {
            {"chatId",chatID },
            { "body", text }
        };
        return await SendRequest("sendMessage", JsonConvert.SerializeObject(data));
    }

Este método tiene como parámetros:

  • chatId - ID de chat donde desea enviar el mensaje
  • text - El texto del mensaje que desea enviar.

Para formar una cadena Json, usaremos la biblioteca apropiada Newtonsoft.Json. Crearemos un diccionario cuya clave es una cadena con el campo Json necesario, de acuerdo con documentación, y el valor son: nuestros parámetros. Basta con llamar al método JsonConvert.SerializeObject y pasar nuestro diccionario para que forme una cadena Json.

Llamamos al método SendRequest, pasándole el nombre del método para enviar mensajes y nuestra cadena json.

De esta manera, nuestro bot responderá a los usuarios.

El envio de uma mensagem de voz

public async Task<string> SendOgg(string chatID)
    {
        string ogg = "https://firebasestorage.googleapis.com/v0/b/chat-api-com.appspot.com/o/audio_2019-02-02_00-50-42.ogg?alt=media&token=a563a0f7-116b-4606-9d7d-172426ede6d1";
        var data = new Dictionary<string, string>
        {
            {"audio", ogg },
            {"chatId", chatID }
        };

        return await SendRequest("sendAudio", JsonConvert.SerializeObject(data));
    }

A continuación, la lógica de la construcción de métodos es similar. lo vemos en documentación y enviamos los datos necesarios al servidor, llamando a los métodos necesarios.

Para enviar un mensaje de voz, usamos el enlace en el archivo formato .ogg y el método sendAudio

El método para el envío de geolocalización

public async Task<string> SendGeo(string chatID)
    {
        var data = new Dictionary<string, string>()
        {
            { "lat", "55.756693" },
            { "lng", "37.621578" },
            { "address", "Your address" },
            { "chatId", chatID}
        };
        return await SendRequest("sendLocation", JsonConvert.SerializeObject(data));
    }

La creación de grupos

Crie uma conferência na qual estará você e o bot

public async Task<string> CreateGroup(string author)
    {
        var phone = author.Replace("@c.us", "");
        var data = new Dictionary<string, string>()
        {
            { "groupName", "Group C#"},
            { "phones", phone },
            { "messageText", "This is your group." }
        };
        return await SendRequest("group", JsonConvert.SerializeObject(data));
    }

El método para el envío de archivos

Hemos discutido algunos puntos relacionados con este método. Es compatible con los parámetros:

  •   chatID - ID de chat
  •   format - formato de archivo que desea enviar.

Para enviar un archivo, la documentación proporciona varias formas:

  •   Enlace al archivo que desea enviar
  •   Cadena que es un archivo codificado usando el método Base64.

Es recomendable enviar archivos usando el segundo método, codificando los archivos en formato Base64. En Capítulo 4 Hablaremos de esto con más detalle. Y ahora, vale la pena saber que hemos descrito una clase estática Base64String, en la que escribimos las propiedades que registran todos los archivos de prueba de los formatos deseados. En el método, simplemente llamamos a la propiedad del formato deseado y transferimos la cadena Base64 suministrada al servidor.

public async Task<string> SendFile(string chatID, string format)
    {
        var availableFormat = new Dictionary<string, string>()
        {
            {"doc", Base64String.Doc },
            {"gif",Base64String.Gif },

            { "jpg",Base64String.Jpg },
            { "png", Base64String.Png },
            { "pdf", Base64String.Pdf },
            { "mp4",Base64String.Mp4 },
            { "mp3", Base64String.Mp3}
        };

        if (availableFormat.ContainsKey(format))
        {
            var data = new Dictionary<string, string>(){
                { "chatId", chatID },
                { "body", availableFormat[format] },
                { "filename", "yourfile" },
                { "caption", $"My file!" }
            };

            return await SendRequest("sendFile", JsonConvert.SerializeObject(data));
        }

        return await SendMessage(chatID, "No file with this format");
    }

Esto describe la funcionalidad básica de nuestra clase API. Ahora conectamos nuestro controlador Capítulo 1 y API.

Capítulo 3. Procesamiento de solicitudes.

Volvamos al controlador del primer capítulo. Describimos un método dentro del controlador que manejará las solicitudes post que lleguen a nuestro servidor chat-api.com. Llamaremos al método Post (el nombre puede ser cualquiera) y lo marcamos con el atributo [HttpPost], lo que significa responder a las solicitudes Post:

[HttpPost]
public async Task<string> Post(Answer data)
    {
        return "";
    }

Nuestro método será reconocido por la clase Answer, que es un objeto deserializado de la cadena json. Para describir la clase Answer, necesitamos averiguar qué json aparecerá.

Para eso, se puede usar la conveniente sección "Pruebas" - " Simulación Webhooka" en su cuenta personal

A la derecha podemos ver el cuerpo json que aparecerá.

Utilizaremos el servicio Convertir JSON en C#. O describimos la clase nosotros mismos, usando los atributos de la biblioteca Newtonsoft.Json:

public partial class Answer
    {
        [JsonProperty("instanceId")]
        public string InstanceId { get; set; }

        [JsonProperty("messages")]
        public Message[] Messages { get; set; }
    }

public partial class Message
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("body")]
        public string Body { get; set; }

        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("senderName")]
        public string SenderName { get; set; }

        [JsonProperty("fromMe")]
        public bool FromMe { get; set; }

        [JsonProperty("author")]
        public string Author { get; set; }

        [JsonProperty("time")]
        public long Time { get; set; }

        [JsonProperty("chatId")]
        public string ChatId { get; set; }

        [JsonProperty("messageNumber")]
        public long MessageNumber { get; set; }
    }

Ahora que tenemos una representación de objeto de la solicitud entrante, la procesaremos en el controlador.

Dentro del controlador, crearemos un campo estático, que será nuestra API referencia y token:

private static readonly WaApi api = new WaApi("https://eu115.chat-api.com/instance12345/", "123456789token");

En el bucle del método, revisamos todos los mensajes que nos han llegado y verificamos si el mensaje que se está procesando no es nuestro. Esto es necesario para que el bot no entre en ciclos por sí mismo. Sin embargo, si el mensaje es de sí mismo, saltamos:

[HttpPost]
public async Task<string> Post(Answer data)
    {
        foreach (var message in data.Messages)
        {
            if (message.FromMe)
                continue;
        }
    }

A continuación, describimos el switch al que transmitiremos los comandos recibidos. Aplique a la propiedad Body el método Split(), para dividir el mensaje por espacios. Transferimos al switch la primera orden, llamando al método ToLower() para que el estilo de escritura del comando no desempeñe un papel y sea procesado de la misma manera:

[HttpPost]
public async Task<string> Post(Answer data)
    {
        foreach (var message in data.Messages)
        {
            if (message.FromMe)
                continue;

            switch (message.Body.Split()[0].ToLower())
            {
                case "chatid":
                    return await api.SendMessage(message.ChatId, $"Your ID: {message.ChatId}");
                case "file":
                    var texts = message.Body.Split();
                    if (texts.Length > 1)
                        return await api.SendFile(message.ChatId, texts[1]);
                    break;
                case "ogg":
                    return await api.SendOgg(message.ChatId);
                case "geo":
                    return await api.SendGeo(message.ChatId);
                case "group":
                    return await api.CreateGroup(message.Author);
                default:
                    return await api.SendMessage(message.ChatId, welcomeMessage);
            }
        }
        return "";
    }

En case escribiremos todos los comandos que necesitamos y llamaremos a los métodos de nuestro objeto API que los implementa. Y default procesará comandos que no existen. Para hacer esto, simplemente envíe un mensaje desde el menú del bot al usuario.

Whatsapp bot en C#

Guía detallada para el desarrollo de bots usando Csharp

Nuestro bot está listo. Él ya puede responder y procesar los comandos del usuario, todo lo que queda es agregarlo al alojamiento y especificar el dominio como webhook en la cuenta de usuario de chat-api.com.

Las fuentes de bot están disponibles en el enlace de github: https://github.com/chatapi/whatsapp-csharp-bot-en. No se olvide de reemplazar su token de su cuenta personal y número de instancia.

Get API key

Capítulo 4. Base64

Base64 - Este es un estándar de codificación de datos. Con este patrón, podemos codificar archivos en una cadena y transferirlos de esta manera. En su cuenta personal está disponible servicio, lo que ayudará a generar una cadena de este formato. Para escribir este bot, fue necesario proporcionar algunos datos estáticos para la prueba, así que introdujimos las cadenas recibidas en la clase auxiliar y accedimos a ellas desde el código.

Puede ser más útil para usted codificar archivos "al vuelo".

Para la generación de tales strings también puede utilizar herramientas internas del lenguaje C#.

Capítulo 5. Publicación del servidor.

Para instalar el servidor como un weebhook, es necesario subir del mismo servidor en Internet. Para eso, puede utilizar los servicios de prestación de servicios de alojamiento, servidores vps o vds.

Es necesario elegir y pagar por un servicio que ofrezca soporte a la tecnología ASP.Net.

Los posibles problemas que pueden ocurrir

  •  Si no se puede conectar con el servidor de alojamiento para publicar su servidor. Solución: póngase en contacto con el soporte técnico y solicite habilitar la Web Deploy para su Servicio

  •  HTTP ERROR 500.0 la solución está aquí