Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# WABA360Dialog.NET
C# .NET API Wrapper Classes for 360Dialog WhatsApp Business APIs (On-Premises) and Partner APIs
C# .NET API Wrapper Classes for 360Dialog WhatsApp Business APIs (On-Premises and Cloud API) and Partner APIs

- 360Dialog WhatsApp Business API (On-Premises) Client
- 360Dialog WhatsApp Business API (Cloud API) Client
- 360Dialog WhatsApp Business API Sandbox Client
- 360Dialog Partner API
- Webhook Models

## Getting Started
To get started, you must own an API Key managed by 360Dialog.

- **WABA360DialogApiClient.cs / WABA360DialogApiSandboxClient.cs** (WhatsApp API Business API)
- **WABA360DialogApiClient.cs / WABA360DialogCloudApiClient.cs / WABA360DialogApiSandboxClient.cs** (WhatsApp API Business API)
```c#
var client = new WABA360DialogApiClient("your-api-key");
var createMessageResposne = await client.SendMessageAsync(MessageObjectFactory.CreateTextMessage("whatsapp-id", "Hello World!"));
Expand Down Expand Up @@ -48,6 +49,23 @@ Task<CheckPhoneNumberResponse> CheckPhoneNumberAsync(CancellationToken cancellat
Task<HealthCheckResponse> HealthCheckAsync(CancellationToken cancellationToken = default);
```

- **WABA360DialogCloudApiClient.cs ** (WhatsApp API Business API (Cloud API))
```c#
Task<GetWebhookUrlResponse> GetWebhookUrlAsync(CancellationToken cancellationToken = default);
Task<SetWebhookUrlResponse> SetWebhookUrlAsync(string url, Dictionary<string, string> headers, CancellationToken cancellationToken = default);
Task<SendMessageResponse> SendMessageAsync(MessageObject message, CancellationToken cancellationToken = default);
Task<SendMessageResponse> SendMessageAsync(object message, CancellationToken cancellationToken = default);
Task<MarkMessagesAsReadResponse> MarkMessagesAsReadAsync(string messageId, CancellationToken cancellationToken = default);
Task<GetMediaInformationResponse> GetMediaInformationAsync(string mediaId, CancellationToken cancellationToken = default);
Task<GetMediaResponse> GetMediaAsync(string relativeUrl, CancellationToken cancellationToken = default);
Task<GetMediaResponse> GetMediaAsync(string relativeUrl, Dictionary<string, string> queryParams, CancellationToken cancellationToken = default);
Task<UploadMediaResponse> UploadMediaAsync(string fileName, byte[] fileBytes, string contentType, CancellationToken cancellationToken = default);
Task<GetTemplateResponse> GetTemplateAsync(int limit = 1000, int offset = 0, string sort = null, CancellationToken cancellationToken = default);
Task<CreateTemplateResponse> CreateTemplateAsync(CreateTemplateObject template, CancellationToken cancellationToken = default);
Task<DeleteTemplateResponse> DeleteTemplateAsync(string templateName, CancellationToken cancellationToken = default);
Task<UpdateBusinessProfileResponse> UpdateBusinessProfileAsync(IEnumerable<string> vertical, IEnumerable<string> websites, string email, string description, string address, CancellationToken cancellationToken = default);
```

- **WABA360DialogPartnerClient.cs** (360Dialog Partner API)
```c#
Task<CreatePartnerWhatsAppBusinessApiTemplateResponse> CreatePartnerWhatsAppBusinessApiTemplateAsync(string whatsAppBusinessApiAccountId, string name, string category, WhatsAppLanguage language, TemplateComponentObject components, CancellationToken cancellationToken = default);
Expand Down Expand Up @@ -99,6 +117,8 @@ The Enums definition, value, converters are located at these folders:
ApiClient\Payloads\Converters\...
ApiClient\Payloads\Enums\...

Cloud\ApiClient\Payloads\Enums

PartnerClient\Payloads\Converters\...
PartnerClient\Payloads\Enums\...

Expand All @@ -113,11 +133,16 @@ However, once Facebook updated the enums string value may cause issues during JS
## Webhook Models
Webhook Models classes (Recommend using Newtonsoft.Json >= 13.01 for JSON deserialization):

- WhatsApp API Business API
- WhatsApp API Business API (On-Premises)
```c#
ApiClient\Payloads\Models\WABA360DialogWebhookPayload.cs
```

- WhatsApp API Business API (Cloud API)
```c#
Cloud\ApiClient\Payloads\Models\WebhookObjects\WebhookNotification.cs
```

- 360Dialog Partner API
```c#
PartnerClient\Payloads\Models\WABA360DialogPartnerWebhookPayload.cs
Expand Down
212 changes: 212 additions & 0 deletions WABA360Dialog.NET.Example/Controllers/Waba360DialogCloudController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Serilog;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WABA360Dialog.Cloud;
using WABA360Dialog.Cloud.ApiClient.Interfaces;
using WABA360Dialog.Cloud.ApiClient.Payloads.Models.MessageObjects;
using WABA360Dialog.Cloud.ApiClient.Payloads.Models.WebhookObjects;
using WABA360Dialog.Cloud.ApiClient.Payloads.Responses;
using WABA360Dialog.Common.Enums;
using ONPREM = WABA360Dialog.ApiClient.Payloads;

