paint-brush
Crie um bot de perguntas e respostas para WhatsApp com Twilio e ASP.NET Corepor@zadok
2,842 leituras
2,842 leituras

Crie um bot de perguntas e respostas para WhatsApp com Twilio e ASP.NET Core

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

Muito longo; Para ler

Neste guia tutorial, você aprenderá como criar um quiz de perguntas e respostas usando Twilio para WhatsApp, ASP.NET Core e The Trivia API.
featured image - Crie um bot de perguntas e respostas para WhatsApp com Twilio e ASP.NET Core
Zadok J. HackerNoon profile picture
0-item
1-item

Os jogos de curiosidades proporcionam uma experiência envolvente e educacional onde você pode aprender novos fatos e expandir seu conhecimento em vários assuntos. Hoje em dia, os aplicativos móveis e da web do Trivia quiz são as áreas mais comuns para tal atividade. Que tal jogar um jogo de perguntas e respostas no WhatsApp?


Neste guia tutorial, você aprenderá como criar um aplicativo de perguntas e respostas usando Twilio para WhatsApp, ASP.NET Core e A API de curiosidades ( licenciado sob CC BY-NC 4.0 ). O objetivo deste tutorial é criar um aplicativo de jogo de perguntas e respostas que permita aos usuários jogar e responder questões de múltipla escolha usando o WhatsApp. Você vai aproveitar sessões no ASP.NET Core para armazenar e recuperar o progresso do usuário, acompanhar a pontuação e manter o estado do jogo.


Para obter essas perguntas, você usará a API Trivia, uma API REST, que facilita aos desenvolvedores a criação de aplicativos de teste, fornecendo perguntas triviais de múltipla escolha. Para saber mais sobre a API Trivia, visite a__documentação da API Trivia__ .


Pré-requisitos

Para concluir este tutorial, você precisará de:


O código fonte deste tutorial pode ser encontrado no GitHub .


Configurar um novo projeto ASP.NET Core

Para começar, usando seu terminal shell em um diretório de trabalho preferido, execute os seguintes comandos para criar um novo projeto de API da web:


 dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi


O segundo comando no trecho acima criará um novo projeto de API web com o nome especificado e sem suporte OpenAPI (Swagger). Se você quiser usar o Swagger no projeto, basta omitir --no-openapi no comando acima.


Mude para o diretório do projeto executando este comando:


 cd TwilioWhatsAppTriviaApp


Instale o Biblioteca auxiliar da Twilio para ASP.NET Core Pacote NuGet:


 dotnet add package Twilio.AspNet.Core


Essa biblioteca simplifica o trabalho com webhooks e APIs da Twilio em um aplicativo ASP.NET Core.


Abra o projeto usando seu IDE preferido. Na pasta Controladores , remova o arquivo de controlador de modelo padrão, WeatherForecastController.cs e também remova o WeatherForcast.cs no diretório do projeto.


Crie e execute seu projeto para garantir que tudo o que você fez até agora funcione bem usando os seguintes comandos:


 dotnet build dotnet run


Depois de executar o projeto com êxito, anote qualquer uma das URLs do host local que aparecem no console de depuração. Você pode usar qualquer um desses URLs para configurar um servidor web local acessível publicamente usando o ngrok.


URLs de host local


Implementar sessões

As sessões são uma das diversas maneiras de armazenar os dados de um usuário em um aplicativo ASP.NET Core. Isso é essencial quando você deseja reter os dados do usuário entre solicitações porque, por padrão, o protocolo HTTP não tem estado – isso significa que os dados não são preservados.

Adicione o provedor de sessão na memória modificando Program.cs , conforme mostrado no código a seguir:


 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 o serviço de cache de memória distribuída. Este serviço fornece um cache na memória que pode ser usado para armazenar e recuperar dados em várias solicitações ou sessões.


AddSession() registra os serviços de sessão, permitindo que o aplicativo mantenha o estado da sessão. O parâmetro options permite configurar diversas opções relacionadas à sessão. IdleTimeout é usado para definir a duração da inatividade após a qual uma sessão será considerada inativa. Neste caso, está definido para 40 segundos. Cookie.IsEssential garante que o estado da sessão permaneça funcional mesmo em cenários onde a rejeição de cookies está habilitada.


O suporte de sessão é habilitado adicionando o middleware UseSession ao pipeline da aplicação, ou seja, sua aplicação ganha acesso a um objeto de sessão que pode ser usado para armazenar e recuperar dados.


Crie os modelos

Crie uma nova pasta, Modelos, no diretório do seu projeto. Adicione dois arquivos de classe de modelo, TriviaApiResponse.cs e Question.cs com propriedades conforme mostrado nos exemplos de código a seguir:


 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; } }


O modelo TriviaApiResponse inclui propriedades que representam os campos da resposta da API Trivia. O atributo JsonProperty garante que cada propriedade seja preenchida corretamente com os dados JSON correspondentes.


Para uma maneira simplificada de lidar com perguntas triviais, a classe Question vem em socorro. Esta classe encapsula as informações necessárias para uma pergunta trivial, incluindo o texto da pergunta e uma lista de opções. Cada opção é representada por uma tupla contendo o texto da opção e um valor booleano que indica se é a opção correta.


Adicione a classe de serviço de curiosidades

Crie uma pasta Services no diretório do seu projeto e adicione um novo arquivo de classe chamado TriviaService.cs . Modifique seu conteúdo, conforme mostrado no código a seguir:


 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; } }


A classe TriviaService contém dois métodos: GetTrivia e ConvertTriviaToQuestions . O método GetTrivia envia a solicitação HTTP GET para a API Trivia com um parâmetro de consulta, limit=3 , que especifica que apenas 3 perguntas devem ser retornadas. Sem o parâmetro limit, a API retorna 10 perguntas por padrão.


