paint-brush
Xây dựng Bot WhatsApp Quiz Trivia với Twilio và ASP.NET Coretừ tác giả@zadok
2,604 lượt đọc
2,604 lượt đọc

Xây dựng Bot WhatsApp Quiz Trivia với Twilio và ASP.NET Core

từ tác giả Zadok J.15m2023/09/15
Read on Terminal Reader

dài quá đọc không nổi

Trong hướng dẫn hướng dẫn này, bạn sẽ tìm hiểu cách tạo một câu đố trắc nghiệm bằng Twilio cho WhatsApp, ASP.NET Core và The Trivia API.
featured image - Xây dựng Bot WhatsApp Quiz Trivia với Twilio và ASP.NET Core
Zadok J. HackerNoon profile picture
0-item
1-item

Trò chơi đố vui mang lại trải nghiệm hấp dẫn và mang tính giáo dục, nơi bạn có thể tìm hiểu các sự kiện mới và mở rộng kiến thức của mình về nhiều chủ đề khác nhau. Ngày nay, các ứng dụng web và di động đố vui Trivia là những lĩnh vực phổ biến nhất cho hoạt động như vậy. Chơi một trò chơi đố vui trên WhatsApp thì sao?


Trong hướng dẫn hướng dẫn này, bạn sẽ tìm hiểu cách tạo một ứng dụng đố vui bằng Twilio cho WhatsApp, ASP.NET Core và API câu đố ( được cấp phép theo CC BY-NC 4.0 ). Mục đích của hướng dẫn này là tạo ra một ứng dụng trò chơi đố cho phép người dùng chơi và trả lời các câu hỏi trắc nghiệm bằng WhatsApp. Bạn sẽ tận dụng phiên trong ASP.NET Core để lưu trữ và truy xuất tiến trình của người dùng, theo dõi điểm số và duy trì trạng thái trò chơi.


Để tìm nạp những câu hỏi này, bạn sẽ sử dụng API Trivia, một API REST, giúp các nhà phát triển dễ dàng xây dựng ứng dụng bài kiểm tra bằng cách cung cấp các câu hỏi đố trắc nghiệm. Để tìm hiểu thêm về API Trivia, vui lòng truy cập__ Tài liệu API Trivia __.


Điều kiện tiên quyết

Để hoàn thành hướng dẫn này, bạn sẽ cần:


Mã nguồn của hướng dẫn này có thể được tìm thấy trên GitHub .


Thiết lập dự án ASP.NET Core mới

Để bắt đầu, hãy sử dụng thiết bị đầu cuối shell của bạn trong thư mục làm việc ưa thích, hãy chạy các lệnh sau để tạo dự án API web mới:


 dotnet new webapi -n TwilioWhatsAppTriviaApp --no-openapi


Lệnh thứ hai trong đoạn mã trên sẽ tạo một dự án API web mới với tên được chỉ định và không hỗ trợ OpenAPI (Swagger). Nếu bạn muốn sử dụng Swagger trong dự án, chỉ cần bỏ qua --no-openapi trong lệnh trên.


Thay đổi thư mục dự án bằng cách chạy lệnh này:


 cd TwilioWhatsAppTriviaApp


Cài đặt Thư viện trợ giúp Twilio cho ASP.NET Core Gói NuGet:


 dotnet add package Twilio.AspNet.Core


Thư viện này đơn giản hóa hoạt động với các webhook và API Twilio trong ứng dụng ASP.NET Core.


Mở dự án bằng IDE ưa thích của bạn. Trong thư mục Bộ điều khiển , hãy xóa tệp bộ điều khiển mẫu soạn sẵn, WeatherForecastController.cs , đồng thời xóa WeatherForcast.cs trong thư mục dự án.


Xây dựng và chạy dự án của bạn để đảm bảo mọi thứ bạn đã làm cho đến nay hoạt động tốt bằng cách sử dụng các lệnh sau:


 dotnet build dotnet run


Sau khi chạy dự án thành công, hãy ghi lại bất kỳ URL localhost nào xuất hiện trong bảng điều khiển gỡ lỗi. Bạn có thể sử dụng bất kỳ URL nào trong số này để thiết lập máy chủ web cục bộ có thể truy cập công khai bằng ngrok.


URL localhost


Phiên triển khai

Phiên là một trong nhiều cách lưu trữ dữ liệu của người dùng trong ứng dụng ASP.NET Core. Điều này rất cần thiết khi bạn muốn giữ lại dữ liệu người dùng giữa các yêu cầu vì theo mặc định, giao thức HTTP không có trạng thái - điều đó có nghĩa là dữ liệu không được bảo toàn.

Thêm nhà cung cấp phiên trong bộ nhớ bằng cách sửa đổi Program.cs , như minh họa trong đoạn mã sau:


 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() đăng ký dịch vụ bộ nhớ đệm được phân phối. Dịch vụ này cung cấp bộ đệm trong bộ nhớ có thể được sử dụng để lưu trữ và truy xuất dữ liệu qua nhiều yêu cầu hoặc phiên.


