paint-brush
Cree un bot de WhatsApp de prueba de trivia con Twilio y ASP.NET Corepor@zadok
2,818 lecturas
2,818 lecturas

Cree un bot de WhatsApp de prueba de trivia con Twilio y ASP.NET Core

por Zadok J.15m2023/09/15
Read on Terminal Reader

Demasiado Largo; Para Leer

En esta guía tutorial, aprenderá cómo crear un cuestionario de trivia usando Twilio para WhatsApp, ASP.NET Core y The Trivia API.
featured image - Cree un bot de WhatsApp de prueba de trivia con Twilio y ASP.NET Core
Zadok J. HackerNoon profile picture
0-item
1-item

Los juegos de trivia brindan una experiencia atractiva y educativa en la que puedes aprender nuevos datos y ampliar tus conocimientos en diversos temas. Hoy en día, las aplicaciones web y móviles de Trivia Quiz son las áreas de acceso más comunes para este tipo de actividad. ¿Qué tal jugar un juego de preguntas en WhatsApp?


En esta guía tutorial, aprenderá cómo crear una aplicación de prueba de trivia usando Twilio para WhatsApp, ASP.NET Core y La API de trivia ( Licenciado bajo CC BY-NC 4.0. ). El objetivo de este tutorial es crear una aplicación de juego de trivia que permita a los usuarios jugar y responder preguntas de opción múltiple usando WhatsApp. Aprovecharás sesiones en ASP.NET Core para almacenar y recuperar el progreso del usuario, realizar un seguimiento de la puntuación y mantener el estado del juego.


Para obtener estas preguntas, utilizará The Trivia API, una API REST, que facilita a los desarrolladores la creación de aplicaciones de cuestionarios al proporcionar preguntas de trivia de opción múltiple. Para obtener más información sobre Trivia API, visite la __ documentación de Trivia API __.


Requisitos previos

Para completar este tutorial, necesitará:


El código fuente de este tutorial se puede encontrar en GitHub. .


Configurar un nuevo proyecto ASP.NET Core

Para comenzar, usando su terminal shell en un directorio de trabajo preferido, ejecute los siguientes comandos para crear un nuevo proyecto API web:


 dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi


El segundo comando en el fragmento anterior creará un nuevo proyecto de API web con el nombre especificado y sin soporte OpenAPI (Swagger). Si desea utilizar Swagger en el proyecto, simplemente omita --no-openapi en el comando anterior.


Cambie al directorio del proyecto ejecutando este comando:


 cd TwilioWhatsAppTriviaApp


Instala el Biblioteca Twilio Helper para ASP.NET Core Paquete NuGet:


 dotnet add package Twilio.AspNet.Core


Esta biblioteca simplifica el trabajo con webhooks y API de Twilio en una aplicación ASP.NET Core.


Abra el proyecto usando su IDE preferido. En la carpeta Controladores , elimine el archivo de controlador de plantilla repetitiva, WeatherForecastController.cs , y también elimine WeatherForcast.cs en el directorio del proyecto.


Construya y ejecute su proyecto para asegurarse de que todo lo que ha hecho hasta ahora funcione bien usando los siguientes comandos:


 dotnet build dotnet run


Después de ejecutar exitosamente el proyecto, tome nota de cualquiera de las URL de localhost que aparecen en la consola de depuración. Puede utilizar cualquiera de estas URL para configurar un servidor web local de acceso público utilizando ngrok.


URL de host local


Implementar sesiones

Las sesiones son una de las varias formas de almacenar los datos de un usuario en una aplicación ASP.NET Core. Esto es esencial cuando desea conservar los datos del usuario entre solicitudes porque, de forma predeterminada, el protocolo HTTP no tiene estado, lo que significa que los datos no se conservan.

Agregue el proveedor de sesión en memoria modificando Program.cs , como se muestra en el siguiente código:


 var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(40); options.Cookie.IsEssential = true; }); var app = builder.Build(); app.UseSession(); app.MapControllers(); app.Run();


AddDistributedMemoryCache() registra el servicio de caché de memoria distribuida. Este servicio proporciona un caché en memoria que se puede utilizar para almacenar y recuperar datos en múltiples solicitudes o sesiones.


AddSession() registra los servicios de la sesión, lo que permite que la aplicación mantenga el estado de la sesión. El parámetro options le permite configurar varias opciones relacionadas con la sesión. IdleTimeout se utiliza para establecer la duración de la inactividad después de la cual una sesión se considerará inactiva. En este caso, se establece en 40 segundos. Cookie.IsEssential garantiza que el estado de la sesión siga siendo funcional incluso en escenarios donde el rechazo de cookies está habilitado.