namespace WABA360Dialog.NET.Example.Controllers
{
[ApiController]
[Route("/waba/360dialog/[action]")]
public class Waba360DialogCloudController : ControllerBase
{
private readonly ILogger<Waba360DialogCloudController> _logger;
private readonly IConfiguration _configuration;
private readonly IWABA360DialogCloudApiClient _client;

public Waba360DialogCloudController(ILogger<Waba360DialogCloudController> logger, IConfiguration configuration, IHttpContextAccessor contextAccessor)
{
_logger = logger;
_configuration = configuration;

var headerWabaApiKey = contextAccessor.HttpContext?.Request.Headers["360DialogChannelApiKey"];
_client = new WABA360DialogCloudApiClient(!string.IsNullOrWhiteSpace(headerWabaApiKey) ? headerWabaApiKey : _configuration["WABA360Dialog:ChannelApiKey"]);
}

[HttpPost]
public async Task<ActionResult> Webhook()
{
var stream = new MemoryStream();
await HttpContext.Request.Body.CopyToAsync(stream);
var byteArr = stream.ToArray();

var payloadString = Encoding.UTF8.GetString(byteArr, 0, byteArr.Length);

Log.Information($"Webhook:{payloadString}");
var notification = JsonConvert.DeserializeObject<WebhookNotification>(payloadString);
var payload = notification?.Entry?.FirstOrDefault().Changes?.FirstOrDefault()?.Value;
Log.Information($"Contacts: {payload?.Contacts?.Count() ?? 0}, Messages: {payload?.Messages?.Count() ?? 0}.");

return Ok(payload);
}

[HttpPost]
public ActionResult WebhookObject([FromBody] WebhookNotification webhookPayload)
{
return Ok(webhookPayload);
}

public record CheckContactRequest(string PhoneNumber);

public record SendMessageRequest(string WhatsappId, string TextMessage);

[HttpPost]
public async Task<SendMessageResponse> SendText([FromBody] SendMessageRequest request)
{
return await _client.SendMessageAsync(ONPREM.Models.MessageObjects.MessageObjectFactory.CreateTextMessage(request.WhatsappId, request.TextMessage));
}

public record SendMediaMessageRequest(string WhatsappId, string Link, string Caption);

[HttpPost]
public async Task<SendMessageResponse> SendImage([FromBody] SendMediaMessageRequest request)
{
return await _client.SendMessageAsync(ONPREM.Models.MessageObjects.MessageObjectFactory.CreateImageMessageByLink(request.WhatsappId, request.Link, request.Caption));
}

public record SendDocumentMessageRequest(string WhatsappId, string Link, string Filename, string Caption);

[HttpPost]
public async Task<SendMessageResponse> SendDocument([FromBody] SendDocumentMessageRequest request)
{
return await _client.SendMessageAsync(ONPREM.Models.MessageObjects.MessageObjectFactory.CreateDocumentMessageByLink(request.WhatsappId, request.Filename, request.Link, request.Caption));
}

public record SendNoCaptionMediaMessageRequest(string WhatsappId, string Link);

[HttpPost]
public async Task<SendMessageResponse> SendSticker([FromBody] SendNoCaptionMediaMessageRequest request)
{
return await _client.SendMessageAsync(ONPREM.Models.MessageObjects.MessageObjectFactory.CreateStickerMessageByLink(request.WhatsappId, request.Link));
}

public record SendTemplateMessageRequest(string WhatsappId, string TemplateNamespace, string TemplateName, WhatsAppLanguage Language, List<ONPREM.Models.MessageObjects.InteractiveObjects.TemplateMessageComponentObject> Components);

[HttpPost]
public async Task<SendMessageResponse> SendTemplate([FromBody] SendTemplateMessageRequest request)
{
return await _client.SendMessageAsync(ONPREM.Models.MessageObjects.MessageObjectFactory.CreateTemplateMessage(request.WhatsappId, request.TemplateNamespace, request.TemplateName, request.Language, request.Components));
}

public record SendInteractiveMessageRequest(string WhatsappId, ONPREM.Models.MessageObjects.InteractiveObjects.InteractiveObject InteractiveObject);

[HttpPost]
public async Task<SendMessageResponse> SendInteractive([FromBody] SendInteractiveMessageRequest request)
{
return await _client.SendMessageAsync(ONPREM.Models.MessageObjects.MessageObjectFactory.CreateInteractiveMessage(request.WhatsappId, request.InteractiveObject));
}

public record SendLocationMessageRequest(string WhatsappId, double Latitude, double Longitude, string Name = null, string Address = null);

[HttpPost]
public async Task<SendMessageResponse> SendLocation([FromBody] SendLocationMessageRequest request)
{
return await _client.SendMessageAsync(new MessageObject
{
RecipientType = "individual",
To = request.WhatsappId,
Type = ONPREM.Enums.MessageType.location,
Location = new ONPREM.Models.MessageObjects.LocationObjects.LocationObject()
{
Longitude = request.Longitude,
Latitude = request.Latitude,
Name = request.Name,
Address = request.Address,
}
});
}

public record SendContactRequest(string WhatsappId, ONPREM.Models.MessageObjects.ContactObjects.ContactObject ContactObject);

[HttpPost]
public async Task<SendMessageResponse> SendContact([FromBody] SendContactRequest request)
{
return await _client.SendMessageAsync(new MessageObject
{
RecipientType = "individual",
To = request.WhatsappId,
Type = ONPREM.Enums.MessageType.contacts,
Contacts = request.ContactObject
});
}

public record UploadMediaRequest(IFormFile File);

[HttpPost]
public async Task<UploadMediaResponse> UploadMedia([FromForm] UploadMediaRequest request)
{
await using var ms = new MemoryStream();
await request.File.CopyToAsync(ms);

return await _client.UploadMediaAsync(request.File.FileName, ms.ToArray(), request.File.ContentType);
}

public record MarkMessagesAsReadRequest(string MessageId);
[HttpPost]
public async Task<MarkMessagesAsReadResponse> MarkMessagesAsRead([FromForm] MarkMessagesAsReadRequest request)
{
return await _client.MarkMessagesAsReadAsync(request.MessageId);
}

public record GetTemplatesRequest(int Limit = 1000, int Offset = 0);
[HttpPost]
public async Task<ONPREM.GetTemplateResponse> GetTemplates([FromBody] GetTemplatesRequest request)
{
var response = await _client.GetTemplateAsync(request.Limit, request.Offset);

return response;
}

public record CreateTemplateExampleRequest(ONPREM.Models.MessageObjects.TemplateObjects.CreateTemplateObject TemplateObject);
[HttpPost]
public async Task<ONPREM.CreateTemplateResponse> CreateTemplate([FromBody] CreateTemplateExampleRequest request)
{
var response = await _client.CreateTemplateAsync(request.TemplateObject);

return response;
}

public record GetMediaByIdRequest(string MediaId);
[HttpPost]
public async Task<ActionResult> GetMediaFile([FromBody] GetMediaByIdRequest request)
{
var mediaInformationResponse = await _client.GetMediaInformationAsync(request.MediaId);
var response = await _client.GetMediaAsync(mediaInformationResponse.Url);

return File(response.FileBytes, response.ContentType.MediaType, response.ContentDisposition.FileName);
}
public record SetWabaWebhookRequest(string Url, string HeaderName, string HeaderValue);

[HttpPost]
public async Task<SetWebhookUrlResponse> SetWebhook([FromBody] SetWabaWebhookRequest request)
{
var dict = new Dictionary<string, string>
{
{
request.HeaderName, request.HeaderValue
}
};

return await _client.SetWebhookUrlAsync(request.Url, dict);
}

[HttpPost]
public async Task<GetWebhookUrlResponse> GetWebhook()
{
return await _client.GetWebhookUrlAsync();
}
}
}
10 changes: 5 additions & 5 deletions WABA360Dialog.NET.Example/WABA360Dialog.NET.Example.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.26" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
Expand Down
Loading