AddSession() đăng ký dịch vụ phiên, cho phép ứng dụng duy trì trạng thái phiên. Tham số options cho phép bạn định cấu hình các tùy chọn khác nhau liên quan đến phiên. IdleTimeout được sử dụng để đặt khoảng thời gian không hoạt động, sau đó phiên sẽ được coi là không hoạt động. Trong trường hợp này, nó được đặt thành 40 giây. Cookie.IsEssential đảm bảo rằng trạng thái của phiên vẫn hoạt động và ngay cả trong các trường hợp bật tính năng từ chối cookie.


Hỗ trợ phiên được kích hoạt bằng cách thêm phần mềm trung gian UseSession vào đường dẫn ứng dụng, nghĩa là ứng dụng của bạn có quyền truy cập vào một đối tượng phiên có thể được sử dụng để lưu trữ và truy xuất dữ liệu.


Tạo mô hình

Tạo một thư mục mới, Models trong thư mục dự án của bạn. Thêm hai tệp lớp mô hình, TriviaApiResponse.csCâu hỏi.cs với các thuộc tính như trong các mẫu mã sau:


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


Mô hình TriviaApiResponse bao gồm các thuộc tính đại diện cho các trường của phản hồi API Trivia. Thuộc tính JsonProperty đảm bảo rằng mỗi thuộc tính được điền chính xác với dữ liệu JSON tương ứng.


Để có cách xử lý các câu hỏi đố một cách hợp lý, lớp Question sẽ giải cứu bạn. Lớp này gói gọn các thông tin cần thiết cho một câu hỏi đố, bao gồm nội dung câu hỏi và danh sách các tùy chọn. Mỗi tùy chọn được biểu thị bằng một bộ chứa văn bản tùy chọn và giá trị boolean cho biết liệu đó có phải là tùy chọn chính xác hay không.


Thêm lớp dịch vụ đố

Tạo thư mục Dịch vụ trong thư mục dự án của bạn và thêm tệp lớp mới có tên TriviaService.cs . Sửa đổi nội dung của nó, như được hiển thị trong đoạn mã sau:


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


Lớp TriviaService chứa hai phương thức: GetTriviaConvertTriviaToQuestions . Phương thức GetTrivia gửi yêu cầu HTTP GET tới API Trivia với tham số truy vấn, limit=3 , chỉ định rằng chỉ nên trả về 3 câu hỏi. Nếu không có tham số giới hạn, API sẽ trả về 10 câu hỏi theo mặc định.


Phương thức ConvertTriviaToQuestions chuyển đổi phản hồi từ API thành một cách có tổ chức. Phương pháp này cũng xáo trộn ngẫu nhiên tất cả các tùy chọn câu hỏi, do đó một tùy chọn duy nhất sẽ không phải là câu trả lời cho tất cả các câu hỏi.


Để đăng ký TriviaService và ứng dụng khách HTTP trong vùng chứa Dependency Insert (DI) của ứng dụng của bạn, hãy sửa đổi Program.cs như trong mã sau:


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


Tạo Bộ điều khiển đố

Thêm một lớp trình điều khiển API trống trong tệp có tên TriviaController.cs vào thư mục Bộ điều khiển và sửa đổi nội dung của nó như trong mã sau:


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


Lớp trình điều khiển này chịu trách nhiệm xử lý các tin nhắn đến, quản lý trạng thái phiên và tạo phản hồi. Nó kế thừa từ lớp TwilioController do thư viện Twilio.AspNet.Core cung cấp, cho phép bạn truy cập vào phương thức TwiML . Bạn có thể sử dụng phương pháp này để trả lời với TwiML, là Ngôn ngữ đánh dấu Twilio . Lớp TriviaController sử dụng các phương thức HttpContext.Session để tương tác với phiên.

Đầu vào hợp lệ là các phần tử trong mảng chỉ đọc StartCommandsOptionValues . Nội dung của tin nhắn đến được so sánh với các thành phần này để đảm bảo rằng người dùng đã gửi thông tin đầu vào phù hợp, nếu không, một thông báo sẽ được gửi đến người dùng để nhắc họ thực hiện thông tin đầu vào phù hợp dựa trên trạng thái hiện tại của trò chơi. Các trường khác có tiền tố “SessionKey” được sử dụng để xác định các chuỗi hằng số riêng tư cho các khóa phiên trong chương trình.


Phương thức Index là phương thức hành động chính xử lý các yêu cầu HTTP POST đến từ WhatsApp thông qua tuyến /Trivia . Nó tải dữ liệu phiên bằng cách sử dụng HttpContext.Session.LoadAsync() và truy xuất dữ liệu liên quan đến trạng thái trò chơi từ phiên bằng cách sử dụng các phương thức HttpContext.Session.GetString()HttpContext.Session.GetInt32() .