El soporte de sesión se habilita agregando el middleware UseSession a la canalización de la aplicación, es decir, su aplicación obtiene acceso a un objeto de sesión que se puede usar para almacenar y recuperar datos.


Crear los modelos

Cree una nueva carpeta, Modelos, en el directorio de su proyecto. Agregue dos archivos de clase de modelo, TriviaApiResponse.cs y Question.cs con propiedades como se muestra en los siguientes ejemplos de código:


 using Newtonsoft.Json; namespace TwilioWhatsAppTriviaApp.Models; public class TriviaApiResponse { [JsonProperty("category")] public string Category { get; set; } [JsonProperty("correctAnswer")] public string CorrectAnswer { get; set; } [JsonProperty("incorrectAnswers")] public List<string> IncorrectAnswers { get; set; } [JsonProperty("question")] public string Question { get; set; } [JsonProperty("type")] public string? Type { get; set; } [JsonProperty("difficulty")] public string Difficulty { get; set; } }


 namespace TwilioWhatsAppTriviaApp.Models; public class Question { public string QuestionText { get; set; } public List<(string option, bool isCorrect)> Options { get; set; } }


El modelo TriviaApiResponse incluye propiedades que representan los campos de la respuesta de Trivia API. El atributo JsonProperty garantiza que cada propiedad se complete correctamente con los datos JSON correspondientes.


Para una forma simplificada de manejar las preguntas de trivia, la clase Question viene al rescate. Esta clase resume la información necesaria para una pregunta de trivia, incluido el texto de la pregunta y una lista de opciones. Cada opción está representada por una tupla que contiene el texto de la opción y un valor booleano que indica si es la opción correcta.


Agregar la clase de servicio Trivia

