Setting Up a Free WhatsApp OTP service with C#

Over time, One Time Password has become essential in building products that require users signing in and low-level user verification. Integrating this service is not only expensive but also might be difficult or unstable depending on the platform you decide to use.

They are various ways of achieving OTP service, but this writeup will be focusing on using WhatsApp and C#. The requirements for this exercise include

  1. A smartphone with WhatsApp
  2. Dotnet core SDK
  3. Visual Studio
  4. Basic knowledge of C#

Create a new .Net Core API Project

Create a “Model” folder with the following classes in  it

  1. Message
public class Message
{
   public string Contact { get; set; }
   public string Text { get; set; }
  
   public bool IsDigit()
   {
       const string numbers = "0123456789";
       var count = (from ch in Contact
           where !numbers.Contains(ch)
           select ch).Count();
       var isNumber = (count == 0);
       return isNumber;
   }
}
  1. WhatsAppMetadata
public class WhatsAppMetadata
{
   public static string MainPanel = "#pane-side";
   public static string SearchField = "._2S1VP";
   public static string UserChat = "#pane-side span[title=\"XXX\"]";
   public static string UserName = "#main > span[title=\"XXX\"]";
   public static string MessageInputField = "._2S1VP";
}

3. Install Puppeteer

Go to your nugget package manager and install PuppeterSharp. This package is used to crawl the web using C# and chro4me

4. Create a Service folder with a WhatsAppService class

  Add the following properties

This class have five important functions

  1. InitBrowser():  this function will download a chrome browser on the first run and start it
private async Task InitBrowser()
{
   Debug.WriteLine("Downloading. . .");
   await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
   _browser = await Puppeteer.LaunchAsync(new LaunchOptions()
   {
       UserDataDir = Path.Combine(".", "user-data-dir"),
       Headless = false
   });
   Debug.WriteLine("Browser downloaded. . .");
}
  1. InitWhatsApp(): This function will open a new tab and load the WhatsApp page requesting you to scan the barcode
private async void InitWhatsApp()
{
   var qrCodeAbsolutePath = _binPath.Replace("bin", "") + QrCodeImagePath;
   _whatsAppPage = await _browser.NewPageAsync();
   await _whatsAppPage.GoToAsync(WhatsAppUrl);
   Try
   {
       await _whatsAppPage.WaitForSelectorAsync(WhatsAppMetadata.MainPanel);
   }
   catch (Exception)
   {
       Console.WriteLine("As a new User, you need to login");
       await _whatsAppPage.ScreenshotAsync(qrCodeAbsolutePath);
       Console.WriteLine($"send image in {qrCodeAbsolutePath} to your email && print to console");
       await _whatsAppPage.WaitForSelectorAsync(WhatsAppMetadata.MainPanel);
   }
   await _whatsAppPage.ScreenshotAsync(qrCodeAbsolutePath);
}
  1. SendMessages(): This removes a message from or queue and sends with
private void SendMessages()
{
   while (_messages.Any())
   {
       var message = _messages.Dequeue();
       SendMessage(message);
   }
}
  1. SendMessage(Message message): This function is responsible for sending the selected message
private async void SendMessage(Message message)
{
  if (message.IsDigit())
   {
       //load the new url and press send
       var url = NewContactUrl.Replace("<PHONE>", message.Contact).Replace("<MESSAGE>",message.Text);
       await _whatsAppPage.GoToAsync(url);
       await _whatsAppPage.WaitForSelectorAsync(WhatsAppMetadata.MessageInputField);
       await _whatsAppPage.Keyboard.PressAsync("Enter");
       //Message Sent
       Console.WriteLine("Message Sent");
       return;
   }  
   //Search complete
   var searchInput = await _whatsAppPage.QuerySelectorAsync(WhatsAppMetadata.SearchField);
   await searchInput.TypeAsync(message.Contact);
   await _whatsAppPage.WaitForTimeoutAsync(500);  
   var fiendLocation = WhatsAppMetadata.UserChat.Replace("XXX", message.Contact);
   var contactHandle = await _whatsAppPage.QuerySelectorAsync(fiendLocation);
   await contactHandle.ClickAsync();
   var messageSplit = message.Text.Split("\n");
   foreach (var word in messageSplit)
   {
       await _whatsAppPage.Keyboard.DownAsync("Shift");
       await _whatsAppPage.Keyboard.PressAsync("Enter");
       await _whatsAppPage.Keyboard.UpAsync("Shift");
       await _whatsAppPage.Keyboard.TypeAsync(word);
   }
   await _whatsAppPage.Keyboard.PressAsync("Enter");
}
  1. AddMessage(Message message): There’s is no direct method to call from an external class to send messages, instead this method is called to add a new message to an already existing queue.
public void AddMessage(Message message)
{
   _messages.Enqueue(message);
   if (_messages.Count == 1)
   {
       SendMessages();
   }
}

Go to your Startup.cs class and inject WhatsAppService as a singleton class

var whatAppService = new WhatsAppService();
services.AddSingleton(whatAppService);

With all this done, you can go ahead and use the WhatAppService in your controllers to send Messages (including users One Time Password)