Việc sử dụng dấu gạch dưới (_) và dấu hoa thị (*) ở đầu và cuối của một số chuỗi nhất định là để đạt được định dạng văn bản in nghiêng và in đậm tương ứng trong tin nhắn WhatsApp được hiển thị.


Mỗi phương thức trợ giúp trong TriviaController thực hiện một nhiệm vụ cụ thể hỗ trợ chức năng chính của lớp.

  • Phương thức StartGame khởi tạo trò chơi bằng cách truy xuất các câu hỏi đố, chuyển đổi chúng thành định dạng phù hợp với trò chơi và lưu trữ chúng trong phiên.
  • Phương thức ProcessUserAnswer xử lý câu trả lời của người dùng cho một câu hỏi và xác định xem nó có đúng hay không.
  • Phương thức PresentQuestionWithOptions chịu trách nhiệm định dạng và trình bày câu hỏi cùng với các tùy chọn của nó.
  • Phương thức AddNewQuestionsToSession lưu trữ danh sách các câu hỏi trong phiên. Nó chuyển đổi các câu hỏi sang định dạng JSON và lưu chuỗi JSON trong phiên.
  • Phương thức RetrieveQuestionFromSession truy xuất câu hỏi từ phiên bằng cách sử dụng chỉ mục câu hỏi.
  • Phương thức EndTrivia tạo ra một thông báo để kết thúc trò chơi đố vui. Phương pháp này cũng loại bỏ dữ liệu phiên liên quan đến trò chơi. Dựa trên cấu hình của dịch vụ phiên trong Program.cs , điều này tự động xảy ra khi phiên không hoạt động trong 40 giây.


Kiểm tra ứng dụng

Để kiểm tra ứng dụng, bạn cần thiết lập Twilio Sandbox cho WhatsApp, làm cho điểm cuối ứng dụng của bạn có thể truy cập công khai và thêm URL điểm cuối trong cấu hình Sandbox dưới dạng webhook.

Thiết lập Hộp cát Twilio cho WhatsApp

đi đến Bảng điều khiển Twilio , điều hướng đến Tin nhắn > Dùng thử > Gửi tin nhắn WhatsApp .


Bảng điều khiển Twilio


Làm theo hướng dẫn trên trang để kết nối với hộp cát bằng cách gửi tin nhắn WhatsApp từ thiết bị của bạn đến số Twilio được cung cấp để tạo kết nối thành công với hộp cát WhatsApp. Tương tự, những cá nhân khác muốn thử nghiệm ứng dụng của bạn bằng số tương ứng của họ sẽ cần phải làm theo quy trình tương tự.

Hiển thị webhook của bạn bằng ngrok để thử nghiệm

Bây giờ, hãy mở shell terminal và chạy lệnh sau để khởi động ngrok và hiển thị ứng dụng ASP.NET Core cục bộ của bạn bằng cách thay thế <localhost-url> bằng URL đầy đủ của localhost mà bạn đã sao chép ban đầu:


 ngrok http <localhost-url>


ngrok sẽ tạo một URL công khai chuyển tiếp yêu cầu tới ứng dụng ASP.NET cục bộ của bạn. Tìm URL chuyển tiếp có nhãn Chuyển tiếp trong cửa sổ terminal ngrok và sao chép nó.


URL chuyển tiếp


Quay lại trang Twilio Dùng thử WhatsApp, nhấp vào Cài đặt hộp cát và thay đổi url Khi tin nhắn đến ở điểm cuối với URL chuyển tiếp được tạo bởi ngrok cộng với tuyến /Trivia và đảm bảo phương thức được đặt thành POST. Sau đó nhấn Save để lưu cấu hình sandbox mới.


Hộp cát Twilio


Trình diễn dự án

Chạy dự án ASP.NET Core của bạn bằng lệnh sau:


 dotnet run


Bây giờ, hãy kiểm tra ứng dụng của bạn bằng cách gửi tin nhắn trong cuộc trò chuyện đầu tiên bằng số Twilio Sandbox.


Kiểm tra ứng dụng của bạn trong WhatsApp


Phần kết luận

Bằng cách tận dụng sức mạnh của nền tảng Twilio và WhatsApp, bạn đã tạo ra trải nghiệm trò chơi đố vui hấp dẫn để người dùng thưởng thức. Bạn cũng đã học cách lưu và truy xuất dữ liệu từ các phiên.


Có một số cách mà dự án này có thể được cải thiện. Bạn có thể cải thiện hơn nữa dự án này bằng cách thêm bộ đếm thời gian, xử lý các ngoại lệ, cho phép người dùng chọn độ khó và áp dụng độ khó đã chọn làm tham số truy vấn thông qua URL API Trivia (ví dụ: https://the-trivia-api.com/api/questions?difficulty=hard ). Ngoài ra, bạn có thể khám phá khả năng tạo giải pháp cho phép người dùng điền vào khảo sát thông qua WhatsApp.