Cree una carpeta de Servicios en el directorio de su proyecto y agregue un nuevo archivo de clase llamado TriviaService.cs . Modifique su contenido, como se muestra en el siguiente código:


 using Newtonsoft.Json; using TwilioWhatsAppTriviaApp.Models; namespace TwilioWhatsAppTriviaApp.Services; public class TriviaService { private const string TheTriviaApiUrl = @"https://the-trivia-api.com/api/questions?limit=3"; private HttpClient httpClient; public TriviaService(HttpClient httpClient) { this.httpClient = httpClient; } public async Task<IEnumerable<TriviaApiResponse>> GetTrivia() { var response = await httpClient.GetAsync(TheTriviaApiUrl); var triviaJson = await response.Content.ReadAsStringAsync(); var trivia = JsonConvert.DeserializeObject<IEnumerable<TriviaApiResponse>>(triviaJson); return trivia; } public List<Question> ConvertTriviaToQuestions(IEnumerable<TriviaApiResponse> questions) { List<Question> newQuestions = new(); foreach (var question in questions) { var options = new List<(string option, bool isCorrect)>() { (question.CorrectAnswer, true), (question.IncorrectAnswers[0], false), (question.IncorrectAnswers[1], false), (question.IncorrectAnswers[2], false) }; // Shuffle the options randomly Random random = new(); options = options.OrderBy(_ => random.Next()).ToList(); newQuestions.Add(new Question { QuestionText = question.Question, Options = options }); } return newQuestions; } }


La clase TriviaService contiene dos métodos: GetTrivia y ConvertTriviaToQuestions . El método GetTrivia envía la solicitud HTTP GET a la API de Trivia con un parámetro de consulta, limit=3 , que especifica que solo se deben devolver 3 preguntas. Sin el parámetro de límite, la API devuelve 10 preguntas de forma predeterminada.


El método ConvertTriviaToQuestions convierte la respuesta de la API de forma organizada. El método también mezcla aleatoriamente todas las opciones de preguntas, de modo que una sola opción no será la respuesta a todas las preguntas.


Para registrar TriviaService y el cliente HTTP en el contenedor de inyección de dependencia (DI) de su aplicación, modifique Program.cs como se muestra en el siguiente código:


 using TwilioWhatsAppTriviaApp.Services; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(40); options.Cookie.IsEssential = true; }); builder.Services.AddHttpClient(); builder.Services.AddScoped<TriviaService>(); var app = builder.Build(); app.UseSession(); app.MapControllers(); app.Run();


Crear el controlador de trivia

Agregue una clase de controlador API vacía en un archivo llamado TriviaController.cs a la carpeta Controladores y modifique su contenido como se muestra en el siguiente código:


 using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Twilio.AspNet.Core; using Twilio.TwiML; using Twilio.TwiML.Messaging; using TwilioWhatsAppTriviaApp.Models; using TwilioWhatsAppTriviaApp.Services; namespace WhatsappTrivia.Controllers; [Route("[controller]")] [ApiController] public class TriviaController : TwilioController { private const string SessionKeyIsGameOn = "IsGameOn"; private const string SessionKeyScore = "Score"; private const string SessionKeyCurrentQuestionIndex = "CurrentQuestionIndex"; private const string SessionKeyTotalQuestions = "TotalQuestions"; private const string SessionKeyQuestions = "Questions"; private static readonly string[] StartCommands = { "START", "S" }; private static readonly string[] OptionValues = { "A", "B", "C", "D" }; private readonly TriviaService triviaService; public TriviaController(TriviaService triviaService) { this.triviaService = triviaService; } [HttpPost] public async Task<IActionResult> Index() { var response = new MessagingResponse(); var form = await Request.ReadFormAsync(); var body = form["Body"].ToString().ToUpper().Trim(); await HttpContext.Session.LoadAsync(); var isGameOn = Convert.ToBoolean(HttpContext.Session.GetString(SessionKeyIsGameOn)); int currentQuestionIndex = HttpContext.Session.GetInt32(SessionKeyCurrentQuestionIndex) ?? 0; int totalQuestions = HttpContext.Session.GetInt32(SessionKeyTotalQuestions) ?? 0; if (StartCommands.Contains(body) && !isGameOn) { await StartGame(); HttpContext.Session.SetString(SessionKeyIsGameOn, "true"); response.Message(PresentQuestionWithOptions(currentQuestionIndex)); return TwiML(response); } if (OptionValues.Contains(body) && isGameOn) { var result = ProcessUserAnswer(body, currentQuestionIndex); response.Message(result); currentQuestionIndex++; if (currentQuestionIndex <= totalQuestions - 1) { HttpContext.Session.SetInt32(SessionKeyCurrentQuestionIndex, currentQuestionIndex); response.Append(new Message(PresentQuestionWithOptions(currentQuestionIndex))); } else { response.Append(new Message(EndTrivia())); } return TwiML(response); } response.Message(!isGameOn ? "*Hello! Send 'Start' or 'S' to play game*" : "*Invalid Input! Send a correct option 'A', 'B', 'C' or 'D'*"); return TwiML(response); } private async Task StartGame() { if (HttpContext.Session.GetString(SessionKeyQuestions) != null) { HttpContext.Session.Remove(SessionKeyQuestions); } var trivia = await this.triviaService.GetTrivia(); var questions = this.triviaService.ConvertTriviaToQuestions(trivia); AddNewQuestionsToSession(questions); HttpContext.Session.SetInt32(SessionKeyTotalQuestions, questions.Count); } private string ProcessUserAnswer(string userAnswer, int questionIndex) { bool optionIsCorrect = false; int score = HttpContext.Session.GetInt32(SessionKeyScore) ?? 0; var question = RetrieveQuestionFromSession(questionIndex); switch (userAnswer) { case "A": optionIsCorrect = question.Options[0].isCorrect; break; case "B": optionIsCorrect = question.Options[1].isCorrect; break; case "C": optionIsCorrect = question.Options[2].isCorrect; break; case "D": optionIsCorrect = question.Options[3].isCorrect; break; } if (optionIsCorrect) { score++; HttpContext.Session.SetInt32(SessionKeyScore, score); } return optionIsCorrect ? "_Correct ✅_" : $"_Incorrect ❌ Correct answer is {question.Options.Find(o => o.isCorrect).option.TrimEnd()}_"; } private string PresentQuestionWithOptions(int questionIndex) { var question = RetrieveQuestionFromSession(questionIndex); return $""" {questionIndex + 1}. {question.QuestionText} {OptionValues[0]}. {question.Options[0].option} {OptionValues[1]}. {question.Options[1].option} {OptionValues[2]}. {question.Options[2].option} {OptionValues[3]}. {question.Options[3].option} """; } private void AddNewQuestionsToSession(List<Question> questions) => HttpContext.Session.SetString(SessionKeyQuestions, JsonConvert.SerializeObject(questions)); private Question RetrieveQuestionFromSession(int questionIndex) { var questionsFromSession = HttpContext.Session.GetString(SessionKeyQuestions); return JsonConvert.DeserializeObject<List<Question>>(questionsFromSession)[questionIndex]; } private string EndTrivia() { var score = HttpContext.Session.GetInt32(SessionKeyScore) ?? 0; var totalQuestions = HttpContext.Session.GetInt32(SessionKeyTotalQuestions) ?? 0; var userResult = $""" Thanks for playing! 😊 You answered {score} out of {totalQuestions} questions correctly. To play again, send 'Start' or 'S' """; HttpContext.Session.Clear(); return userResult; } }


Esta clase de controlador es responsable de manejar los mensajes entrantes, administrar el estado de la sesión y generar respuestas. Hereda de la clase TwilioController proporcionada por la biblioteca Twilio.AspNet.Core, que le brinda acceso al método TwiML . Puede utilizar este método para responder con TwiML, que es el lenguaje de marcado Twilio . La clase TriviaController utiliza métodos HttpContext.Session para interactuar con la sesión.

Las entradas válidas son elementos de las matrices de solo lectura StartCommands y OptionValues . El cuerpo del mensaje entrante se compara con estos elementos para garantizar que el usuario envió una entrada adecuada; de lo contrario, se enviará un mensaje al usuario solicitándole que realice la entrada correcta según el estado actual del juego. Otros campos con el prefijo "SessionKey" se utilizan para definir cadenas constantes privadas para las claves de sesión en el programa.


El método Index es el método de acción principal que maneja las solicitudes HTTP POST entrantes de WhatsApp a través de la ruta /Trivia . Carga los datos de la sesión usando HttpContext.Session.LoadAsync() y recupera datos sobre el estado del juego de la sesión usando los métodos HttpContext.Session.GetString() y HttpContext.Session.GetInt32() .


El uso de guiones bajos (_) y asteriscos (*) al principio y al final de ciertas cadenas es para lograr formato de texto en cursiva y negrita, respectivamente, en los mensajes de WhatsApp renderizados.


Cada método auxiliar en TriviaController realiza una tarea específica que admite la funcionalidad principal de la clase.

  • El método StartGame inicializa el juego recuperando preguntas de trivia, convirtiéndolas a un formato adecuado para el juego y almacenándolas en la sesión.
  • El método ProcessUserAnswer procesa la respuesta del usuario a una pregunta y determina si es correcta o no.
  • El método PresentQuestionWithOptions es responsable de formatear y presentar una pregunta junto con sus opciones.
  • El método AddNewQuestionsToSession almacena una lista de preguntas en la sesión. Convierte las preguntas al formato JSON y guarda la cadena JSON en la sesión.
  • El método RetrieveQuestionFromSession recupera una pregunta de la sesión utilizando el índice de preguntas.
  • El método EndTrivia genera un mensaje para finalizar el juego de trivia. Este método también elimina los datos de la sesión relacionados con el juego. Según la configuración del servicio de sesión en Program.cs , esto sucede automáticamente cuando la sesión está inactiva durante 40 segundos.


Pruebe la aplicación

Para probar la aplicación, debe configurar Twilio Sandbox para WhatsApp, hacer que el punto final de su aplicación sea de acceso público y agregar la URL del punto final en la configuración de Sandbox como un webhook.

Configurar Twilio Sandbox para WhatsApp

Ve a la Consola Twilio , navega hasta Mensaje > Pruébalo > Enviar un mensaje de WhatsApp .


Consola Twilio


Siga las instrucciones en la página para conectarse al sandbox, enviando un mensaje de WhatsApp desde su dispositivo al número de Twilio proporcionado para crear una conexión exitosa con el sandbox de WhatsApp. De manera similar, otras personas que deseen probar su aplicación con sus respectivos números deberán seguir el mismo procedimiento.

Exponga su webhook usando ngrok para realizar pruebas

Ahora, abra una terminal de shell y ejecute el siguiente comando para iniciar ngrok y exponer su aplicación ASP.NET Core local reemplazando <localhost-url> con la URL completa de su localhost que copió inicialmente:


 ngrok http <localhost-url>


ngrok generará una URL pública que reenviará solicitudes a su aplicación ASP.NET local. Busque la URL de reenvío denominada Reenvío en la ventana del terminal ngrok y cópiela.


URL de reenvío


Vuelva a la página Twilio Pruebe WhatsApp, haga clic en Configuración de Sandbox y cambie la URL del punto final Cuando llega un mensaje con la URL de reenvío generada por ngrok más la ruta /Trivia y asegúrese de que el método esté configurado en POST. Luego haga clic en Guardar para guardar la nueva configuración de la zona de pruebas.


Caja de arena de Twilio


Demostración del proyecto

Ejecute su proyecto ASP.NET Core usando el siguiente comando:


 dotnet run


Ahora, pruebe su aplicación enviando un mensaje en la conversación inicial con el número de Twilio Sandbox.


Probando su aplicación en WhatsApp


Conclusión

Al aprovechar el poder de la plataforma Twilio y WhatsApp, ha creado una experiencia de juego de trivia inmersiva para que disfruten los usuarios. También aprendió a guardar y recuperar datos de sesiones.


Hay varias formas en que se puede mejorar este proyecto. Puede mejorar aún más este proyecto agregando un temporizador, manejando excepciones, permitiendo a los usuarios elegir la dificultad y aplicando la dificultad elegida como parámetro de consulta a través de la URL de la API de Trivia (p. ej. https://the-trivia-api.com/api/questions?difficulty=hard ). Además, se puede explorar la posibilidad de crear una solución que permita a los usuarios completar encuestas a través de WhatsApp.