O método ConvertTriviaToQuestions converte a resposta da API de forma organizada. O método também embaralha todas as opções de perguntas aleatoriamente, de modo que uma única opção não seja a resposta para todas as perguntas.


Para registrar TriviaService e o cliente HTTP no contêiner DI (Injeção de Dependência) do seu aplicativo, modifique Program.cs conforme mostrado no código a seguir:


 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();


Crie o controlador de curiosidades

Adicione uma classe de controlador de API vazia em um arquivo chamado TriviaController.cs à pasta Controllers e modifique seu conteúdo conforme mostrado no código a seguir:


 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 classe controladora é responsável por lidar com mensagens recebidas, gerenciar o estado da sessão e gerar respostas. Ele herda da classe TwilioController fornecida pela biblioteca Twilio.AspNet.Core, que dá acesso ao método TwiML . Você pode usar este método para responder com TwiML, que é a linguagem de marcação Twilio . A classe TriviaController usa métodos HttpContext.Session para interagir com a sessão.

As entradas válidas são elementos nas matrizes somente leitura StartCommands e OptionValues . O corpo da mensagem recebida é comparado com esses elementos para garantir que o usuário enviou uma entrada correta; caso contrário, uma mensagem será enviada ao usuário solicitando que ele faça a entrada correta com base no estado atual do jogo. Outros campos com o prefixo “SessionKey” são usados para definir strings constantes privadas para chaves de sessão no programa.


O método Index é o principal método de ação que lida com solicitações HTTP POST recebidas do WhatsApp por meio da rota /Trivia . Ele carrega os dados da sessão usando HttpContext.Session.LoadAsync() e recupera dados relativos ao estado do jogo da sessão usando os métodos HttpContext.Session.GetString() e HttpContext.Session.GetInt32() .


O uso de sublinhados (_) e asteriscos (*) no início e no final de certas strings é para obter formatação de texto em itálico e negrito, respectivamente, em mensagens renderizadas do WhatsApp.


Cada método auxiliar no TriviaController executa uma tarefa específica que oferece suporte à funcionalidade principal da classe.

  • O método StartGame inicializa o jogo recuperando perguntas triviais, convertendo-as em um formato adequado para o jogo e armazenando-as na sessão.
  • O método ProcessUserAnswer processa a resposta do usuário a uma pergunta e determina se ela está correta ou não.
  • O método PresentQuestionWithOptions é responsável por formatar e apresentar uma pergunta junto com suas opções.
  • O método AddNewQuestionsToSession armazena uma lista de perguntas na sessão. Ele converte as perguntas para o formato JSON e salva a string JSON na sessão.
  • O método RetrieveQuestionFromSession recupera uma pergunta da sessão usando o índice de perguntas.
  • O método EndTrivia gera uma mensagem para encerrar o jogo de perguntas e respostas. Este método também remove dados de sessão relacionados ao jogo. Com base na configuração do serviço de sessão em Program.cs , isso acontece automaticamente quando a sessão fica inativa por 40 segundos.


Teste o aplicativo

Para testar o aplicativo, você precisa configurar o Twilio Sandbox para WhatsApp, tornar o endpoint do seu aplicativo acessível publicamente e adicionar o URL do endpoint na configuração do Sandbox como um webhook.

Configure o Twilio Sandbox para WhatsApp

Vou ao Console Twilio , navegue até Mensagem > Experimente > Enviar uma mensagem do WhatsApp .


Console Twilio


Siga as instruções na página para se conectar ao sandbox, enviando uma mensagem do WhatsApp do seu dispositivo para o número Twilio fornecido para criar uma conexão bem-sucedida com o sandbox do WhatsApp. Da mesma forma, outras pessoas que desejarem testar seu aplicativo com seus respectivos números deverão seguir o mesmo procedimento.

Exponha seu webhook usando ngrok para teste

Agora, abra um terminal shell e execute o seguinte comando para iniciar o ngrok e expor seu aplicativo ASP.NET Core local, substituindo <localhost-url> pela URL completa do seu host local que você copiou inicialmente:


 ngrok http <localhost-url>


O ngrok gerará uma URL pública que encaminhará solicitações para seu aplicativo ASP.NET local. Procure o URL de encaminhamento denominado Encaminhamento na janela do terminal ngrok e copie-o.


URL de encaminhamento


Volte para a página Twilio Try WhatsApp, clique em Sandbox Settings e altere o URL do endpoint When a message comes in endpoint com o URL de encaminhamento gerado pelo ngrok mais a rota /Trivia e certifique-se de que o método esteja definido como POST. Em seguida, clique em Salvar para salvar a nova configuração do sandbox.


Caixa de areia Twilio


Demonstração do projeto

Execute seu projeto ASP.NET Core usando o seguinte comando:


 dotnet run


Agora teste seu aplicativo enviando uma mensagem na conversa inicial com o número do Twilio Sandbox.


Testando seu aplicativo no WhatsApp


Conclusão

Ao aproveitar o poder da plataforma Twilio e do WhatsApp, você criou uma experiência imersiva de jogo de perguntas e respostas para os usuários aproveitarem. Você também aprendeu como salvar e recuperar dados de sessões.


Existem várias maneiras pelas quais este projeto pode ser melhorado. Você pode melhorar ainda mais este projeto adicionando um cronômetro, manipulando exceções, permitindo que os usuários escolham a dificuldade e aplicando a dificuldade escolhida como um parâmetro de consulta por meio do URL da API Trivia (por exemplo https://the-trivia-api.com/api/questions?difficulty=hard ). Além disso, você pode explorar a possibilidade de criar uma solução que permita aos usuários responder pesquisas via WhatsApp.